One intern in my team has been working on a utility, which makes use of binary instrumentation. So I think it's time to recap on that.
Understand the Fundamentals
As we mentioned in Microsoft Binary Technologies and Debugging, there are many binary technologies. Most of these technologies can be used either statically (patch and write back to the disk) or dynamically (hotpatching in memory at runtime). Which one to choose really depends on the requirement.
In most cases, API hooking would be sufficient since it captures the skeleton of execution flow, as well as the inputs and outputs.
API hooking normally happens on the callee side using trampoline technology like Detours. PE header (esp. delay loading, address fix up) and calling convention are must known, and being able to read a bit assembly language is a bonus.
Sometimes you would like to hook API from caller side, normally this would be hijacking the PE import directory. This would eliminate the internal invocations since they are not routed via IAT (e.g. by adding trampoline to a function which invokes itself recursively, each recursion would go through the trampoline thunk).
When it goes deeper such like implementing a profiler or a code coverage analysis tool, Extended Basic Block (EBB) Analysis is unavoidable. This requires solid knowledge over disassembler, compiler backend and linker (code generation, optimization, symbol file, etc.).
If the instrumentation happens in kernel mode, special things need to be considered, such as page-in and page-out, IRQL and spinlock. For SSDT level hooking these normally wouldn't become a problem.
Understand the Runtime and Environment
The art of instrumentation is to live well within the target process.
The instrumentation code would consume additional resources and might introduce side effects:
Last time we revisited x86 segment addressing, which translates logical-address into linear-address. As we mentioned earlier, two stages of address translation would be used to arrive at a physical address: logical-address translation and linear address space paging.
Paging in x86 is optional and is controlled by CR0.PG. If paging is disabled (CR0.PG = 0), the linear-address would be mapped directly into the physical address space of processor. When protection is enabled (CR0.PE = 1), paging can be turned on by having CR0.PG = 1.
As we mentioned, paging is optional, then why do we need paging? I think there are several reasons:
When paging was first introduced to x86 family with the 80386 processor, there was only one paging mode, and the page size will always be 4KB. A lot of features were added as time moves on, such like PAE (Physical Address Extension), PSE (Page Size Extension) and 64bit support. Based on whether certain features are enabled or not, the CPU will determine which paging mode and page size to use.
No matter which paging mode is used, the concept is the same - hierarchical paging structures will be used. Paging always starts from CR3 register, which holds the physical address of the first paging structure, and each paging structure is always 4KB in size. During each step, a portion of the linear address will be used to select an entry from a paging structure, this happens recursively until the entry maps a page instead of referencing another paging structure.
According to the "Intel 64 and IA-32 Architectures Software Developer's Manual", there are three paging modes:
32-Bit Paging
Each paging entry is 4 bytes in size, there are 1024 entries in each paging structure.
The translation process uses 10 bits at a time from a 32-bit linear address:
If PSE enabled, each page is 4-MByte in size, which would reduce one level of indirection (which in turns reduce the TLB pressure):
PAE Paging
Each paging entry is 8 bytes in size, there are 512 entries in each paging structure.
The first paging structure is an exception, which is 32 bytes in size and contains 4 64-bit entries.
The translation process uses 9 bits at a time from a 32-bit linear address, except for the first paging structure:
If PSE enabled, each page is 2-MByte in size.
IA-32e Paging
The translation process uses 9 bits at a time from a 48-bit linear address, except for the first paging structure:
If PSE enabled in PDE, each page is 2-MByte in size.
If PSE enabled in PDPTE, each page is 1-GByte in size.
The main reason of having PSE and large page is to reduce the load of Translation Lookaside Buffer (TLB). However, this requires contiguous physical memory, which would be a problem when physical memory got fragmented (in Windows NT the memory manager would defrag physical memory in kernel mode when contiguous physical memory is required, which is very time consuming).
Now we finished the introduction, and I would recommend some exercises:
Just like we mentioned in The Main Thread Problem, some questions do not have direct answer just because they are invalid by definition.
Today, the invalid question would be:
How do I kill a process tree in Windows?
Unfortunately, the question is invalid, since Windows by design doesn't keep a tree of process creation relationship. Each process does have a parent process ID (except for the Windows Session Manager SMSS.exe), however this information is not going to change when the parent process got terminated.
To verify this, simply run tlist.exe -t and see the rootless processes, at least the following processes don't have a parent on my Win7 machine:
csrss.exe (536) conhost.exe (6036) CicMarshalWnd conhost.exe (8432) CicMarshalWndwinlogon.exe (672)explorer.exe (5640) Program Manager
Now back to the question, probably the intention was to kill all processes spawned from or forked by a certain process. If this is the case, Job Object might help. But please be cautious:
2 and 3 are subject to change in the near future.
Another option I could think of is to hook process creations and maintain our own data structure.
This could be done in either user mode or kernel mode, one possible approach could be:
#include <ddk/ntddk.h> extern "C" DDKAPI NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath); static DDKAPI VOID CreateProcessNotifyRoutine(HANDLE hPPID, HANDLE hPID, BOOLEAN bCreate); static DDKAPI VOID DriverUnload(PDRIVER_OBJECT pDriverObject); DDKAPI NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { NTSTATUS retval; pDriverObject->DriverUnload = DriverUnload; retval = PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, FALSE); return retval; } DDKAPI VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, TRUE); } DDKAPI VOID CreateProcessNotifyRoutine(HANDLE hPPID, HANDLE hPID, BOOLEAN bCreate) { DbgPrint("%s(PPID=%u, PID=%u)", bCreate ? "CreateProcess" : "TerminateProcess", hPPID, hPID); }
I wouldn't recommend the driver way for several reasons:
In my previous blog Early Debugging, we've demonstrated how early can you get using a user mode debugger.
Normally we don't want to be such early, there are some other places we would want to start with:
Now let's talk a bit about the native C/C++ Runtime. When you start writing applications using C/C++ on Windows, normally you would be using CRT already, unless you explicitly tell the linker not to use it, like what I did in A Debugging Approach to IFEO.
The CRT (C Runtime Library) comes with Windows and Visual C++ Redistributable (let's not talk about the special version which serves CLR), also you can link a static version into your EXE/DLL.
CRT provides the fundamental C++ runtime support, some obvious features are:
Let's get to the code:
/* crtexport.cpp */ #define WIN32_LEAN_AND_MEAN #include <Windows.h> class CFoobar { public: CFoobar() { OutputDebugString(TEXT("CFoobar::CFoobar()\n")); } ~CFoobar() { OutputDebugString(TEXT("CFoobar::~CFoobar()\n")); } }; CFoobar g_foobar; __declspec(dllexport) BOOL WINAPI Foobar() { return TRUE; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvContext) { switch(fdwReason) { case DLL_PROCESS_ATTACH: OutputDebugString(TEXT("DLL_PROCESS_ATTACH\n")); break; case DLL_PROCESS_DETACH: OutputDebugString(TEXT("DLL_PROCESS_DETACH\n")); break; case DLL_THREAD_ATTACH: OutputDebugString(TEXT("DLL_THREAD_ATTACH\n")); break; case DLL_THREAD_DETACH: OutputDebugString(TEXT("DLL_THREAD_DETACH\n")); break; default: DebugBreak(); } return TRUE; }
Note: don't put DebugBreak inside DLL entry point as I do, unless you understand that the loader lock would make JIT debugger unhappy.
/* crtimport.cpp */ #define WIN32_LEAN_AND_MEAN #include <Windows.h> BOOL WINAPI Foobar(); int main() { Foobar(); return 0; }
cl.exe /LD /Zi crtexport.cpp
cl.exe /Zi crtimport.cpp crtexport.lib
Set two breakpoints, one at DllMain and one at the main function, then launch the application in Visual Studio Debugger:
Since our DLL is statically imported, the entry point of DLL is executed before the entry point of EXE.
As you might have noticed, the actual OEP is _DllMainCRTStartup. You can double click on the crtexport.dll!_DllMainCRTStartup frame and bring up the CRT startup code to start reading - on my machine the startup code is located at C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dllcrt0.c.
Also, by taking a look at the Output window, we can see that CFoobar::CFoobar() has already been called, which means the global object was initialized before entering our DllMain. This is of course done by the CRT initialization code in __DllMainCRTStartup, which understands the contract between compiler and runtime.
Now you understand how the constructor of global variables gets called, think about the destructor semantic:
The actual OEP for the EXE is __tmainCRTStartup. You can double click on the crtimport.exe!__tmainCRTStartup frame and take a look at the code - on my machine the startup code is located at C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\crt0.c.
As we mentioned in The Main Thread Problem, __tmainCRTStartup runs in the "main thread", and would kill all the other threads before it is going to destroy the global variables. One thing to mention is that CRT makes use of _endthreadex instead of calling ExitThread directly, since _endthreadex would destruct objects constructed on the stack and free the related TLS data, while ExitThread knows nothing about the _tiddata block.
A few more questions:
Memory segmentation was first introduced to x86 family with 8086, to make it possible to access 1MB physical memory under 16bit addressing mode.
Real Mode
Logical address points directly into physical memory location.
Logical address consists of two parts: segment and offset.
Physical address is calculated as Segment * 16 + Offset, and if the A20 line is not enabled, the physical address is wrapped around into 1MiB range.
Protected Mode
Two stages of address translation would be used to arrive at a physical address: logical-address translation and linear address space paging.
Logical address consists of two parts: segment selector and offset.
Segment selector is used to determine the base address as well as the access rights. Segment selelctors are maintained in a table known as GDT (Global descriptor-table) or LDT (Local descriptor-table), which is referenced by GDTR or LDTR register.
When a thread is running in kernel mode, fs:[0] points to the PCR (Processor Control Region). Let's find it by manually translating the logical address:
kd> r fs fs=00000030 kd> .formats @fs Evaluate expression: Hex: 00000030 Decimal: 48 Octal: 00000000060 Binary: 00000000 00000000 00000000 00110000 Chars: ...0 Time: Thu Jan 01 08:00:48 1970 Float: low 6.72623e-044 high 0 Double: 2.37152e-322
According to the x86 architecture specification, we have Index = 6 (0000000000110), Table Indicator = GDT (0) and RPL (Requested Privilege Level) = 0.
kd> dd @gdtr 8003f000 00000000 00000000 0000ffff 00cf9b00 8003f010 0000ffff 00cf9300 0000ffff 00cffb00 8003f020 0000ffff 00cff300 200020ab 80008b04 8003f030 f0000001 ffc093df 00000fff 0040f300 8003f040 0400ffff 0000f200 00000000 00000000 8003f050 a0000068 80008954 a0680068 80008954 8003f060 2f40ffff 00009302 80003fff 0000920b 8003f070 700003ff ff0092ff 0000ffff 80009a40
We can get the layout of segment descriptor from x86 architecture specification:
Now we can verify the address translation by looking at the memory contents:
kd> dg @fs P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- -------- -------- ---------- - -- -- -- -- -------- 0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P Nl 00000c93
kd> dd fs:[0] 0030:00000000 805495d0 80549df0 80547000 00000000 0030:00000010 00000000 00000000 00000000 ffdff000 0030:00000020 ffdff120 0000001c 00000000 00000000 0030:00000030 ffff2050 80545bb8 8003f400 8003f000 0030:00000040 80042000 00010001 00000001 00000064 0030:00000050 00000000 00000000 00000000 00000000 0030:00000060 00000000 00000000 00000000 00000000 0030:00000070 00000000 00000000 00000000 00000000 kd> dd ffdff000 ffdff000 805495d0 80549df0 80547000 00000000 ffdff010 00000000 00000000 00000000 ffdff000 ffdff020 ffdff120 0000001c 00000000 00000000 ffdff030 ffff2050 80545bb8 8003f400 8003f000 ffdff040 80042000 00010001 00000001 00000064 ffdff050 00000000 00000000 00000000 00000000 ffdff060 00000000 00000000 00000000 00000000 ffdff070 00000000 00000000 00000000 00000000
It's time to dump the KPCR structure:
kd> dt nt!_KPCR ffdff000 +0x000 NtTib : _NT_TIB +0x01c SelfPcr : 0xffdff000 _KPCR +0x020 Prcb : 0xffdff120 _KPRCB +0x024 Irql : 0x1c '' +0x028 IRR : 0 +0x02c IrrActive : 0 +0x030 IDR : 0xffff2050 +0x034 KdVersionBlock : 0x80545bb8 Void +0x038 IDT : 0x8003f400 _KIDTENTRY +0x03c GDT : 0x8003f000 _KGDTENTRY +0x040 TSS : 0x80042000 _KTSS +0x044 MajorVersion : 1 +0x046 MinorVersion : 1 +0x048 SetMember : 1 +0x04c StallScaleFactor : 0x64 +0x050 DebugActive : 0 '' +0x051 Number : 0 '' +0x052 Spare0 : 0 '' +0x053 SecondLevelCacheAssociativity : 0 '' +0x054 VdmAlert : 0 +0x058 KernelReserved : [14] 0 +0x090 SecondLevelCacheSize : 0 +0x094 HalReserved : [16] 0 +0x0d4 InterruptMode : 0 +0x0d8 Spare1 : 0 '' +0x0dc KernelReserved2 : [17] 0 +0x120 PrcbData : _KPRCB kd> !pcr KPCR for Processor 0 at ffdff000: Major 1 Minor 1 NtTib.ExceptionList: 805495d0 NtTib.StackBase: 80549df0 NtTib.StackLimit: 80547000 NtTib.SubSystemTib: 00000000 NtTib.Version: 00000000 NtTib.UserPointer: 00000000 NtTib.SelfTib: 00000000 SelfPcr: ffdff000 Prcb: ffdff120 Irql: 0000001c IRR: 00000000 IDR: ffff2050 InterruptMode: 00000000 IDT: 8003f400 GDT: 8003f000 TSS: 80042000 CurrentThread: 80552840 NextThread: 00000000 IdleThread: 80552840 DpcQueue:
Every few months I heard people asking the same question:
Given a process ID (or handle), how can I get its main thread ID (or handle)?
Normally that would raise another question:
What is the definition of a main thread?
While the Windows operating system doesn't have a concept called main thread, and threads donnot have parent-child relationship at all.
Let's reuse the sample code from Pop Quiz - Debug Event Loop and Timeslice Quota:
If we compiled the code using cl.exe test.cpp, the generated test.exe would return immediately after we run it. If we take a quick debug, the call stack would look like this:
test!ILT+10(WinMain) test!__tmainCRTStartup+0x154 kernel32!BaseThreadInitThunk+0xd ntdll!RtlUserThreadStart+0x1d
That's because the compiler has made the decision that we need to use CRT initialization, although actually we are not using it either explicitly or implicitly. It is the CRT exit code which called ntdll!RtlExitUserProcess and terminated our worker threads, and this is by design.
Now let's switch to the following command:
cl test.cpp /link /NODEFAULTLIB /ENTRY:WinMain /SUBSYSTEM:CONSOLE kernel32.lib
As you can see, test.exe would enter an endless loop.
Now let's try to give some possible definitions of main thread:
It looks like option 4 and 5 have the most clean definition. If we use option 4 then we should stay with the facts that a process may not have a main thread, and that's why we would normally end up with option 5.
Which one do you prefer and what is your own definition? Which option do you think the Visual Studio Debugger would use?
The developers in Microsoft have done a great job by bringing a great number of nice features, however, some of these features are poorly documented or even not documented at all.
Autos Window in the Visual Studio Debugger is one of the best example of the gaps between implementation and documentation. I'm sure you have seen this window before, as it's shown by default while you are debugging, if not, you can always find it from the Debug menu:
The MSDN document hasn't been updated since Visual Studio .NET 2003, which can be found at http://msdn.microsoft.com/en-us/library/aa290702.aspx:
The Autos window displays variables used in the current statement and the previous statement. (For Visual Basic, it displays variables in the current statement and three statements on either side of the current statement.)
The document does leave us with some questions:
Generally speaking, debuggers wouldn't care about source code, it knows nothing about the C++ preprocessing or the syntax (the only exception is the expression evaluator, which would be another topic). The magic of source debugging was brought by symbol files. The private PDB contains the path and checksum of source files, as well as the line number information (PDB actually supports line and column number, and this has been used in C# already), debugger just read from the symbols to get these information.
Let's open Visual Studio 2010 and take a look at the following snippet in C:
int x; int y; int z; int a; int b; int c; int main(int argc) { return b + c; }
Set a breakpoint at the line of return statement, bring up the Autos Window:
First, it looks like the ordering in the Autos Window is based on variable name, so we got another question "what would happen if there is a duplicated variable name?".
Now let's switch to another snippet:
int foo(int* x) { return *x; } int main(int argc) { return foo(&argc); }
When we hit the breakpoint, the Autos Window would be:
Step into the function foo and step out, now the Autos Window would look like:
As you see, the return value is displayed as "foo returned" in the Autos Window, if you right click and select Add Watch, you will be welcomed with an error message CXX0013: Error: missing operator.
Okay, I've given enough questions and hints, now it is time to try out by yourself, hopefully you could understand a bit more on what the Autos Window is and the way it works.
Early debugging is a wide topic, on a Windows PC it might be:
Application Startup
As we have demonstrated in the user mode debug event loop, when an application was launched from a debugger, the first debug event is CREATE_PROCESS_DEBUG_EVENT. Process creation event is the earliest point a user mode debugger could even reach to.
Windows debuggers by default would break at ntdll!LdrpDoDebuggerBreak, but we can alter this behavior:
cdb.exe -xe cpr -xe ld notepad.exe
CommandLine: notepad.exe ModLoad: 01000000 01014000 notepad.exe 0:000> lm start end module name 01000000 01014000 notepad (deferred) 0:000> !teb TEB at 7ffdf000 error InitTypeRead( TEB )...
As you can see, debugger extension complains since we are too early. However there are always workarounds as we discussed in Undocumented WinDBG:
0:000> .imgscan; * Where is Mark Zbikowski? MZ at 01000000, prot 00000002, type 01000000 - size 14000 Name: notepad.exe MZ at 7c900000, prot 00000002, type 01000000 - size b2000 Name: ntdll.dll 0:000> .reload /s /f ntdll.dll=7c900000 0:000> lm start end module name 01000000 01014000 notepad (deferred) 7c900000 7c9b2000 ntdll (pdb symbols) 0:000> !teb TEB at 7ffdf000 ExceptionList: ffffffff StackBase: 00080000 StackLimit: 0006f000 SubSystemTib: 00000000 FiberData: 00001e00 ArbitraryUserPointer: 00000000 Self: 7ffdf000 EnvironmentPointer: 00000000 ClientId: 000007d4 . 000005b4 RpcHandle: 00000000 Tls Storage: 00000000 PEB Address: 7ffd8000 LastErrorValue: 0 LastStatusValue: 0 Count Owned Locks: 0 HardErrorMode: 0
If the application is launched by another process, IFEO might help, but always keep in mind there can be side effects.
Service Startup
Most of the knowledge about application debugging applies to services, since they are both user mode processes. The only difference is that service can share a single hosting process (e.g. svchost.exe), and would normally start in a different session.
An excellent article about service debugging can be found at:
Windows Setup, OS Loader, CSRSS and WinLogon
The document shipped with Debugging Tools for Windows has some brief introduction.
MSDN also described these debugging tasks in Specialized Debugging Techniques.
POST and MBR
Most of these are real mode code (although MBR might switch CPU to protected mode) dealing with the low level hardware. Not many people are still working on the old memory models (e.g. TINY, SMALL, COMPACT, MEDIUM, LARGE and HUGE) and the A20 line (do you remember Tim Paterson and his debug program?).
MBR is relatively small, and can be simply debugged using a software emulator.
I have never worked on POST, but I think people would use software emulators in combination with ICE (In-circuit emulator).
WinDBG has limited support for real mode debugging.
(to be continued...)
Abstraction and encapsulation are good because they make it easier to build complex systems, however, there are times you have to peek inside the abstraction and demistify the encapsulation. This is especially true for debugging and performance tuning (I will not talk about reverse engineering this time). Familiar yourself with the right tools are very important, and one way to achieve this is to debug into these tools...
Before getting started, I would like to wetting your appetite with a few questions:
Let's revisit the commands in WinDBG. There are three types of commands available:
The documented way of working with extensions contains a few commands like .chain, .extmatch, .load, .loadby, .setdll, .unload and .unloadall, an undocumented command .extcmds is also available.
0:000> .extmatch /e dbghelp * !dbghelp.chksym !dbghelp.dh !dbghelp.homedir !dbghelp.lmi !dbghelp.stackdbg !dbghelp.sym
However, once you understand how these extension DLLs work, you can dump the PE/COFF Export Directory to get a full list:
0:000> .sympath . Symbol search path is: . Expanded Symbol search path is: . 0:000> .reload /f dbghelp.dll *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Debugging Tools for Windows (x64)\dbghelp.dll - 0:000> x dbghelp!* 00000000`68013dd0 dbghelp!ExtensionApiVersion (<no parameter info>) 00000000`68013de0 dbghelp!WinDbgExtensionDllInit (<no parameter info>) 00000000`68014090 dbghelp!fptr (<no parameter info>) 00000000`68014130 dbghelp!vc7fpo (<no parameter info>) 00000000`680141b0 dbghelp!stackdbg (<no parameter info>) 00000000`680146e0 dbghelp!stack_force_ebp (<no parameter info>) 00000000`680148c0 dbghelp!sym (<no parameter info>) 00000000`68014a50 dbghelp!symsrv (<no parameter info>) 00000000`68014bd0 dbghelp!lminfo (<no parameter info>) 00000000`68015060 dbghelp!lmi (<no parameter info>) 00000000`680164e0 dbghelp!itoldyouso (<no parameter info>) 00000000`68016720 dbghelp!chksym (<no parameter info>) 00000000`68016960 dbghelp!block (<no parameter info>) 00000000`68016b50 dbghelp!omap (<no parameter info>) 00000000`68016e10 dbghelp!homedir (<no parameter info>) 00000000`68017030 dbghelp!srcfiles (<no parameter info>) 00000000`68018a90 dbghelp!dh (<no parameter info>)
0:000> !itoldyouso !IToldYouSo <module> [symbol] !IToldYouSo tests the validity of a module against a symbol file. The module can be specified by either its name or base address. If a symbol file is not specified, then the loaded symbol is tested. Otherwise, if a pdb or dbg symbol file path is specified, it is tested against the loaded module.
Most of the meta commands are implemented in dbgeng.dll, a few are implemented inside specific debuggers (e.g. .browse and .wtitle are WinDBG specific meta commands). Some meta commands are only available for certain modes/targets/platforms, for example, .kdfiles is only available for live kernel debugging on x86-based and Itanium-based processors.
Some meta commands might be undocumented (e.g. .aliascmds and .sxcmds) or documented separately (e.g. .perfer_dml is documented in dml.doc which can be found under the WinDBG installation folder, .jdinfo is only documented in .help), there is no centralized place to get all these commands.
The following command would dump all the dot commands exported by dbgeng.dll (dbgeng!DotCommand is an exception, which is not a meta command):
0:000> x dbgeng!Dot* 00000000`517aba70 dbgeng!DotRestart = <no type information> 00000000`51801c70 dbgeng!DotExtMatch = <no type information> 00000000`517a9ac0 dbgeng!DotLogAppend = <no type information> 00000000`517ae600 dbgeng!DotTTime = <no type information> 00000000`517a3d70 dbgeng!DotEchoTimestamps = <no type information> 00000000`517aacd0 dbgeng!DotPromptAllow = <no type information> 00000000`517a53e0 dbgeng!DotEventStr = <no type information> 00000000`517aa0b0 dbgeng!DotOCommand = <no type information> 00000000`5179de00 dbgeng!DotPageIn = <no type information> 00000000`517a09f0 dbgeng!DotCache = <no type information> 00000000`517aaa80 dbgeng!DotPrintf = <no type information> 00000000`517a60a0 dbgeng!DotFnRet = <no type information> 00000000`5179d3c0 dbgeng!DotThread = <no type information> 00000000`517a9a40 dbgeng!DotLocale = <no type information> 00000000`517a46f0 dbgeng!DotEndSrv = <no type information> 00000000`517a33b0 dbgeng!DotDumpOff = <no type information> 00000000`517a5900 dbgeng!DotExr = <no type information> 00000000`517a2880 dbgeng!DotCxr = <no type information> 00000000`517a4f50 dbgeng!DotEvents = <no type information> 00000000`5179fb30 dbgeng!DotApplyDbp = <no type information> 00000000`517ae350 dbgeng!DotTrap = <no type information> 00000000`517a98c0 dbgeng!DotKFrames = <no type information> 00000000`517aca70 dbgeng!DotSleep = <no type information> 00000000`5179f9e0 dbgeng!DotAllowImageMapping = <no type information> 00000000`517a3e20 dbgeng!DotEcxr = <no type information> 00000000`517a5e30 dbgeng!DotFixImports = <no type information> 00000000`517aa3b0 dbgeng!DotOpenDump = <no type information> 00000000`517a0500 dbgeng!DotBpSync = <no type information> 00000000`5179d9b0 dbgeng!DotProcess = <no type information> 00000000`517ac280 dbgeng!DotServers = <no type information> 00000000`5179dd80 dbgeng!DotKernelKill = <no type information> 00000000`517a2ea0 dbgeng!DotDmlStart = <no type information> 00000000`517ab890 dbgeng!DotRecordBranches = <no type information> 00000000`517a06c0 dbgeng!DotBugCheck = <no type information> 00000000`517a2bd0 dbgeng!DotDebugSwWow = <no type information> 00000000`517a1510 dbgeng!DotContext = <no type information> 00000000`517a4740 dbgeng!DotEnumTag = <no type information> 00000000`517a8b40 dbgeng!DotIf = <no type information> 00000000`5179db50 dbgeng!DotFiber = <no type information> 00000000`517ab810 dbgeng!DotReboot = <no type information> 00000000`517a03a0 dbgeng!DotBpCmds = <no type information> 00000000`5183b550 dbgeng!DotDmlFlow = <no type information> 00000000`517ace20 dbgeng!DotSrcPath = <no type information> 00000000`517a3380 dbgeng!DotDumpDebug = <no type information> 00000000`517adf50 dbgeng!DotProcessInfo = <no type information> 00000000`5179f950 dbgeng!DotAllowBpBaConvert = <no type information> 00000000`517ab960 dbgeng!DotReCxr = <no type information> 00000000`518568c0 dbgeng!DotPCmd = <no type information> 00000000`517aba10 dbgeng!DotReload = <no type information> 00000000`5179fe60 dbgeng!DotAttach = <no type information> 00000000`517aa070 dbgeng!DotNoVersion = <no type information> 00000000`518eb230 dbgeng!DotLines = <no type information> 00000000`517a2dd0 dbgeng!DotDmlFile = <no type information> 00000000`517a7740 dbgeng!DotFormats = <no type information> 00000000`517accb0 dbgeng!DotSrcNoisy = <no type information> 00000000`517a3170 dbgeng!DotDumpCab = <no type information> 00000000`517a8e00 dbgeng!DotIgnoreWowKdContext = <no type information> 00000000`517ad880 dbgeng!DotTList = <no type information> 00000000`517ab3c0 dbgeng!DotReadMem = <no type information> 00000000`517acb20 dbgeng!DotSrcFix = <no type information> 00000000`517a5670 dbgeng!DotExPtr = <no type information> 00000000`517a6540 dbgeng!DotForceBranchTrace = <no type information> 00000000`517a3540 dbgeng!DotDumpPOff = <no type information> 00000000`517aec40 dbgeng!DotWake = <no type information> 00000000`517a0370 dbgeng!DotBlock = <no type information> 00000000`517abcb0 dbgeng!DotSendFile = <no type information> 00000000`517a62d0 dbgeng!DotFor = <no type information> 00000000`517a3140 dbgeng!DotDrivers = <no type information> 00000000`517a9b10 dbgeng!DotLogFile = <no type information> 00000000`517a37d0 dbgeng!DotDvAlloc = <no type information> 00000000`517ab0a0 dbgeng!DotPop = <no type information> 00000000`517d9c20 dbgeng!DotDump = <no type information> 00000000`517a7c30 dbgeng!DotFrame = <no type information> 00000000`517af540 dbgeng!DotHelp = <no type information> 00000000`517a9be0 dbgeng!DotLogOpen = <no type information> 00000000`517af8b0 dbgeng!DotCommand = <no type information> 00000000`517a9c00 dbgeng!DotNetSyms = <no type information> 00000000`517ace80 dbgeng!DotStepFilter = <no type information> 00000000`517a1660 dbgeng!DotContinue = <no type information> 00000000`517a05d0 dbgeng!DotBreakin = <no type information> 00000000`517a4170 dbgeng!DotEnableLongStatus = <no type information> 00000000`517a6680 dbgeng!DotForceSystemInit = <no type information> 00000000`517a5f90 dbgeng!DotFnEnt = <no type information> 00000000`517a0e70 dbgeng!DotCatch = <no type information> 00000000`517a9840 dbgeng!DotKdFiles = <no type information> 00000000`517a0ea0 dbgeng!DotChain = <no type information> 00000000`517a2380 dbgeng!DotCrash = <no type information> 00000000`5183a420 dbgeng!DotAsm = <no type information> 00000000`517ac900 dbgeng!DotShowSymFailures = <no type information> 00000000`517ac1b0 dbgeng!DotServer = <no type information> 00000000`517ac800 dbgeng!DotShowReadFailures = <no type information> 00000000`517a6810 dbgeng!DotForEach = <no type information> 00000000`517e9d60 dbgeng!DotEventLog = <no type information> 00000000`517ad210 dbgeng!DotSymOpt = <no type information> 00000000`517a6620 dbgeng!DotForceRadixOutput = <no type information> 00000000`517a7160 dbgeng!DotFpo = <no type information> 00000000`517a3fa0 dbgeng!DotEffMach = <no type information> 00000000`517a7d80 dbgeng!DotFrameRel = <no type information> 00000000`517ae430 dbgeng!DotTss = <no type information> 00000000`51725a30 dbgeng!DotAliasCmds = <no type information> 00000000`517a41d0 dbgeng!DotEnableUnicode = <no type information> 00000000`517aa030 dbgeng!DotNoShell = <no type information> 00000000`517aa540 dbgeng!DotOutMask = <no type information> 00000000`517a9880 dbgeng!DotKdTrans = <no type information> 00000000`517a3d20 dbgeng!DotEchoTime = <no type information> 00000000`517aa800 dbgeng!DotPreferDml = <no type information> 00000000`517a9a10 dbgeng!DotLeave = <no type information> 00000000`517a7ec0 dbgeng!DotFrameEbpFix = <no type information> 00000000`517a05a0 dbgeng!DotBreak = <no type information> 00000000`517a3a30 dbgeng!DotDvFree = <no type information> 00000000`517a54a0 dbgeng!DotExpr = <no type information> 00000000`517a30b0 dbgeng!DotDbgDbg = <no type information> 00000000`517a5930 dbgeng!DotExtCmds = <no type information> 00000000`517aed10 dbgeng!DotWriteMem = <no type information> 00000000`517ad040 dbgeng!DotSxCmds = <no type information> 00000000`517a59e0 dbgeng!DotExtPath = <no type information> 00000000`517aafc0 dbgeng!DotPush = <no type information> 00000000`517ad0e0 dbgeng!DotSymFix = <no type information> 00000000`517a2420 dbgeng!DotCreate = <no type information> 00000000`517ac2b0 dbgeng!DotShell = <no type information> 00000000`517a3010 dbgeng!DotDo = <no type information> 00000000`517aec90 dbgeng!DotWhile = <no type information> 00000000`517a9d40 dbgeng!DotNetUse = <no type information> 00000000`517ae170 dbgeng!DotTimeZone = <no type information> 00000000`517a0f90 dbgeng!DotChildDbg = <no type information> 00000000`517a1130 dbgeng!DotClients = <no type information> 00000000`517a4680 dbgeng!DotEndPSrv = <no type information> 00000000`517a8910 dbgeng!DotHoldMem = <no type information> 00000000`517a8ea0 dbgeng!DotImgScan = <no type information> 00000000`517ad3e0 dbgeng!DotTListFromNtQuerySystemInformation = <no type information> 00000000`517a4b40 dbgeng!DotEventCode = <no type information> 00000000`517a3630 dbgeng!DotDumpAddRgn = <no type information> 00000000`517a9790 dbgeng!DotJdInfo = <no type information> 00000000`517ad330 dbgeng!DotSymPath = <no type information> 00000000`517ade40 dbgeng!DotTime = <no type information> 00000000`517a5430 dbgeng!DotExePath = <no type information> 00000000`517a8d60 dbgeng!DotIgnoreMissingPages = <no type information> 00000000`517aeaa0 dbgeng!DotTxtSym = <no type information> 00000000`517a3c70 dbgeng!DotEchoCpuNum = <no type information> 00000000`517a3c20 dbgeng!DotEcho = <no type information> 00000000`517aa010 dbgeng!DotNoEngErr = <no type information> 00000000`517aa7d0 dbgeng!DotPCache = <no type information> 00000000`5179f8b0 dbgeng!DotAllowExecCmds = <no type information> 00000000`517a1ab0 dbgeng!DotCorDll = <no type information> 00000000`517a26e0 dbgeng!DotCreateDir = <no type information> 00000000`517a1860 dbgeng!DotCopySym = <no type information> 00000000`517a9af0 dbgeng!DotLogClose = <no type information> 00000000`517a4230 dbgeng!DotTypeOpt = <no type information> 00000000`517a0b70 dbgeng!DotCall = <no type information> 00000000`517abc10 dbgeng!DotSecure = <no type information> 00000000`517aa230 dbgeng!DotOFilter = <no type information> 00000000`517a11f0 dbgeng!DotCloseHandle = <no type information> 00000000`517ab180 dbgeng!DotQuitLock = <no type information> 00000000`517a9990 dbgeng!DotLastEvent = <no type information> 00000000`517a2220 dbgeng!DotCorStack = <no type information>
Also, you might have noticed that .elif and .else donnot have their corresponding exports like .if does, they just look like meta commands (the document called them Command Tokens). To understand how the command line got parsed and executed (like a simplified version of compiler front end), I would recommend debugging the debugger by setting a breakpoint on the Win32 Beep function (kernel32!Beep or KERNELBASE!Beep) and use .beep, below is what I got on my Windows XP box:
0:000> k kernel32!Beep windbg!DirectCommand+0x12a windbg!CmdExecuteCmd+0x90 windbg!WinCommand::OnNotify+0x467 windbg!WinBase::BaseProc+0xc91 USER32!InternalCallWinProc+0x28 USER32!UserCallWinProcCheckWow+0x150 USER32!SendMessageWorker+0x4a5 USER32!SendMessageW+0x7f MSFTEDIT!CW32System::SendMessage+0x3d MSFTEDIT!CTxtWinHost::TxNotify+0x97 MSFTEDIT!RichEditWndProc+0x19b USER32!InternalCallWinProc+0x28 USER32!UserCallWinProcCheckWow+0x150 USER32!DispatchMessageWorker+0x306 USER32!DispatchMessageW+0xf windbg!ProcessNonDlgMessage+0x2a2 windbg!ProcessPendingMessages+0x64 windbg!wmain+0x24a windbg!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep ntsd!UiCommand+0x287 ntsd!MainLoop+0x48f ntsd!main+0x232 ntsd!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep cdb!UiCommand+0x287 cdb!MainLoop+0x48f cdb!main+0x232 cdb!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep kd!UiCommand+0x287 kd!MainLoop+0x48f kd!main+0x1ed kd!_initterm_e+0x163 kernel32!BaseProcessStart+0x23
In order to answer why Visual Studio lacks support for child process debugging, you need to know how the user mode native debugger works, especially how to write a debug event loop. I have an example in Data Breakpoints, and I've purposely left a note in the "A few things to mention" section #4. You can use these hints and debug into the Visual Studio Debugger.
Regarding the symbol for remote debugging, you will need some background of metadata (ECMA-335 Partition 2) and PDB, also a bit of ICorDebug and ICLRData (mscordacwks!CLRDataCreateInstance). Launch procmon.exe and see who is consuming the symbol files.
What if a WinDBG command fails to do the job, or is not working as expected? There is no magic, just grab a debugger and debug it, in fact the debugger team has presented you a gift called .dbgdbg to make things a little easier!
0:000> uf dbgeng!DotReadMem dbgeng!DotReadMem: 0213fc90 8bff mov edi,edi 0213fc92 55 push ebp 0213fc93 8bec mov ebp,esp 0213fc95 81ec64080000 sub esp,864h 0213fc9b a10c513302 mov eax,dword ptr [dbgeng!__security_cookie (0233510c)] 0213fca0 33c5 xor eax,ebp 0213fca2 8945fc mov dword ptr [ebp-4],eax 0213fca5 833d4450360200 cmp dword ptr [dbgeng!g_Process (02365044)],0 0213fcac 750c jne dbgeng!DotReadMem+0x2a (0213fcba) dbgeng!DotReadMem+0x1e: 0213fcae 6a00 push 0 0213fcb0 6818100000 push 1018h 0213fcb5 e8b6ba1400 call dbgeng!ReportError (0228b770) dbgeng!DotReadMem+0x2a: 0213fcba a1004d3302 mov eax,dword ptr [dbgeng!g_SymOptions (02334d00)] 0213fcbf 2500000400 and eax,40000h 0213fcc4 740c je dbgeng!DotReadMem+0x42 (0213fcd2) dbgeng!DotReadMem+0x36: 0213fcc6 6a00 push 0 0213fcc8 682a100000 push 102Ah 0213fccd e89eba1400 call dbgeng!ReportError (0228b770) dbgeng!DotReadMem+0x42: 0213fcd2 c785c0f7ffff00000000 mov dword ptr [ebp-840h],0 0213fcdc 8d8dbcf7ffff lea ecx,[ebp-844h] 0213fce2 51 push ecx 0213fce3 6a0d push 0Dh 0213fce5 e876c41400 call dbgeng!StringValue (0228c160) 0213fcea 8985c4f7ffff mov dword ptr [ebp-83Ch],eax 0213fcf0 8b154c1a3502 mov edx,dword ptr [dbgeng!g_CurCmd (02351a4c)] 0213fcf6 8995e4f7ffff mov dword ptr [ebp-81Ch],edx 0213fcfc a14c1a3502 mov eax,dword ptr [dbgeng!g_CurCmd (02351a4c)] 0213fd01 668b8dbcf7ffff mov cx,word ptr [ebp-844h] 0213fd08 668908 mov word ptr [eax],cx 0213fd0b c785e8f7ffff00000100 mov dword ptr [ebp-818h],10000h 0213fd15 c785ecf7ffff00000000 mov dword ptr [ebp-814h],0 0213fd1f 6800001000 push 100000h 0213fd24 6a03 push 3 0213fd26 6a01 push 1 0213fd28 8d95e8f7ffff lea edx,[ebp-818h] 0213fd2e 52 push edx 0213fd2f 8d85c8f7ffff lea eax,[ebp-838h] 0213fd35 50 push eax 0213fd36 e8053b0400 call dbgeng!GetRange (02183840) 0213fd3b 33c9 xor ecx,ecx 0213fd3d 8b95e4f7ffff mov edx,dword ptr [ebp-81Ch] 0213fd43 66890a mov word ptr [edx],cx 0213fd46 6a00 push 0 0213fd48 6880000000 push 80h 0213fd4d 6a03 push 3 0213fd4f 6a00 push 0 0213fd51 6a00 push 0 0213fd53 6800000080 push 80000000h 0213fd58 8b85c4f7ffff mov eax,dword ptr [ebp-83Ch] 0213fd5e 50 push eax 0213fd5f ff158c743302 call dword ptr [dbgeng!kernel32_CreateFileW_Ptr (0233748c)] 0213fd65 8985f4f7ffff mov dword ptr [ebp-80Ch],eax 0213fd6b 83bdf4f7ffffff cmp dword ptr [ebp-80Ch],0FFFFFFFFh 0213fd72 7512 jne dbgeng!DotReadMem+0xf6 (0213fd86)
As you can see, the .readmem command makes use of kernel32!CreateFileW with dwDesiredAccess = GENERIC_READ and dwShareMode = 0 (instead of FILE_SHARE_READ), that would explain why you got an error "Unable to open file" in some cases. A quick debugging showed CreateFileW failed and last error value is ERROR_SHARING_VIOLATION.
The documented syntax for .readmem is .readmem FileName Range, while there is no notes for how big the Range value can go, I'll leave this as a homework for you, cheers.
Many people who has been using Emacs for decades were shocked when they heard that Emacs is actually a text editor instead of an operating system.
- vi advocator
Sharing a similar spirit as Emacs, Windows Debuggers are also super good at non-debugging tasks.
Calculator
The builtin expression evaluator of Windows Debuggers can be used as a handy calculator:
0:000> ?? 1+2+3 int 0n6
0:000> .formats 0x00905a4dEvaluate expression: Hex: 00000000`00905a4d Decimal: 9460301 Octal: 0000000000000044055115 Binary: 00000000 00000000 00000000 00000000 00000000 10010000 01011010 01001101 Chars: ......ZM Time: Mon Apr 20 19:51:41 1970 Float: low 1.32567e-038 high 0 Double: 4.67401e-317
Process Manager
I try not to use the term Task Manager, since the name is already occupied by taskmgr.exe, and we have nothing to do with tasks (taskmgr also has nothing to do with tasks).
0:000> .tlist -v windbg*
Shell
0:000> .shell tlist -t
0:000> .shell dir
0:000> !!dir
Binary Editor
The basic idea is to load a portion of file data into the address space of a debuggee, perform some inspection or modification, then write back to the file. WinDBG even has a Memory Window, which makes it a perfect GUI Hex Editor!
To examine the file information such like name, permission and size, use the .shell command.
0:000> .shell dir *.netmodule 10/23/2011 12:12 PM 2,048 bar.netmodule 10/23/2011 12:12 PM 2,048 foo.netmodule .shell: Process exited Press ENTER to continue
We can load the file into the debugee's user mode virtual address space, given that the pages are commited.
0:000> .dvalloc 0n2048 Allocated 1000 bytes starting at 00020000
0:000> .readmem foo.netmodule 20000 L0n2048 Reading 800 bytes.
0:000> dt ntdll!_IMAGE_DOS_HEADER 20000 +0x000 e_magic : 0x5a4d +0x002 e_cblp : 0x90 +0x004 e_cp : 3 +0x006 e_crlc : 0 +0x008 e_cparhdr : 4 +0x00a e_minalloc : 0 +0x00c e_maxalloc : 0xffff +0x00e e_ss : 0 +0x010 e_sp : 0xb8 +0x012 e_csum : 0 +0x014 e_ip : 0 +0x016 e_cs : 0 +0x018 e_lfarlc : 0x40 +0x01a e_ovno : 0 +0x01c e_res : [4] 0 +0x024 e_oemid : 0 +0x026 e_oeminfo : 0 +0x028 e_res2 : [10] 0 +0x03c e_lfanew : 0n128
0:000> dt -r ntdll!_IMAGE_NT_HEADERS 20000+0n128 +0x000 Signature : 0x4550 +0x004 FileHeader : _IMAGE_FILE_HEADER +0x000 Machine : 0x14c +0x002 NumberOfSections : 2 +0x004 TimeDateStamp : 0x4ea39447 +0x008 PointerToSymbolTable : 0 +0x00c NumberOfSymbols : 0 +0x010 SizeOfOptionalHeader : 0xe0 +0x012 Characteristics : 0x2102 +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER +0x000 Magic : 0x10b +0x002 MajorLinkerVersion : 0x8 '' +0x003 MinorLinkerVersion : 0 '' +0x004 SizeOfCode : 0x400 +0x008 SizeOfInitializedData : 0x200 +0x00c SizeOfUninitializedData : 0 +0x010 AddressOfEntryPoint : 0x22de +0x014 BaseOfCode : 0x2000 +0x018 BaseOfData : 0x4000 +0x01c ImageBase : 0x400000 +0x020 SectionAlignment : 0x2000 +0x024 FileAlignment : 0x200 +0x028 MajorOperatingSystemVersion : 4 +0x02a MinorOperatingSystemVersion : 0 +0x02c MajorImageVersion : 0 +0x02e MinorImageVersion : 0 +0x030 MajorSubsystemVersion : 4 +0x032 MinorSubsystemVersion : 0 +0x034 Win32VersionValue : 0 +0x038 SizeOfImage : 0x6000 +0x03c SizeOfHeaders : 0x200 +0x040 CheckSum : 0 +0x044 Subsystem : 3 +0x046 DllCharacteristics : 0x8540 +0x048 SizeOfStackReserve : 0x100000 +0x04c SizeOfStackCommit : 0x1000 +0x050 SizeOfHeapReserve : 0x100000 +0x054 SizeOfHeapCommit : 0x1000 +0x058 LoaderFlags : 0 +0x05c NumberOfRvaAndSizes : 0x10 +0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY +0x000 VirtualAddress : 0 +0x004 Size : 0
0:000> !dh 20000
0:000> .writemem foo.netmodule 20000 L0n2048
0:000> .dvfree 20000 0
Be cautious while using .dvfree, the debugger simply backed this by VirtualFreeEx:
kernel32!VirtualFreeEx dbgeng!LiveUserDebugServices::FreeVirtual+0x1e dbgeng!LiveUserTargetInfo::FreeVirtual+0x47 dbgeng!DotDvFree+0xcb dbgeng!DotCommand+0x3f dbgeng!ProcessCommands+0x4e1 dbgeng!ProcessCommandsAndCatch+0x49 dbgeng!Execute+0x2b9 dbgeng!DebugClient::ExecuteWide+0x6a windbg!ProcessCommand+0x156 windbg!ProcessEngineCommands+0xb2 windbg!EngineLoop+0x366 kernel32!BaseThreadStart+0x37
This means .dvfree can be used to free any block of virtual memory owned by the target process.
When .dvfree is used without the /d option, it would use MEM_RELEASE instead of MEM_DECOMMIT (this can be verified with .dbgdbg), and you have to make sure BaseAddress is the value returned by !address and Size is always zero.