Fundamentals 62 min read

Understanding Linux ptrace: Implementation, Usage, and Debugging Techniques

This article provides a comprehensive overview of the Linux ptrace system call, explaining its role in process debugging, detailing request parameters, demonstrating practical C examples for tracing system calls, single‑stepping, setting breakpoints, and exploring how DWARF debugging information links source code to executable addresses.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding Linux ptrace: Implementation, Usage, and Debugging Techniques

In software development, debugging is essential for ensuring program quality, and the GNU Debugger (GDB) relies heavily on the Linux ptrace system call to implement its powerful features.

1. What is ptrace?

ptrace is a Linux system call that allows one process to observe and control another, enabling breakpoints, register inspection, and memory manipulation. It is the core mechanism behind tools like GDB and can also be used for security checks and code injection.

1.1 ptrace request parameters

The request argument selects the operation (e.g., PTRACE_TRACEME , PTRACE_PEEKUSER , PTRACE_CONT , PTRACE_GETREGS ). The pid identifies the target process, while addr and data are used for memory reads/writes depending on the request.

1.2 Example: tracing a child process

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <stdio.h>

int main(){
    pid_t child = fork();
    if(child==0){
        ptrace(PTRACE_TRACEME,0,NULL,NULL);
        execl("/bin/ls","ls",NULL);
    }else{
        wait(NULL);
        ptrace(PTRACE_GETREGS,child,0,&regs);
        printf("orig_rax: %d\n", regs.orig_rax);
    }
    return 0;
}

This program forks a child, marks it traceable with PTRACE_TRACEME , executes /bin/ls , and the parent retrieves the child's registers to identify the system call number.

2. Single‑step debugging (PTRACE_SINGLESTEP)

Setting the trap flag in the child’s eflags register causes the CPU to generate a SIGTRAP after each instruction. The kernel then stops the child and notifies the parent, allowing fine‑grained control.

2.1 Implementing breakpoints with int 3

Breakpoints are created by replacing the first byte of the target instruction with the 0xCC opcode ( int 3 ). The original byte is saved, and when the breakpoint is hit, the debugger restores the byte, rewinds the instruction pointer, and resumes execution.

unsigned long data = ptrace(PTRACE_PEEKTEXT, child, (void*)addr, 0);
unsigned long data_with_trap = (data & 0xFFFFFF00) | 0xCC;
ptrace(PTRACE_POKETEXT, child, (void*)addr, (void*)data_with_trap);

3. Debugging information (DWARF)

Compilers embed DWARF sections in ELF binaries to map source lines, functions, and variables to machine addresses. The .debug_info section contains DIEs (Debugging Information Entries) that describe subprograms, their low/high PC values, and variable locations.

For example, the function do_stuff has DW_AT_low_pc = 0x8048604, allowing a debugger to set a breakpoint at the correct address. Variable locations are expressed as offsets from the frame base (e.g., DW_OP_fbreg: -20 for a stack variable).

3.1 Accessing DWARF programmatically

Libraries such as libdwarf provide APIs to read these sections and retrieve function addresses, line tables, and variable locations, enabling full source‑level debugging.

4. ptrace implementation in the Linux kernel

The kernel entry point sys_ptrace dispatches requests via a switch statement. For PTRACE_TRACEME it sets the PT_PTRACED flag; for PTRACE_SINGLESTEP it sets the trap flag in the child’s eflags . Memory reads/writes use access_process_vm , which walks the target’s page tables to copy data from the child’s address space.

When a traced process receives a signal (e.g., SIGTRAP), the kernel’s do_signal routine puts the process into TASK_STOPPED state, notifies the parent with SIGCHLD, and yields the CPU, allowing the debugger to intervene.

Overall, the article demonstrates how ptrace underpins debugging tools, how to manipulate execution flow with single‑step and breakpoints, and how DWARF links executable code back to the original source.

DebuggingC++linuxassemblysystem callbreakpointsDWARFptrace
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.