3

I'm making a managed .NET debugger using MDBG sample.

Currently I'm struggling with StepInto behavior, while StepOut and StepOver seems to work.

To achieve Just-My-Code stepping I'm calling SetJMCStatus on modules load. That works fine and allow me to debug just my code.

But since I'm setting entire module as JMC, some auto-generated code comes into play and ruin stepping-into. An example of such code could be auto-property.

Since debugger is executing Il instructions, with step-into I'm getting inside auto-generated get_propertyName and set_propertyName method, which are marked as my code, because they are part of my module.

To distinguish such auto-generated code from my code I can use presence of debugging symbols, that are missing in case of auto-generated code. And then I could simply mark method as not my code to skip it during stepping.

The problem is I don't know which methods are auto-generated before I'm getting inside during stepping. When I stepped inside a method that has no debugging symbols I can mark it as not my code, but it's too late - debugger stopped where it supposed not to stop.

Theoretically I could iterate over my module methods using IMetadataImport and set their JMCStatus when debugger starts, but it seems quite expensive:

 foreach (var methodToken in mdbgModule.Importer.EnumerateAllMethodTokens()) {
                var func = mdbgModule.GetFunction(methodToken);
                    if (func.SymMethod == null)
                        func.CorFunction.JMCStatus = false;
            }

If only I would know what function is going to be executed next, then I would be able to set it's status and prevent stepping inside auto-generated code for the first time.

I'm sticking with MDBG approach for stepping, not changing anything, just calling SetJMCStatus where it's needed, so I'm not sure if it makes sense to provide any code... If so, I'll edit the question, just add a comment!

Any suggestion on topic is greatly appreciated!

Regards,

3615
  • 3,787
  • 3
  • 20
  • 35

1 Answers1

1

Mike Stall hinted at one option, you could set JMC for the entire module and then when the debugger stepper breaks, check to see if the method is debuggable, if not then disable it's JMC status and rerun the stepper. (I'm not sure if this will result in a change in behaviour if the resuming stepper would need to step out before stepping in again.)

You could probably improve things by only setting JMC for modules that have a pdb available and by disabling JMC for classes/methods with [DebuggerNonUserCode] applied (and perhaps [DebuggerHidden] too). But rather than enumerating all the classes/methods and checking if they have the attribute, enumerate the custom attributes and work back (IMetaDataImport::EnumCustomAttributes with tkType set but not tk, then use IMetaDataImport::GetCustomAttributeProps to get the thing its applied to).

You might be able to do something similar with the [CompilerGenerated] attribute if its applied at the method level, but will get false positives when applied at the class level (the compiler applies it to state machines for iterators and async methods, but both will likely have non-generated code too).

3615
  • 3,787
  • 3
  • 20
  • 35
Brian Reichle
  • 2,798
  • 2
  • 30
  • 34
  • Thank you, Brian! I've read that post of Mike Stall several times, but even now I wasn't able to fetch the hint from it! Anyway, rerunning the stepper seems like a wounderful idea! I'll try it immediatly and let you know. The problem with CompilerGenerated attribute is that properties get/set can contain user code sometimes. – 3615 Aug 12 '16 at 11:23
  • Btw, I've updated the question with some code related to my initial idea - iteration over the methods. I'm getting the number of methodDefs from IMetadataTables.GetTableInfo and then just creating sequential tokens which are used in ICorDebugModule.GetFunctionFromToken. – 3615 Aug 12 '16 at 11:29
  • The hint I referred to was in the last sentence of the second last paragraph. "This also lets a debugger delay setting JMC status until the user first stops in that method." – Brian Reichle Aug 12 '16 at 11:47
  • Oh, now I see it! But about rerunning the stepper: Seems that I need to call Continue() to check if method is debuggable so I could retrive it from current Frame. But after Continue is called StepOut will not help me, I would have to return back to previous sequence point. Isn't this moving back-forward also quite expensive? Another point of concern is while moving I could be stopped at some breakpoints/exception... – 3615 Aug 12 '16 at 12:03
  • 1
    You would need to call the Step[Out] method before calling Continue. And yes, starting/stopping the debuggee a lot can get expensive, but the nature of stepping through code means that there isn't much opportunity for the delay to accumulate to noticeable levels, but you would have to functionally test the impact to see if it's acceptable. – Brian Reichle Aug 12 '16 at 12:35
  • Finally, at the ICorDebug level, Steppers, breakpoint and exceptions don't interfere with each other. I have had several concurrent steppers on the same thread without issue (though you do need to be careful if two steppers are completed at the same time and you care about their respective order of completion) – Brian Reichle Aug 12 '16 at 12:35