Debugger Exercise: Displaying a Function's Return Value
March 22nd, 2006
I found myself needing to automatically manipulate the return value of a managed function in release code today. I thought this would make an interesting little writeup.
For the purposes of the demonstration, let’s use this example program:
class Program { static void Main() { while (!Console.KeyAvailable) { Console.WriteLine(FooBar(false)); Console.WriteLine(FooBar2()); } } static string FooBar(bool b) { if (b) { return "b"; } return "a"; } static string FooBar2() { return FooBar(true); } }
The function we’re interested in will be FooBar. I’ve made things interesting by calling the function from two places—let’s assume we’re not sure where the function is called from. We’ll make things more interesting still by accepting the limitation that we won’t cheat and set a breakpoint on the ret instruction in FooBar. The function I needed to instrument today was a little monolithic, so let’s take it as a given that we need to be able to handle a function that might be long and might have more than one exit point.
Compile this program and start it in WinDbg. Start by loading SOS:
0:003> .loadby sos mscorwks
The first thing we’ll do is set a breakpoint on FooBar. (I’m going to do this manually so that these steps will work with the CLR v1.1. The new version of SOS makes this slightly easier. I’m also going to cover this quickly, so if you want a clearer explanation of what’s going on in this next step see Eran Sandler’s writeup here.)
To set the breakpoint, let the program run for a second so that the method is compiled by the JIT. Then we need to find and dump out the MethodTable for the class:
0:003> !name2ee retaddr.Program
Module: 00912d5c (retaddr.exe)
Token: 0x02000002
MethodTable: 00913140
EEClass: 009112ec
Name: retaddr.Program
0:003> !dumpmt -md 00913140
EEClass: 009112ec
Module: 00912d5c
Name: retaddr.Program
mdToken: 02000002
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 8
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
...
00d00070 00913120 JIT retaddr.Program.Main()
00d000d8 00913128 JIT retaddr.Program.FooBar(Boolean)
00d00130 00913130 JIT retaddr.Program.FooBar2()
...
Now that we have the MethodTable, we need to dump out the MethodDesc for FooBar to get the virtual address of the jitted code.
0:003> !dumpmd 00913128
Method Name: retaddr.Program.FooBar(Boolean)
Class: 009112ec
MethodTable: 00913140
mdToken: 06000002
Module: 00912d5c
IsJitted: yes
m_CodeOrIL: 00d000d8
The compiled method begins at m_CodeOrIL
(this is called “Method VA” in SOS 1.1). We can set a breakpoint there with bp 00d000d8
.
Here comes the trick. We know that the return address to the method should be on the top of the stack when the method is called. In other words, the ESP register points at the return address. We can set a dynamic breakpoint on that return address using a command like this:
bp 00d000d8 "bp poi(@esp); g"
If you try this, you’ll notice that we break right after FooBar has returned to its caller. As with any function obeying one of the standard calling conventions, the return value is stored in the EAX register. We can give the dynamic breakpoint a command of its own that manipulates this value like this:
bp 00d000d8 "bp poi(@esp) \"!do!do -nofields @eax; g\"; g"
If we run the sample program now, we’ll see this output repeating itself:
breakpoint 1 redefined
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 20(0x14) bytes
String: a
breakpoint 2 redefined
Name: System.String
MethodTable: 790fa3e0
EEClass: 790fa340
Size: 20(0x14) bytes
String: b
Voila.