Beefier Breakpoints Using Debugger Scripts
Written by Ron Stock
I recently collaborated with a third-party vendor which required me to track down a problem during the Vista upgrade process. The issue came down to catching a Microsoft component opening a specific registry service key during the setup process. Issues like this frequently call for live debugging to catch registry access in real time. I could have set a breakpoint on RegOpenKeyExW() and examined every requested key passed into the function, but this method is very time consuming considering RegOpenKeyExW() is a highly used code path. This methodology involves setting a break point on the function, waiting for the break in, checking the second parameter (lpSubKey) for a match to the desired registry key (string), and hitting “g” if the key doesn’t match. I could have potentially repeated these steps a hundred times before finding the call containing the key I needed. I wanted some way to set a ‘break on access’ only when my key was touched. We ran into a similar issue requiring the ability to catch a file system function working with a specific file when possibly hundreds of files were in use. So how do you gain the automation needed for this type of debugging? The answer is debugger scripts.
Let’s run through an example with Windows Explorer.exe using this method. In this scenario we’ll attempt to catch Explorer.exe opening the HKEY_LOCAL_MACHINE\SYSTEM\Setup key. Windows Explorer opens several keys per second, so the focus is to ‘break in’ when this specific key is passed to RegOpenKeyEx() without having to manually wade through hundreds of possible keys passed into the function.
1. Identify the function in question.
Per MSDN, RegOpenKeyEx is defined as the following, and we’re interested in the second parameter because it contains the name of the key.
LONG WINAPI RegOpenKeyEx(
__in HKEY hKey,
__in_opt LPCTSTR lpSubKey,
__reserved DWORD ulOptions,
__in REGSAM samDesired,
__out PHKEY phkResult
);
2. Create the debugger script
The script logic is straight forward. Our debugger script will break in every time RegOpenKeyEx() is called. It then checks the second parameter for the string SYSTEM\Setup. If it finds a match it stays broken in, otherwise it returns command to the running thread. Our script will look like this -
r$t0=poi(esp+8)
.if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
.if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
r$t0 is considered a pseudo-register and is used as a variable in this script. Since the second parameter of RegOpenKeyEx() is passed via esp+8, our debugger command program will dereference esp+8 and move it into r$t0. Our pseudo-register now holds the memory address of lpSubKey. The second line of the program simply checks to see if r$t0 is NULL, and will setup our alias named _str at the address held in r$t0 if r$t0 is not NULL. The third line uses the MASM string operator, $spat, looking for a string pattern which matches system\setup. If it doesn’t find a match it prints the string, and returns command back to the executing thread. It’s the equivalent of typing ‘g’ in the debugger. If the string pattern system\setup is found, the string is printed out on the screen, and the debugger remains broken in. Then we can debug the underlying problem in the appropriate context. Now save them into a txt located at c:\debuggers\strings.txt so we can reference it from our breakpoint.
3. Set the break point.
Next we break into the Explorer process from the kernel debugger
kd> !process 0 0 Explorer.exe
PROCESS 8340d6a8 SessionId: 1 Cid: 0270 Peb: 7ffd5000 ParentCid: 01b0
DirBase: 2d48f000 ObjectTable: 90171348 HandleCount: 559.
Image: explorer.exe
kd> !bpid 0270
Finding wininit.exe (1)...
Finding winlogon.exe (1)...
Waiting for winlogon.exe to break. This can take a couple of minutes...
Break instruction exception - code 80000003 (first chance)
Break into process 270 set. The next break should be in the desired process.
Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
77f17dfe cc int 3
Then we set a breakpoint on ADVAPI32!RegOpenKeyExW instructing it to use our script to evaluate whether SYSTEM\Setup is the key passed in at the second parameter. If it finds a match to our key it stays broken in otherwise execution is passed back to the running Explorer thread.
kd> bp ADVAPI32!RegOpenKeyExW "$$<c:\\debuggers\\strings.txt"
kd> bl
0 e 76e8f09d 0001 (0001) ADVAPI32!RegOpenKeyExW "$$<c:\debuggers\strings.txt"
This is the output we would expect to see for all of the non-matches.
kd> g
kd> r$t0=poi(esp+8)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
System\CurrentControlSet\Services\DnsCache\Parameters
kd> r$t0=poi(esp+8)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
Software\Policies\Microsoft\Windows NT\DnsClient
(Several entries have been removed here to conserve space in the blog)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
System\CurrentControlSet\Services\DnsCache\Parameters
kd> r$t0=poi(esp+8)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
Software\Policies\Microsoft\Windows NT\DnsClient
kd> r$t0=poi(esp+8)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
System\CurrentControlSet\Services\DNS
kd> r$t0=poi(esp+8)
kd> .if (@$t0 != 0) { as /mu ${/v:_str} @$t0 } .else { ad /q ${/v:_str} }
kd> .if ($spat("${_str}x", "*system\Setup*") == 0) { .echo _str;g } .else { .echo _str }
System\Setup
ADVAPI32!RegOpenKeyExW:
76e8f09d 8bff mov edi,edi
Bingo! It detected that System\Setup was the string passed in and remained broken in allowing us to start any necessary debugging.
kd> kv
ChildEBP RetAddr Args to Child
077aeae0 760dfb75 80000002 760dfb94 00000000 ADVAPI32!RegOpenKeyExW (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
077aed18 760dfbb7 00000004 00000000 0000003a 0x760dfb75
077aee20 7678c1b2 00000000 00000000 00000000 0x760dfbb7
077aee24 00000000 00000000 00000000 00000000 kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
kd> dc 760dfb94
760dfb94 00790053 00740073 006d0065 0053005c S.y.s.t.e.m.\.S.
760dfba4 00740065 00700075 ff530000 4ce81075 e.t.u.p...S.u..L
760dfbb4 8bffffff 0ffb3bf8 0032c284 89fb3b00 .....;....2..;..
760dfbc4 840ff47d 0000012b f875ff56 013ee857 }...+...V.u.W.>.
760dfbd4 f08b0000 840ff33b 000000f2 0000c3e9 ....;...........
760dfbe4 90909000 ff8b9090 83ec8b55 458b1cec ........U......E
760dfbf4 db335308 fc458956 e8f45d89 fffff583 .S3.V.E..]......
760dfc04 ff18758b 1e891475 fff5a9e8 89c33bff .u..u........;..
Debugger scripts are another cool tool to add to your debugging arsenal. I’ve personally benefited from this type of scripting many times. Again check the debugger help files for more information on the available commands.
Share this post : |
Comments
Anonymous
May 30, 2008
PingBack from http://www.windows-vista.luiscorreia.com/beefier-breakpoints-using-debugger-scripts/Anonymous
May 30, 2008
cool !! is like condition property for breakpoints in Visual Studio with managed code. I'll try in mi code now !!Anonymous
May 31, 2008
Hi, This is a very useful post. I must say that windbg scripting is not for the faint of heart. Can you provide further examples of such scripts so the community will be able to use them and modify them as needed. Thanks, Eran.Anonymous
June 11, 2008
Thanks for this article. It will save me time in developing the needed scripts on conditional breakpoints. TorstenAnonymous
June 18, 2008
This is a great blog! Thanks for taking the time to write these blogs.