March 1998
Download W32Mar98.exe (5KB)
Jeffrey Richter wrote Advanced Windows, Third Edition (Microsoft Press, 1998) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32 programming courses (http://www.jeffreyrichter.com.)
|
A Before I answer this question, let's take a close look at what is going on here and explain why Task Manager is displaying the message box (see Figure 1). When you select a process in Task Manager and click the End Process button, Task Manager grabs the ID of the chosen process and executes the following pseudocode:
|
The message box is displayed because OpenProcess can't open a handle to the specified process. This fails because the service is running under the Local System security account and you, even when logged on as an administrator, do not have sufficient access to open a handle to the service process. In fact, Task Manager calls GetLastError immediately after OpenProcess returns NULL and receives an error code of ERROR_ACCESS_DENIED (5). Task Manager places text representing this error in the message box. |
Figure 1 Error Message Box |
|
Figure 2 User Rights Policy |
|
|
When you execute this command line, EnableDebugPrivAndRun first calls OpenProcessToken. This returns a handle to the access token currently associated with EnableDebugPrivAndRun's process. If I get a handle to the access token successfully, I call EnablePrivilegemy own function that simply packages up all the steps necessary to enable or disable a privilege.
EnablePrivilege requires three parameters: the handle of the access token containing the user's privilege information, a string name that identifies the privilege that you want to alter, and a Boolean indicating whether you want to enable or disable this privilege. With these three parameters, EnablePrivilege initializes a TOKEN_PRIVILEGES structure. It's actually a variable-length structure because it allows you to specify several privileges at once. Since my EnablePrivilege function allows you to change only one privilege at a time, the code is much simpler than what it would have to be to alter two or more privileges at once. The LookupPrivilegeValue function converts the privilege string name into a 64-bit numeric equivalent called a LUID (locally unique identifier). This will seem a bit strange to most developers. Usually, developers like to work with numbers and avoid strings; but when working with privileges, programs always start out by referring to them by their programmatic string names. You then convert this string name to a number by calling LookupPrivilegeValue. LUIDs, by the way, are numbers that are guaranteed to be unique on the local system. You cannot pass a LUID between machines and, in fact, you cannot hold on to a LUID across a reboot on the same machine. In other words, passing the debug privilege string to LookupPrivilegeValue may cause two different LUIDs to be returned if you reboot the machine between calls. Once you have the current LUID for the debug privilege, EnablePrivilege calls AdjustTokenPrivileges to either enable or disable the privilege. You'll notice that when I call OpenProcessToken, I request TOKEN_ADJUST_PRIVILEGES access; this is required to call AdjustTokenPrivileges successfully. After EnablePrivilege returns, I pass EnableDebugPrivAndRun's command line off to the ShellExecute function. ShellExecute will call CreateProcess internally to spawn the new process. Remember that access tokens are inherited, so this new process will inherit EnableDebugPrivAndRun's access token, which now has the debug privilege enabled. This means the new process will also have the debug privilege enabled and will be able to call OpenProcess successfully. By the way, this change will also allow you to use Task Manager to change the priority class and the processor affinity of a service. Bonus features! I like to have the Task Manager running on my system so that I can always see my CPU's performance by looking at the little icon just to the left of the clock on my taskbar. Here's what I do to make sure that Task Manager is always running with the debug privilege enabled. First, I build my EnableDebugPrivAndRun program and place it in a directory. Then I create a shortcut that runs the Task Manager with its window initially minimized. I place this shortcut's .LNK file in the same directory as the EnableDebugPrivAndRun program. I create another shortcut that runs EnableDebugPrivAndRun, passing it the Task Manager shortcut's file name as an argument. The shortcut's command-line string looks like this: |
|
I place this second shortcut in my Start Menu's Startup folder so that the system invokes it as soon as I log on. And there you have it.
Q In your book, Advanced Windows (3rd edition, page 127), you mention that when Windows 95 or Windows NT runs an executable image from a floppy disk, the system actually loads the entire file's image into RAM (and pages it to the system's paging file if necessary). The system does this so that the diskette can be removed from the drive without affecting the application. Frank Merrow
A Starting with Windows NT 4.0, Microsoft added support for this in the loader. When you build an executable image (EXE or DLL), you can specify the following switch to the linker: |
|
This switch tells the linker to turn on a certain flag in the resulting image file. When the loader loads your image file, the loader checks to see if this bit is turned on and if the image is being loaded from a network drive. If so, the loader forces the file's image into RAM in order to prevent paging across the network.
The linker also supports this form of the SWAPRUN switch: |
|
This switch tells the loader to force the image into RAM if the file image is running from a CD-ROM drive. Note that for these switches to work, your linker must support the switch and the loader must recognize the switch and do the right thing. Windows NT 4.0 and later recognizes the
/SWAPRUN:NET and /SWAPRUN:CD bits in the executable file image; Windows® 95 does not.
Currently, the loader does not support forcing compressed or encrypted image files into RAM. But it is very easy to write a function that forces an entire image file into RAM. Figure 4 shows just such a function, RunImageLocally. This function takes a single parameter, the HINSTANCE (or HMODULE) of an executable image. This parameter is either the value passed to your WinMain function, the value passed to your DllMain function, or the value returned from a call to LoadLibrary. The function then calls VirtualQuery in a loop cycling through all of the memory regions contained inside the executable image (that is, regions that all share the same allocation base). For each region, I check to see if the pages are committed and are not guard pages. If the pages aren't committed, there is no storage there to force into RAM. If the pages have the PAGE_GUARD attribute, then touching them would generate a STATUS_GUARD_PAGE_VIOLATION exception, which the application is presumably waiting to catch with an exception handler. I don't want to confuse the application, so I can't force these pages into RAM either. For all other pages, I check to see if they are writeable. If they aren't, then I must change the protection on the pages so that I can write to them. I use PAGE_EXECUTE_READWRITE because this is the most liberal of all protections. Once I know that the pages are writeable, I then write to a single DWORD on each page in the region. Performing this write causes the system to make a copy of the page in RAM (later to be swapped to the paging file if necessary). After all of the pages in the region have been written to, I change the page protections back to what they originally were, if necessary. Finally, I repeat this for any additional regions contained inside the mapped executable image file. It should be noted that this procedure is not thread-safe and there is really no way to make it thread-safe. It is possible that other threads in the process can be changing the protections of the file image's pages executing code or touching data within these pages. Executing code or touching data should not be a problem at all. But if another thread is changing the page protections, this could be a potential problem. Realistically, however, this is not something that I would worry about. I tested this code only on Windows NT, but it should work just fine on Windows 95. Also, there is less reason to use this technique on Windows 95 because it does not support the copy-on-write mechanism. When you load an image into Windows 95, the loader automatically and immediately allocates RAM (and paging file space) for all the writeable pages in the image file. Only read-only pages would be swapped between RAM and the original file. Have a question about programming in Win32? Send your questions via email to Jeffrey Richter from his website at http://www.jeffreyrichter.com. |
From the March 1998 issue of Microsoft Systems Journal.