Pavel Yosifovich, co-author of “Windows Internals 7th Edition, Part 1” and author of “Windows Native API Programming”, continues his series building a code version of VMMap. Part 1 walked a process’s address space with VirtualQueryEx and located the Process Environment Block. This time the tool learns to identify what file backs a mapped or image block, which surfaces a Windows quirk most developers never have to think about: the path Windows hands back is not the one you typed into Explorer.
Why Cache the PEB Address Instead of Querying It Every Time?
Before adding anything new, Pavel cleans up something wasteful left over from Part 1: the tool was calling NtQueryInformationProcess to locate the Process Environment Block every single time it examined a block, even though the PEB address for a given process never changes. A simple fix is a static local variable that holds the cached address. The first time through, if it is still unset, the tool queries it and stores the result; every call after that reuses the cached value instead of repeating the same syscall for no reason. It is a small change, and it does not alter the output at all, but it is the kind of inefficiency Pavel says he likes to catch before moving on to new functionality.
How Do You Get the File Backing a Mapped or Image Block?
VMMap shows the actual file behind any block of type Image or Mapped, and getting that in code turns out to be one function call: GetMappedFileName. It lives in psapi.h, the Process Status API header, which needs its own separate include. Called with the process handle, the block’s base address, and a buffer sized to MAX_PATH, it returns the path of the file mapped at that address, or zero if there is no file backing the block, which is the tool’s cue to just return early for anything that is not MEM_IMAGE or MEM_MAPPED. Running it against Explorer immediately shows the mapped data files. Images take slightly more interpretation: VMMap goes a step further and parses the PE headers of an image block to break it into named sections, something this tool does not attempt, but the read-only header block followed by a read-execute block is already a strong hint that a given region is an image, not just mapped data.
Why Does the Path Start with \Device\HarddiskVolume3 Instead of C:\?
The first path GetMappedFileName returns is not what most Windows developers expect: something like \Device\HarddiskVolume3\Windows\explorer.exe instead of C:\Windows\explorer.exe. This is an NT path, rooted in the object manager namespace that the Windows kernel actually uses internally, and C: is not fundamental to that namespace at all. It is a symbolic link, visible in WinObj under the \?? directory, that points at the real device object, \Device\HarddiskVolume3 on this system. VMMap resolves that link and shows the familiar drive-letter path instead, which is the more readable format Pavel wants his own tool to match.
How Do You Convert an NT Path to a DOS Path?
Doing the same resolution means walking the mapping in the other direction: given \Device\HarddiskVolume3\..., find which drive letter points there. GetLogicalDrives returns a bitmask of every drive letter currently mounted on the system, one bit per letter starting from A. Pavel walks that bitmask a bit at a time, and for every drive that exists, calls QueryDosDevice with the drive letter to get back the real device path it resolves to, then stores both directions in a std::map keyed by the device path. Like the PEB address, this mapping is calculated once and cached, since it will not change while the tool runs.
With the map built, converting a path is a matter of finding where the device name ends and the actual file path begins. Every NT path in this map starts with two backslashes before the volume name and a third backslash marking the start of the real path, so strchr searching from a fixed offset finds that boundary. The substring before it is the device prefix; looking that prefix up in the map returns the matching drive letter, which gets concatenated with everything from the third backslash onward. Once that logic is right, the tool’s output finally reads C:\Windows\... the way VMMap shows it.
What Went Wrong Along the Way?
Getting there was not a straight line. The first bug showed up in the drive-letter loop itself: an off-by-one in how the drive letter character was assembled meant the map was built with the wrong keys until Pavel corrected the offset and reran it against his own C, D, and E drives. The second bug hit in the lookup itself, where the initial code checked the wrong iterator against the map’s end and reported a lookup failure even though the entry was actually there. Both were the ordinary kind of typo that shows up when you write this much pointer-and-index code in one sitting, and both were fixed by breaking into the debugger and stepping through the exact values rather than guessing.
What This Means Practically
- Cache anything that queries fixed process state, like the PEB address, in a static variable instead of re-querying it on every iteration
- Use
GetMappedFileNamefrompsapi.hto identify the file backing an image or mapped memory block - Expect NT paths like
\Device\HarddiskVolumeN\...from low-level APIs;C:\drive letters are symbolic links in the object manager namespace, not the real path - Build a device-path-to-drive-letter map once with
GetLogicalDrivesandQueryDosDevice, and reuse it, since drive mappings do not change mid-run - When a lookup unexpectedly fails, check the iterator comparison against the container’s
end()before assuming the data itself is wrong
Part 3 picks up with Thread Environment Blocks: getting there means enumerating every thread in the process.
Keep Learning
If you want to go further with the Native API and virtual memory internals behind this tool, these TrainSec courses go deeper:
- Windows Native API Programming: covers
NtQueryInformationProcess, the PEB, and the rest of the native API surface in full - Windows System Programming 2: covers the virtual memory and file APIs this tool is built on
Related reading in the knowledge library: Process Memory Map in Code, Part 1, which builds the VirtualQueryEx walk and finds the PEB, and VMMap Basics, the GUI-first walkthrough this series builds toward matching.