반응형

리눅스에서 사용하는 디버거인 gdb 에서 알아보는 시간을 가져볼까 합니다. 

 

1. gdb 실행방법 

2. gdb 다양한 명령어들의 예시와 설명 

3. 한눈에 보는 gdb 명령어 

순으로 정리되어있습니다.  

 

명령어 정리된 것만 보고 싶으신 분들은 쭈욱 내려가서 3번만 보시면 되겠습니다. 

 

 

1. gdb 실행방법

 

위 예시처럼

gcc 를 이용하여 c소스파일을 컴파일 할 때 옵션으로 -q를 사용합니다.

그리고 gdb -q 실행파일이름 을 입력해주시면 gdb가 실행되면서 디버깅 모드로 들어갑니다. 

 

2. gdb 다양한 명령어들의 예시와 설명 

 

또한 위 예시에 보듯이 

list 명령어를 입력하면 C 소스파일의 내용이 출력됩니다.

 

 

또한 위 예시와 같이 disassemble main 을 입력하면

main 함수의 어셈블리 명령어가 나옵니다. 여기서의 어셈블리어는 sparc 어셈블리어입니다.

인텔 어셈블리어를 이용하실분은 set dis intel 이라는 명령어를 입력하면 됩니다.

 

번 입력하기 귀찮으신 분들은 아래 예시처럼 환경설정을 intel로 바꿔버리시면 됩니다. 

 

 

이렇게 입력하시면 gdb 를 실행하면 자동으로 set dis intel 명령어를 입력하게 됩니다.

 

 

break 명령어는 특정 위치에 중지점을 설정합니다

break 다음에 줄 수를 입력하셔도 되고 함수이름을 입력하셔도 됩니다.

위의 예제에서는 break main 해서 main 함수에 중지점을 설정하고

run 명령어를 이용하여 프로그램을 실행시킵니다.

만약 break 명령어로 중지점을 설정하지 않고 run 명령어를 실행시키면 

그냥 프로그램이 실행을 끝내 버립니다. 

 

 

또한 예제에 보면 info register 라는 명령어가 있습니다.

이 명령어는 현재 중지점에서의 모든 레지스터들의 값을 보여주는 명령어이며 

줄여서 i r 로 쓸수 있습니다. 

다양한 레지스터들이 나오지만 이 포스트는 레지스터를 설명하는 포스트가 아니고 

gdb를 설명하는 포스트이기 때문에 설명은 생략하겠습니다. 

 

또한 i r 레지스터이름 처럼 입력하여 특정 레지스터의 값만 확인할 수 있습니다.

 

그 내용을 아래 예제에서 확인 할 수 있습니다.

 

위 예제에서 사용한 eip 라는 레지스터는

 

현재 실행되는 어셈블리 명령어를 나타내는 레지스터입니다 

(아마도 pc(프로그램 카운터)과 같은것 같습니다) 

i r eip 명령어의 결과로 0x8048384 라는 값이 나왔습니다.

저 뜻인즉슨 eip 레지스터 안에 값이 0x8048384 가 들어있다는 것입니다.

 

gdb에서는 해당 주소의 메모리에 어떤 값이 들어있는지 확인할 수 있는데

그 명령어는 examine을 줄인 x 명령어를 통해 확인할 수 있습니다.

 

x (/ 숫자 진법 바이트) 주소값 or $레지스터이름  

 명령어를 이용하시면 해당 레지스터가 가리키는 메모리의 주소안에 값을 확인할 수 있습니다.

위에 예제에서 진법을 나타내는 옵션으로 /o, /x, /u, /t의 옵션이 나왔는데 

각각 8진법, 16진법, 부호없는10진법, 2진법으로 나타내라는 의미입니다.

예제에서 결과를 확인하실 수 있습니다.



 

또한 각 옵션 앞에 숫자를 붙이면 해당 메모리 주소에서부터 그 숫자 갯수만큼

다음 메모리 주소를 접근하여 내용을 확인할 수 있습니다.

진법옵션 뒤에 바이트 옵션이 붙었는데 /b, /h, /w, /g 로서

단일 바이트, 2바이트 하프워드, 4바이트 워드, 8바이트 자이언트 를 나타냅니다.

가끔 워드가 2바이트로 되는경우가 있는데 그 경우는 더블워드(DWORD)가 4바이트 크기입니다.

 

위에서 x/2x $eip 와 x/8xb $eip, x/8xh $eip 의 16진수를 잘 보시면

처음에는 0x00fc45c7 에서

0x45c7 0x00fc

0xc7 0x45 0xfc 0x00 이렇게 변하는것을 볼 수 있습니다.

 

현재 gdb를 실행하는 프로세서는 인텔 프로세서 여서

바이트 순서를 little-endian 바이트 순서로 저장을 합니다

 

즉 메모리의 제일 낮은 위치에 가장 낮은 자릿값이 들어있는 셈입니다.

4바이트 크기로는 0x00fc45c7 이지만

실제로 이것을 1바이트 크기로 끊어서 표현하면

메모리 낮은 주소부터 0xc7 0x45 0xfc 0x00 메모리 높은주소

이런형태로 저장이 된다는것을 유의하시면 되겠습니다.

 

 

위에서 설명했던 진법옵션과 바이트옵션 말고 또다른 옵션이 있는데 i 입니다

x/i 메모리주소 or $레지스터이름 을 사용하면 해당 레지스터의 메모리 주소에 들어있는 값들을

어셈블리 명령어로 출력해 줍니다.

 

중간에 print $ebp-4 라는 명령어를 쓰는데

이것은 해당 레지스터를 임시로 저장한다는 뜻입니다

명령어의 결과로 $1 = (void*) 0xbffff814가 나왔는데

이는 ebp-4 가 가리키는 주소에 들어있는 값을 $1로 임시 저장한다는 뜻이며

이후 x/dw $1 명령어로 다른 레지스터와 마찬가지로 쓸 수 있음을 보여줍니다.

 

nexti 명령어는 next instruction으로 4비트 다음 주소로 이동하는 명령어입니다.

(즉 어셈블리 한 instruction 단위로 움직입니다.)
 

 

위 예제에서 5번째 줄에 nexti 명령어를 입력한 결과로

9 printf("Hello, world!\n"); 명령어를 실행하고

현재 eip 의 값을 확인하니 mov DWORD PTR [esp], 0x8048484

라는 명령어가 있습니다

여기서 0x8048484 주소값을 확인해 보았습니다.

진법 옵션에서 'c' 옵션을 사용하면 아스키 코드에 해당하는 숫자를 한 문자 단위로 보여줍니다.

또한 's' 옵션을 이용하면 해당 주소값에 들어있는 문자열을 통째로 보여줍니다

 

quit 명령어는 gdb를 종료하고 쉘로 나갈때 이용합니다.

 

 

위 예제는 보기가 조금 어지러울 수도 있지만 (gdb) 가 있는 줄을 기준으로 잘 살펴보시면

첫번째 중지점 main 에서 시작하여 eip 레지스터의 값을 확인한 결과가 0x80483c4 였습니다.

그리고 bt 라는 명령어를 사용하는데 이 명령어는 현재 스택에 들어있는 내용을 확인합니다.

 

c언어에서 함수를 호출하여 사용할때

eip 레지스터의 값은 처음에는 main에서 밑으로 죽 내려가다가

함수를 호출하는 시점에서 해당 함수가 있는 메모리공간으로 점프를 하게 됩니다.

점프를 하면서, 이전에 있었던 main함수의 위치를 스택에 저장해 놓고 함수로 점프를 합니다.

 

위의 예제에서 최초 bt를 실행하였을때는

#0 main() 만 있습니다.

 

그리고 cont 명령어를 실행하는데 이 명령어는 continue 의 간단한 표현으로

현재 위치에서 다음 중지점까지 이동합니다

다음 중지점은 strcpy 함수를 호출하는 부분에 설정해놓았는데

그곳에서 eip 레지스터의 값을 확인한 결과 0xb7f076f4로 기존 0x80483c4에서 

엄청나게 이동한것을 알 수 있습니다. 

여기서 bt 명령어를 실행해보면

#0 0xb7f076f4 in strcpy

#1 0x080483d7 in main 과 같은 형태로

스택의 바닥에 점프했었던 main 함수의 주소를 저장해놓은 것을 알 수 있습니다. 

즉,  strcpy의 실행이 끝나면 스택을 확인하여 저장해놨던 주소로 돌아가는 것입니다. 

 

 

3. 한눈에 보는 gdb 명령어 

 명령어 이름

 명령어가 하는 일

list

디버깅중인 프로그램의 C언어 소스를 보여준다 

disassemble

 디버깅중인 프로그램을 어셈블리 명령어로 보여준다

set dis intel

disassemble 명령어 사용시 intel 어셈블리어로 보여준다

break

 명령어 뒤에 오는 줄수나 함수에 중지점을 설정한다

run

 디버깅을 시작한다

info rigister (i r)

 뒤에오는 레지스터가 가리키는 주소값을 보여준다 (입력 없을시 레지스터 전체 보여줌)

examine (x)

 뒤에오는 레지스터가 가리키는 주소값에 들어있는 값을 보여준다

x / (숫자) (t, o, u, x, c, s) (b, h, w, g)

레지스터가 가리키는 주소값부터 시작하여 숫자 갯수만큼 값을 보여준다

t : 2진수, o : 8진수, u : 부호없는 10진수, x : 16진수, c : 아스키값에 해당하는 문자

s : 아스키값에 해당하는 문자열, b : 1바이트, h : 2바이트, w : 4바이트, g : 8바이트 

x/i

 레지스터가 가리키는 주소값에 들어있는 값을 어셈블리 명령어로 보여준다

print

 레지스터의 값을 임시변수로 저장한다 ($숫자 형태)

cont

 현재 중지점에서 다음 중지점까지 프로그램을 실행시킨다

nexti

 현재 위치에서 어셈블리 인스트럭션 하나를 실행시킨다

bt

 현재 프로그램 스택에 있는 내용을 보여준다

quit

 gdb를 종료한다

 

포스팅을 많이 했으면 좋겠으나.. 너무 바빠서..

나중에 또 봅시다.

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기