Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A Resource Monitor for NT 4.0


February 1999/A Resource Monitor for NT 4.0


Compared to Windows 3.1, NT offers programmers many improvements over its 16-bit predecessor. For example, NT does away with many of the memory and resource limitations imposed by Windows 3.1. One particular Achilles’ heel for Windows 3.1 was the system-wide 64K memory limit for GDI objects, such as brushes and fonts. With such a low limit, it was relatively easy for Windows 3.1 to run out of space for GDI objects. NT 4.0 offers some improvement in this area, but there are still system-wide limits that you might find surprising. The same is true for USER objects, such as windows and menus. The bottom line is that efficient resource usage is still an important goal for Win32 programmers. In this article, I’ll present win32mon, a NT 4.0 utility that uses several undocumented kernel-mode services to report the number of GDI and USER objects in use by a particular process, or all processes system-wide.

The Utility

win32mon displays the dialog box shown in Figure 1. The listbox on the left shows the number of handles allocated for different types of USER objects. Similarly, the listbox on the right shows handle counts for GDI objects. You’ll notice that some of the entries in the lists are labeled “unknown.” The reason is that there simply isn’t documentation that explains any of the object types. I identified the object types myself by using a debugger to examine system code in win32k.sys. The “unknown” entries are object types that I was unable to locate. Figure 2 contains the list of object types I was able to identify.

Initially, win32mon displays information for all processes. You can limit the display to a particular process by choosing Options|Process and selecting a process ID or filename. To obtain the list of processes, win32mon relies on several functions exported from psapi.dll, a redistributable component available in the Platform SDK. If you don’t have psapi.dll already, you’ll need to obtain a copy before you can use win32mon. At any point, you can click “Refresh Now” to update the display. Or, you can choose Options|Auto Refresh to have win32mon automatically update its display every 1.5 seconds. This is handy because you can leave win32mon running in the background while you work with a particular application.

To run win32mon successfully, there’s a minor security problem that must be overcome. The undocumented services that win32mon relies on will fail with STATUS_ACCESS_DENIED unless a special flag has been set in the registry. You can set this flag using the gflags.exe utility available in the NT 4.0 Resource Kit. To make the required change, simply run gflags.exe and make sure the checkbox labeled “Enable pool tagging” is turned on. Note that the new setting will not take effect until you restart NT.

If you don’t have access to gflags.exe, and you’re comfortable using regedit.exe, you can make the modification manually by editing the following registry value:

\HKEY_LOCAL_MACHINE
  \SYSTEM
    \CurrentControlSet
      \Control
        \Session Manager
        GlobalFlag:REG_DWORD

The “GlobalFlag” value is a DWORD containing a series of flags. To set the “Enable pool tagging” flag without affecting other flags, use the existing value of “GlobalFlag” (if present) and bitwise OR it with 0x00000400. As with gflags.exe, changes will not take effect until you restart Windows.

Retrieving Statistics

The key to win32mon is two undocumented kernel-mode services implemented by win32k.sys. These services are available only in NT 4.0, so win32mon will not run on other versions of Windows, such as the NT 5.0 beta. Calling these services isn’t as straightforward as calling a regular Win32 function, so I created my own interface functions to hide the details. The code for my interface functions, named UserGetStats() and GdiGetStats(), is in getstats.h (Listing 1) and getstats.c (Listing 2). These modules do not depend on any of the other code in win32mon, so they can be easily reused in other projects.

Both functions require four parameters:

BOOL UserGetStats(
    DWORD pid,
    DWORD flags,
    DWORD objCount[],
    int maxEntries);
BOOL GdiGetStats(
    DWORD pid,
    DWORD flags,
    DWORD objCount[],
    int maxEntries);

The first two parameters determine whether you’re requesting statistics for a particular process or for all processes. If you pass STATSTYPE_PID in the flags parameter, you must supply a valid process ID for pid. If you pass STATSTYPE_GLOBAL for flags, the pid parameter is ignored, and data is returned for all processes. The third parameter is an array of DWORDs that is filled in by the function. Each element of the array receives the count of handles that are currently in use for a particular type of object (e.g., brush, font, etc.) The last parameter should contain the number of entries available in the array. The minimum number of entries is given as GDITYPE_MAX and USERTYPE_MAX in getstats.h (Listing 1). For example, here’s a code fragment to determine the number of brushes currently allocated by all processes:

DWORD count[GDITYPE_MAX];
if (GdiGetStats(0, STATSTYPE_GLOBAL, count, GDITYPE_MAX))
    printf("Number of brushes = %d", count[GDITYPE_BRUSH]);

Implementation

GdiGetStats() and UserGetStats() rely on some undocumented features of NT 4.0. According to the symbol files provided with NT 4.0, the services I need for win32mon are named NtGdiGetStats() and NtUserGetStats() and are implemented by win32k.sys. However, knowing the function names isn’t good enough because you can’t call them directly from user mode. win32k.sys resides in system address space above linear address 0x80000000, which isn’t visible to a regular Win32 process running in user mode. The key to solving this problem is realizing that many Win32 APIs exported from gdi32.dll and user32.dll are actually implemented by win32k.sys. To make this work, NT provides a special mechanism for user-mode code to call specific services in win32k.sys. Once you understand this mechanism, you can use it to call NtGdiGetStats() and NtUserGetStats().

Win32 applications typically call functions that are exported from gdi32.dll and user32.dll. However, in many cases these functions are just wrappers for the real code located in win32k.sys. To illustrate how this works, consider the user32.dll implementation of GetDC():

GetDC:
        mov     eax,00001156
        lea     edx,[esp+04]
        int     2E
        ret     0004

If you use a debugger to step through this code, you’ll find that when you execute the int 2e instruction, control transfers to a function in ntoskrnl.exe named KiSystemService(). KiSystemService() uses the value in the eax register to determine which service to call. The value of 0x1156 happens to be the service number for NtUserGetDC() located in win32k.sys. In other words, when you call GetDC() exported from user32.dll, you’re really executing a win32k.sys function named NtUserGetDC(). If you wanted, you could bypass the documented GetDC() function completely and write your own wrapper to call NtUserGetDC() via the int 2e interface. In practice, you wouldn’t want to do this because the service number for NtUserGetDC() often changes each time a new service pack is released. For example, 0x1156 is the correct service number for NtUserGetDC() in NT 4.0 without service packs, but not with service pack 3.

This creates a bit of a problem for win32mon because there simply aren’t any wrapper functions available for NtGdiGetStats() or NtUserGetStats(). To call these services, win32mon needs to use the int 2e interface, which requires a service number. One solution would be to use hard-coded values based on the version number information returned by GetVersionEx(). Instead, I opted for a different solution that isn’t foolproof, but has a better chance of working with future NT 4.0 service packs.

During my examination of the various services provided by win32k.sys, I noticed that the service numbers seem to be assigned in alphabetical order by function name. While the actual service number for NtUserGetStats() varies, it’s always one greater than the service number for NtUserGetProcessWindowStation(). NtUserGetProcessWindowStation() is called by a documented Win32 API named GetProcessWindowStation() using the same mechanism I described earlier in the example for GetDC(). Since I know that the first instruction in GetWindowProcessStation() looks like this:

mov eax,<service number>

I can find the service number for NtUserGetProcessWindowStation() by using GetProcAddress() to locate the address for GetProcessWindowStation() in user32.dll. Using the address returned by GetProcAddress(), I retrieve the service number by reading a DWORD value starting one byte past that address. Once I have the service number for NtUserGetProcessWindowStation(), I simply add one to get the service number for NtUserGetStats(). The same trick also works for NtGdiGetStats() using GetProcAddress() to locate GdiGetSpoolMessage() in gdi32.dll. GdiGetSpoolMessage() is the user-mode wrapper for NtGdiGetSpoolMessage() in win32k.sys. I add one to the service number for NtGdiGetSpoolMessage() to find the service number for NtGdiGetStats().

This method of locating service numbers isn’t bulletproof. Microsoft could, in a future service pack, break this code by changing their numbering scheme or adding a new service with a name that is alphabetically between NtUserGetProcessWindowStation() and NtUserGetStats(). However, there really isn’t a better alternative. I’ve successfully tested win32mon up to and including service pack 3, and there’s a good chance it will continue to work with future service packs, but there are no guarantees.

Win32 Handle Limits

Earlier, I alluded to NT 4.0 limits for GDI and USER objects. I’ve heard more than one person claim that the maximum number of GDI or USER objects you can create at one time is determined by the amount of memory available. In reality, you’re much more likely to run out of handles before you reach any memory limit. This is because the handles for all GDI and USER objects are stored in two global handle tables maintained by win32k.sys. The GDI handle table has a limit of 0x4000 handles, and the USER handle table has a limit of 0xFFFF handles. Both tables are shared by all processes, so these limits are system-wide limits. For example, if one process creates 0x4000 brushes, no other process will be able to create any type of GDI object because there won’t be any handles available.

NT does offer some protection against this scenario in the form of quotas. By default, NT 4.0 will not allow a single process to create more than 0x3000 GDI objects at one time. You can change the quota by editing (or creating) the “ProcessHandleQuota” value in the registry under:

\HKEY_LOCAL_MACHINE
  \SOFTWARE
    \Microsoft
      \NT
        \CurrentVersion
          \Windows
          ProcessHandleQuota:REG_DWORD

The valid range for “ProcessHandleQuota” is 0 to 0x4000, but you should be extremely careful with low values because you could render your system unusable. If “ProcessHandleQuota” does not exist in the registry, win32k.sys uses a hard-coded default of 0x3000. This prevents a single rogue process from exhausting all GDI handles, but doesn’t stop two processes from allocating all GDI handles. There doesn’t appear to be any quota for USER handles, so the theoretical maximum for a single process is 0xFFFF objects.

Summary

Avoiding resource leaks or excessive resource usage continues to be an important ingredient of good Windows applications. win32mon can provide the information you need to help spot potential problems and determine how well your application manages its GDI and USER resources when running on NT 4.0.

References

Pietrek, Matt. “Poking Around Under the Hood: A Programmer’s View of NT 4.0.” Microsoft Systems Journal, August 1996.
A sidebar that accompanies this article explains the how and why of the int 2e interface for win32k.sys services.
Solomon, David. “Inside NT,” 2nd ed. Microsoft Press, 1998.
A good source of NT internals information covering a broad range of topics. o

Chris Branch is a software engineer at FSCreations, Inc. in Cincinnati, Ohio. He can be reached at [email protected].

Get Source Code


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.