Debugging with GDB
Introduction
The GNU Project Debugger, is a portable debugger that allows us to see what is going on inside a program when it is running. It can be used to debug program based on C/C++,Objective-C, Free Pascal, Fortran, Go, and partially others.
Installation
sudo apt install gdb
Before diving in let’s make our-self familiar with few terms:
- Assembly Code : It is low-level computer language. Assembly language is processor dependent because assembler will translate this code directly to machine language which is understand by CPU. Learn about various instructions from Here
Example:ADD R0, R1, R2 #it will add R1 and R2 store the result in R0
SUB R0, R1, R2 #it will subtract R2 from R1 store the result in R0
- Registers : These are like variables for CPU, used to store/transfer data or instruction used by CPU. These are temporary storage units. We will focus on General purpose registers.
General purpose registers can be further divided into Data registers and Pointer registers.
let’s start now
I have written a simple C code in hello.c and we will analyze
#include <stdio.h>int main(){
int i =0;
for(i; i <=5 ; i++){
printf("Hey its me!!\n");
}
return 0;
}
Now compile it with gcc -g hello.c
it will give output in a.out
file , -g flag here will provide debug info.
gdb -q a.out
, -q flag to extract debug info which will give gdb access to source code.
use list
command to get source code
Now to disassemble a function use disassemble <function-name> or disas <function-name>
We will disassemble main function disas main
- It is memory address.
- It is operation to execute.
- These are data or values stored at registers on whom the operation will be executed.
The syntax which most people use during debugging is AT&T or Intel, the above image shows AT&T but I am more comfortable with Intel syntax so i will stick with that.
To switch Intel syntax either use set disassembly-flavor intel
in gdb or echo "set dis intel” > ~/.gdbinit
this file is read by gdb whenever we start the gdb.
memory-address operation <destination>,<source>
Setting a Breakpoint (breakpoint is the place where you want to pause the execution of the process) break <function-name> or break * <memory-address>
Let’s set break point at main function and look into the values of various register using info registers
.
Note: You can’t info registers without running the program. You can run the program inside gdb using
run
command
- Setting break point at main
- run the program and it will pauses at break point which is main
- Outputting values of various registers , the format is as follow
register-name <raw-format>(it can either be memory address or constant value in hexadecimal) <natural-format>(it is in decimal or hexadecimal depends on the format of set by xml file of gbd source code)
rax 0x555555555135(this is memory address) 93824992235829(this is decimal representation of 0x555555555135 )
info register <register> or i r <register>
This command is used to look into a particular register.
rip value it is pointing to 0x55555555513d memory address inside main function <main+8> which means +8 memory address from starting of main function.
Now you are wondering why <main+8> why not exactly at starting of main function.
The operation shown in red box above is called function prologue and it is in the beginning of a function and generated by compiler to setup the stack and register for the use of function. Similarly there is function epilogue which is at the end of function and is reverse of function prologue it returns the control to calling function.
Debugger is smart enough to skip this.
Examining memory with GDB
GDB provides you the power of examining the memory address using command x it is short for examine.
Command takes two arguments one is how to display memory and other is memory location.
Format for displaying memories are:
- o Display in octal.
- x Display in hexadecimal.-
- u Display in unsigned, standard base-10 decimal.
- t Display in binary.
command: x/<format> address
orx/<format> $<register>
$<register> this is equivalent to examining value at memory address register is pointing.
Default size the gdb will show is 4 bytes which is equivalent to 4 bytes. You can change the size.
- b A single byte
- h A halfword, which is two bytes in size
- w A word, which is four bytes in size
- g A giant, which is eight bytes in size
Command : x/4xb <memory address>
examine 4 bytes with byte length of single and display it in hexadecimal format.
Note: The values are stored in little endian byte order
Now let’s analyze the code
Currently our rip register is pointing to main function.
#include <stdio.h>int main(){
int i =0;
for(i; i <=5 ; i++){
printf("Hey its me!!\n");
}
return 0;
}--------------------------------------------------------------------0x0000000000001135 <+0>: push rbp
0x0000000000001136 <+1>: mov rbp,rsp
0x0000000000001139 <+4>: sub rsp,0x10
=>0x000000000000113d <+8>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000001144 <+15>: jmp 0x1156 <main+33>
0x0000000000001146 <+17>: lea rdi,[rip+0xeb7] # 0x2004
0x000000000000114d <+24>: call 0x1030 <puts@plt>
0x0000000000001152 <+29>: add DWORD PTR [rbp-0x4],0x11
Look the bold line above so it is assembly instruction to mov the value 0 into the memory address 4 less then the address at rbp. which is setting the value of i = 0 and DWORD is 32 bits which means 4 bytes which is equivalent to size of int.
if we look at the value at $rbp-4
we will get garbage value because this instruction is not yet done.
To move one instruction forward we use nexti
which stands for next instruction. Now examine the memory address $rbp-4
x/uw $rbp-4
It value at that memory address is zero. Now next instruction is to jump to address <main + 33>
0x000055555555513d <+8>: mov DWORD PTR [rbp-0x4],0x0
0x0000555555555144 <+15>: jmp 0x555555555156 <main+33>
0x0000555555555146 <+17>: lea rdi,[rip+0xeb7] # 0x555555556004
0x000055555555514d <+24>: call 0x555555555030 <puts@plt>
0x0000555555555152 <+29>: add DWORD PTR [rbp-0x4],0x1
0x0000555555555156 <+33>: cmp DWORD PTR [rbp-0x4],0x5
0x000055555555515a <+37>: jle 0x555555555146 <main+17>
At that it is comparing the value of $rbp-4 with 5 . The next instruction jle
says if it is less than 5 jump to <main+17>.
Where next instruction lea
which means load effective address of rip+0xeb7 to rdi which is 0x555555556004 then calling printf function. Let’s examine the value of rdi.
It contains memory address and after this instruction it is calling printf function so this address may contain “Hey its me!!\n” string but how to examine string. Thanks to gdb it has command that shows us ascii format of the value.
Command: x/s <memory-address>
Let’s look into some other useful command
x/<digit>i [$register|memory-address]
This command means show z instruction where z=digit from the given memory address or address register is pointing to.
Watchpoints
Watchpoints are similar to breakpoints but instead of setting breakpoint on function or line of code, watchpoint are set on variables. It breaks when those variables are read/write.
The variables which is set to be watched must be in scope.
- awatch
Let’s set a breakpoint on main function and use awatch on variable i
At 1&2 value of i was read so function breaks there.
At 3 value of i was written.c
is short for continue which will run program until it hits next breakpoint.
To delete breakpoint use info breakpoints
and use del <breakpoint-number>
- rwatch
Similar to awatch it will only pauses when value is read.
Catch
you can use catch command to catch an exception, throw an exception, catch a syscall.
Let’s catch the syscall write which is used by printf function
catch syscall <name>
We hit the syscall write and we are also provided with buffer address and we can examine the string in buffer.
To only catch the once you can use tcatch
which will catch for first time and then removes the breakpoint.
catch load , catch load <lib-name>, catch unload, catch unload <lib-name>.
it catches whenever a lib is loaded if specified if not specified will break at if any lib is loaded and same for unload.
info locals
Get information about all local variable in current stack.
info variables
Get information about all global and local variables.
info proc cmdline
Display the command line argument of the process
shell <os-command>
Executing os command inside gdb
You can do much more than this with GDB .
I will end this blog here but if you want to explore more of gdb commands
help all
to display all the commands you can use in gdb.help <full-command> eg: help info variables
to get detail description of usage of this particular command.
I hope you like it thanks for reading 😃
Find list of Commands used Here