Linux Mini Debugger

0.0(0)
Studied by 0 people
call kaiCall Kai
Locked
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
GameKnowt Play
Card Sorting

1/44

encourage image

There's no tags or description

Looks like no tags are added yet.

Last updated 10:02 PM on 8/30/26
Name
Mastery
Learn
Test
Matching
Spaced
Call with Kai
Chat

No analytics yet

Send a link to your students to track their progress

45 Terms

1
New cards

What problem does this project solve?

It gives a hands-on, from-scratch implementation of what tools like gdb do: attach to a running program, control its execution, and inspect its state, so I could understand debugging as an OS-level mechanism instead of a black box.

2
New cards

What are the major components of the debugger?

Process control (fork/exec + ptrace), a breakpoint manager (instruction patching), memory/register access, an ELF/DWARF parser for debug info, and a command loop that ties them together.

3
New cards

Why build this instead of just using gdb?

The goal wasn't to replace gdb, it was to learn the underlying mechanisms - ptrace, ELF, DWARF, signals - that gdb itself is built on.

4
New cards

What can the debugger actually do at a high level?

Launch a target program under trace, set breakpoints by address, source line, or function name, step through execution at instruction or source level, and inspect registers and memory.

5
New cards

What is the core OS-level insight that makes any of this possible?

Linux lets one process (the tracer) control another process (the tracee) through the ptrace system call, which the kernel mediates - this is the foundation everything else is built on.

6
New cards

What are the two processes involved and how are they related?

The debugger forks a child process; the child calls PTRACE_TRACEME and then execs the target program, becoming the tracee, while the parent becomes the tracer and issues ptrace calls to control it.

7
New cards

How does the debugger "communicate" with the target process?

Not through normal IPC - the kernel mediates everything via the ptrace syscall, letting the tracer read/write the tracee's memory and registers and control its execution, while the tracee is stopped and reported to the tracer via signals like SIGTRAP.

8
New cards

What are the major subsystems and how do they interact?

Process control starts and stops the tracee, the breakpoint manager patches/restores instructions at addresses the debug-info layer resolves, and the command loop translates user input into calls across all of these.

9
New cards

How does a breakpoint actually work at a low level?

The debugger saves the original byte at the target address, writes the INT3 (0xCC) trap instruction in its place, and when the CPU executes it the process raises SIGTRAP, the debugger catches it, rewinds PC, and later restores the original byte to step past it.

10
New cards

How does the debugger know where to put a breakpoint for a given source line?

It parses the DWARF line number program, which maps ranges of source file/line to instruction addresses, and looks up the address for the requested line.

11
New cards

How does the debugger resolve a breakpoint by function name?

It walks the DWARF DIE tree for subprogram entries matching the name and uses that entry's low_pc as the starting address.

12
New cards

How does step-over avoid single-stepping every instruction?

It uses the DWARF line table to find the address range of the current source line and sets temporary breakpoints at line boundaries and the return address, instead of stepping one instruction at a time.

13
New cards

What is libelfin's role in the architecture?

It parses ELF sections and DWARF debug info programmatically, so the debugger doesn't need a hand-rolled binary/DWARF parser to get line tables, symbol locations, and DIE trees.

14
New cards

Why fork/exec the target instead of attaching to an already-running process?

It's a simpler model for a from-scratch debugger - you control the full lifecycle from the very first instruction, including the initial load address, though PTRACE_ATTACH to a running process is a legitimate alternative real debuggers also support.

15
New cards

Why use software breakpoints (INT3 patching) instead of hardware breakpoints?

Hardware breakpoints use a small fixed number of CPU debug registers (typically 4) and are more fiddly to program, while software breakpoints via INT3 are effectively unlimited and simpler, at the cost of temporarily modifying the target's memory.

16
New cards

Why does the debugger need to handle PIE binaries specially?

Modern Linux binaries default to Position Independent Executables for ASLR, so the link-time addresses in the ELF/DWARF data don't match where the binary actually loads at runtime, requiring the debugger to read /proc/pid/maps and add a load bias.

17
New cards

What's the tradeoff of investing in DWARF parsing versus skipping source-level debugging?

DWARF parsing is a lot of added complexity, but it's what turns the tool from a raw register/memory poker into something resembling a real debugger, and it's where most of the interesting binary/compiler knowledge shows up.

18
New cards

Why a synchronous command loop instead of an event-driven or multithreaded design?

Debugging is inherently a request/wait cycle already - you send a ptrace request, then block on waitpid for the tracee to stop - so a blocking REPL matches that flow naturally without added concurrency complexity.

19
New cards

What are the current limitations compared to production debuggers like gdb?

Single-threaded targets only, no remote debugging, limited/no expression evaluation, unreliable variable inspection, and no type-aware value formatting.

20
New cards

Why is variable inspection harder than breakpoints or stepping?

It requires evaluating DWARF location expressions, a small stack-based bytecode language that can describe a variable's location as a register, a computed frame-relative offset, or more complex composite locations, not just a fixed address.

21
New cards

What is ptrace and what can it do?

A Linux syscall that gives one process privileged control over another: reading/writing memory and registers, single-stepping, continuing execution, and intercepting signals, after the tracee has consented via PTRACE_TRACEME or been attached to.

22
New cards

Why does a breakpoint hit deliver SIGTRAP specifically?

Because the injected INT3 instruction causes a CPU trap when executed, which the kernel converts into a SIGTRAP delivered to the tracer.

23
New cards

Why must the debugger rewind the program counter after a breakpoint trap?

INT3 is one byte, so executing it advances the instruction pointer past the patched byte; the debugger resets PC back by one so execution resumes at the correct original instruction.

24
New cards

What is a load address / load bias and why does it matter?

For PIE binaries the OS picks a randomized base address at load time; the load bias is the difference between the address the binary was linked for and where it actually sits in memory, and it must be applied to translate between static DWARF/ELF addresses and live runtime addresses.

25
New cards

What is DWARF and what does it contain?

A standardized debug-info format embedded in the ELF binary, encoding a tree of Debug Info Entries for functions, variables, and types, plus a line number program mapping instruction addresses to source file/line.

26
New cards

What's the difference between ELF and DWARF?

ELF is the binary container format itself (headers, sections, symbol tables); DWARF is debug metadata usually stored inside specific ELF sections, describing source-level structure on top of the raw binary.

27
New cards

How does frame-pointer based stack unwinding work?

With frame pointers preserved, each stack frame stores the caller's saved rbp and return address at fixed offsets from the current rbp, so walking that chain of saved rbp values reconstructs the call stack.

28
New cards

Why compile the target with -O0 and -fno-omit-frame-pointer?

Optimizations can reorder or eliminate variables and inline functions, breaking the mapping from source lines to instructions, and omitting frame pointers breaks the simple rbp-chain stack walking the debugger relies on.

29
New cards

If asked to design this from scratch, what's the first architectural decision?

How the debugger will control the target's execution - on Linux that means committing to the ptrace-based tracer/tracee model via fork, PTRACE_TRACEME, and exec.

30
New cards

After launching and stopping the target, what's the next capability to build?

Basic execution control - issuing continue and waiting on signals, so you can run the program and reliably detect when and why it stops.

31
New cards

After basic execution control, what's the next natural feature and why?

Breakpoints at raw memory addresses, since instruction patch/restore is the simplest debugging primitive, before adding any symbol or source-level knowledge on top.

32
New cards

How would you evolve raw address breakpoints into source-line breakpoints?

Add ELF/DWARF parsing to read the line number program, mapping a requested file/line to an address, then feed that resolved address into the existing address-breakpoint mechanism.

33
New cards

How would you design stepping, building up from simplest to most capable?

Start with raw single-instruction stepping since it's the cheapest primitive, then layer source-line-aware stepping on top by repeatedly single-stepping until the line table shows a new line, then optimize step-over using temporary breakpoints instead of stepping every instruction.

34
New cards

How would you add PIE support if you'd only built for static binaries first?

Introduce a load-address concept from the start, even if zero for static binaries, read /proc/pid/maps once the child is running for dynamic binaries, and route every address translation through one consistent helper rather than scattering the math.

35
New cards

What would you build last in this system and why?

Variable and expression inspection, since it depends on everything else - reliable breakpoints, stepping, and address translation - and adds the most complex layer, DWARF location-expression evaluation, on top.

36
New cards

Why did you write this in C++ instead of using an existing debugger?

The point wasn't to replace gdb or lldb, it was to learn how OS-level debugging primitives, binary formats, and debug info actually work by implementing them directly.

37
New cards

What happens if the target crashes with a signal like SIGSEGV?

wait_for_signal detects it through WIFSIGNALED/WTERMSIG and reports it, though the debugger doesn't yet do a full crash-time backtrace on unexpected signals - a clear extension point.

38
New cards

What happens if you try to set a breakpoint at an invalid or read-only address?

The underlying ptrace POKETEXT call would fail; the current implementation doesn't robustly surface or handle that failure, which is a real gap I'd call out and fix with proper error checking.

39
New cards

Why does variable inspection sometimes produce wrong values?

DWARF location expressions for locals are often frame-relative, and evaluating them correctly means combining that offset with the live frame pointer at the exact breakpoint moment - getting the offset direction or sign wrong produces garbage addresses, which is a concrete bug I hit and diagnosed.

40
New cards

What would you change if you rebuilt this today?

Centralize PIE address translation into a single well-tested layer instead of repeating the offset math in multiple functions, and add proper error handling around ptrace calls and DWARF expression evaluation.

41
New cards

How would you extend this to support multithreaded programs?

ptrace operates per-thread at the kernel level, so you'd need to track multiple tids, use PTRACE_O_TRACECLONE to catch new threads as they're created, and manage breakpoint state per thread instead of assuming one flow of control.

42
New cards

How would you support remote debugging?

Split the tool into a client that handles commands/UI and a stub that runs on the target machine performing the actual ptrace calls, communicating over a protocol similar to gdb's remote serial protocol.

43
New cards

Why is a debugger fundamentally an OS-specific tool?

Because it depends on kernel-provided process control primitives, ptrace on Linux versus different APIs on Windows or macOS, and on platform-specific binary and debug-info formats like ELF/DWARF versus PE/PDB or Mach-O, so the core mechanism isn't portable without a full separate backend.

44
New cards

What was the hardest bug you hit and what did it teach you?

An address double-offsetting bug where the PIE load address was applied twice across different code paths, corrupting addresses during stepping - it taught me to have one single source of truth for address translation instead of repeating the same logic in multiple places.

45
New cards

Tell me about this project (STAR)

Situation: I wanted to understand how debuggers work internally instead of just using gdb, so I built a Linux debugger in C++ from scratch on top of ptrace. Task: My goal was to support the core debugging workflow - launching a traced process, setting breakpoints, stepping through source, and inspecting registers and memory - while handling real complications like PIE binaries under ASLR. Action: I implemented the tracer/tracee model with fork and PTRACE_TRACEME, built a breakpoint system using INT3 instruction patching with byte save/restore, and integrated libelfin to parse ELF and DWARF so breakpoints could be set by source line or function name instead of raw addresses; I added load-address translation from /proc/pid/maps for PIE binaries, and implemented instruction and source-level stepping plus basic frame-pointer based stack unwinding. Result: I ended up with a working command-line debugger that traces a program, hits breakpoints, steps through source, and dumps register and memory state, and along the way I diagnosed real bugs like a PIE address double-offsetting issue and unreliable DWARF location-expression evaluation for locals, which taught me a lot about disciplined address handling and how much machinery underlies something as simple as printing a variable.