Running NASM inside C inside GDB. Part 3. Debugging
In previous posts Running NASM inside C inside GDB. Part 1. Integers and Running NASM inside C inside GDB. Part 2. More arguments we executed simple programs.
The problem with simple programs, is that they are simple. You don’t even always need to debug it. But what happens in the real life, is that applications are much bigger and complex. And if your program doesn’t work as expected, you need to understand what went wrong.
Debugger to the rescue!
My experience with debuggers
I first learned to debug programs in mid 90s, when I played around with Borland TurboPascal 5.5 and later 7.0. It has already an IDE with a decent debugger. It allowed me to execute programs step by step, watch variables and understand where the problems came from.
The most awesome was TurboDebugger, which showed memory, stack, registers, CPU flags, program listing. It was really easy to understand how ASM commands change registers and flags.
Unfortunatelly, I haven’t find anything similar for the x86 64bit assembly, but there’s something even more powerful.
GDB / LLDB
In fact, GDB exists for 30 years already! You can still debug everything from the command line.
I will show the examples of working with LLDB
Let’s try to run num_calc
inside the debugger: lldb ./num_calc
Time to execute it for the first time:
Ok cool, it runs, but we don’t really do anything.
Breakpoints
Time to pause the execution of the program inside our ASM function. For this we’ll need to add a breakpoint. There are multiple ways of setting it (check manual), but we’ll add a breakpoint by function name asm_compute
:
What will happen next, if we’d run
the programm again, it would stop right in the beginning of the function:
We can check the state of the registers with the help of re r
or re r/d
(for decimal), or just some of the registers re r rax rbx rdx
What can we learn from this screen? It shows that our arguments are now in rdi
, rsi
, rdx
registers (7
, 8
, 9
).
We can also learn that other registers are pretty random (better not to touch rbp
, rsp
, rip
registers, as they are pretty important).
Ok, let’s get back to the code: disassemble
or di
will show us where we paused. Time to proceed with the next
or n
command:
And we could see that first command rdx-5
executed successfully, the value of the register changed.
Next command will multiply rdi
by 8 by shifting 3 bits left:
Quite easy to see that by three 111
bits shifted by 3 positions left, and we have 7*8=56
in rdi
.
Next command will divide rsi
by 4
or is the same as shifting bits right by 2 positions:
Again, bits shifted two positions right, and the answer is 8/4=2
easy ;)
Next we would add rsi
to the rdi
Here we have 56+2=58
. Time to multiply the numbers:
Final piece of calculation done: rax = rax * rdi
or rax = 58 * 4 = 232
Conclusion
That’s it. Setting breakpoints in the code, stepping over the lines of code and examining the state of the registers can help understanding where calculation goes wrong.
Source code on github: nasm-c-gdb