21.10.3.1.2. arm-none-eabi-addr2line 유틸리티를 활용하여 PC(Program Counter) 주소를 타겟 C++ 파일의 라인 넘버로 역추적(Backtrace)하여 Null Pointer 오류 발생 지점 색출
이전 장에서 우리는 널 포인터를 참조하다 터진 픽스호크의 SD 카드에서 PC: 0x0802c11e 라는 치명적인 16진수 암호를 훔쳐 오는 데 성공했다.
이제 리눅스의 해독기를 돌려 이 16진수 숫자가 내 PayloadAutoDrop.cpp의 정확히 몇 번째 줄(Line)인지 역산해 낼 시간이다.
이 마법 같은 흑마술(Backtrace)을 가능하게 해주는 도구가 바로 GCC 컴파일러 툴체인에 기본 내장되어 있는 arm-none-eabi-addr2line 유틸리티다.
1. 역추적의 조건: 엘프(ELF) 파일의 존재
16진수 숫자를 소스 코드로 번역하려면, 빌드 당시의 모든 디버깅 심벌(Symbol) 기호와 좌표가 기록된 거대한 나침반 파일이 필요하다.
이 파일이 바로 펌웨어 빌드 시 생성되는 수십 메가바이트짜리 엘프(*.elf) 파일이다.
이 파일 안에는 “0x0802… 번지 메모리 내용은 PayloadAutoDrop.cpp의 142번 줄에서 컴파일된 것이다“라는 매핑 테이블이 존재한다.
(주의점: SD 카드에서 뽑아온 크래시 로그(PC 값)는 반드시 그 로그를 뿜어낸 시점과 100% 동일한 소스 코드로 빌드된 .elf 파일과 짝을 맞춰 분석해야 한다. 소스 코드를 한 줄이라도 바꾸고 다시 빌드해버리면 메모리 주소가 다 밀려버려서 엉뚱한 범인을 가리키게 된다.)
.elf 파일은 보통 PX4 소스코드 디렉터리의 build/px4_fmu-vX_default/ 폴더 깊숙한 곳(예: px4_fmu-vX_default.elf)에 생성되어 있다.
2. 터미널(Terminal)에서 칼춤 추기
터미널을 열고 build/... 폴더 경로로 이동한 뒤, 앞서 구했던 흉기(PC) 주소를 무기 삼아 addr2line 명령어를 휘두르자.
# 기본 문법:
# arm-none-eabi-addr2line -e [사용한_엘프_파일] [16진수_PC_주소]
# 실전 투입:
$ arm-none-eabi-addr2line -e build/px4_fmu-v6x_default/px4_fmu-v6x_default.elf 0802c11e
이 명령어를 치면, 화면에는 단 한 줄의 싸늘한 진실이 출력된다.
/home/user/px4-autopilot/src/modules/payload_autodrop/PayloadAutoDrop.cpp:142
놀랍지 않은가? 드론이 추락할 때 비명을 질렀던 그 숫자(0x0802c11e)의 정체가 바로 PayloadAutoDrop.cpp 파일의 142번째 줄이었다.
3. 확인 사살 (Confirmation)
VScode 편집기를 열어 그 악명 높은 PayloadAutoDrop.cpp 파일의 142번 줄을 열어보아라.
140: void PayloadAutoDrop::Run() {
141: ...
142: _local_pos_sub->update(); // [CRASH!] 여기서 펑 터졌다!
143: ...
142번 줄을 보니 _local_pos_sub 라는 클래스 내의 포인터 변수가 메서드(->update())를 소환하고 있다.
아뿔싸, 생각해보니 생성자 쪽에서 _local_pos_sub = new uORB::Subscription(...) 같은 메모리 할당(동적 바인딩)을 실수로 주석 처리 해놓고 잊어버린 것이다. (앞선 장에서 설명한 FAR: 00000000 널 포인터를 다시 떠올려보라).
이처럼 PC와 addr2line의 콤보 공격은 추락한 기체의 SD 카드 텍스트 단 한두 줄만으로도 원격으로 서울에 앉아서 남극에서 추락한 드론의 C++ 코드 라인 번호를 족집게처럼 집어내는 궁극의 부검술이다.
만약 PC가 가리키는 곳이 이상하다면 앞서 구해두었던 LR 값도 똑같이 addr2line에 집어넣어, 배후 함수의 콜 스택(Call Stack) 라인 번호를 캘 수도 있다.
사후 세계(Post-mortem)에서의 부검 능력을 갖추었다면, 이제는 아예 범행이 일어나기 전에 기판 위에서 펄떡거리는 심장을 멈춰 세우고 레지스터를 실시간으로 조작해 버리는 하드웨어 디버깅의 정수, ’JTAG 하드웨어 트레이스와 GDB 브레이크포인트(Breakpoint)’의 세계인 21.10.4장으로 발걸음을 옮겨 화려한 피날레를 장식해 보자.