R_DEBUG(3)

๐Ÿ“ฆ
version 6.2.2 ยท 2 versions
, in alpine 3.24, arch rolling, nixos 26.05, opensuse-leap 16.0, ubuntu resolute, ubuntu stonking

๐Ÿ’– R_DEBUG โ€” radare2 debugger library ๐Ÿ’–

okay bestie, welcome to r_debug, the radare2 debugger library, aka the part of radare2 that actually pokes at running processes. it's a <r_debug.h> situation. this page is long so buckle up, we're doing this in parts ๐Ÿซถ


โœจ SYNOPSIS โœจ

#include <r_debug.h>

๐ŸŒธ DESCRIPTION ๐ŸŒธ

the r_debug library gives radare2 its debugging superpowers, no cap. we're talking process attachment, stepping, breakpoints, register manipulation, memory inspection, and a whole bunch of other debugging operations across different platforms and architectures. she's versatile ๐Ÿ’…

the main character here is the RDebug structure. she holds everything: debugger state, plugins, breakpoints, registers, and process info. it's giving control center energy ๐Ÿ‘‘


๐ŸŽ€ INITIALIZATION ๐ŸŽ€

RDebug * r_debug_new(int hard)

creates and hands you back a brand new debugger context. this is literally the first thing you do when you're writing code that drives the debugger programmatically. the optional hard parameter turns on hardware-specific debugging features (when the backend actually has them, bestie) like hardware breakpoints.

typical glow-up sequence: make the context with r_debug_new (), configure the RDebug fields (setting the core pointer, event hooks, whatever), then call r_debug_start () or r_debug_attach () to actually start debugging.

important bestie: always free the returned object with r_debug_free () when you're done, or you're leaking resources and that's not the vibe ๐Ÿ˜ญ

void r_debug_free(RDebug *dbg)

frees every resource tied to the debugger context and all its lil substructures. after you call this, the RDebug * is done โ€” do not touch it, do not look at it, do not use it. it's over.

the classic pattern:

RDebug *dbg = r_debug_new (0);
// configure dbg->coreb.core, dbg->anal, etc.
// attach or start target
// debug loop
r_debug_free (dbg);

๐Ÿฆ‹ PROCESS CONTROL ๐Ÿฆ‹

bool r_debug_attach(RDebug *dbg, int pid)

attaches to a process that's already out there living its life, and gets the internal plugin and IO callbacks ready to control it. after a successful attach you usually call r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false) to fill in the register state, plus r_debug_bp_update (dbg) if breakpoints need resolving against symbol expressions.

when you're attaching to remote backends, whichever plugin got selected (that's dbg->current->plugin) decides how r_debug_attach () behaves and how all the I/O after it goes down.

bool r_debug_detach(RDebug *dbg, int pid)

detaches the debugger from the target. use this when you wanna leave the debuggee running on its own, free-range, main character style. after detaching you can still call r_debug_info () to read cached info, but most of the control functions? yeah they're not available anymore, sorry babe ๐Ÿ™ˆ

bool r_debug_start(RDebug *dbg, const char *cmd)

starts and spawns a whole new process for debugging with the command you give it. this sets up the debugging plugin's process creation flow โ€” think fork/exec on native backends, or a remote protocol for remote backends.

after r_debug_start () the usual flow is: call r_debug_wait () to catch that initial stop event, then set your breakpoints, then continue. periodt.


๐Ÿ”ฅ EXECUTION CONTROL ๐Ÿ”ฅ

int r_debug_step(RDebug *dbg, int steps)

does one or more single instruction steps on the selected thread. use r_debug_step () for instruction-level stepping. if you want higher-level stepping that steps over function calls, that's r_debug_step_over (). and if you need to run until a specific address, that's r_debug_continue_until ().

when you step after hitting a software breakpoint, the library handles the whole "recoil" situation for you (clearing the breakpoint, stepping once, restoring the breakpoint). callers don't normally have to implement recoiling themselves, but heads up: r_debug_step () may return a value less than requested when a breakpoint was involved. not a bug, just how it be ๐Ÿ’€

int r_debug_step_over(RDebug *dbg, int steps)

steps over function calls for steps instructions. iconic when you're implementing source-level stepping or you just wanna skip stepping into library code. not today, library internals โœ‹

int r_debug_continue(RDebug *dbg)

resumes execution until the debuggee stops because of a breakpoint, a signal, or some other event. before you call r_debug_continue (), make sure software breakpoints are properly restored โ€” call r_debug_bp_update () if you changed breakpoint expressions, and sync registers if you changed them locally.

for multi-threaded targets, prefer the continue_all_threads flag on RDebug when the backend supports it ๐Ÿงš

bool r_debug_continue_until(RDebug *dbg, ut64 addr)

resumes execution and stops when the program counter reaches addr. so convenient for temporary range stepping: either add a temporary breakpoint with r_debug_bp_add () and then call r_debug_continue (), or just use r_debug_continue_until () and skip the whole explicit breakpoint-management saga when the plugin supports it. slay.


โญ WAITING AND REASONS โญ

RDebugReasonType r_debug_wait(RDebug *dbg, RBreakpointItem **bp)

blocks until the debuggee stops, then returns the reason as an RDebugReasonType. r_debug_wait () is the canonical way to write a debugging loop: call it after r_debug_continue () or r_debug_step (), then inspect the returned reason via r_debug_stop_reason () or by just testing the return value.

if bp is non-NULL and the stop reason involves a breakpoint, *bp gets set to the RBreakpointItem * that got hit. chef's kiss ๐ŸคŒ

typical usage pattern:

r_debug_continue (dbg);
RBreakpointItem *bp = NULL;
RDebugReasonType why = r_debug_wait (dbg, &bp);
if (why == R_DEBUG_REASON_BREAKPOINT && bp) {
    // inspect registers and memory here
}

RDebugReasonType r_debug_stop_reason(RDebug *dbg)

returns the last stop reason stored in dbg->reason. so useful when you can't block on r_debug_wait () โ€” like when you're integrating with an event loop โ€” and you need to poll the current state instead. lowkey essential for async girlies ๐Ÿ’…

const char * r_debug_reason_tostring(int type)

returns a human-readable string for the given reason code. perfect for logging or user-facing messages when you're explaining why the debuggee stopped.


๐Ÿ’œ REGISTER MANAGEMENT ๐Ÿ’œ

bool r_debug_reg_sync(RDebug *dbg, int type, int write)

syncs registers between the local RReg view and the debuggee. call it with write == false to read registers from the debuggee into the local RReg, and with write == true to push your local register values into the debuggee.

always call r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false) after attaching, otherwise r_debug_reg_get () will hand you values that mean absolutely nothing. facts.

ut64 r_debug_reg_get(RDebug *dbg, const char *name)

returns the value of the register named name, pulled from the local RReg cache. reminder bestie: this value is only as fresh as the last r_debug_reg_sync (). stale register values are not the vibe ๐Ÿ™ˆ

bool r_debug_reg_set(RDebug *dbg, const char *name, ut64 num)

sets a register locally. after calling r_debug_reg_set (), call r_debug_reg_sync (dbg, R_REG_TYPE_GPR, true) to actually write the change to the debuggee. common pattern: read PC with r_debug_reg_get (), update a register, write it back with r_debug_reg_sync (), then r_debug_continue (). it's a whole ritual and it works ๐ŸŽ€

bool r_debug_reg_list(RDebug *dbg, int type, int size, PJ *pj, int rad, const char *use_color)

produces a formatted list of registers that's ready for printing. use this when you're implementing info registers-style commands in front-ends. built for display, no cap โœจ


๐Ÿฉฐ BREAKPOINTS ๐Ÿฉฐ

RBreakpointItem * r_debug_bp_add(RDebug *dbg, ut64 addr, int hw, bool watch, int rw, char *module, st64 m_delta)

adds a breakpoint at addr. here's the parameter rundown bestie:

  • hw โ€” request a hardware breakpoint when the backend supports it
  • watch โ€” flag this as a watchpoint
  • rw โ€” specify read/write conditions for watchpoints
  • module and m_delta โ€” used for module-relative expressions

after adding breakpoints, call r_debug_bp_update () to resolve the expression-based ones and make sure the plugin has the correct breakpoint list loaded.

the library distinguishes software and hardware breakpoints, and this distinction matters:

  • software breakpoints are typically implemented by writing a trap instruction into the target process. that means they must be temporarily removed whenever the debugger needs to execute target code (like when implementing recoil).
  • hardware breakpoints don't require patching memory at all, but they're usually limited in number. sad but true ๐Ÿ’€

void r_debug_bp_update(RDebug *dbg)

resolves breakpoint expressions and updates the internal breakpoint addresses. call this after adding breakpoints by name or expression.

internally the implementation calls r_bp_get_at () and r_bp_restore () to manage software breakpoints. front-ends: do not duplicate that logic, it's already handled for you ๐Ÿซถ


๐ŸŒˆ MEMORY MANAGEMENT ๐ŸŒˆ

RList * r_debug_map_list_new(void)

creates an empty list for storing memory map entries (same format that r_debug_map_get () uses). use it to enumerate memory regions when you're implementing memory listing, or to build a synthetic map for analysis.

RDebugMap * r_debug_map_get(RDebug *dbg, ut64 addr)

returns the RDebugMap for whichever memory region contains addr. important: the returned map is owned by the RDebug internals, so callers should not free it directly. hands off, babe โœ‹

use r_debug_map_protect () to change memory protections when you need writable patching, or to make a region executable for r_debug_execute ().

bool r_debug_map_protect(RDebug *dbg, ut64 addr, int size, int perms)

changes memory protection for a region starting at addr. useful when you wanna write code into the target and execute it (trampolines, anyone?).

heads up: lots of backends require cooperation with the plugin here, and the call will fail if the underlying OS doesn't permit the kind of change you're asking for. no amount of manifesting fixes that ๐Ÿ˜ญ


๐Ÿ‘‘ PROCESS INFORMATION ๐Ÿ‘‘

RDebugInfo * r_debug_info(RDebug *dbg, const char *arg)

queries miscellaneous info about the debugged process โ€” current working directory, command line, executable path, that kind of metadata. backends populate this structure from platform-specific sources, and it's super convenient for front-ends that need to show off process metadata. it's giving dossier ๐Ÿ’…

void r_debug_info_free(RDebugInfo *rdi)

frees the RDebugInfo structure that r_debug_info () gave you. clean up after yourself, bestie ๐ŸŽ€


๐Ÿงš THREADS AND PROCESSES ๐Ÿงš

RList * r_debug_pids(RDebug *dbg, int pid)

returns a list of process/thread identifiers that the backend knows about. use this to build thread list UIs. each list element is typically an RDebugPid or something similar, depending on the plugin.

bool r_debug_select(RDebug *dbg, int pid, int tid)

selects a specific process and thread (if supported) for everything that comes next. once a thread is selected, calls like r_debug_step () or r_debug_reg_sync () operate on that thread.

reminder: call r_debug_reg_sync () after switching threads to refresh the register state, otherwise you're looking at the old thread's regs and that's confusing fr fr ๐ŸŒธ


๐Ÿ’€ SIGNALS ๐Ÿ’€

int r_debug_signal_send(RDebug *dbg, int num)

sends the signal num to the debuggee. use it to emulate user actions like SIGINT, or to forward signals from a front-end.

int r_debug_signal_resolve(RDebug *dbg, const char *signame)

resolves a textual signal name like "SIGINT" into its numeric value.

const char * r_debug_signal_resolve_i(RDebug *dbg, int signum)

returns the canonical name for signum (like SIGSEGV). great for logging and user messages. naming things is half the battle โœจ


okay that's part 1 bestie โ€” more is coming, don't go anywhere ๐Ÿ’–

๐Ÿ”ฎ TRACING ๐Ÿ”ฎ

okay so tracing is literally the main character of dynamic analysis and i'm obsessed ๐Ÿ’–

  • RDebugTrace * r_debug_trace_new(void) โ€” builds you a brand-new trace context and gets it all initialized, ready for the tracing facilities inside the debugger to do their thing. basically the trace APIs let you record the program's execution history โ€” think PCs, memory snapshots, all of it โ€” so you can come back and analyze it later. it's giving time travel ngl ๐Ÿฆ‹
  • void r_debug_trace_free(RDebugTrace *dbg) โ€” yeets a trace context into the void. specifically, it frees one you made with r_debug_trace_new (). clean up after yourself bestie ๐Ÿ’…
  • RDebugTracepointItem * r_debug_trace_add(RDebug *dbg, ut64 addr, int size) โ€” drops a tracepoint at addr. and when that tracepoint gets hit? the tracer may record state, depending on how you configured the trace. lowkey iconic โœจ

๐Ÿงฉ PLUGINS ๐Ÿงฉ

plugins are how the debugger gets its whole personality โ€” architecture and OS specific behavior lives here, like process control, register IO, memory access. the built-in backends (native, gdb, etc.) are literally just plugins, and front-ends can add their own custom ones to support extra transports. we love a modular queen ๐Ÿ‘‘

  • bool r_debug_plugin_add(RDebug *dbg, RDebugPlugin *plugin) โ€” registers a debugger plugin implementation with your RDebug instance. slay.
  • bool r_debug_plugin_remove(RDebug *dbg, RDebugPlugin *plugin) โ€” unregisters a plugin you added earlier. โš ๏ธ heads up though: removing the current plugin while you're actively debugging is not supported and will typically just fail. don't do it, it's not the vibe ๐Ÿ˜ญ

โšก EXECUTION โšก

  • bool r_debug_execute(RDebug *dbg, const ut8 *buf, int len, ut64 *ret, bool restore, bool ignore_stack) โ€” runs the machine code sitting in buf (that's len bytes of it) inside the debugged process, and optionally hands you back a value through ret. if restore is true, the original memory contents and registers get put back exactly how they were after execution. the ignore_stack flag lets the plugin tweak the stack-related safety checks while it's injecting code. this one is highkey useful for evaluating small snippets in the target process during dynamic analysis, or for implementing functions that need to run in the target context. chef's kiss ๐Ÿซถ

๐Ÿ’€ DEBUG REASONS ๐Ÿ’€

the library defines a bunch of reasons why a debuggee might stop. here's the whole set, no skips:

  • R_DEBUG_REASON_BREAKPOINT โ€” hit a breakpoint ๐ŸŽฏ
  • R_DEBUG_REASON_SIGNAL โ€” received a signal ๐Ÿ“ก
  • R_DEBUG_REASON_STEP โ€” completed a step ๐Ÿ‘Ÿ
  • R_DEBUG_REASON_SEGFAULT โ€” segmentation fault ๐Ÿ’ฅ (the classic)

๐ŸŒˆ EXAMPLES ๐ŸŒˆ

these are the real-world sequences you'll actually see in radare2's front-ends and plugins. they assume a native backend, but bestie the exact same patterns apply to remote backends through their plugin implementations ๐Ÿ’œ

Minimal attach-and-continue loop

RDebug *dbg = r_debug_new (0);
// attach and populate registers
if (!r_debug_attach (dbg, pid)) {
    // handle error
}
if (!r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false)) {
    // cannot read registers
}
// set a breakpoint at a symbol or address
RBreakpointItem *bp = r_debug_bp_add (dbg, 0x400000, 0, false, 0, NULL, 0);
r_debug_bp_update (dbg);
// resume and wait for events
r_debug_continue (dbg);
RBreakpointItem *hit = NULL;
RDebugReasonType why = r_debug_wait (dbg, &hit);
if (why == R_DEBUG_REASON_BREAKPOINT && hit) {
    ut64 pc = r_debug_reg_get (dbg, "PC");
    // inspect/modify registers or memory
}

Step-and-inspect example (useful for implementing a step debugger)

RDebug *dbg = r_debug_new (0);
// start or attach
r_debug_step (dbg, 1);
if (r_debug_wait (dbg, NULL) == R_DEBUG_REASON_STEP) {
    // make sure registers are synced
    r_debug_reg_sync (dbg, R_REG_TYPE_GPR, false);
    ut64 sp = r_debug_reg_get (dbg, "SP");
    // read memory via dbg->iob.read_at or use r_debug_map_get to find protections
}

Executing injected snippets safely

// prepare the small machine code to run (architecture dependent)
const ut8 code[] = { 0x90, 0x90 }; // nop; nop
ut64 ret = 0;
if (r_debug_execute (dbg, code, sizeof (code), &ret, true, false)) {
    // result available in ret
}

๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

go read these next, i promise they're worth it:


and that's the whole page bestie!! ๐ŸŽ€ this one's from the Debian crew, version date September 20, 2025, and the page itself is R_DEBUG(3) โ€” go break some breakpoints (respectfully) ๐Ÿ’–โœจ

๐Ÿ“ฆ where this page comes from