Search code examples
dtrace

How to change returned value of function


There is a function in this program, that currently returns a 1. I would prefer for it to return a 0.

From this I deduce: we can add the offset to the program counter, uregs[R_PC]+arg0, to find the address of the return value.

I have allocated a 32-bit "0", and I try to write 2 bytes of it into the address where the return value lives (our function expects to return a BOOL16, so we only need 2 bytes of 0):

sudo dtrace -p "$(getpid)" -w -n '
int *zero;
BEGIN { zero=alloca(4); *zero=0; }
pid$target::TextOutA:return {
    copyout(zero, uregs[R_PC]+arg0, 2);
}'

Of course I get:

dtrace: error on enabled probe ID 2 (ID 320426: pid60498:gdi32.dll.so:TextOutA:return): invalid address (0x41f21c) in action #1 at DIF offset 60

uregs[R_PC] is presumably a userspace address. Probably copyout() wants a kernel address.

How do I translate the userspace address uregs[R_PC] to kernel-space? I know that with copyin() we can read data stored at user-space address, into kernel-space. But that doesn't give us the kernel address of that memory.

Alternatively: is there some other way to change the return value using DTrace?


Solution

  • DTrace is not the right tool for this. You should instead use a debugger like dbx, mdb or gdb.

    In the meantime, I'll try to clarify some of the concepts that you've mentioned.

    To begin, you may well see in the source code for a simple function that there is a single return. It is quite possible that the compiled result, i.e. the function's machine-specific implementation, also contains only a single point of exit. Typically, however, the implementation is likely to contain more than one exit point and it may be useful for a developer to know from which specific one a function returned. It is this information, described as an offset from the start of the function, that is given by a return probe's arg0. Your D script, then, is attempting to update part of the program or library itself; although the addition of arg0 makes the destination address somewhat random, the result is most likely still within the text section, which is read-only.

    Secondly, in the common case, a function's implementation returns a value by storing it in a specific register; e.g. %rax on amd64. Thus overriding a return value would neccessitate overriding a register value. This is impossible because DTrace's access to the user-land registers is read-only.

    It is possible that a function is implemented in such a way that, as it returns, it recovers the return value from a specific memory location before writing it into the appropriate register. If this were the case then one could, indeed, modify the value in memory (given its location) just before it is accessed. However, this is going to work for only a subset of cases: the return value might equally be contained in another register or else simply expressed as a constant in the program text itself. In any case, it would be far more trouble than it's worth given the existence of more appropriate debugging tools.