IntroductionAs we know, Windows CE threads can migrate between processes while executing API calls, it borrows the caller's thread and places it in the server process. Such servers are called "Protected Server Libraries" (PSLs) and are processes that export services like DLLs.
Protected Server Libraries (PSL) are used by Windows CE to create API sets that in their own process address space and can server multiple clients concurrently. Windows CE implements all of its system APIs using this technique including all WIN32 APIs, User interface, device driver, and many of its DirectX interfaces. There is a one to one mapping between an API call and its corresponding PSL entry.
Windows CE implements PSL support through a clever technique generating special processor fault conditions that cause the execution of PSL entry points. The system maintains a table of
32 PSL entries. All entries between
0 and
24 are preassigned and may not be used for custom PSLs. PSLs register their APIs, associating their API with one particular entry in the PSL table. Part of the PSL registration process is providing the system with a function table of all the API entry points for a particular PSL. There are also a set of macros defined in
psyscall.h which take the PSL id and an index into the registered function table to indicate which particular function for the specified PSL is to be called. This macro generates a 'function pointer' that really is an
invalid memory address. When this function pointer gets used it causes the processor to fault. The fault handling code then inspects the faulty address and sees that it is one of the special PSL addresses and extracts the PSL id and function table index. It then turns around and calls the correct entry point in the PSL, just as if the caller had directly jumped to it – with one significant difference in that process address spaces have been jumped across as well. When the PSL function finishes execution and returns, the OS resumes the application that called the PSL to begin with by returning from the fault condition with the return value from the PSL function. The application resumes execution and it looks just as if a function call had occurred. Because PSLs are the core way APIs are implemented in the system the code path for executing this process is very streamlined.
Components of PSLPSLs have four main components:
- Functions implementing the various entry points of the PSL.
- Function table (a Vtable) that is an array of function pointers containing all the entry points of the PSL. This is an array of pointers to the functions making up the API set (PSL entries). The first two entries are reserved for the kernel. The first entry is the notification procedure that the kernel will call any time a thread or a process is stopped or started, low memory occurs, or the system is booted. The second entry is reserved by the kernel and should be zero. All remaining entries are available to the PSL implementer.
- Signature table describing all the argument types for each function in the function table. Made up of an array of 2 or more entry points with a one to one correspondence to the API function table. You can use these macros: FNSIG0-12, DW - DWORD, PTR - Pointer, I64 - 64 bit integer. PSL functions can have one of three argument types (DW, PTR, I64) or no arguments at. Those types are a DWORD (unsigned long), a pointer (void *), or a 64 bit integer (long long). There return values are the same. They can be used in any combination within a function or across function. There can be no other parameter types but the current parameter types can be used to contain other types (like a WORD or WCHAR in a DWORD, a BSTR in a void *). PSL functions can have from zero to 12 arguments. As can be seen above, each PSL function signiture is defined by using an FNSIG macro that is followed by the number of arguments the function has. The FNSIG macro is then filled in with an argument type specification for each argument it has.
The data in the signature table is used by the kernel to marshal arguments for the PSL function calls across process address spaces so it is imperative that these be correct or arguments will not get passed correctly from a client calling your PSL into the actual function in your PSL that implements the API call. - A .lib that clients of the PSL link to which contains the code that generates the special invalid memory address that causes the processor to fault and execute the PSL. The code also can be inline functions defined in the header file.
Example
The following is the code fragment of using the PSL technique to add APIs to our customizing Power Manager. The example shows the steps to add the SetSystemPowerRequirement API.
// 1. Functions
// 1.1 Notify function
VOID
PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId)
{
switch (dwEvent)
{
case DLL_PROCESS_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_EXITING:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_EXITING ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_SYSTEM_STARTED:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_SYSTEM_STARTED ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
default:
break;
}
}
// 1.2 Real API
EXTERN_C HANDLE WINAPI
PmSetSystemPowerRequirement(LPCTSTR pszName)
{
HANDLE hRequirement = NULL;
//...
return hRequirement;
}
// 2. API function table
static VOID PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId);
const PFNVOID VTable[] = {
(PFNVOID)PmAPINotifyProc,
(PFNVOID)NULL,
(PFNVOID)PmSetSystemPowerRequirement,
};
// 3. Signatures table
#define NUM_APIS (sizeof(VTable) / sizeof(VTable[0]))
const DWORD SigTable[NUM_APIS] = {
FNSIG3(DW, DW, DW), // PmAPINotifyProc
FNSIG0(), // Kernel reserved entry
FNSIG1(PTR), // Signature of PmSetSystemPowerRequirement
};
// 4. Client APIs (defined in header file)
#define PM_APISET_ID 25 // APISet ID
#define PM_APISET_NAME "CPM" // APISet Name
#define PmSetSystemPowerRequirement_xxx \
PRIV_IMPLICIT_DECL(HANDLE, PM_APISET_ID, 3, (LPCTSTR psz))
__inline HANDLE
SetSystemPowerRequirement(LPCTSTR pszState) {
ASSERT(QueryAPISetID(PM_APISET_NAME) == PM_APISET_ID);
return IsAPIReady(PM_APISET_ID) ? PmSetSystemPowerRequirement_xxx(pszState) : NULL;
}
// 5. Create and register APISet
// PSL Initialization
ghPmAPISet = CreateAPISet(PM_APISET_NAME, NUM_APIS, VTable, SigTable);
if (ghPmAPISet == NULL) {
PMLOGMSG(ZONE_ERROR, (_T("%s: CreateAPISet() failed: %d\r\n"), pszFname, GetLastError()));
return FALSE;
}
if (!RegisterAPISet(ghPmAPISet, PM_APISET_ID)) {
PMLOGMSG(ZONE_ERROR, (_T("%s: RegisterAPISet() failed: %d\r\n"), pszFname, GetLastError()));
CloseHandle(ghPmAPISet);
return FALSE;
}