Often, i get the question on how to see what the framework is doing, how to set breakpoints, do live debugging in order to understand what is under the hood.

So, I decided to write on the subject and about the approach I usually take when doing live debugging

Scenario
.Net application that provides a host for workflow-based services.

Tools Required
Debugging Tools for Windows
Lutz Reflector
SOS debugger extension

First, open windbg and attach to the process. After attaching, the debugger will break. So, first question.

How do we find where to stop first? It depends J,  but usually I use Lutz Reflector to get the entry point. So in our sample, I open WFService.exe (my application) in Reflector and see that Main is the entry point (this applies to any method you want to break into).

NOTE: To get the correct syntax look at lower left corner in Reflector where it shows declaring type and assembly name.

Now that we found what we were looking for, here comes question number two.

How to set the breakpoint? Let’s go back to windbg. First step is to load SOS extension. Then we will use !bpmd command to set our breakpoint.
Syntax is: 
         !BPMD <module name> <method name> 
         !BPMD -md <MethodDesc>

Also, help on BPMD is very detailed so in case of any doubt: !help !bpmd

So, let’s set our breakpoint on WFService.Program.Main

0:003> .loadby sos mscorwks
0:003> !bpmd WFService.exe WFService.Program.Main
Found 1 methods...
MethodDesc = 000007ff000433f0
Setting breakpoint: bp 000007FF00180120 [WFService.Program.Main(System.String[])]

Now, press g (go) and let our breakpoint be hit. After our breakpoint got hit I usually look at callstack: !clrstack.

Note: Clrstack shows in the first two columns ESP and EIP.

To set further breakpoints we can follow the same approach as before, but sometimes we want to set breakpoints not only on a method but also inside the method.
In these cases, one other approach is required (also I find it faster than going to Reflector) and that is to use the command BP (for details on breakpoints and different commands do: .hh bp on the debugger)

Lets un assembly the method we are in. (e.g. breakpoint right before calling the constructor below)

WorkflowServiceHost host = new WorkflowServiceHost(typeof(Workflow1), new Uri[0]);

In green is EIP (after doing !clrstack we get this address).
Uf is the command to un assembly function (further details .hh uf in windbg)

0:000> uf 000007ff`00180182
WFService!WFService.Program.Main(System.String[])+0x62
   37 000007ff`00180182 48894528        mov     qword ptr [rbp+28h],rax
   37 000007ff`00180186 90              nop
   38 000007ff`00180187 48b9c8600400ff070000 mov rcx,7FF000460C8h
   38 000007ff`00180191 e8bad458f5      call    mscorwks!RuntimeTypeHandle::GetRuntimeType (000007fe`f570d650)
   38 000007ff`00180196 48894530        mov     qword ptr [rbp+30h],rax
   38 000007ff`0018019a 48b91ab704f2fe070000 mov rcx,offset System_ni+0x4b71a (000007fe`f204b71a)
   38 000007ff`001801a4 33d2            xor     edx,edx
   38 000007ff`001801a6 e8b5fc58f5      call    mscorwks!JIT_NewArr1OBJ_MP_InlineGetThread (000007fe`f570fe60)
   38 000007ff`001801ab 488bd8          mov     rbx,rax
   38 000007ff`001801ae 48b9f85a23e5fe070000 mov rcx,offset System_WorkflowServices_ni+0x165af8 (000007fe`e5235af8)
   38 000007ff`001801b8 e813fb58f5      call    mscorwks!JIT_TrialAllocSFastMP_InlineGetThread (000007fe`f570fcd0)
   38 000007ff`001801bd 48894538        mov     qword ptr [rbp+38h],rax
   38 000007ff`001801c1 4c8bc3          mov     r8,rbx
   38 000007ff`001801c4 488b5530        mov     rdx,qword ptr [rbp+30h]
   38 000007ff`001801c8 488b4d38        mov     rcx,qword ptr [rbp+38h]
   38 000007ff`001801cc e8ffccfae4      call    System.ServiceModel.WorkflowServiceHost..ctor(System.Type, System.Uri[]) (000007fe`e512ced0)
   38 000007ff`001801d1 4c8b5d38        mov     r11,qword ptr [rbp+38h]
   38 000007ff`001801d5 4c895d08        mov     qword ptr [rbp+8],r11
   40 000007ff`001801d9 488b4508        mov     rax,qword ptr [rbp+8]
(…)
   59 000007ff`0018023f 488da588000000  lea     rsp,[rbp+88h]
   59 000007ff`00180246 5d              pop     rbp
   59 000007ff`00180247 5b              pop     rbx
   59 000007ff`00180248 f3c3            rep ret

So i will set a breakpoint right before ctor gets called (see yellow above and below)

0:000> bp 000007ff`001801cc
0:000> g
Breakpoint 0 hit
WFService!WFService.Program.Main(System.String[])+0xac:
000007ff`001801cc e8ffccfae4      call    System.ServiceModel.WorkflowServiceHost..ctor(System.Type, System.Uri[]) (000007fe`e512ced0)

Now, all depends on what we want to do. If we want to check all register values (!clrstack can also show you these, using the parameter –R), we can use windbg windows in VIEW Menu like (Locals, Registry, …). Also I like to have the un assembly window open so that I can step-by-step (visually, because you can use t and p to step into or step over). Register window will show in red what has changed.

In my sample I will step into this method using windbg icons

untitled

If we already know what our final destination is (step-by-step I described just above is very useful when trying to understand how something works), we can take some shortcuts and use Reflector to dig and find our stop.

In my sample, i want to set a breakpoint in System.WorkflowServices.dll in

private void InitializeDescription(WorkflowDefinitionContext workflowDefinitionContext, UriSchemeKeyedCollection baseAddresses)

So, i´m going to use !bpmd like before

0:003> !bpmd System.WorkflowServices.dll System.ServiceModel.WorkflowServiceHost.InitializeDescription
Found 1 methods...
MethodDesc = 000007fee5cb32e8
Setting breakpoint: bp 000007FEE5DA4070 [System.ServiceModel.WorkflowServiceHost.InitializeDescription(System.Workflow.Runtime.WorkflowDefinitionContext, System.ServiceModel.UriSchemeKeyedCollection)]

Press g

0:003> g
ModLoad: 000007fe`ed980000 000007fe`ed9d9000   C:\Windows\assembly\NativeImages_v2.0.50727_64\SMDiagnostics\98ef29a4dddcc3de6a1510907db4a165\SMDiagnostics.ni.dll
ModLoad: 000007fe`e4980000 000007fe`e4ac3000   C:\Windows\assembly\NativeImages_v2.0.50727_64\System.Configuration\1df55745d2cb08fa7a01af7382f81d50\System.Configuration.ni.dll
ModLoad: 000007fe`ef920000 000007fe`effc4000   C:\Windows\assembly\NativeImages_v2.0.50727_64\System.Xml\e185ca4d880a9803d1c8aecba9045b14\System.Xml.ni.dll
Breakpoint 0 hit
System_WorkflowServices_ni!System.ServiceModel.WorkflowServiceHost.InitializeDescription(System.Workflow.Runtime.WorkflowDefinitionContext, System.ServiceModel.UriSchemeKeyedCollection):
000007fe`e5da4070 53              push    rbx

So, i have reached InitializeDescription. When troubleshooting a badboy that changes a variable that shouldn´t or i´m just trying to get more details I look at what is on my stack. And for that we can use !dso (DumpStackObjects)

TIP: registry window show in red, values that have changed when doing step-by-step.

So imagine you are interested on WorkflowDefinitionContext parameter above. After our breakpoint being hit I will use !dso

0:000> !dso
OS Thread Id: 0x15cc (0)
RSP/REG          Object           Name
rbx              00000000025b4bd0 System.ServiceModel.UriSchemeKeyedCollection
rcx              0000000002587520 System.ServiceModel.WorkflowServiceHost
rdx              0000000002587608 System.Workflow.Runtime.CompiledWorkflowDefinitionContext
rsi              0000000002587520 System.ServiceModel.WorkflowServiceHost
rdi              0000000002587608 System.Workflow.Runtime.CompiledWorkflowDefinitionContext
rbp              0000000002587500 System.Object[]
r8               00000000025b4bd0 System.ServiceModel.UriSchemeKeyedCollection
00000000001dea90 0000000002587520 System.ServiceModel.WorkflowServiceHost
00000000001dea98 0000000002587608 System.Workflow.Runtime.CompiledWorkflowDefinitionContext
00000000001deaa0 00000000025b4bd0 System.ServiceModel.UriSchemeKeyedCollection
00000000001dead0 0000000002587500 System.Object[]    (System.Uri[])
00000000001deae0 0000000002587520 System.ServiceModel.WorkflowServiceHost
00000000001deae8 00000000025874d8 System.RuntimeType
00000000001deaf0 0000000002587500 System.Object[]    (System.Uri[])
00000000001deb28 0000000002581308 System.String
00000000001deb30 00000000025874d8 System.RuntimeType
00000000001deb38 0000000002587520 System.ServiceModel.WorkflowServiceHost
00000000001deba0 0000000002583808 System.Object[]    (System.String[])
00000000001ded28 0000000002583808 System.Object[]    (System.String[])
00000000001def10 0000000002583808 System.Object[]    (System.String[])
00000000001def38 0000000002583808 System.Object[]    (System.String[])

As you can see above in black (object address) and in green (object i´m interested on). Now we just use !do (DumpObject) to look inside objects we are interested on.

0:000> !do 0000000002587608
Name: System.Workflow.Runtime.CompiledWorkflowDefinitionContext
MethodTable: 000007fee5de2420
EEClass: 000007fee5c9ec20
Size: 40(0x28) bytes
(C:\Windows\assembly\GAC_MSIL\System.WorkflowServices\3.5.0.0__31bf3856ad364e35\System.WorkflowServices.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
0000000000000000  40001e2        8                       0 instance 0000000000000000 workflowRuntime
000007fee4c1e3d8  40001e4       10 ...entModel.Activity  0 instance 0000000000000000 rootActivity
000007fef33209b8  40001e5       18          System.Type  0 instance 00000000025874d8 workflowType
000007fef33209b8  40001e3      290          System.Type  0   static 0000000002587630 activityType

Now, let’s take some more steps and go to Reflector. Below is InitializeDescription code

protected void InitializeDescription(UriSchemeKeyedCollection
baseAddresses)
{
    foreach (Uri uri in baseAddresses)
    {
        this.baseAddresses.Add(uri);
    }
    IDictionary<string, ContractDescription> implementedContracts = null;
    ServiceDescription description = this.CreateDescription(out implementedContracts);
    this.description = description;
    this.implementedContracts = implementedContracts;
    this.ApplyConfiguration();
    this.initializeDescriptionHasFinished = true;

(…)

 

Let’s suppose we want to see what those URI are (imagine that the only way to find was to step into the foreach). Here we would take the same approach by looking at the method assembly (u) and then use BP to set breakpoint on that line.

I know it might look a bit confusing, but try with some simple samples just to get to know the commands. Also be patient when trying to understand what´s going on inside because sometimes it´s not simple and it requires restarting over and over, long hours but in the end I find it very rewarding.

Till next time.
Bruno