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:
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