Search code examples
c++linuxptrace

What is the range for PTRACE_TRACEME?


If I have a code like this:

void child() {
    do_something();
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    do_some_other_things();
}

then will do_something() be traced by the parent?

I found in the linux documentation, there were not such thing. It only said this should be called in the tracee.

PTRACE_TRACEME

Indicate that this process is to be traced by its parent. A process probably shouldn't make this request if its parent isn't expecting to trace it. (pid, addr, and data are ignored.)


Solution

  • See How Debuggers Work Part 1 for a better explanation, but in summary, no, it will not trace the do_something() function until the tracee receives a signal.

    In the description of the ptrace call from that same man ptrace you quoted:

    A process can initiate a trace by calling fork(2) and having the resulting child do a PTRACE_TRACEME, followed (typically) by an execve(2). Alternatively, one process may commence tracing another process using PTRACE_ATTACH or PTRACE_SEIZE.

    While being traced, the tracee will stop each time a signal is delivered, even if the signal is being ignored. (An exception is SIGKILL, which has its usual effect.) The tracer will be notified at its next call to waitpid(2) (or one of the related "wait" system calls); that call will return a status value containing information that indicates the cause of the stop in the tracee.

    When the tracee calls exec, it receives a signal, that's why it stops.

    To illustrace, tracer program mainer.c with no bells or whistles:

    //mainer.c
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/ptrace.h>
    #include <sys/user.h>
    #include <unistd.h>
    
    int main(int argc, char ** argv)
    {
            pid_t child_pid;
            char * programname = argv[1];
    
            child_pid = fork();
            if (child_pid == 0)
            {
                    ptrace(PTRACE_TRACEME, 0, 0, 0);
                    execl(programname, programname, NULL);
            }
    
            else if (child_pid > 0) 
            {
                int status;
    
                wait(&status);
    
                while (WIFSTOPPED(status))
                {
                        struct user_regs_struct regs;
                        ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
                        unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0);
                        printf("EIP = 0x%08x, instr = 0x%08x\n", regs.eip, instr);
    
                        ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0);
    
                        wait(&status);
                }
    
            }
    }  
    

    When the tracee hits exec, it will receive a signal and pass control to the parent, who is waiting. Assembly program to trace, there's just way too much going on when tracing a C program to extract anything useful:

    ; hello.asm
    section .text
        global _start
    
    _start:
        mov edx,len1
        mov ecx,hello1
        mov ebx,1
        mov eax,4
    
        int 0x80
    
    
        mov edx,len2
        mov ecx,hello2
        mov ebx,1
        mov eax,4
    
        int 0x80
    
        mov eax,1
        int 0x80
    
    section .data
        hello1 db "Hello",0xA
        len1 equ $ - hello1
    
        hello2 db "World",0xA
        len2 equ $ - hello2
    

    Running this, ./mainer hello

    EIP = 0x08048080, instr = 0x00000000
    EIP = 0x08048085, instr = 0x00000000
    EIP = 0x0804808a, instr = 0x00000000
    EIP = 0x0804808f, instr = 0x00000000
    EIP = 0x08048094, instr = 0x00000000
    Hello
    EIP = 0x08048096, instr = 0x00000000
    EIP = 0x0804809b, instr = 0x00000000
    EIP = 0x080480a0, instr = 0x00000000
    EIP = 0x080480a5, instr = 0x00000000
    EIP = 0x080480aa, instr = 0x00000000
    World
    EIP = 0x080480ac, instr = 0x00000000
    EIP = 0x080480b1, instr = 0x00000000
    

    If we modify mainer.c so the child process calls do_something() before it's exec the result of the trace is the exact same. This is just how I modified it, you can confirm yourself if you like that the results are the same.

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/ptrace.h>
    #include <sys/user.h>
    #include <unistd.h>
    
    int do_something(void)        //added this function
    {
            printf("Doing something");
            return 0;
    }
    
    
    int main(int argc, char ** argv)
    {
            pid_t child_pid;
            char * programname = argv[1];
    
            child_pid = fork();
            if (child_pid == 0)
            {
                    ptrace(PTRACE_TRACEME, 0, 0, 0);
                    do_something();      //added this function call
                    execl(programname, programname, NULL);
            }
    
            else if (child_pid > 0) 
            {
                int status;
    
                wait(&status);
    
                while (WIFSTOPPED(status))
                {
                        struct user_regs_struct regs;
                        ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
                        unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0);
                        printf("EIP = 0x%08x, instr = 0x%08x\n", regs.eip, instr);
    
                        ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0);
    
                        wait(&status);
                }
    
            }
    }   
    

    So the tracee won't stop until it receives a signal, which is what happens when it calls exec, and calling functions doesn't generate a signal for the tracee, but there are other ways to send a signal to the tracee and begin tracing, although they are not as tidy as exec and wait.