GDB 디버거

GDB 디버거는 GNU Project Debugger의 약자로

GNU toolchain 핵심 구성요소 중에 하나입니다.

 

www.sourceware.org 에서는 GDB의 정의를

GDB, the GNU Project debugger, allows you

to see what is going on 'inside' another program

while it extcutes (다른 프로그램에서 무슨 일이

일어나고 있는지 알 수 있게 해준다) 고 합니다.

 

GDB는 디버거입니다.

 

- 프로그램을 시작하고 그 행동에 영향을

미칠 어떤 것을 지정한다.

 

- 특정 조건에서 프로그램을 정지시킨다

 

- 프로그램이 정지했을 때 무슨일이

일어나는지 검사한다.

 

- 프로그램에서 뭔가를 변경해서

하나의 버그를 수정한 결과를 실험하고

다른 문제를 알 수 있게 한다

 

GDB가 지원하는 언어는 Ada, Assembly,

C, C++, Go, Rust 등 다양합니다.

리눅스 시스템은 대부분 C언어 개발되어

구글링 해보면 C프로그램을 GDB로 디버그하는

소스를 많이 찾아 볼 수 있는데요.

이런 다양한 언어들도 다 벗겨보면

결국 어셈블리어로 변환되서 기계어

(오브젝트 코드)로 변환되므로 본질적으로

하나의 디버거가 많은 언어를 취급할 수 있습니다.

 

 

*NASM 컴파일러를 사용한 x86-64

어셈블리어 프로그램은 GDB와 함께

사용하는게 매우 자연스럽습니다.

 

어셈블리어는 어떤 면에서 프로그램이

취할 수 있는 가장 밑바닥인 샌드박스에

가깝기 때문에 목적이 없는 코드도

그 자체로 의미가 있습니다.

(프로그래밍의 GTA같은 것,

자유도가 무한하고 목적은 필요없다)

 

레지스터와 메모리를 직접 조작하고

시스템콜로 운영체제에 요청하는

그 자체가 시스템을 운영하는 과정입니다.

 

GDB는 레지스터와 플래그, 메모리의

상황 등 프로그램의 모든 실행현황을

CPU 인스트럭션 하나가 진행될 때 마다

적나라하게 보여주기 때문에

프로그램이 제대로 동작하고 있는지

매우 상세하게 알 수 있습니다.

 

*단점은 사용법이 완전히 쉽지는 않습니다.

GDB 사용법만 책 한권이라...

NASM 컴파일러 메뉴얼도 책 한권,

어셈블리어 교재도 책 한권,

GDB 도 책 한권... 이러다 보면 끝이 없어

좌절하기 쉽지만 GNU toolchain 에는

공통적인 내용도 많이 나와서 어느 시점을

넘어가면 아주 포기하고 싶을 정도는 아닙니다.

그냥 시간이 좀 걸릴 뿐입니다.

 

프로그래밍 격언에는 이런게 있습니다.

If you give someone a program, you will frustrate them for a day;
if you teach them to program, you will frustrate them for a lifetime.

 

당신이 누군가에게 프로그램을 준다면

당신은 그들을 하루종일 좌절시킬 것이다.

 

당신이 그들에게 프로그램하는 법을 가르친다면,

당신은 그들을 평생동안 좌절시킬 것이다.

 

*이 글을 쓴 사람이 얼마나 고통을 당했나

그 고통이 잘 전달이 됩니다. 지는 프로그래밍을

배워서 일평생 맥임을 당했다는 거지요.

 

어셈블리어는 약간 그런 측면이 있습니다.

물론 지금 시대는 어셈블리어로 최신의

상업용 프로그램을 만드는 시대는 아닙니다.

C언어 컴파일러 조차 최적화가 잘되어 있어

어슬픈 어셈블리어 보다 C언어의 성능이

더 뛰어나다는 주장이 좀 대세입니다.

벤치마킹 도구를 사용해 보면 프로그램의 성능이란

복합적인 요소에 의해 결정되기 때문에

단순히 코드 몇개를 잘 짰다고 결정되지는 않습니다.

어셈블리어가 그냥 빠르다는 인식이 있지만

그것도 프로그램의 최종사용자의 목적에 맞게

최적화가 잘 되었을 때 가능한 이야기입니다.

 

다만 그렇다고 어셈블리어가 과거의 유물이냐?

그렇지는 않습니다. 리눅스는 커널을 비롯해

대부분 GNU C컴파일러로 개발되었고

C언어 코드를 까보면 결국은 어셈블리어로 되어

있습니다. CPU의 종류에 따라

IA(instruction architecture)가 다르기 때문에

구조를 알기 위해서는 그 CPU에 맞는

코드를 다시 컴파일해야 합니다.

 

*GDB 사용법 기초 - 레지스터

그럼 GDB를 시작해보겠습니다.

다음과 같은 어셈블리 코드를 작성합니다.

 

뭐 아무내용이 없습니다만, 목적은 레지스터가

어떻게 작동하는지 알기 위해서 입니다.

 

section .data

section .text
    global _start

_start:
    ;add
    mov rax, 3
    add rax, 5
    xor rax, rax

_last:
    mov rax, 60
    mov rdi, 1
    syscall

GDB에서 디버그하려면 컴파일 옵션에

디버그 정보를 포함시켜야 합니다.

 

다음의 옵션 중에 -g -F dwarf 가 옵션입니다.

nasm -g -F dwarf -f elf64 main.asm -o main.o -l main.lst

nasm -f elf64 -y 로 보면 dwarf, stabs 포맷이

있습니다. dwarf 가 stabs 보다 향상된

버전이므로 dwarf 를 선택했습니다.

(elf는 Executable and linkable format의 약자,

elf64는 실행가능하고 링킹가능한 64비트 포맷)

 

Makefile을 아래와 같이 만들어 두면 편리합니다.

output: main.o
	ld main.o -o output

main.o: main.asm
	nasm -g -F dwarf -f elf64 main.asm -o main.o -l main.lst

clean:
	rm main.o main.lst output

 

그렇다면 gdb를 사용해 보겠습니다.

아마 build-essential 을 설치했다면 설치가

되었을 텐데 gdb가 안되면 다음과 같이 설치합니다.

같이 설치되는 의존성 패키지는 C 라이브러리

종류니까 같이 설치해주면 됩니다.

 

sudo apt-get install gdb

 

위에서 빌드한 output 파일을 gdb로 엽니다.

 

gdb output

 

브레이크 포인트를 break _start로 설정하고

run 합니다.

break _start

run

run 실행하면 소스코드 라인인 8과

인스트럭션인 mov, rax, 3 이 보입니다.

이것은 바로 다음에 실행할 인스트럭션입니다.

 

GDB 어셈블리어 디버거

 

레지스터의 값을 보려면 info register입니다.

rax 부터 gs 플래그까지 보여줍니다.

레지스터 하나씩 볼려면 'info register 레지스터' 입니다.

info register

다음 인스트럭션은 step 으로 실행합니다.

info register rax와 번갈아 가며 실행해봅니다.

rax에 3을 넣고 add 5를 하니 8이 됩니다.

이렇게 하나의 명령(instruction)에 따른

결과 값을 확인할 수 있습니다.

 

rax 60 시스템콜은 프로그램 종료입니다.

rdi가 1이면 exit code 1을 리턴합니다.

 

GDB 명령어에는 꼼수가 있는데

break 를 b 처럼 한글자 숏컷이 있습니다.

위의 커맨드를 간략하게 하면...

 

break _start -> b _start

info registers -> i r

infor register rax -> i r rax

step -> s

 

한번 개념을 잡은 후에는

타이핑을 줄이는게 효율적입니다.

변수와 메모리

변수는 레지스터와 문법이 다르게

동작하기 때문에 새로운 예제로 보겠습니다.

 

section .data에는 초기화된 정적 변수들이 있고

section .bss에는 초기화되지 않은 정적 변수가 있습니다.

 

section .data

    ;dd for 4 bytes
    var1 dd 7772
    var2 dd 15700

    ;dq for 8 bytes
    var3 dq 124950 
    var4 dq 3750000

section .bss
    bssVar1: resb 4 ;reserves 4 bytes
    bssVar2: resb 4 ;reserves 4 bytes

section .text
    global _start

_start:

    mov [bssVar1], dword 1234567
    mov [bssVar2], dword 7654321

    ;terminate program
    mov rax, 60
    mov rdi, 0
    syscall

 

gdb에 들어가서 break _start 후에

s를 두번 입력하면 mov rax, 60 까지 갑니다.

 

info variables 로 변수들의 메모리 주소가 보입니다.

 

 

print (int) var1

print (int) var2

...

 

개별 변수를 출력합니다. 선언된 변수의 바이트와

타입이 맞지 않으면 세그멘트 오류가 납니다.

 

GDB 스크립트 만들기

GDB에서 복잡한 디버깅도 해결할 수 있어서

좋은데 문제는 이것도 반복작업입니다.

 

GDB는 인터프리터니까 스크립트 파일도

사용할 수 있습니다.

 

예를 들어 아래와 같이 스크립트를 작성하고

이름을 basic.gdb 로 저장합니다.

 

set pagination off
break _start
run
info registers

실행은 아래와 같이 -x 옵션 다음에

스크립트 파일을 넣으면 됩니다.

gdb ./output -x basic.gdb

타이핑을 여러번 할 필요없이

원하는 지점에서 디버깅을 시작할 수 있습니다.

 

사실 GDB는 하나의 스크립트 프로그램으로

if 나 while 등 분기와 제어도 할 수 있습니다.

꽤 방대한 내용이니 더 많은 내용은

GNU 문서를 참고하도록 합니다.

 

 

 

 

 

 

GDB: The GNU Project Debugger (sourceware.org)

 

GDB: The GNU Project Debugger

GDB: The GNU Project Debugger [bugs] [GDB Maintainers] [contributing] [current git] [documentation] [download] [home] [irc] [links] [mailing lists] [news] [schedule] [song] [wiki] GDB: The GNU Project Debugger What is GDB? GDB, the GNU Project debugger, al

www.sourceware.org

https://ncona.com/2019/12/debugging-assembly-with-gdb/

 

Debugging assembly with GDB

I wrote a couple of articles about assembly before, and in order to understand what I was doing, I thought it would be useful to take a look at the contents of memory and registers to confirm my understanding was correct. I looked around, and found that GD

ncona.com

tutorials/Assembly/Nasm/gdb at main · neokayken/tutorials

 

GitHub - neokayken/tutorials: programming tutorial for beginner

programming tutorial for beginner. Contribute to neokayken/tutorials development by creating an account on GitHub.

github.com

 

공유하기

facebook twitter kakaoTalk kakaostory naver band