Skip to content

Debugging Flow of Stopping Events

Before diving into each libdebug stopping event, it's crucial to understand the debugging flow that these events introduce, based on the mode selected by the user.

The flow of all stopping events is similar and adheres to a mostly uniform API structure. Upon placing a stopping event, the user is allowed to specify a callback function for the stopping event. If a callback is passed, the event will trigger asynchronously. Otherwise, if the callback is not passed, the event will be synchronous. The following flowchart shows the difference between the two flows.


Syncronous and Asyncronous Flow Syncronous and Asyncronous Flow
Flowchart of different handling modes for stopping events

When a synchronous event is hit, the process will stop, awaiting further commands. When an asynchronous event is hit, libdebug temporarily stops the process and invokes the user callback. Process execution is automatically resumed right after.

Tip: Use cases of asynchronous stopping events

The asynchronous mode for stopping events is particularly useful for events being repeated as a result of a loop in the executed code.

When attempting side-channel reverse engineering, this mode can save a lot of your time.

Types of Stopping Events

libdebug supports the following types of stopping events:

Event Type Description Notes
Breakpoint Stops the process when a certain address is executed Can be a software or a hardware breakpoint
Watchpoint Stops the process when a memory area is read or written Alias for a hardware breakpoint
Syscall Stops the process when a syscall is made Two events are supported: syscall start and end
Signal Stops the process when a signal is received

Multiple callbacks or hijacks

Please note that there can be at most one user-defined callback or hijack for each instance of a stopping event (the same syscall, signal or breakpoint address). If a new stopping event is defined for the same thing, the new stopping event will replace the old one, and a warning will be printed.

Internally, hijacks are considered callbacks, so you cannot have a callback and hijack registered for the same event.

Common APIs of Stopping Events

All libdebug stopping events share some common attributes that can be employed in debugging scripts.

Enable/Disable

All stopping events can be enabled or disabled at any time. You can read the enabled attribute to check the current state of the event. To enable or disable the event, you can call the enable() or disable() methods respectively.

Callback

The callback function of the event can be set, changed or removed (set to None) at any time. Please be mindful of the event mode resulting from the change on the callback parameter. Additionally, you can set the callback to True to register an empty callback.

Hit Records

Stopping events have attributes that can help you keep track of hits. For example, the hit_count attribute stores the number of times the event has been triggered.

The hit_on() function is used to check if the stopping event was the cause of the process stopping. It is particularly useful when debugging multithreaded applications, as it takes a ThreadContext as a parameter. Refer to multithreading for more information.

Hijacking

Hijacking is a powerful feature that allows you to change the flow of the process when a stopping event is hit. It is available for both syscalls and signals, but currently not for other stopping events. When registering a hijack for a compatible stopping event, that execution flow will be replaced with another.

Hijack of a Signal Hijack of a Signal
Example hijacking of a SIGALRM to a SIGUSR1

For example, in the case of a signal, you can specify that a received SIGALRM signal should be replaced with a SIGUSR1 signal. This can be useful when you want to prevent a process from executing a certain code path. In fact, you can even use the hijack feature to "NOP" the syscall or signal altogether, avoiding it to be executed / forwarded to the processed. More information on how to use this feature in each stopping event can be found in their respective documentation.

Recursion

Mixing asynchronous callbacks and hijacking can become messy. Because of this, libdebug provides users with the choice of whether to execute the callback for an event that was triggered by a callback or hijack.

This behavior is enabled by the parameter recursive, available when instantiating a syscall handler, a signal catcher, or their respective hijackers. By default, recursion is disabled.

Recursion Loop Detection

When carelessly doing recursive callbacks and hijacking, it could happen that loops are created. libdebug automatically performs checks to avoid these situations and raises an exception if an infinite loop is detected.

For example, the following code raises a RuntimeError:

handler = d.hijack_syscall("read", "write", recursive=True)
handler = d.hijack_syscall("write", "read", recursive=True)