Pushing the Limits of Windows: USER and GDI Objects – Part 2
Last time, I covered the limits and how to measure usage of one of the two key window manager resources, USER objects. This time, I’m going to cover the other key resource, GDI objects. As always, I recommend you read the previous posts before this one, because some of the limits related to USER and GDI resources are based on limits I’ve covered. Here’s a full index of my other Pushing the Limits of Windows posts:
Pushing the Limits of Windows: Physical Memory
Pushing the Limits of Windows: Virtual Memory
Pushing the Limits of Windows: Paged and Nonpaged Pool
Pushing the Limits of Windows: Processes and Threads
Pushing the Limits of Windows: Handles
Pushing the Limits of Windows: USER and GDI Objects – Part 1
GDI objects represent graphical device interface resources like fonts, bitmaps, brushes, pens, and device contexts (drawing surfaces). As it does for USER objects, the window manager limits processes to at most 10,000 GDI objects, which you can verify with Testlimit using the –g switch:
You can look at an individual process’s GDI object usage on the Performance page of its Process Explorer process properties dialog and add the GDI Objects column to Process Explorer to watch GDI object usage across processes:
Also like USER objects, 16-bit interoperability means that USER objects have 16-bit identifiers, limiting them to 65,535 per session. Here’s the desktop as it appeared when Testlimit hit that limit on a Windows Vista 64-bit system:
Note the Start button on the bottom left where it belongs, but the rest of the task bar at the top of the screen. The desktop has turned black and the sidebar has lost most of its color. Your mileage may vary, but you can see that bizarre things start to happen, potentially making it impossible to interact with the desktop in a reliable way. Here’s what the display switched to when I pressed the Start button:
Unlike USER objects, GDI objects aren’t allocated from desktop heaps; instead, on Windows XP and Windows Server 2003 systems that don’t have Terminal Services installed, they allocate from general paged pool; on all other systems they allocate from per-session session pool.
The kernel debugger’s “!vm 4” command dumps general virtual memory information, including session information at the end of the output. On a Windows XP system it shows that session paged pool is unused:
On a Windows Server 2003 system without Terminal Services, the output is similar:
The GDI object memory limit on these systems is therefore the paged pool limit, as described in my previous post, Pushing the Limits of Windows: Paged and Nonpaged Pool. However, when Terminal Services are installed on the same Windows Server 2003 system, you can see from the non-zero session pool usage that GDI objects come from session pool:
The !vm 4 command in the above output also shows the session paged pool maximum and session pool sizes, but the session paged pool maximum and session space sizes don’t display on Windows Vista and higher because they are variable. Session paged pool usage on those systems is capped by either the amount of address space it can grow to or the System Commit Limit, whichever is smaller. Here’s the output of the command on a Windows 7 system showing the current session paged pool usage by session:
As you’d expect, the main interactive session, Session 1, is consuming the most session paged pool.
You can use the Testlimit tool with the “–g 0” switch to see what happens when the storage used for GDI objects is exhausted. The number you specify after the –g is the size of the GDI bitmap objects Testlimit allocates, but a size of 0 has Testlimit simply try and allocate the largest objects possible. Here’s the result on a 32-bit Windows XP system:
On a Windows XP or Windows Server 2003 that doesn’t have Terminal Services installed you can use the Poolmon utility from Windows Driver Kit (WDK) to see the GDI object allocations by their pool tag. The output of Poolmon the while Testlimit was exhausting paged pool on the WIndows XP system looks like this when sorted by bytes allocated (type ‘b’ in the Poolmon display to sort by bytes allocated), by inference indicating that Gh05 is the tag for bitmap objects on Windows Server 2003:
On a Windows Server 2003 system with Terminal Services installed, and on Windows Vista and higher, you have to use Poolmon with the /s switch to specify which session you want to view. Here’s Testlimit executed on a Windows Server 2003 system that has Terminal Services installed:
The command “poolmon /s1” shows the tags with the largest allocation contributing for Session 1. You can see the Gh15 tag at the top, showing that a different pool tag is being used for bitmap allocations:
Note how Testlimit was able to allocate around 58 MB of bitmap data (that number doesn’t account for GDI’s internal overhead for a bitmap object) on the Windows XP system, but only 10MB on the Windows Server 2003 system. The smaller number comes from the fact that session pool on the Windows Server 2003 Terminal Server system is only 32 MB, which is about the amount of memory Poolmon shows attributed to the Gh15 tag. The output of “!vm 4” confirms that session pool for Session1 is been consumed and that subsequent attempts to allocate GDI objects from session pool have failed:
You can also use the !poolused kernel debugger command to look at session pool usage. First, switch to the correct session by using the .process command with the /p switch and the address of a process object that’s connected to the session. To see what processes are running in a particular session, use the !sprocess command. Here’s the output of !poolmon on the same Windows Server 2003 system, where the “c” option to !poolused has it sort the output by allocated bytes:
Unfortunately, there’s no public mapping between the window manager’s heap tags and the objects they represent, but the kernel debugger’s !poolused command uses the triage.ini file from the debugger’s installation directory to print more descriptive information about a tag. The command reports that Gh15 is GDITAG_HMGR_SPRITE_TYPE, which is only slightly more helpful, but others are more clear.
Fortunately, most GDI and USER object issues are limited to a particular process hitting the per-process 10,000 object limit and so more advanced investigation to figure out what process is responsible for exhausting session pool or allocating GDI objects to exhaust paged pool is unnecessary.
Next time I’ll take a look at System Page Table Entries (System PTEs), another key system resource that can has limits that can be hit, especially on Remote Desktop sessions on Windows Server 2003 systems.