반응형
GDB 디버깅 에러 메시지와 발생 원인 정리
GDB에서 보게 되는 메시지는 크게 세 부류로 나뉜다.
- 프로그램이 받은 시그널
- glibc가 스스로 멈추면서 찍는 메시지
- GDB 자체의 경고·에러
앞의 두 개는 내 코드에 버그가 있다는 뜻이고, 세 번째는 대부분 컴파일 옵션이나 GDB 사용법 문제다.
1. 프로그램이 받은 시그널
Program received signal SIGxxx, ... 형태로 나온다.
- SIGSEGV (Segmentation fault) : NULL 역참조, 이미
free한 메모리 접근, 초기화 안 된 포인터 사용, 배열 범위 밖 접근, 무한 재귀로 인한 스택 오버플로, 문자열 리터럴(char *s = "abc") 수정 - SIGABRT (Aborted) :
abort()호출,assert실패, glibc의 힙 무결성 검사 실패(2번 참고), 스택 버퍼 오버플로 감지 - SIGFPE (Arithmetic exception) : 정수를 0으로 나누거나
%0 연산,INT_MIN / -1 - SIGBUS (Bus error) : 정렬되지 않은 주소 접근(아키텍처에 따라 다름),
mmap한 파일의 범위를 넘은 접근 - SIGILL (Illegal instruction) : 손상된 함수 포인터로 호출, 컴파일러가 UB 지점에 넣은 트랩 명령(
ud2) 실행 - SIGPIPE (Broken pipe) : 상대가 닫은 소켓이나 파이프에
write - SIGINT (Interrupt) : Ctrl+C 입력. 버그가 아니며, 무한 루프가 어디서 도는지 확인할 때 일부러 쓴다
- SIGKILL (Killed) : 메모리 부족으로 OOM killer가 종료시킴. 무한
malloc루프를 의심
SIGSEGV 주소로 원인 추정하기
크래시 지점에서 p ptr로 포인터 값을 찍어 보면 원인이 좁혀진다.
0x0→ NULL 포인터 역참조0x8,0x10처럼 작은 값 →NULL->멤버접근. 값이 그 멤버의 오프셋이다. 예를 들어node->next에서node가 NULL이면 이런 값이 나온다.0x7ffd...로 시작 → 스택 주소. 이미 반환된 함수의 지역 변수 주소를 쓰고 있을 가능성이 있다.0x5555.../0x4...근처 → 힙이나 전역 영역. 해제 후 사용(use-after-free)이나 범위 초과를 의심한다.- 완전히 쓰레기 같은 값(예:
0x3a2f1b...) → 초기화 안 된 포인터이거나, 버퍼 오버플로가 포인터를 덮어쓴 경우다.
2. glibc 힙·스택 검사 메시지
이 메시지들이 찍힌 뒤 SIGABRT가 온다.
크래시 지점이 실제 버그 위치가 아닌 경우가 많다는 점이 핵심이다. 망가진 건 훨씬 전이고, 다음 malloc/free에서 그걸 발견한 것일 수 있다.
free(): double free detected in tcache 2: 같은 포인터를 두 번freefree(): invalid pointer:malloc이 반환하지 않은 주소를free. 스택 변수나ptr + 1처럼 중간 주소를 넘긴 경우free(): invalid size/munmap_chunk(): invalid pointer: 청크 헤더가 손상됨. 앞 블록에서 범위를 넘어 쓴 경우가 대부분malloc(): corrupted top size: 힙 끝 쪽 블록에서 할당 크기를 넘어 씀malloc(): unaligned tcache chunk detected: 해제된 블록에 쓰기(use-after-free)로 내부 링크가 망가짐corrupted size vs. prev_size: 인접 블록 경계를 넘는 쓰기realloc(): invalid next size:realloc대상 블록 뒤쪽이 오버플로로 손상됨* stack smashing detected *: terminated: 지역 배열 범위를 넘어 써서 스택 카나리가 덮임.strcpy,gets, 인덱스 계산 실수 등
stack smashing은 함수가 반환할 때 감지되므로, 그 함수 안의 배열 쓰기를 확인해야 한다.
이런 부류의 버그는 -fsanitize=address(ASan)로 컴파일하면 망가지는 순간의 위치를 바로 알려 준다. GDB와 함께 쓰면 좋다.
3. GDB 자체 메시지
3-1. 심볼·컴파일 관련
No debugging symbols found in ./a.out:-g없이 컴파일함.gcc -g -O0으로 다시 빌드No symbol table is loaded. Use the "file" command.: 위와 같은 원인이거나, 실행 파일 없이gdb만 실행함<optimized out>/value has been optimized out:-O2같은 최적화로 변수가 레지스터로 옮겨지거나 사라짐. 디버깅할 때는-O0사용No symbol "x" in current context.: 변수가 현재 프레임의 스코프 밖에 있음.frame N으로 이동하거나, 최적화로 제거된 경우Function "foo" not defined. Make breakpoint pending on future shared library load?: 함수 이름 오타,static함수가 다른 파일에 있음, 또는 아직 로드 안 된 공유 라이브러리의 함수
디버깅용 빌드는 항상 -g -O0으로 하는 것이 기본이다.
3-2. 실행 상태 관련
The program is not being run.:run전에next/step/continue를 입력함No stack.: 프로그램이 실행 중이 아닌데bt를 입력함. 이미 종료했거나 아직 시작 전[Inferior 1 (process N) exited normally]:return 0으로 정상 종료[Inferior 1 (process N) exited with code 01]: 0이 아닌 값으로 종료.exit(1)이나return 1이 호출된 경로 확인Single stepping until exit from function X, which has no line number information.:step으로printf같은 라이브러리 함수에 들어감. 라이브러리 함수는next로 건너뛰고, 들어갔다면finish로 빠져나온다
3-3. 크래시 직후에 자주 보는 메시지
__GI_raise (...) at ../sysdeps/unix/sysv/linux/raise.c:50: No such file or directory.: 버그가 아니다. glibc 소스가 없다는 뜻일 뿐.bt로 콜스택을 보고frame N으로 내 코드의 프레임으로 이동0x0000... in ?? (): 심볼이 없는 주소를 실행 중. 함수 포인터가 손상됐거나, 스택 오버플로로 반환 주소가 덮였을 가능성이 큼Backtrace stopped: previous frame inner to this frame (corrupt stack?): 스택 자체가 손상됨. 지역 배열 오버플로를 의심Cannot access memory at address 0x...:p *ptr로 출력하려는 주소가 유효하지 않음. 바로 그 포인터가 크래시 원인일 가능성이 높다Missing separate debuginfos, use: debuginfo-install ...: 시스템 라이브러리의 디버그 정보가 없다는 뜻. 무시해도 된다
raise.c: No such file or directory는 에러가 아니다. 처음 보면 당황하기 쉬운데, 콜스택을 따라 내 코드로 내려가면 된다.
3-4. 환경·권한 관련
warning: Error disabling address space randomization: Operation not permitted: Docker 컨테이너에서 자주 나온다.docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined로 실행하거나, GDB에서set disable-randomization off입력. 경고일 뿐이라 디버깅은 된다ptrace: Operation not permitted.: 실행 중인 프로세스에attach할 권한이 없음.sudo로 실행하거나/proc/sys/kernel/yama/ptrace_scope설정 확인Watchpoint N deleted because the program has left the block in which its expression is valid.: 지역 변수에 건 watchpoint가 함수 반환으로 사라짐. 정상 동작
3-5. 출력 명령 실수
Attempt to take contents of a non-pointer value.: 포인터가 아닌 변수에*를 붙임There is no member named xxx.: 구조체 멤버 이름 오타, 또는.과->의 대상 타입이 다름
4. 크래시가 났을 때 기본 순서
(gdb) run # 크래시 재현
(gdb) bt # 콜스택 확인: 어느 함수에서 죽었나
(gdb) frame N # 내 코드 프레임으로 이동
(gdb) info locals # 지역 변수 전체 확인
(gdb) p ptr # 의심 포인터 값 확인 (0x0? 쓰레기?)
(gdb) p *ptr # 역참조 가능한지 확인
bt 맨 위(#0)가 raise, abort, malloc 같은 라이브러리 함수라면, 아래로 내려가면서 처음 나오는 내 소스 파일의 프레임을 찾으면 된다.
2번의 힙 손상 메시지라면 그 위치보다 앞에서 같은 메모리를 건드린 코드를 추적해야 한다.
반응형