강좌 & 팁
entering [init]-- tests entering [raise_seg]-- ptr: (nil), offset: 100 Segmentation fault (core dumped)
다음과 같은 명령을 통해서 생성될 core 파일의 최대 사이즈를 설정할 수 있습니다.
$ ulimit -c unlimited
설정된 이후로 실행을 하면 작업 디렉토리에 core라는 파일이 생성됩니다. 이 파일은 오류가 발생했을 때 상황을 저장하고 있어서 오류 상황에 대한 분석을 할 수 있도록 도와줍니다.
$ gdb ./seg core GNU gdb (Ubuntu 7.9-1ubuntu1) 7.9 Copyright (C) 2015 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./seg...done. [New LWP 13718] Core was generated by `./seg'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000004005c6 in raise_seg (ptr=0x0, offset=100) at seg.c:6 6 ptr[0] = 1; (gdb)
위와 같이 gdb를 실행해보면 일단 종료될 때의 오류 상황부터 표시해줍니다.
Program terminated with signal SIGSEGV, Segmentation fault.
이렇게 표시가 되는 걸 봐서는 이 프로그램은 Segmentation falut로 인해 종료가 되어버렸습니다.
좀 더 구체적인 정보를 얻기 위해서 지나온 경로를 알아봅니다.
(gdb) bt #0 0x00000000004005c6 in raise_seg (ptr=0x0, offset=100) at seg.c:6 #1 0x000000000040061a in init (str=0x40070a "tests") at seg.c:13 #2 0x000000000040063e in main () at seg.c:18 (gdb)
main -> init -> raise_seg 순으로 함수가 호출된 것을 알 수 있습니다.
친절하게도 함수를 호출할 때의 인자값또한 표시를 해주고 있습니다.
좀 더 세밀한 정보는 아래와 같이 얻을 수 있습니다.
(gdb) info frame 0 Stack frame at 0x7ffd8a571900: rip = 0x4005c6 in raise_seg (seg.c:6); saved rip = 0x40061a called by frame at 0x7ffd8a571920 source language c. Arglist at 0x7ffd8a5718f0, args: ptr=0x0, offset=100 Locals at 0x7ffd8a5718f0, Previous frame's sp is 0x7ffd8a571900 Saved registers: rbp at 0x7ffd8a5718f0, rip at 0x7ffd8a5718f8
일단 오류가 발생한 위치를 알아봤으니 추적을 해보면서 오류의 상황을 분석할 수 있습니다.
우선 gdb로 프로그램을 실행할 준비를 합니다.
$ gdb ./seg GNU gdb (Ubuntu 7.9-1ubuntu1) 7.9 Copyright (C) 2015 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./seg...done.
문자가 발생한 raise_seg 함수에 브레이크 포인트를 잡고 실행을 해보겠습니다.
(gdb) b raise_seg Breakpoint 1 at 0x400595: file seg.c, line 4. (gdb) run Starting program: /home/leios/work/temp/seg/seg entering [init]-- tests Breakpoint 1, raise_seg (ptr=0x0, offset=100) at seg.c:4 4 printf("entering [%s]--\n", __func__);
그러면 이렇게 실행이 되자마자 브레이크 포인트에 걸려서 프로그램이 일시 중지되고 디버깅이 가능한 상태가 되었습니다.
l(ist)명령을 통해서 현재 멈춘 곳의 소스를 확인해볼 수 있습니다.
(gdb) l 1 #include <stdio.h> 2 3 void raise_seg(char * ptr, int offset) { 4 printf("entering [%s]--\n", __func__); 5 printf("ptr: %p, offset: %d\n", ptr, offset); 6 ptr[0] = 1; 7 printf("leaving [%s]--\n", __func__); 8 } 9 10 void init(char * str) {
저 위의 frame정보를 보면 #0 0x00000000004005c6 in raise_seg (ptr=0x0, offset=100) at seg.c:6 로 표시되는 것을 보아 6라인에서 오류가 발생했고, 지금 멈춘 곳은 4라인입니다.
6라인까지 진행을 해봅니다.
(gdb) n entering [raise_seg]-- 5 printf("ptr: %p, offset: %d\n", ptr, offset); (gdb) n ptr: (nil), offset: 100 6 ptr[0] = 1;
ptr의 값을 확인해봅니다.
(gdb) print ptr $1 = 0x0
아.. ptr이 0x0 즉 NULL 포인터입니다. NULL 포인터에 값을 억세스를 하려고 하니 당연히 오류가 발생한거였습니다.
이렇게 gdb를 활용하면 printf로 디버깅할 때보다 빌드 횟수를 줄일 수도 있고, 도움이 될만한 단서를 쉽게, 정확하게 얻을 수 있습니다.