Last Steps and the Shutdown of the Program
If the title of this tutorial led you to believe that we would visit EVERY executed instruction in Wilderland.EXE, I am sorry to disappoint you. That would be a virtually impossible task as may be inferred by looking at the size of the Index. There have been, however, a number of highlights along the way that may entice you to explore areas of interest in the CLR. Instead, we will now turn our attention to a brief overview of the shutdown of the program, and, more importantly, how PEBrowse Interactive can help you understand this phase. Do you remember setting an additional breakpoint inside the main method of the Wilderland program a while back? Let us now close down the application (don't terminate it inside the debugger (Debug/Terminate Process)!) The disassembly window should now be displaying the JITted code for Wilderland.WilderlandForm::Main with the current instruction located just after the System.Windows.Forms.Application::Run call. Single-step to the second call statement (remembering to F10 (Debug/Step Over the first call statement) and step into it -- see Figure 12.
Figure 12
For those of you who have read my discussion on the JIT thunk layer, this code may look familiar; to the others I say do not step into the call statement for "mscorwks.dll!_PreStubWorker" -- there are spiders lurking! All should now run to the return statement, and single-step (F11) until you see something looking like the code found in Listing 1.
Listing 1
Disassembly of THUNK at 0x071D1070 0x71D1070: PUSH EDX 0x71D1071: PUSH 0x791B2FD8 0x71D1076: PUSH EBP 0x71D1077: PUSH EBX 0x71D1078: PUSH ESI 0x71D1079: PUSH EDI 0x71D107A: LEA ESI,DWORD PTR [ESP+0x10] 0x71D107E: PUSH ECX 0x71D107F: PUSH EDX 0x71D1080: MOV EBX,DWORD PTR FS:[0xE2C] 0x71D1087: MOV EDI,DWORD PTR [EBX+0x8] 0x71D108A: MOV DWORD PTR [ESI+0x4],EDI 0x71D108D: MOV DWORD PTR [EBX+0x8],ESI 0x71D1090: PUSH DWORD PTR [ESI+0x10] 0x71D1093: MOV EAX,DWORD PTR [ESI+0x14] 0x71D1096: TEST EAX,EAX 0x71D1098: JZ 0x71D109D ; (*+0x5) 0x71D109A: ADD EAX,0xC 0x71D109D: PUSH EAX ; <==0x071D1098(*-0x5) 0x71D109E: MOV EAX,DWORD PTR [ESI-0x18] 0x71D10A1: TEST EAX,EAX 0x71D10A3: JZ 0x71D10A8 ; (*+0x5) 0x71D10A5: ADD EAX,0xC 0x71D10A8: PUSH EAX ; <==0x071D10A3(*-0x5) 0x71D10A9: PUSH DWORD PTR [ESI-0x14] 0x71D10AC: MOV BYTE PTR [EBX+0x4],0x0 0x71D10B0: TEST BYTE PTR [EBX],0x1F 0x71D10B3: JNZ 0x71D10DB ; (*+0x28) 0x71D10B5: MOV EAX,DWORD PTR [ESI+0x8] ; <==0x071D10E0(*+0x2B) 0x71D10B8: CALL DWORD PTR [EAX+0x8] <<<<<<<<<<<< RUN TO HERE! 0x71D10BB: MOV BYTE PTR [EBX+0x4],0x1 0x71D10BF: CMP DWORD PTR [0x793DCA78],0x0 0x71D10C6: JNE 0x71D10E2 ; (*+0x1C) 0x71D10C8: LEA ESP,DWORD PTR [ESI-0x18] ; <==0x071D10E7(*+0x1F) 0x71D10CB: MOV DWORD PTR [EBX+0x8],EDI 0x71D10CE: ADD ESP,0x8 0x71D10D1: POP EDI 0x71D10D2: POP ESI 0x71D10D3: POP EBX 0x71D10D4: POP EBP 0x71D10D5: ADD ESP,0xC 0x71D10D8: RET 0x8 0x71D10DB: CALL mscorwks.dll!InstructionFormat::CanReach + 0x10BE66 ; (0x792C1F55) ; <==0x071D10B3(*-0x28) 0x71D10E0: JMP 0x71D10B5 0x71D10E2: CALL mscorwks.dll!UMThunkStubRareDisableWorker - 0x90EFE ; (0x7923139C) ; <==0x071D10C6(*-0x1C) 0x71D10E7: JMP 0x71D10C8
This code is one of the interop or PInvoke (or Platform Invoke) thunk layer variants between managed and native code -- there might be additional call statements, mostly concerned with security setup on your system. Run until the call statement:
0x71D10B8: CALL DWORD PTR [EAX+0x8]
and step into this call. You will find yourself now magically transported into the MessageBoxW API inside of USER32.DLL, or unmanaged code! Return from this system DLL call (after dismissing the small dialog box) and in short order you will return back to the sheltered, managed environment of .NET. Notice how seamlessly you have moved between the two environments. Hopefully, this little demonstration will convince you that it is possible to debug your application in one session when you have PInvoke calls present. PInvoke calls through COM can also be debugged, only here the interface is more complex and imposing. I will leave it to you to discover how much thicker this layer is.
As the small dialog box informed us, "The adventure is ending". Therefore, we now will briefly examine what transpires when your .NET application is ending. If you turn your attention to the stack frame display, you will find that there are a number of frames displayed. Looking more closely and if you are familiar with the JIT compiler, you may realize from the names that the code has not returned from the first call to the first method that the JIT compiler was asked to handle! Single-step over the next several instructions until you hit the code for mscorwks.dll:SystemDomain::ExecuteMainMethod. If you wish later to more closely investigate what you are skipping over, press View/Execution Path (or Ctrl+X) and you will see a new window with the caption, "Execution Path". PEBrowse Interactive is recording all of the statements it broke on, i.e., that you visited, in the background. You can now copy the instructions, perhaps after appending a comment or two (Edit/Add Remark), to the clipboard, and save the record of your session in a separate file for later review. If you leave this window open, you can see how new instructions are added at the bottom of the display (this action is also configurable). The number in parentheses after the address is a visit count, which may help you recognize when you are stepping through a loop.
Eventually, single stepping brings us to a routine internal to MSCORWKS named EEShutDown where as you might expect the CLR shutdown activities commence. Shortly after that you will find a call to another internal routine, SafeExitProcess -- let's step into that. Aha! We see a call to KERNEL32's API, ExitProcess, which as many who have explored a program's termination know, leads to LdrShutdownProcess inside of NTDLL (bringing us full circle as it were) and after notifying DLLs and the debugger (in the call to CsrClientCallServer) that the process is ending, the code calls to the kernel service, ZwTerminateProcess, which takes care of the remaining cleanup tasks in kernel memory. This is the last user mode instruction for Wilderland.EXE and the last statement PEBrowse Interactive can show you, so I have lived up to at least part of my promise to lead you through the program from its beginning to its end. One final note: the execution path contents remain until you start a new debugging session or close down the debugger, so you will have one final chance to save information about this session before losing it for good.
| | 1st page | next page |