디버깅에 대해서 #1 : http://forum.falinux.com/zbxe/index.php?document_srl=865340
디버깅에 대해서 #2 : http://forum.falinux.com/zbxe/index.php?document_srl=866245

지난 시간까지 printf를 사용해서 버그가 발생한 위치를 찾는 법에 대해서 살펴보았습니다.

이번에도 지난번에 사용했던 소스를 그대로 사용할 예정입니다.

우선 첨부된 소스를 빌드하여 실행하면 다음과 같은 결과가 출력됩니다.

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로 디버깅할 때보다 빌드 횟수를 줄일 수도 있고, 도움이 될만한 단서를 쉽게, 정확하게 얻을 수 있습니다.