Stupid debugger tricks: Calling functions and methods
Back in the old days, if you wanted to call a function from
inside the debugger, you had to do it by hand:
Save the registers, push the parameters onto the stack
(or into registers if the function uses
push the address of the
move the instruction pointer to the start of the function you want to call,
then hit “g” to resume execution.
The function runs then returns to the
where the debugger regains control and you can look at the results.
Then restore the registers (including the original instruction pointer)
and resume debugging.
(That paragraph was just a quick recap;
I’m assuming you already knew that.)
The Windows symbolic debugger engine (the debugging engine
can now automate this process.
Suppose you want to call this function:
int DoSomething(int i, int j);
You can ask the debugger to do all the heavy lifting:
0:001> .call ABC!DoSomething(1,2) Thread is set up for call, 'g' will execute. WARNING: This can have serious side-effects, including deadlocks and corruption of the debuggee. 0:001> r eax=7ffde000 ebx=00000001 ecx=00000001 edx=00000003 esi=00000004 edi=00000005 eip=10250132 esp=00a7ffbc ebp=00a7fff4 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 ABC!DoSomething: 10250132 55 push ebp 0:001> dd esp 00a7ffbc 00a7ffc8 00000001 00000002 ccfdebcc
Notice that the debugger nicely pushed the parameters onto the stack
and set the
eip register for you.
All you have to do is hit “g” and the
function will run.
Once it returns, the debugger will restore the original state.
This technique even works with C++ methods:
// pretend that we know that 0x00131320 is an IStream pointer 0:001> .dvalloc 1000 Allocated 1000 bytes starting at 00a80000 0:001> .call ABC!CAlphaStream::Read(0x00131320, 0xa80000, 0x1000, 0) Thread is set up for call, 'g' will execute. WARNING: This can have serious side-effects, including deadlocks and corruption of the debuggee.
Notice that when calling a nonstatic C++ method,
you have to pass the “this” parameter as an explicit first parameter.
The debugger knows what calling convention to use and puts the registers
in the correct location.
In this case, it knew that
stdcall calling convention, so the parameters have
all been pushed onto the stack.
And what’s with that
That’s another debugger helper function that allocates some memory
in the debugged process’s address space.
Here, we used it to allocate a buffer that we want to read into.
But what if you want to call a method on an interface, and you
don’t have the source code to the implementation?
For example, you want to read from a stream that was passed to you
from some external component.
Well, you can play a little trick.
You can pretend to call a function that you do
have the source code to, one that has the same function signature,
and then move the
eip register to the desired
// pretend that we know that 0x00131320 is an IStream pointer 0:000> dp 131320 l1 00131320 77f6b5e8 // vtable 0:000> dps 77f6b5e8 l4 77f6b5e8 77fbff0e SHLWAPI!CFileStream::QueryInterface 77f6b5ec 77fb34ed SHLWAPI!CAssocW2k::AddRef 77f6b5f0 77f6b670 SHLWAPI!CFileStream::Release 77f6b5f4 77f77474 SHLWAPI!CFileStream::Read 0:000> .call SHLWAPI!CFileStream::Read(0x00131320, 0xa80000, 0x1000, 0) ^ Symbol not a function in '.call SHLWAPI!CFileStream::Read'
That error message is the debugger’s somewhat confusing way of saying,
“I don’t have enough information available to make that function call.”
But that’s okay, because we have a function that’s “close enough”,
0:001> .call ABC!CAlphaStream::Read(0x00131320, 0xa80000, 0x1000, 0) Thread is set up for call, 'g' will execute. WARNING: This can have serious side-effects, including deadlocks and corruption of the debuggee. 0:000> r eip=SHLWAPI!CFileStream::Read 0:000> r eax=00131320 ebx=0007d628 ecx=00130000 edx=0013239e esi=00000000 edi=00000003 eip=77f77474 esp=0007d384 ebp=0007d3b0 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 SHLWAPI!CFileStream::Read: 77f77474 8bff mov edi,edi
ABC!CAlphaStream::Read to push all the parameters
for us, and then whoosh we swap out that function
CFileStream::Read in its place.
Now you can hit “g” to execute the
This just skims the surface of what you can do with the
Mix in some C++ expression evaluation and you’ve got yourself
a pretty nifty “pseudo-immediate mode” expression evaluator.