Reputation: 121
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
How should the child communicate with parent that it has completed foo
and is ready to be modified? raise(SIGSTOP)
maybe?
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
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.
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.
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.
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.
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
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