I wasn’t lucky enough to receive the Acer 1420P laptop given out at PDC this year since I was “staff”, however I wound up picking one up on eBay for a very reasonable price. I received it last week, added an additional 2GB of RAM, used my Windows Home Server to save off the original hard drive image, and then repaved the machine, installing Windows 7 Ultimate x64, drivers, and applications from scratch. Earlier this week I noticed the machine was consuming over 3GB of memory with only 1 or 2 applications open. Looking at Task Manager, the RAM usage by process looked normal, which didn’t match the total memory usage. After reading the Performance tab a bit more, I saw that the operating system had over 5 million handles open!
Back on the Processes tab, I turned on the Handles column and saw that a process named SDTabletPC.exe was consuming 5 million handles, and it was growing by 10 handles per second. Check it out for yourself.
SDTabletPC.exe file is installed and run after installing the latest accelerometer driver (v1.00.00.16) from Acer’s support site. This driver and executable are responsible for rotating the screen based on the machine orientation in tablet mode. On the original PDC laptop image, an older version of the driver is used which uses rundll32 and load SDTablet.dll. This version suffers from the same issue but I did not write this fix to work with that version since the newer driver is available. Back to the research…
Next, I fired up Processor Explorer and saw that almost all of the 5 million handles were open file handles to \Device\STHall, which is the device name for the internal accelerometer. I downloaded the free copy of IDA, an extremely powerful native code disassembler, and started poking around the executable. I found several functions which made calls to CreateFile, trying to open the above device, but never closed those handles with a call to CloseHandle. Excellent.
I was unable to fix the executable by patching in calls to CloseHandle, so I took another approach. I wrote a very small app in C which finds the SDTabletPC.exe process, opens a process handle to it, enumerates all of its open handles, and forcefully closes all those that are handles to \Device\STHall. The application then sleeps for 5 seconds and repeats the process forever.
I have put the application and its source code up for download here.
Note that this fix is only for the latest version of the driver shown here, version 1.00.00.16. Another note: The leaky handle issue exists in both the original driver included with the PDC laptop image (which runs as a rundll32 process against SDTablet.dll), and this updated driver, but my fix only works for the updated driver. I know it won’t work with the driver installed in the default PDC image and I can’t guarantee it’ll work with newer drivers, or that newer drivers will even need the fix.
Why force you to upgrade to this version? Well, as noted on Microsoft’s PDC Tablet site, there’s supposed to be an updated driver on Windows Update that gets rid of the amazingly annoying “Portrait mode may not work with certain applications" (or words to that effect) message box every single time you rotate the machine, however, it’s not on Windows Update. This driver on Acer’s site is the updated version that fixes that issue. Sadly, it doesn’t fix the leaking problem. I know the tablet site says “don’t install any drivers outside Windows Update”, but this is the only way to get the updated rotation driver, or to install the driver with a clean Win7 install.
Update 2/6/10: An additional note for those of you on the original PDC image updating to this newer driver: Be sure to check for the following registry key and if it exists, remove it:
HKLM\Software\Microsoft\Windows\CurrentVersion\Run, SDTablet = Rundll32 %SystemRoot%\System32\SDTablet.dll,MainThread
This key starts the original version of the rotation driver, and it appears it is not properly cleaned up for everyone and may result in TWO processes eating 10 handles per second. Thanks to Chad Boles for getting the key location for me.
So, if you’re still running the original PDC driver, make sure you head to Acer’s support site and download this version:
If you don’t care about the details, download and give it a try. For those that care about down and dirty C, let’s continue to the details on how it works…
Step 1: Find the SDTabletPC.exe process and get its process ID
Digging around, I found this sample from MSDN which demonstrates how to use the Tool Help Library to take a snapshot of all system processes and query information about them. For this application, the code can be shortened to just enumerate the running processes, find the one whose process name matches SDTabletPC.exe, and get its process ID:
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
DisplayError(L"Could not take snapshot of processes");
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
DisplayError(L"Could not query first process");
CloseHandle(hProcessSnap); // clean the snapshot object
// Now walk the snapshot of processes, and
// display information about each process in turn
if(_wcsicmp(L"SDTabletPC.exe", pe32.szExeFile) == 0)
pid = pe32.th32ProcessID;
Next up, I found this sample from SysInternals which tells us the rest of what we need to know: how to enumerate handles belonging to a specific process, get the name of the handle, and close the handle. I was able to modify that code into the following:
Step 2: Open a duplicable handle to the process
A handle can be opened to any process by calling OpenProcess and passing in the process ID of the process to open. By passing the PROCESS_DUP_HANDLE argument, we can later use this handle in a call to NtDuplicateObject to get a queryable handle.
/* open the SDTabletPC.exe process */
if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid)))
DisplayError(L"Could not open SDTabletPC.exe PID.");
Step 3: Get all system handles
This code retrieves all system handles from all processes via the NtQuerySystemInformation method.
/* NtQuerySystemInformation won't give us the correct buffer size, so we guess by doubling the buffer size. */
while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
Step 4: Enumerate the handles, close those that match
The handles are enumerated, looking for those that belong to the SDTabletPC.exe process based on the process ID which owns the handle. For each of the handles that match, its ObjectNameInformation is retrieved using the NtQueryObject method. This stru
ct contains the handle name. This name is compared with \Device\STHall, and, if it matches, it is closed with a call to DuplicateHandle, which closes the handle by passing in the DUPLICATE_CLOSE_SOURCE parameter. The application then sleeps for 5 seconds and repeats this process again.
for (i = 0; i < handleInfo->HandleCount; i++)
SYSTEM_HANDLE handle = handleInfo->Handles[ i ];
HANDLE dupHandle = NULL;
/* Check if this handle belongs to the PID the user specified or if it has the access mask below which will hang NtQueryObject */
if (handle.ProcessId != pid || handle.GrantedAccess == 0x0012019f)
/* Duplicate the handle so we can query it. */
if(!NT_SUCCESS(NtDuplicateObject(processHandle, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, 0)))
/* query the handle for its name information */
if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, objectNameInfoSize, &returnLength)))
objectNameInfoSize = returnLength;
/* Reallocate the buffer and try again. */
objectNameInfo = realloc(objectNameInfo, returnLength);
if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, returnLength, NULL)))
/* Cast our buffer into an UNICODE_STRING. */
objectName = *(PUNICODE_STRING)objectNameInfo;
/* Kill the original handle */
if (objectName.Length && _wcsicmp(objectName.Buffer, L"\\Device\\STHall") == 0)
DuplicateHandle(processHandle, (HANDLE)handle.Handle, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE);
/* Kill the duplicated handle */
There is a little more going on in the actual application, so be sure to check out the included source code if you’re interested in this kind of thing. Give it a try and let me know how it works for you. It’s been running stably for myself and a few other testers, so please let me know if it does not work for you.
And a huge “thank you” to Greg Duncan for testing out the app several times as I wrote it!