Coding in assembly can be quite tricky. The syntax is unintuitive. For example when we want to open a file, we don’t call a simple one line function with a name like “open”, instead we have to set multiple registers and use a system call. It is also extremely unforgiving, we can easily set the wrong register or set a register to the wrong value without noticing, and then our code will just fall over and there will be no helpful error message.
How do we diagnose these errors? Well thankfully we can debug assembly with the standard GNU debugger gdb. The process of debugging assembly in gdb is very similar to debugging C or C++.
Suppose we have an assembly program that is supposed to write to stdout
but unfortunately does not:
.section .data
msg:
.string "Hello world\n"
.section .text
.globl _start
_start:
movq $1, %rax
movq $10, %rdi
movq $msg, %rsi
movq $12, %rdx
syscall
movq $60, %rax
movq $0, %rdi
syscall
We have carefully combed through this code, but have not found the error yet, so we decide to debug it. To debug, first we must assemble the code with debug symbols. To do this we assemble with the extra command line option –gstabs+:
as --gstabs+ write.s -o write.o
We then link the file as we normally do:
ld write.o -o write
Now instead of running the binary file that has been created we pass it as an argument to gdb:
gdb write
This will load the write binary into gdb, after gdb spits out some general information you should have a command line that looks like
Reading symbols from write...
(gdb)
To set breakpoints in gdb we use the command:
b <filename>:<linenumber>
In our case we will set a breakpoint at the start label:
b write.s:10
We then tell gdb to start the execution of our program with the run command, ‘r’. If this runs succesfully our command line will look like:
Breakpoint 1, _start () at write.s:10
10 movq $1, %rax
(gdb)
While debugging we can step to the next line of code executed with the ‘s’ command. Let’s say we step all the way to line 14 as so:
(gdb) s
11 movq $10, %rdi
(gdb) s
12 movq $msg, %rsi
(gdb) s
13 movq $12, %rdx
(gdb) s
14 syscall
(gdb)
Let’s have a look at what’s in the registers, to do this we use the ‘info registers’ command:
(gdb) info registers
rax 0x4 1
rbx 0xa 0
rcx 0x402000 0
rdx 0xc 12
rsi 0x0 4202496
rdi 0x0 10
rbp 0x0 0x0
rsp 0x7fffffffd870 0x7fffffffd870
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x401014 0x401014 <_start+20>
eflags 0x202 [ IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb)
Each of the individual registers is listed along with the value it contains, first in hex and then in a more human readable form. If we just want to see the value in a single register we use ‘info registers <register name>’, for example,
info registers rdi
gives,
rdi 0xa 10
(gdb)
Now, as this point in the code we are attempting to write to stdout
, so the rdi
register should be set to 1, however we can see it is set to ten. If we look back at the code we have stepped through we see that on line 11 we set the rdi
register to 10, whoops!
There are a few other useful commands we have left out.
- info – prints a numbered list of breakpoints
- delete <breakpoint number> – deletes a breakpoint
- c – continues execution to the next breakoint and
- r – can be called at any point to restart execution from the beginning.
We’ll learn more about debugging in a later post, but this should be all you need to get started!