Breakpoints
Breakpoints are the killer feature of any debugger, the fundamental stopping event. They allow you to stop the execution of your code at a specific point and inspect the state of your program to find bugs or understand its design.
Multithreading and Breakpoints
libdebug breakpoints are shared across all threads. This means that any thread can hit the breakpoint and cause the process to stop. You can use the hit_on()
method of a breakpoint object to determine which thread hit the breakpoint (provided that the stop was indeed caused by the breakpoint).
A breakpoint can be inserted at any of two levels: software or hardware.
Software Breakpoints
Software breakpoints in the Linux kernel are implemented by patching the code in memory at runtime. The instruction at the chosen address is replaced with an interrupt instruction that is conventionally used for debugging. For example, in the i386
and AMD64
instruction sets, int3
(0xCC) is reserved for this purpose.
When the int3
instruction is executed, the CPU raises a SIGTRAP
signal, which is caught by the debugger. The debugger then stops the process and restores the original instruction to its rightful place.
Pros and Cons of Software Breakpoints
Software breakpoints are unlimited, but they can break when the program uses self-modifying code. This is because the patched code could be overwritten by the program. On the other hand, software breakpoints are slower than their hardware counterparts on most modern CPUs.
Hardware Breakpoints
Hardware breakpoints are a more reliable way to set breakpoints. They are made possible by the existence of special registers in the CPU that can be used to monitor memory accesses. Differently from software breakpoints, their hardware counterparts allows the debugger to monitor read and write accesses on top of code execution. This kind of hardware breakpoint is also called a watchpoint. More information on watchpoints can be found in the dedicated documentation.
Pros and Cons of Hardware Breakpoints
Hardware breakpoints are not affected by self-modifying code. They are also usually faster and more flexible. However, hardware breakpoints are limited in number and are hardware-dependent, so their support may vary across different systems.
Hardware Breakpoint Alignment in AArch64
Hardware breakpoints have to be aligned to 4 bytes (which is the size of an ARM instruction).
libdebug API for Breakpoints
The breakpoint()
function in the Debugger object sets a breakpoint at a specific address.
Function Signature
Parameters:
Argument | Type | Description |
---|---|---|
position |
int | str |
The address or symbol where the breakpoint will be set. |
hardware |
bool |
Set to True to set a hardware breakpoint. |
condition |
str |
The type of access in case of a hardware breakpoint. |
length |
int |
The size of the word being watched in case of a hardware breakpoint. |
callback |
Callable | bool (see callback signature here) |
Used to create asyncronous breakpoints (read more on the debugging flow of stopping events). |
file |
str |
The backing file for relative addressing. Refer to the memory access section for more information on addressing modes. |
Returns:
Return | Type | Description |
---|---|---|
Breakpoint |
Breakpoint | The breakpoint object created. |
Limited Hardware Breakpoints
Hardware breakpoints are limited in number. If you exceed the number of hardware breakpoints available on your system, a RuntimeError
will be raised.
Usage Example
from libdebug import debugger
d = debugger("./test_program")
d.run()
bp = d.breakpoint(0x10ab, file="binary") # (1)
bp1 = d.breakpoint("main", file="binary") # (3)
bp2 = d.breakpoint("printf", file="libc") # (4)
d.cont()
print(f"RAX: {d.regs.rax} at the breakpoint") # (2)
if bp.hit_on(d):
print("Breakpoint at 0x10ab was hit")
elif bp1.hit_on(d):
print("Breakpoint at main was hit")
elif bp2.hit_on(d):
print("Breakpoint at printf was hit")
- Set a software breakpoint at address 0x10ab relative to the program's base address
- Print the value of the RAX register when the breakpoint is hit
- Set a software breakpoint at the
main
symbol - Set a software breakpoint at the
printf
symbol in thelibc
library
Callback Signature
If you wish to create an asynchronous breakpoint, you will have to provide a callback function. If you want to leave the callback empty, you can set callback to True
.
Parameters:
Argument | Type | Description |
---|---|---|
t |
ThreadContext | The thread that hit the breakpoint. |
bp |
Breakpoint | The breakpoint object that triggered the callback. |