gdb watch를 이용한 메모리 감시 기능 – 특정 주소나 변수의 변경을 Hardware 적으로 감시하기
제 블로그의 [프로그래밍 Tip]쪽에 보시면 아마도 스택침범(Stack Overflow)에 대한 강의가 하나 있을겁니다.
그 강의와 이번에 알려드릴 gdb 의 watch 기능을 함께 공부해보시면 밝혀내기 힘든 메모리 깨먹는 버그에 대해 한층 원인 분석이 쉬우실 겁니다.
"gdb 의 watch 기능은 특정 주소 영역의 값을 감시하여, 해당 주소 영역의 값이 변경이 되었을 경우 이를 알려주는 기능"입니다.
만약, 특정 변수가 있는데 이 변수의 값이 어느 순간부터 원하지 않는 이상한 쓰레기(garbage)값으로 할당되어 연속적인 문제를 일으킬 경우, 이 변수 주소 영역을 watch 를 이용하여 감시하면 변경의 원인이 되는 코드를 손쉽게 찾을 수 있습니다.
다음의 코드를 보시죠.
위에서 언급한 강의쪽에 나온 소스를 약간 수정한 버전이며 stack 변수를 깨먹는 버그를 안고 있는 코드입니다.
#include <stdio.h> int count = 0; int main(int argc, char **argv) { char buf[8]; if( argc > 1 ) { int x = 1; printf("before [%d]n", x); char * s = argv[1]; char * d = buf; while(*s != '') { ++count; *d++ = *s++; } printf("after [%d]n", x); } return 0; }
이 코드를 컴파일한 후 수행하면 아래와 같이 변수 x 의 영역이 쓰레기 값으로 변해버립니다.
원인은 위에 언급한 강의쪽에 상세하게 기록되어 있으니 한번 읽어보시기 바랍니다.
sh> gcc x.c sh> ./a.out 123456789012345678901234567890 before [1] after [909456435]
그럼 이 변수의 값을 이렇게 바꾸어버리는 코드는 어디일까를 gdb 의 watch 를 통해 찾아보겠습니다.
gdb 의 watch 기능은 gdb 의 command 창에서 아래와 같이 수행하면 됩니다.
wa[tch] *(변수형 *)주소값
example> wa *(int *) 0x7fffffffdd3c
sh> gcc -g x.c sh> gdb ./a.out ... (gdb) b main # main 함수에 breakpoint Breakpoint 1 at 0x4004d3: file x.c, line 9. (gdb) r 012345678901234567890 # buf 크기를 넘는 string 을 인자로 주고 실행 Starting program: /home/bitmyer/tmp/a.out 012345678901234567890 Breakpoint 1, main (argc=2, argv=0x7fffffffde38) at x.c:9 9 if( argc > 1 ) Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6_3.6.x86_64 (gdb) n 11 int x = 1; (gdb) 13 printf("before [%d]n", x); (gdb) display x # 변수 x 의 값을 매번 찍도록 함. 1: x = 1 (gdb) n before [1] 15 char * s = argv[1]; 1: x = 1 (gdb) n 16 char * d = buf; 1: x = 1 (gdb) n 18 while(*s != '') 1: x = 1 (gdb) display count # 변수 count 의 값을 매번 찍도록 함. 2: count = 0 (gdb) p &x # 변수 x 의 주소를 알아냄 $1 = (int *) 0x7fffffffdd3c (gdb) wa *(int *)0x7fffffffdd3c # x 변수 주소에 watch point 를 걸어둠 (int 형 주소) Hardware watchpoint 2: *(int *)0x7fffffffdd3c (gdb) n 20 ++count; 2: count = 0 1: x = 1 ... 중략 (gdb) n 21 *d++ = *s++; 2: count = 13 1: x = 1 (gdb) n Hardware watchpoint 2: *(int *)0x7fffffffdd3c Old value = 1 # 원래 값과 바뀐 값을 알려줌 New value = 50 0x000000000040052c in main (argc=2, argv=0x7fffffffde38) at x.c:21 # 변경을 일으킨 코드 위치 21 *d++ = *s++; 2: count = 13 1: x = 50 (gdb) p *d $2 = 50 '2' (gdb) p *s $3 = 50 '2'
x 라는 int 형 변수의 주소에 watch point 를 걸어두고 프로그램 실행을 계속하면, while loop 를 돌면서 인자로 들어온 문자 하나하나를 local 배열 buf 에 복사하다가 어느 순간 int 영역까지 넘어서서 값을 변경하게 되어 감시에 걸리게 됩니다.
이 때 debugger 는 어디에서 값 변경을 일으켰는지를 함수와 파일이름, 라인수로 알려줍니다.
참 편리하지 않나요 ?
이 메모리 value 감시기능인 watch 기능은 특히 멀티스레드(multi-thread) 프로그램에서 문제 발생 시 굉장히 유용하게 써먹을 수 있습니다.
멀티스레드 프로그램에서는 아시다시피 다수의 스레드가 같은 영역이나 변수를 공유하기도 합니다.
이 때 예기치 않게 이러한 문제를 발생시키는 경우가 굉장히 많은데 원인을 밝혀내려면 많은 시간이 필요할 때가 많습니다.
여러분은 이제 특정 변수의 값이 원하지 않는 값으로 갑자기 변경이 되었을 때, 이 값을 예전처럼 printf 로 열나게 찍어보려고 하지 마시고 watch 기능을 애용하시기 바랍니다. 디버깅 시간을 1/10 로 줄일 수 있습니다. ^^