Difference between revisions of "ptrace notes"

From Noah.org
Jump to navigationJump to search
m
m
Line 2: Line 2:
 
__TOC__
 
__TOC__
  
 +
== 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_notes|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.
 
The '''ptrace''' API gives you much finer control from what you can get with [[LD_PRELOAD_notes|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 ==
 
== Slowing down a subprocess ==
  
This adds a '''usleep''' after each system call.
+
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 `time ./slow find /tmp` gave this:
+
Running a normal baseline `time find /tmp` gave this:
 +
<pre>
 +
pcpu    4.93
 +
real    0m0.081s
 +
user    0m0.004s
 +
sys    0m0.000s
 +
</pre>
 +
Running the same command under '''slow''', `time ./slow find /tmp`, gave this:
 
<pre>
 
<pre>
 
pcpu    0.48
 
pcpu    0.48
Line 15: Line 25:
 
sys    0m0.116s
 
sys    0m0.116s
 
</pre>
 
</pre>
Running `time find /tmp` gave this:
 
<pre>
 
pcpu    4.93
 
real    0m0.081s
 
user    0m0.004s
 
sys    0m0.000s
 
</pre>
 
Note that my environment has TIMEFORMAT=$'\npcpu\t%P\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'.
 
  
 +
== source code to 'slow' ==
 
<pre>
 
<pre>
 
/**
 
/**

Revision as of 12:24, 9 April 2014

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;
}