gdb란?
- gdb는 GNU에서 만든 디버거이다.
- gdb는 C, C++, Objective-C, Java, Go, Rust 등의 많은 언어를 지원하며, arm, x86, IA-64 등 수 많은 아키텍쳐를 지원한다.
gdb를 이용한 간단한 디버깅
아래와 같은 코드가 있다고 가정하자.
// main.c
#include <stdio.h>
~
위 코드를 다음과 같은 명령어로 컴파일하자.
gcc -g -o main main.c
- gdb를 이용해 컴파일하려면
-g
옵션을 붙여야 한다. 이는 컴파일 시 실행파일에 여러 디버깅 정보(심볼 문자열, 심볼 주소, 컴파일에 사용된 소스파일, 인스트럭션에 해당하는 코드 정보 등)를 삽입하는 옵션이다.
-g
옵션을 주고 컴파일 시 최적화 옵션(-O
등)을 사용하지 않는 것이 좋다. 이는 코드 및 어셈블리 코드에 변형을 가하기 때문에 디버깅이 힘들어질 수 있다.
이 상태로 ./main
명령어를 이용해 실행한다면 오류가 발생하는 것을 확인할 수 있다.
실행 및 종료
gdb를 이용해 프로그램을 load하는 방법은 아래와 같다.
gdb {program_name}
: gdb main
gdb {program_name} {core_file_name}
: gdb main core.1408
gdb {program_name} {실행중인 PID}
: gdb main 1408
gdb를 종료하는 방법은 아래와 같다.
gdb를 이용해 프로그램을 load한 상태에서 프로그램을 실행시키는 방법은 다음과 같다.
r
r arg1 arg2
: arg1과 arg2를 프로그램 인자로 실행
k
: 프로그램 실행 종료
현재 지점에서 라인 단위, 또는 다음 브레이크 포인트까지 진행하는 명령어는 다음과 같다.
명령어 |
설명 |
s (step) |
현재 행 수행후 멈춤. |
함수호출 지점에서는 함수 내부로 들어감 |
|
s 6 |
s를 6번 수행한 것과 같음 |
n (next) |
현재 행 수행후 멈춤 |
함수 호출 지점에서는 함수 내부로 들어가지 않고 수행 후 다음 행으로 감 |
|
n 6 |
n을 6번 수행한 것과 같음 |
c (continue) |
다음 브레이크 포인트를 만날 때 까지 수행 |
u (until) |
현재 loop문에서 빠져나올 때 까지 수행 |
for |
|
finish |
현재 함수를 수행하고 빠져나감 |
return |
현재 함수를 수행하지 않고 빠져 나감 |
return 1 |
현재 함수를 수행하지 않고 빠져 나감 |
return 값으로 1을 줌 |
|
advance 20 |
현재 파일의 20번 라인을 만날 때 까지 진행 |
advance file.c:20 |
file.c 파일의 20번 라인을 만날 때 까지 진행 |
소스 보기
gdb를 이용해 프로그램을 load한 뒤 소스 코드를 보는 방법은 다음과 같다.
명령어 |
설명 |
l |
main 함수를 기점으로 소스 출력 |
l 10 |
Line number 10 을 기준으로 출력 |
l func |
func 함수의 소스를 출력 |
l - |
출력된 행의 이전 행을 출력 |
l file.c:func |
file.c파일의 func 함수 부분을 출력 |
l file.c:10 |
file.c의 Line number 10 부근을 출력 |
set listsize 20 |
l 명령의 기본 출력 코드수를 20라인으로 설정 |
브레이크 포인트
프로그램 수행 시 특정 지점 또는 특정 조건에서 break하고자 하는 경우가 있다. 이 때 다음 명령어를 사용하면 된다.
명령어 |
설명 |
b func |
func 심볼의 시작 부분(함수 내부 진입 전)에 브레이크 포인트 설정 |
b 10 |
10번 라인에 설정 |
b file.c:func |
file.c 파일의 func 심볼에 설정 |
b file.c:10 |
file.c 파일의 10번 라인에 설정 |
b +2 |
현재 라인에서 2개 라인 이후 지점에 설정 |
b -2 |
현재 라인에서 2개 라인 이전 지점에 설정 |
b *0x8040000 |
0x8040000 주소에 설정(어셈블리 디버깅 시 사용) |
b 10 if myVar == 0 |
10번 라인에서 myVar의 값이 0 일때 브레이크 |
rb func* |
func*에 해당하는 모든 심볼에 설정 |
rb ^fun |
fun으로 시작하는 모든 심볼에 설정 |
rb TestClass:: |
TestClass에 해당하는 모든 심볼에 설정 |
브레이크 포인트를 설정하게 되면 브레이크 포인트마다 고유한 숫자(1부터 시작)가 부여된다. 이 때, 해당 브레이크 포인트에 추가적인 조건을 걸고 싶을 때 다음과 같은 명령어를 사용하면 된다.
condition {brakpoint_ID} myVar == 0
condition {brakpoint_ID} myFunc(0) > 0
: myFunc(0)의 결과값이 0보다 클 때 브레이크
ignore {brakpoint_ID} 100
: 해당 브레이크 포인트가 100번 지나갈 때 까지 무시. 이는 loop문에서 유용하다.
이러한 브레이크 포인트는 gdb가 종료될 때 까지 유효하다. 따라서 한 번만 사용하는 브레이크 포인트는 b
명령어 대신 tb
명령어를 사용하면 된다.
브레이크 포인트 제거
명령어 |
설명 |
cl func |
func 함수의 시작 부분에 브레이크 포인트 지움 |
cl 10 |
10번 라인의 브레이크 포인트 지움 |
d |
모든 브레이크 포인트를 지움 |
disable br |
모든 브레이크 포인트 비활성화 |
disable br 1 3 |
1번, 3번 브레이크 포인트 비활성화 |
enable br |
모든 브레이크 포인트 활성화 |
enable br 1 |
1번 브레이크 포인트 활성화 |
enable br once 1 |
1번 브레이크 포인트를 활성화하고, 한 번만 걸리고 비활성화되게 설정 |
enable br delete 1 |
1번 브레이크 포인트를 활성화 하고, 한 번ㅁ나 걸리고 제거되게 설정 |
현재 설정되어있는 브레이크 포인트를 보려면 다음의 명령어를 사용하면 된다.
와치 포인트 설정
특정 변수의 값이 바뀔 때 마다 브레이크를 걸려면 와치 포인트를 사용한다. 이 때, 지역변수에 대한 설정은 해당 지역 변수의 스코프 내에서 설정하여야 한다.
이는 다음과 같이 사용할 수 있다.
watch {myVariable}
: myVariable에 값이 쓰여질 때 마다 브레이크
rwatch {myVariable}
: myVariable의 값이 읽혀질 때 마다 브레이크
awatch {myVariable}
: myVariable에 값이 쓰여지거나 읽혀질 때 마다 브레이크
변수 및 레지스터의 값 검사
명령어 |
설명 |
info locals |
현재 스코프에서 모든 지역변수의 값들을 확인 |
info variables |
모든 전역변수의 값들을 확인 |
Non-debugging 심볼들도 포함되어 출력됨. |
|
info (all-)registers |
모든 레지스터(MMX 레지스터 포함)의 값을 확인한다. |
p myVariable |
myVariable의 값을 출력 |
p *ptr |
|
p **ptr |
포인터 변수의 경우 주소값이 아닌 해당 주소에 저장되어있는 값을 확인 |
p *ptr@4 |
ptr이 가리키는 것이 4개 크기의 배열일 경우 4개의 값을 모두 출력 |
p $eax |
특정 레지스터의 값을 확인 |
p 'file.c'::myVariable |
특정 파일의 전역변수(myVariable)을 확인 |
p myFunc::myVariable |
특정 함수의 static 변수의 값을 확인 |
p (char *)ptr |
특정 형식으로 변환해서 값을 출력 |
void *ptr = "abc"; 와 같은 경우 |
|
p myVariable = 10 |
myVariable의 값을 10으로 설정 |
p ptr + 4 |
특정 byte-offset 위치를 지정하여 출력 |
p/{출력 옵션} myVariable |
여기서 출력 옵션에 들어갈 수 있는 값을 다음과 같다. |
- t : 2진수로 출력 |
|
- o : 8진수로 출력 |
|
-d : 부호가 있는 10진수로 출력 |
|
-u : 부호가 없는 10진수로 출력 |
|
-x : 16진수로 출력 |
|
-c : 최소 1바이트 값을 문자형으로 출력 |
|
-f : 부동 소수점 형식으로 출력 |
|
display myVariable |
변수의 값을 매번 디스플레이한다. |
n , s 명령어를 통해 라인 단위로 실행하며 값을 확인하고자 할 때 유용 |
|
display/{출력 옵션} myVariable |
출력 옵션에 맞추어 값을 출력 |
p 명령어의 출력옵션과 동일 |
|
undisplay {display_ID} |
디스플레이 설정을 없앤다 |
disable display {display_ID} |
디스플레이를 비활성화 한다 |
enable display {display_ID} |
디스플레이를 활성화 한다 |
스택 프레임의 정보를 이용해 디버깅
보통 프로그램을 실행하여 특정 부분에서 오류가 발생해 종료된 경우 스택이 깨지지 않은 이상 스택 프레임은 종료될 당시의 상태를 유지한다. 이 때, bt(backtrace)
명령어를 통해 전체 스택프레임 정보를 확인할 수 있다. 이와 관련하여 스택프레임에 대한 자세한 명령어들은 다음과 같다.
명령어 |
설명 |
info frame |
스택 프레임의 정보를 출력 |
frame {frame_number} |
특정 스택 프레임으로 변경 |
up |
상위 스택 프레임으로 이동 |
up N |
N번 상위 스택 프레임으로 이동 |
down |
하위 스택 프레임으로 이동 |
info args |
함수가 호출될 때 인자를 출력 |
info locals |
함수의 지역 변수를 출력 |
info catch |
함수의 예외 핸들러를 출력 |
기타 유용한 명령어
어셈블리 코드 보기
disas myFunc
: func 함수의 어셈블리 코드를 보여준다.
call 명령을 통한 함수별 모듈 테스트
call myFunc(123)
: 현재 상태에서 임의로 함수를 출력한다. 이는 리턴값을 추가로 출력해준다.
jump 명령을 통한 임의의 행, 주소, 함수로 분기
jump *0x0804000
: 해당 주소로 무조건 분기
jump 10
: 10번 라인으로 분기
jump myFunc
: myFunc 함수로 분기
프로세스에 시그널 보내기
signal SIGKILL
: 현재 수행중인 프로그램에 signal을 보낸다.
메모리 특정 영역에 값을 설정
set {{type}}{address} = {value}
:
이는 set {int}0x804000 = 100
와 같이 사용되며 특정 메모리 영역에 값을 설정하게 해 준다.
또한 이는 p *0x804000=100
와 같은 결과를 가져다 준다.
gdb 환경 설정 관련 명령
명령 |
설명 |
info set |
set 명령으로 설정가능한 명령어들의 리스트를 보여준다. |
set print array on |
array를 출력할 때 여러 줄로 출력해 준다.
$2 = {0x804000 "one", 0x804004 "two", 0x804008 "three", 0x0} |
info functions |
함수들의 리스트를 출력 |
info types |
선언된 타입들을 출력 |