Some rogue code was changing our current directory from under our feet, and we needed to catch it in action. I was looking for a memory location to set a data breakpoint on.
(Note: for the remainder of this post it is assumed that ntdll.dll symbols are loaded.)
The High road
The natural repository for process-wide data is the Process Environment Block and indeed you can get to the current directory from the there. The PEB and its internal structures are almost entirely undocumented, but the venerable Nir Sofer (and others) got around it using MS public debug symbols: the path is PEB->ProcessParameters->CurrentDirectory. After translating the private field locations to offsets, you get a rather horrifying – but functional – expression, that you can paste in a watch window:
(((_CURDIR*)&((((((PTEB)$TIB)->ProcessEnvironmentBlock)->ProcessParameters)->Reserved2)[5]))->DosPath).Buffer
To break execution when your current folder changes, you can set a data breakpoint on:
&(((((PTEB)$TIB)->ProcessEnvironmentBlock)->ProcessParameters)->Reserved2)[5]
An Easier Alternative
Interestingly when inspecting GetCurrentDirectory disassembly it turns out it doesn’t go the TEB/PEB way, but takes a detour:
Ntdll.dll!RtlpCurDirRef is undocumented, but is included in the public ntdll debug symbols and so can be used in the debugger. The Cygwin guys mention it as the backbone of their unix-like cwd command, and their _FAST_CWD_8 type seems to still accurately reflect the windows type (as of July 2017). If you’re willing to modify your source to enable better variable watch go ahead and add –
typedef struct _FAST_CWD_8 { LONG ReferenceCount; HANDLE DirectoryHandle; ULONG OldDismountCount; UNICODE_STRING Path; LONG FSCharacteristics; WCHAR Buffer[MAX_PATH]; } FAST_CWD_8, *PFAST_CWD_8;
And inspect in the debugger:
If you can’t or don’t want to modify the source, you can set the watch with a direct offset –
(*(wchar_t**)ntdll.dll!RtlpCurDirRef)+22
SetCurrentDirectory() replaces the contents of RtlpCurDirRef, so you can set a data breakpoint directly on it.
How did you get PTEB and all those other types to work in the expression window? I also tried the WinDbg syntax with module!typename, but to no avail (tried both nt and ntdll).