'디버깅'에 해당되는 글 1건

  1. 2008/02/01 GDB Favorites (2)
2008/02/01 12:49

GDB Favorites

개발 경험이 적을 때는... 아니 베테랑들도 애용하는 디버깅 도구는... 당연히 printf입니다(C프로그래머...-_-).
print 문을 이용하는 방식은 일종의 logging의 범주에 드는데 print문을 쓰든 아니면 log4j같은 로깅 프레임워크를 사용하든 코드를 새로 집어넣어야 되는 건 마찬가지죠. 그리고 동적이지 못하다는게 가장 불편합니다. 로그를 집어 넣을 때마다 재컴파일을 해야 되니까요.

그럼에도 불구하도 저 역시 습관처럼 printf를 주로 쓰긴 합니다만, 때로는 GDB 같은 디버거가 사실 장점이 많긴 합니다.
segmentation fault가 날 때도 위치를 금방 알 수 있고, 한 줄씩 순차적으로 실행시켜 나갈 수도 있고, 특정 변수의 데이터를 감시할 수도 있습니다.

너무 장점만 나열했군요 -_- 의도대로 자주 쓰는 GDB 명령어들을 하나씩 살펴 보겠습니다.
-g 옵션으로 컴파일해야 한다는 것 등은 생략하겠습니다 ㅡㅡ


1. backtrace(bt)

괄호 안은 약어 명령입니다. 사용할 경우 main에서부터 현재 위치까지의 함수 호출 구조를 보여줍니다.

대략 다음과 같은 식입니다.

#0 __bam_c_get (dbc=0x80c67e0, key=0xbfbea670, data=0xbfbea690, flags=16) at ..//btree/bt_cursor.c:544

#1 0x080808e0 in cascadeBtreeUpdate (femtoEnv=0x80bd008,tableInfo=0x80c11f0, indexInfo=0x80c1980, indexCount=15,org_record=0x80c5160, record=0x80c3b60) at ../../femto_capi/c/femtocascade.c:344

#2 0x08077718 in femtoSaveRecord (femtoEnv=0x80bd008,record=0x80c3b60, flag=2 '\002') at ../../femto_capi/c/femtorecord.c:643

...

처음의 번호는 스택 프레임 번호이며 실제 코드랑은 상관없습니다. 이어서 주소와 함수 이름이 보이고 ()안은 함수 호출할 때의 인자 값 혹은 포인터일 경우 주소를 보여줍니다. 마지막엔 정확한 소스 라인 위치를 보여줍니다.

보통 segmentation fault가 발생했을 때 이 명령어로 위치를 확인합니다.


2. print(p)

변수 값 등을 찍어봅니다. 구조체의 필드 값 등도 확인 가능하며 포인터일 경우 주소를 보여주고 *를 붙일 경우 모든 필드값을 확인할 수 있습니다.

(gdb) p *cp
$3 = {dbc = 0x80c67e0, sp = 0x80c6138, csp = 0x80c6138, esp = 0x80c6174,
stack = {{page = 0xb7e9a2a4, indx = 0, lock = 52}, {page = 0x0, indx = 0, lock = 0}, {page = 0x0, indx = 0,lock = 0}, {page = 0x0, indx = 0, lock = 0}, {page = 0x0, indx = 0, lock = 0}}, page = 0xb7ec4ccc, pgno = 46, indx = 0, dpgno = 109, dindx = 0, lock = 46, mode = DB_LOCK_NG, recno = 0, compkey = 0x0, flags = 0}

(gdb) p cp->pgno
$4 = 46

하지만 변수 값 확인은 현재 스코프에서만 가능하며 현재 함수를 호출한 함수 내의 변수들은 확인하지 못합니다. 이 때는 up, down 명령으로 bt 명령에서 리스트된 프레임 스택을 위 아래로 옮겨가며 확인할 수 있습니다.

프린트 뿐 아니라 "p cp->pgno = 50" 이런 형태로 값을 바꿔치기 해 동작을 살펴볼 수도 있습니다.


3. break(b)

segmentation fault는 아니지만 동작이 이상하거나 에러가 나는 경우, 혹은 seg. fault라도 이전 어딘가에서 실행을 멈추고 값을 확인해 보는 경우 등을 위해서 breakpoint가 필요합니다.

함수 이름이나 라인 번호를 이용해 브레이크 포인트를 걸 수 있습니다.

(gdb) b __os_malloc --> __os_malloc() 함수에 진입할 때 정지합니다.

(gdb) b record.c:123 --> record.c의 123라인에서 정지합니다.

두번째 예제의 경우 해당 라인이 if 절 안에 있거나 하면 조건이 안 맞을 경우는 멈추지 않습니다. break가 걸리면 seg. fault와 마찬가지로 변수 값 등을 검사해 볼 수 있습니다.

또한 rbreak라는 명령은 정규 표현식을 써서 패턴 매칭되는 함수에 동시에 브레이크를 걸 수도 있습니다.


4. watch

watch 명령은 특정 변수 값을 살펴보는 명령입니다. 물론 p로 살펴 볼 수도 있지만 watch는 약간 용도가 다릅니다. 지정한 변수나 주소의 데이터가 바뀔 때 자동으로 break가 걸립니다.

예를 들면 func(char *c) 함수에서 진입 시에는 c가 이상이 없는데 함수 수행도중 NULL이 들어가면서 동작이 이상하게 된다면?

break func -> r(run) -> 브레이크 걸림 -> watch c -> c(continue)

이런 과정을 거치면 c의 값이 변하는 장소에서 딱 멈추게 됩니다.

출력값은 Old value, New Value와 frame 정보, 소스 라인 등이 나옵니다.
말했다시피 변수 이름 대신 주소를 줄 수도 있습니다.

그리고 변수의 경우 함수나 블록을 빠져나가면 해당 변수의 스코프를 벗어나게 되므로 watchpoint가 자동으로 사라지게 되며 나중에 다시 설정해야 합니다. 헷갈리지 마세요


5. info

각종 정보들을 확인 가능합니다. 몇 가지만 소개합니다.

info b, 브레이크 포인트와 와치 포인트들을 모두 볼 수 있습니다.
info locals, 로컬 변수들을 보여줍니다.
info variables, 글로벌과 정적 변수들을 보여줍니다.
info threads, 쓰레드와 id들을 보여줍니다(멀티쓰레딩 디버깅시 필요)

그 외의 것들은 help info 해보세요.


6. etc.

그 외 잡다한 명령들입니다.
예를 들어 내가 원하는 브레이크 지점이 루프를 100번 돈 후에 에러가 나는 경우라면, 혹은 다른 브레이크 포인트를 걸고 잠시 원래의 브레이크 포인트를 정지하고 싶다면...?

ignore는 원하는 횟수만큼 브레이크 포인트를 무시하게 해줍니다.
info b로 봤을 때 1번 브레이크 지점이 100번 지나간 후에 필요하다면

(gdb) ignore 1 100

이렇게 해주면 됩니다.

아니면 위의 경우가 for 문의 i 변수가 100이 되었을 때라면 cond(condition)를 이용해도 됩니다.

(gdb) cond 1 i == 100

어떤 조건을 걸었는지는 info b로 모두 확인 가능합니다. 또한 조건문은 C 스타일이 모두 가능하며 함수도 사용 가능합니다. "cond 8 foo(i) > bar(rand())" 이런 식으로나 "cond 2 key->size == 100 && key->ptr == 0x801234" 이렇게도 가능합니다.

브레이크 포인트를 잠시 무력화 하거나 다시 재생하려면 disable, enable 문을 쓰면 됩니다.
disable 2 4(2번 4번 정지), disable 2-5(2~5번 정지) 식으로도 쓸 수 있습니다.

프로그램 시작이 run(r)인건 다 아시죠? 정지되었던 프로그램을 계속 돌리는 것은 continue(c)입니다.
그리고 next(n)는 문장 하나씩 실행합니다. 브레이크 포인트에서부터 하나씩 진행해가며 문제의 원인을 찾아볼 수 있습니다.

하지만 루프가 100번을 돌면 next는 100*문장 수 만큼 해야 하므로 매우 피곤해집니다. 이 때 루프를 빠져 나가려면 until 명령을 사용합니다. 기본적으로는 next와 똑같이 한 줄씩 수행하지만 루프의 마지막에서는 루프가 끝날 때까지 실행한 후 루프 다음 문장으로 갑니다.

또한 advance 명령은 뒤에 라인 번호를 주어 해당 라인까지 실행을 시키게 합니다.

.....

제가 많이 쓰는 명령들을 중심으로 소개했기 때문에 정식 gdb 매뉴얼과는 많이 다를 수 있으며 세세한 점들은 제가 잘 모를 수도 있습니다. 좀 더 품위있고(?) 효과적인 디버깅을 위해 한 번 익혀 보시기 바랍니다.

여담이지만 vi를 제가 처음 익힐 때는 절대 다른 에디터를 쓰지 않고 아무리 느려도 vi만을 사용했었습니다. GDB 역시도 printf를 쓰지 않고 GDB만으로 디버깅하려고 당분간 노력한다면 금방 익숙해질 것입니다.

디버깅에 생산성 향상이 있길 빕니다.
다음에 더 고수가 되면 후속편을 올려 보도록 하겠습니다 -_-
이올린에 북마크하기(0) 이올린에 추천하기(0)
Trackback 0 Comment 2
  1. BlogIcon sec 2008/05/10 08:27 address edit & del reply

    글좀 퍼가도 될까요? ^^

    • BlogIcon eminency 2008/05/10 16:55 address edit & del

      넵, 원출처만 밝혀 주세요 ^^;