Linux useful tool - gdb


debugging 심볼 기본 위치
※ 심볼의 위치는 설정에 따라 변경 가능합니다.
  • source : /usr/src/debug/<SOURCE_PATH>
  • lib : /usr/lib/.debug/lib<NAME>.so*
  • bin : /usr/bin/.debug/<BIN>
샘플 코드

실행 방법 1. gdb 실행 후 프로그램 실행

$ gdb ./gdbanalysis
(gdb) r arg1 arg2 arg3
 : prompt에서 run 가능

실행 방법 2. gdb 실행 시 옵션과 함께 실행

$ gdb -ex=r --args ./gdbanalysis arg1 arg2 arg3

실행 방법 3. running process

$ gdb -p PID
또는
$ gdb
(gdb) attach
* gdb 실행 시 break가 됩니다. '(gdb) c' 명령어로 다시 run이 가능합니다.

분석 시점 1. fault가 발생하는 지점에서 분석

fault가 나는 지점이 발생하면 자동으로 gdb가 멈춥니다.

gdb는 fault가 발생한 지점에서 frame 별로 보여줍니다.
위의 소스는 38번째 줄에서 abrot가 발생했습니다.
 (gdb) bt
#0  0x00000034cd498bf6 in __strcpy_sse2_unaligned () from /lib64/libc.so.6
#1  0x00000000004006eb in main () at hello.c:38

#0, #1은 frame을 표시한 것 입니다. 숫자가 높은 순에서 낮은 순으로 수행이 되었으며 수행될 때의 상태가 frame별로 저장이 된 것 입니다.
분석은 숫자가 가장 높은 최초의 call을 한 함수부터 frame을 내려가면서 분석을 하는 것이 좋습니다.


분석 시점 2. core file 분석

core dump는 fault가 나는 시점에 모든 정보가 담긴 파일입니다.
core dump파일과 symbol이 있으면 이슈를 재현하지 않고도 분석이 가능합니다.
아래 명령어로 gdb에서 core dump파일을 로딩할 수 있습니다.

$ gdb ./gdbanalysis -c ./core.dump


분석 시점 3. break point

break로 source line을 잡은 후 run을 하게되면 해당 라인에 멈춘 상태로 디버깅이 가능합니다.
(gdb) b 24
Breakpoint 1 at 0x40068e: file hello.c, line 24.
(gdb) r
Starting program: /home/B160020/tmp/hello/hello
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main () at hello.c:24
24        int age = 30;
Missing separate debuginfos, use: debuginfo-install glibc-2.18-19.fc20.x86_64 libuuid-2.24.2-2.fc20.x86_64

break를 잡은 후 s(step)나 n(next)를 입력하면 다음 동작을 수행합니다.
또는 c(continue)로 다음 break point까지 동작을 수행합니다.

현재 break point 보기.
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040068e in main at hello.c:24
2       breakpoint     keep y   0x0000000000400695 in main at hello.c:25

변수 보기

'frame'(또는 f) 명령어는 frame을 이동할 수 있는 명령어 입니다.
'$ f 1' 와 같이 f 다음 보고싶은 frame을 입력하여 이동합니다.
(gdb) f 1
#1  0x00000000004006eb in main () at hello.c:38
38        strcpy(pNonmem,pPerson->name);

위의 프로그램에서는 에러를 발생시킨 최초 call이 1번 frame이므로 'f 1'을 입력하여 1번 frame을 봅니다.
strcpy의 복사할 대상인 pNonmem 포인터가 메모리 할당이 되어있지 않은데 strcpy함수로 메모리에 접근하려다가 발생한 에러입니다.

아래와 같이 변수의 정보를 보면서 디버깅을 하면 쉽게 문제의 원인을 찾을 수 있습니다.
(gdb) p pNonmem
$1 = 0x4006f0 <__libc_csu_init> "AWA\211\377AVI\211\366AUI\211\325ATL\215%\370\006 "
(gdb) p pPerson->name
$2 = 0x400784 "maeng"

'print'(또는 p) 명령어는 선택된 frame에서 변수의 값을 출력해줍니다.
'$ p pNonmem'와 같이 변수를 출력하면 메모리 할당이 되어있지 않기 때문에 주소만 출력되며 쓰레기 값이 출력됩니다.

아래와 같이 tab을 누르면 구조체에 있는 변수를 볼 수 있습니다.
(gdb) p pPerson->
age   name
(gdb) p pPerson->name
$14 = 0x400784 "maeng"
(gdb)

변수 형을 변환해서 보기
p/FMT 변수
(gdb) p/x pPerson->age
$15 = 0x1e
FMT는 printf와 동일한 형식으로 사용할 수 있습니다.
'p/d', 'p/f' 등...

trace 변수보기

$ info locals : 현재 frame의 stack 변수 출력
(gdb) info locals
age = 30
name = 0x400784 "maeng"
pPerson = 0x602010
pNonmem = 0x4006f0 <__libc_csu_init> "AWA\211\377AVI\211\366AUI\211\325ATL\215%\370\006 "


$ info variables : 모든 전역변수, stack 변수 출력
(gdb) info variables
All defined variables:

File hello.c:
struct {
    int age;
    char *name;
} g_Person;
int g_age;
char *g_name;

Non-debugging symbols:
0x0000000000400770  _IO_stdin_used
0x0000000000400778  __dso_handle
0x00000000004008d8  __FRAME_END__
0x0000000000600e00  __frame_dummy_init_array_entry
0x0000000000600e00  __init_array_start
0x0000000000600e08  __do_global_dtors_aux_fini_array_entry
0x0000000000600e08  __init_array_end
0x0000000000600e10  __JCR_END__
0x0000000000600e10  __JCR_LIST__
0x0000000000600e18  _DYNAMIC
0x0000000000601000  _GLOBAL_OFFSET_TABLE_
0x0000000000601040  __data_start
0x0000000000601040  data_start
0x0000000000601058  __TMC_END__
0x0000000000601058  __bss_start
0x0000000000601058  _edata
0x0000000000601060  completed
0x0000000000601080  _end
0x00000034ccc19d20  debopts
..


Tip.

gdb의 symbol 위치를 변경하는 방법

key point

  • debug shared lib loading
  • add source

우선 shared lib의 from 주소값을 알아냅니다.
(gdb) info sharedlibrary
From        To          Syms Read   Shared Object Library
...
0xb5876bf8  0xb5899508  Yes (*)     /usr/lib/libhello.so.0
...


from에서 알아낸 주소값에 symbol을 추가합니다.
(gdb) add-symbol-file /tmp/libhello.so.0.0.0 0xb5876bf8


source의 경로를 알아냅니다.
(gdb) info sources
Source files for which symbols have been read in:
/home/root/tmp/hello/hello.c
...


source 경로를 수정합니다.
(gdb) set substitute-path /home/root/tmp/hello/ /tmp/hello/

shared lib debug symbol과 source가 모두 연결되어 디버깅이 가능합니다.

유용한 명령어

모든 명령어는 첫 스펠링만 입력해도 됩니다.
i.e.)
(gdb) break = (gdb) b

break point 잡기

(gdb) break filename:linenum
(gdb) break filename:function
(gdb) break linenum

break point 보기

(gdb) info break

break point 지우기

(gdb) clear filename:linenum
(gdb) delete [breakpoints] [bnums...]

break 상태에서 디버깅

(gdb) next
  • 다음 라인으로 이동합니다.

(gdb) step
  • next와 비슷합니다. 차이점은 다음 라인에 함수가 호출되면 호출되는 함수 내부로 이동하여 정보를 출력합니다.

(gdb) until
  • next와 비슷합니다. 차이점은 반복분(for,while)에서 until을 이용하면 반복문을 break없이 수행합니다.

(gdb) print
print/출력형식 (변환할 형)변수
  • 출력 형식
    • x : 16진수 정수형
    • d : 부호화된 10진수형
    • u : 부호화되지 않은 10진수 형
    • o : 8진수 형
    • t : 이진수 형
    • a : 16진수 주소와 가장 가까운 전 심볼에서 오프셋 출력
    • c : 정수로 간주하고 문자 상수로 출력
    • f : 부동소수점
  • 이 외에도 아래와 같이 구조체도 형변환하여 출력이 가능합니다.
    i.e.)
    p/(ST_Mystruct) mystruct
    p *(ST_Mystruct*)mystruct

sysroot 지정하기

set sysroot /home/${MYPATH}/
※ /usr 전 폴더가 위치해야함.
   i.e.) .../sysroots/armv7ahf-neon-poky-linux-gnueabi/

source path 기본경로 변경하기

set substitute-path [기본경로] [변경할 경로]
set substitute-path /usr/src/debug/ /home/${MYPATH}/usr/src/debug/

댓글