How to break WinDbg in an anonymous method?
Asked Answered
J

3

8

Title kinda says it all. The usual SOS command !bpmd doesn't do a lot of good without a name.

Some ideas I had:

  • dump every method, then use !bpmd -md when you find the corresponding MethodDesc
    • not practical in real world usage, from what I can tell. Even if I wrote a macro to limit the dump to anonymous types/methods, there's no obvious way to tell them apart.
  • use Reflector to dump the MSIL name
    • doesn't help when dealing with dynamic assemblies and/or Reflection.Emit. Visual Studio's inability to read local vars inside such scenarios is the whole reason I turned to Windbg in the first place...
  • set the breakpoint in VS, wait for it to hit, then change to Windbg using the noninvasive trick
    • attempting to detach from VS causes it to hang (along with the app). I think this is due to the fact that the managed debugger is a "soft" debugger via thread injection instead of a standard "hard" debugger. Or maybe it's just a VS bug specific to Silverlight (would hardly be the first I've encountered).
  • set a breakpoint on some other location known to call into the anonymous method, then single-step your way in
    • my backup plan, though I'd rather not resort to it if this Q&A reveals a better way
Justiciable answered 12/3, 2010 at 6:33 Comment(1)
Nota bene: I made an accidental discovery that rendered WinDbg unnecessary. If you programmatically emit the DebuggableAttribute (msdn.microsoft.com/en-us/library/…) during dynamic assembly creation, you'll be able to see local vars in Visual Studio just fine.Justiciable
T
9

The anonymous method isn't really anonymous. It just hides behind a compiler generated name.

Consider this small example:

Func<int, int> a = (x) => x + 1;

Console.WriteLine(a.Invoke(1));

To find the return value, we need to find the name of the method implementation. To do that we need to locate the MethodDesc of the surrounding method. In this example it is Main(), so:

0:000> !name2ee * TestBench.Program.Main
Module: 6db11000 (mscorlib.dll)
--------------------------------------
Module: 00162c5c (TestBench.exe)
Token: 0x06000001
MethodDesc: 00163010
Name: TestBench.Program.Main()
JITTED Code Address: 001e0070

Via the MethodDesc we can dump the IL for Main()

0:000> !dumpil 00163010
ilAddr = 003f2068
IL_0000: nop 
IL_0001: ldstr "press enter"
IL_0006: call System.Console::WriteLine     
IL_000b: nop 
IL_000c: call System.Console::ReadLine 
IL_0011: pop 
IL_0012: ldsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_0017: brtrue.s IL_002c
IL_0019: ldnull 
IL_001a: ldftn TestBench.Program::<Main>b__0
IL_0020: newobj class [System.Core]System.Func`2<int32,int32>::.ctor 
IL_0025: stsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_002a: br.s IL_002c
IL_002c: ldsfld TestBench.Program::CS$<>9__CachedAnonymousMethodDelegate1
IL_0031: stloc.0 
IL_0032: ldloc.0 
IL_0033: ldc.i4.1 
IL_0034: callvirt class [System.Core]System.Func`2<int32,int32>::Invoke 
IL_0039: call System.Console::WriteLine 
IL_003e: nop 
IL_003f: ret 

Notice the funny looking names. They are the names of the generate delegate type and the actual method. The method is called <Main>b__0. Let's look at the method:

0:000> !name2ee * TestBench.Program.<Main>b__0
Module: 6db11000 (mscorlib.dll)
--------------------------------------
Module: 00152c5c (TestBench.exe)
Token: 0x06000003
MethodDesc: 00153024
Name: TestBench.Program.<Main>b__0(Int32)
Not JITTED yet. Use !bpmd -md 00153024 to break on run. 

There you have it. MethodDesc is 00153024 and as the comment say, you can use !bpmd to set the breakpoint using the MethodDesc.

Thinnish answered 12/3, 2010 at 6:33 Comment(0)
F
0

If finding the "<>...." name is tricky for your scenario, how about making it a regular method? This is usually very simple; the only tricky thing is captured variables, but that isn't too bad - for example these do the same thing:

    static void Main()
    {
        List<int> list = new List<int> { 1, 2, 3, 4, 5 };
        int div = 2;
        foreach (var item in list.Where(x => x % div == 0))
        {
            Console.WriteLine(item);
        }

        ListSearcher ls = new ListSearcher();
        ls.div = 2;
        foreach (var item in list.Where(ls.Test))
        {
            Console.WriteLine(item);
        }
    }
    class ListSearcher
    {
        public int div;
        public bool Test(int x)
        {
            return x % div == 0;
        }
    }
Franke answered 12/3, 2010 at 7:8 Comment(0)
A
0

Dump the method descriptor the action points to. Directions here.

Alexipharmic answered 9/12, 2012 at 14:52 Comment(1)
Hi Igor, this is kinda link-only answer, not including the relevant parts.Besmear

© 2022 - 2024 — McMap. All rights reserved.