Josh
Josh

Reputation: 121

Ptrace - communication with child process

I would like to use ptrace in the following way (pseudocode):

child:

    foo();
    now that foo is done parent should use ptrace to change things
    parent did what he wanted to do
    bar();  

parent:

pid = fork();
if (pid == 0)
    //child
    exec(child_program)
else
    //parent
    attach ptrace
    let child run
    use ptrace to modify it's data
    let child continue
  1. How should the child communicate with parent that it has completed foo and is ready to be modified? raise(SIGSTOP) maybe?

  2. How should the parent wait for child to run foo?

I think we can assume that no SIGSTOP is raised before pthread should be used.

Upvotes: 1

Views: 1767

Answers (2)

ysdx
ysdx

Reputation: 9315

You say you want to modify the registers of he traced process at some point in the execution. You should probably try to clarify your question because it's not really clear what you really want to achieve: why do you want to modify the registers in the first place. What do you expect to find in the registers? Why do you want to change those values?

Are you sure you do not want to communicate with sockets and/or shared memory instead? You should probably give a more details example to explain what you're trying to do.

Breakpoint in the existing code

You have this code in the traced process:

foo();
// You want to modify something there.
bar();

Between foo() and bar() it's really not clear what is in the registers. Let's say we are using x86_64.

If you break when foo returns:

  • EAX contains the return value of foo (if any) which is ignored in your caller anyway (so it does not make much sense to modify it);

  • callee-saved registers might contain some value from the caller but you would have to mess around with DWARF informations to try to make some sense out of it;

  • caller-saved registers will not contain anything useful (but you might use DWARF unwinding information to find some other data which makes sense in the caller).

Breaking at the call site of bar (either in the caller or at the beginning of bar) might be slightly more interesting to you because you have access to the parameters of bar. You can modify them in you tracer process and you can even force a return call with a value if you want to.

Raising signals

Another solution is to raise a signal:

foo();
raise(SIGTRAP);
bar();

As before, it's not clear what is in the registers and you may have to use DWARF to try to locate interesting data (which might or might not work).

A (possibly) cleaner solution would be to raise the exceptions with an instruction:

int     $3

The problem is that if your program is not running under the tracer, it will die.

Add a hook for the tracer

A somewhat cleaner solutions is to add another function between foo and bar:

foo();
int res = delegate_to_tracer(x, y, z);
bar();

where delegate_to_tracer can be stubbed as:

int delegate_to_tracer(int x, int y, int z)
{
  // No-op implementation used when there is no tracer:
  return 0;
}

You can now add a breakpoint at the beginning of this function in order to handle it's functionality in the tracer:

  • you can access the parameters;

  • you can modify them;

  • you can force a return with a given return value.

Another similar solution is to use static tracepoints (SDT, UST) but it probably does not make much sense to try to modify data from them.

Faking a system call

You can use a system call in order to communicate with the tracer:

  • either by using a unused system call (NR_tuxcall?)

  • either by using a unused system call number (but it might become used at some point);

  • or by squatting an existing one.

The idea is that if this does not runs under your tracer, the system call will fail with SIGSYS (or other). However, under your tracer, you will intercept the system call and handle it yourself.

Making a tuxcall:

movq    $184, %rax # tuxcall
movq    $42,  %edi # param1
syscall

Upvotes: 0

krb
krb

Reputation: 16315

I might be misunderstanding it, but is there any specific reason that you're wanting to use `ptrace` for what looks like IPC (Interprocess communication)? `ptrace` on Linux is *generally poorly suited* for IPC, and you should not really be using it to modify data in the child process. If you want your child process to communicate with the parent, there are a multitude of different ways of achieving said tasks (ie. Unix Domain sockets, Pipes, Semaphores, Shared Memory), I suggest you look into them before attempting to do IPC using `ptrace`.


Edit:

You could use a semaphore to let the parent wait for the child (see sem_overview in the Linux man pages) and do what you need to do. You could create a named semaphore using sem_open and have the child and wait for it in the parent, having the child notify the semaphore upon the completion of the said task.

Alternatively, make the traced child process use a breakpoint instruction which will stop it through SIGTRAP allowing you to wait on it and then do what you need to do. I believe a similar approach is used by GDB for debugging (patching instructions). If you're using x86, the following code should work for emitting a breakpoint instruction in your code:

asm volatile ("int3;")

May I also suggest using process_vm_writev instead of ptrace functions for writing process memory (PTRACE_POKETEXT) since they can do bulk reads/writes to process memory.

For further reference, I think debuggers_part2_code is a good example of how to roll your own debugging tools.

Upvotes: 1

Related Questions