Walk the call stack

Jul 13, 2012 at 9:42 AM

Would it be possible to extend WMemoryProfiler with ability to walk the call stack and obtain references to method arguments ?

Jul 21, 2012 at 7:59 PM

This question is a bit hard to understand. When do you want to get a call stack? Method arguments are when passed via registers as value types like int, float, ... not catchable via a reference. If I understand you right you want to log all method arguments when a specific method is entered and get the call stack. Windbg can do this via the !bpmd command to enter managed breakpoint. But I think you want basically a callback in managed code when a method is entered and the method arguments as an object array.

This would involve some hooking which is doable via the debugger or even easier via the profiling Apis to rewrite the IL code of managed methods. Currently this is out of scope of this project.

But I am thinking to add support for the case that you want to break in a managed method when a specific value was set. This will not be totally generic since I cannot make assumptions about the stack layout how arguments are passed but it could still be helpful e.g. to find out who did open this file but forgot to close it but you do not know who did it because the object is only held alive by the finalizer queue.

Right now I am trying to ensure that MdbgEng.GetObjects does only return the objects from your current AppDomain. I think it might be confusing for you to get objects from completely different AppDomains. Interestingly the GC does not seem to know anything about AppDomains. When you download the latest sources you can see this in the sample of AppDomainTests where you can share between AppDomains objects directly.



  Alois Kraus

Jul 21, 2012 at 9:02 PM

I want to read my own/current callstack. The specific use case I have in mind is improving WPF debugging and error reporting.

I can register my own TraceListener to get notification on errors but no useful data will be passed to me from WPF, only an error string. If I could walk the stack and read arguments (even just "this" reference) I would be able to recover the context and do something useful like highlight elements with errors in UI.

Jul 21, 2012 at 9:27 PM

Did you try to use the StackTrace class or the shortcut Environment.StackTrace? This way you can get the full call stack for your thread.

When you want to get the method arguments you can try the Windbg command via MdgEng as

MdbEng debugger = new MdbEng();
debugger.Execute("~*e!ClrStack -p"); // Dump from all threads all call stacks with method arguments

That will give you output like this

OS Thread Id: 0x1554 (11)
Child SP       IP Call Site
GetFrameContext failed: 1
00000000 00000000 <unknown>

OS Thread Id: 0x17c8 (12)
Child SP       IP Call Site
0742ede8 779a7094 [GCFrame: 0742ede8]
0742ee98 779a7094 [HelperMethodFrame_1OBJ: 0742ee98] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
0742ef1c 53a3d382 System.Threading.Monitor.Wait(System.Object, Int32, Boolean)
        obj = <no data>
        millisecondsTimeout = <no data>
        exitContext = <no data>

0742ef2c 03ca6084 NUnit.Core.EventPump.PumpThreadProc()
        this (0x0742ef2c) = 0x0189fe14

0742ef58 53a0c1af System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
        state = <no data>

0742ef64 539e5ca7 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, B
        executionContext = <no data>
        callback = <no data>
        state = <no data>
        preserveSyncCtx = <no data>

From there you can cast the object pointers back to a plain object reference via the PtrConverter class. This can help you in some cases but in many cases the objects are inlined and the debugger is just not good enough at guessing where in the stack the method arguments are hidden. In this case the command !DumpStackObjects can help you out to get from the stack all object references back. But this command will not give you the method back which does use this object back.

Jul 21, 2012 at 9:49 PM

Indeed, sometimes I can't even read "this" due to inlining. In theory it should be possible to disable JIT optimizations but for some reason it didn't work for me :(

Jul 23, 2012 at 2:23 PM

I have tried both options with Windbg (COMPLUS_ZapDisable=1 and .ini file) and none of them seems to work (.NET 3.5 and 4.5). It does work if I start e.g. NUnit as process beeing debugged directly from Visual Studio but using the environment variable or the ini file did not allow me to use VS to attach to a running NUnit application nor Windbg. I am not sure in which circumstances this thing will help. .NET 4.5 has a new option in SOS to give you all data !ClrStack -i -a to see everything (even locals) but I have not got this working. I have asked the debugging Guru John Robbins in his blog here.

Perhaps he can enlighten us how we can get this stuff to work.

In any case the data is on the stack but the storage locations are due to the many optimizations the JITer can perform very flexible and hard to predict. There seems to exist no API to deduce from optimized code the call stack and the passed arguments. But still it is way better compared to C++ where nearly nothing works anymore. If you ever tried to debug optimized x64 native code you know what I mean.



Jul 23, 2012 at 7:54 PM

I finally got VS working with COMPLUS_ZapDisable=1 though the startup time of both VS and my app increased significantly. I can actually read everything I need with optimizations enabled but I don't know if I can count on it (probably no).

Whole thing seems strange to me, as there should be nothing hard about debugging optimized code - JIT certainly knows the code it generates and should be able to give enough info to debugger. In Java there are no special debug builds or silly pdb files yet everything just works and with much more sophisticated optimizations.