Groups | Blog | Home
all groups > dotnet clr > june 2006 >

dotnet clr : How to get current running values from .LocalVariables in .NET 2.0?


Lou Zher
6/5/2006 10:52:11 AM
We've got a project that needs to be able to see the current running values
from the objects returned back by .LocalVariables. How is this done?
LocalVariables only appears to give us the types of the method vars.
-LZ

Mattias Sjögren
6/5/2006 8:02:00 PM

[quoted text, click to view]

It can't be done with Reflection.


Mattias

--
Mattias Sjögren [C# MVP] mattias @ mvps.org
http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Ben Voigt
6/6/2006 7:53:21 AM
[quoted text, click to view]


What are you trying to do? The values of local variables in any method are
only well-defined at very specific times.

If you are trying to add additional information to an exception stack trace,
please realize that by the time control reaches a handler, the call stack is
already unwound, and stack space may have been overwritten by, for example,
finally blocks that have already executed.

Only methods still on the call stack have valid locals.

If you are in the Exception constructor trying to capture the information,
then it is theoretically possible to capture the details, however consider
that the MSIL is converted to machine code, so any solution is going to be
architecture and runtime specific. For example, mono might store local
variables in a different order than ms.net. There's a WinAPI routine
StackWalk that might be of some use... but not much. You basically require
the debugging API distributed with the JIT compiler, since only it can
determine where the variables are stored.

Also remember that in the case of recursion, there may be multiple stack
frames corresponding to a single MethodBody and LocalVariableInfo.

If you're wanting an automated way to enumerate local variables in the
current method, please consider that your code will never be reuseable
because the process of encapsulating it changes the scope of variable
capture.

The runtime provides minidump functionality, I suggest you use that in lieu
of any attempt to capture state information on your own.

Lou Zher
6/6/2006 11:31:36 AM
[quoted text, click to view]
Ben... In a word, Yes. I'm trying to get a stack trace goodies.


[quoted text, click to view]

Well, that's true, but there is a workaround. Exception handling involves a
two phase stack walk. The first walks up the stack to look for a qualifying
catcher of the exception thrown. Using exception filtering via the WHEN
clause in VB.NET, one can generate StackTrace info while the throwing code
is still alive. It has to be since the second unwind hasn't occurred yet,
which is where the finally blocks are executed. Only then would the locals
of each method be allowed to go out of scope. Here's some interesting code
to try out:

Sub Main()
Try
Helper()
Catch ex As Exception When ExceptionFilter()
End Try
End Sub

Sub Helper()
MakeExceptionHappen()
End Sub

Sub MakeExceptionHappen()
Throw New ApplicationException("Bummer")
End Sub

Function ExceptionFilter() as Boolean
Dim st as New StackTrace() 'gets a stack trace at this point
Console.WriteLine(st.ToString)
Return False
End Function

'and what is printed is:
' at blah.blah.ExceptionFilter(Exception ex)
' at blah.blah.Main() <-- This is interesting!
' at blah.blah.MakeExceptionHappen()
' at blah.blah.Helper()
' at blah.blah.Main()
' at System.AppDomain.nExecuteAssembly ....yadda.yadda......

So what appears to be happening is that the first phase of the exception
handling is walking up the stack looking for a qualifying exception handler
and then injecting a 'call' to a fragment inside Main which in turn calls
the ExceptionFilter. This means that everything down to the point of the
thrower is still available just as though the thrower had called the
ExceptionFIlter code itself.

Okay, so forget about all that stuff for a moment because it's not really
relevant. The question remains, can I get the current values of vars still
in scope via Reflection, Introspection, Debugging API, whatever... I'd like
to get statics, object instance vars, method parameters, and method locals.

Thanks for your help so far Ben.
-LZ

Lou Zher
6/7/2006 9:29:03 AM
Mattias,
Hmm... I was afraid of that. Well, at least I know to bag looking for a
solution inside there. Is there something else I should be looking at? I've
looked briefly at the Debugging API, but it seems horribly complicated to
use for this purpose -- and I'm unclear as to how to make the connection
between the references I find via reflection to values I would obtain via
ICorDebug and friends -- or should I discover the stack/heap from the
debugging API, skipping Reflection altogether?
-LZ

[quoted text, click to view]

Ben Voigt
6/7/2006 12:25:22 PM

[quoted text, click to view]

I didn't realize VB.NET gave you access to exception filters... my mistake.
Do you get any exception information (which points to the stack frame where
the exception occurred, since as you noticed the filter is layered on top of
the excepting method) in the exception filter?

For the level of detail you are desiring, you should consider a minidump
file. I think this note in MiniDumpWriteDump is especially important:
MiniDumpWriteDump may not produce a valid stack trace for the calling
thread. To work around this problem, you must capture the state of the
calling thread before calling MiniDumpWriteDump and use it as the
ExceptionParam parameter. One way to do this is to force an exception inside
a __try/__except block and use the EXCEPTION_POINTERS information provided
by GetExceptionInformation

Since you already have an exception, that should not be a problem, although
getting the exception information in the correct format could be.

[quoted text, click to view]

I don't think you will have any luck using the Debugging API within your
managed application to attach to itself.

[quoted text, click to view]

Lou Zher
6/7/2006 1:17:29 PM
[quoted text, click to view]

Yeah, isn't that cool? And yes, you can get exception information. In my
example, ex is accessible to the filter. So I can generate a StackTrace
based on the exception, which would exclude the filter calls, or I can just
gen a new one based on the current context. This is really neat because it
also means I can examine and exception and choose to catch it or not, vs.
catching it, examining it and choosing to rethrow it or not.

You can also add a filter operand to a .try descriptor in MSIL.

[quoted text, click to view]

Hmmm... that seems interesting. I will look into MiniDumpWriteDump more, but
it seems like that is totally outside the managed space.

My understanding of the Debugging API (which is very poor) was that you'd
want to have unmanaged code running in a different thread calling that, but
have it triggerable from the managed code so you can capture context.

A related problem was that I'd like to be able to apply this type of
technique to the SetEnterLeaveFunctionHooks from the Profiling API to be
able to watch state as methods are executed. Anyone have any experience with
this?

This doesn't seem like it should be so dang hard. Does anyone know what
Visual Studio or mdbg uses to see running values in the locals and autos
debugging windows? (I know, I could just download mdbg source and RTFS but
I'm hoping someone here has already done that.)

BTW: I have the Debugging Applications for Microsoft .NET and Microsoft
Windows by John Robbins (http://www.amazon.com/gp/product/0735615365) . An
excellent book, but it falls a little short of helping me with this project.
I've tried some of the goodies that are on the CD that comes with the book
with mixed results. I anxiously await the release of his new book for .NET
2.0 (http://www.amazon.com/gp/product/0735622027). I mention these in case
anyone has had better fortune than I in getting some of the Debugging API
and Profiling API programs working so I can ask, how useful are these? Is it
worth the effort to get these running?

Ben, Thanks again for your help.
-LZ

Lou Zher
6/9/2006 9:14:35 AM
After some more messing around with this idea, I have come to the
realization that I cannot get the running values from .LocalVariables or
..ParameterInfo directly, but I'm not giving up yet. It seems Reflection is
only designed to operate with a static vision of assemblies, not running
ones.

Here's the list of tactics I am looking into:
* still looking into what I can do using ICorDebug
* looking at the mdbg source to see how I can replicate its ability to see
these vars. I stepped through its execution of the 'Print' command, but I
must admit to be a bit perplexed as to where the real magic happens.
* looking at some form of code injection to automate a series of calls to
publicize these scoped vars to a publicly accessible thingy

Is anyone here experienced in doing any of these? Can you offer any
tips/tricks regarding these techniques? Are there other techniques I may be
missing? I looked at MiniDump, per Ben's suggestion, but it doesn't appear
to offer any way to connect its contents with the variable names/types I can
get.

Is there another group that is more appropriate for these types of
questions? Is there a web forum that is worth trying?

I appreciate your help.
-LZ

Ben Voigt
6/9/2006 10:38:14 AM
[quoted text, click to view]

The minidump file is the Windows analogue to a core file. It can be opened
with your normal debugger, and you'll be able to see stack trace, local
variables, static variables, threads, walk through the trees of contained
objects, etc. In addition, you keep your .pdb files in your development
environment.

Under most circumstances, the fact that your users can't connect the
contents of a minidump file with the variable names is "A Good Thing".

[quoted text, click to view]

Lou Zher
6/9/2006 5:01:43 PM
Ben,
I am now playing around with mdbgcore.dll and have pretty good success with
it. Using Mike Stall's example sure helped.

http://blogs.msdn.com/jmstall/archive/2005/11/28/snapshot.aspx

Because this operates as a debugger, the stack capture code has to live in a
different process, which isn't quite what I wanted, but I'm making it work.
I'm just using System.Diagnostics.Debugger.Break() in the exception filter
to signal into the debugger and have it stack dump.

Works quite nicely. Yay!
-LZ

AddThis Social Bookmark Button