ptrace notes

From Noah.org
Revision as of 12:24, 9 April 2014 by Root (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

See Also

LD_PRELOAD_notes is the classic way to manipulate the reality of a program. It is less powerful than ptrace, but it is faster The ptrace API gives you much finer control from what you can get with LD_PRELOAD. Also LD_PRELOAD only lets you hook into shared object library calls. If an application was totally statically linked, then you won't ever have visibility. But ptrace gives single-step process control at the machine instruction level.

Slowing down a subprocess

This adds a usleep after each system call. Build the small C program given and test with the following examples. Note that my environment has export TIMEFORMAT=$'\npcpu\t%P\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'. This environment variable causes the Bash built-in version of time to also display the percentage of CPU used by a process. If you are using a different shell then you may not see the pcpu row output.

Running a normal baseline `time find /tmp` gave this:

pcpu    4.93
real    0m0.081s
user    0m0.004s
sys     0m0.000s

Running the same command under slow, `time ./slow find /tmp`, gave this:

pcpu    0.48
real    0m31.929s
user    0m0.040s
sys     0m0.116s

source code to 'slow'

/**
 * This adds a trace around the given program and adds a usleep after each syscall.
 * Could also set to sleep after every instruction in single-step mode, but that
 * would make the program very slow. In fact, you usually wouldn't need to even
 * add sleep. Just having this hook do nothing would be enough to make the child
 * program painfully slow. In single-step mode you should note that system calls
 * themselves are not traced, so you will still see line output flash one line at
 * a time with a pause in between.
 *
 * To build:
 *     gcc slow.c -o slow
 *
 * Example run:
 *     ./slow find /tmp
 *
 * Copyright (c) 2010, Noah Spurrier <noah@noah.org>
 */

#include <stdio.h>
#include <sys/ptrace.h>
// #include <asm/ptrace-abi.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/syscall.h>

int main(int argc, char ** argv) {
    int child_argv_start;
    int status;
    pid_t child;

    // printf ("argc: %d, argv length: %d\n", argc, argv_length(argv));

    child = fork();
    if(child == 0) {
        // Find end of parent option list to get start of child args.
        // For the moment we assume parent has no options, so we
        // hard code this to 1.
        child_argv_start = 1;
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execvp(argv[child_argv_start], &argv[child_argv_start]);
    }
    else {
        wait(&status);
        // Check if child called exit before we even got started.
        if(WIFEXITED(status))
            _exit (WEXITSTATUS(status));
        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        wait(&status);
        while (1) {
            // I should probably check if the child got terminated by a signal.
            if(WIFEXITED(status))
                break;
            usleep (10000); // in microseconds (millionths of a second)
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
            //ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
            wait(&status);
        }
    }
    _exit (WEXITSTATUS(status));
}

/*
 * This returns the length of a NULL-terminated argv array.
 */
int argv_length(char **argv) {
    int count;
    char **p;

    if (argv == NULL)
        return 0;

    for (count = 0, p = argv; *p; count++, p++)
        ;

    return count;
}