Func-eval can fail while stopped in a non-optimized managed method that pushes more than 256 argument bytes
In this blog entry, Mike describes that func-eval will fail when not a GC-safe point. In VS this results in the error "Cannot evaluate expression because a thread is stopped at a point where garbage collection is impossible, possibly because the code is optimized". Typically non-optimized (debuggable) managed code is compiled as "fully-interruptible" which means every point is a GC-safe point. However, occasionally I hear complaints that VS is giving the error even when in non-optimized code.
In CLR 2.0, the x86 JIT has a limitiation where it will fall back to partially-interruptible code if more than 256 bytes of arguments are pushed onto the stack (which is normally very rare). [Update: corrected claim that the limitation was introduced in 2.0, .NET 1.1 actually had an even smaller limit of 128 bytes]. For example, the code below passes two 128 byte structures by-value to the function 'BigFunc', and so is compiled as partially-interruptible which means FuncEval isn't necessarily possible at all points in the method.
In the above case, we can verify that the method Main is not fully-interruptible by using the GCInfo command in SOS:
.load sos
extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded!GCInfo 003A00A4
entry point 003a0070
Normal JIT generated code
GC info 11e8a09800121a98
Method info block:
method size = 0167
prolog size = 32
epilog size = 8
epilog count = 1
epilog end = yes
callee-saved regs = EDI ESI EBX EBP
ebp frame = yes
fully interruptible= no
double align = no
arguments size = 0 DWORDs
stack frame size = 44 DWORDs
untracked count = 1
var ptr tab count = 1
security check obj = yes
exception handlers = yes
edit & continue = yes
epilog at 015F
argTabOffset = 5
First we load sos in the immediate window, then get an address in the method of interest (eg. by looking at the value of the EIP register while we're stopped there) and pass it to the !GCInfo command. The "fully intteruptible=no" line is unusual for debuggable code (one way you can tell it's indeed debuggable is the "edit & continue = yes" which isn't normally the case in optimized code).
The other, more common, case where this happens is when calling a method with at least 64 arguments (each of 4 bytes). Having a method that takes 64 arguments seems poor style to me (hard to read), but I've seen such code from code-generation tools. Either way, passing 256 bytes by-value on the stack is generally a bad idea from a performance perspective anyway (it's a fair amount of copying of data). It's usually better to group the data into a class and pass that, and/or pass large value-types by reference (eg. with the 'ref' keyword in C#).
We're looking into relaxing this restriction in a future version of the CLR, but in the meantime if you run into problems with this, I suggest you refactor your code to avoid having methods that take 256 bytes or more in arguments. Perhaps somebody should write an FxCop rule for this.
Comments
- Anonymous
August 16, 2008
PingBack from http://hoursfunnywallpaper.cn/?p=1744