Geoff Chappell, Software Analyst
The CSRSS.EXE process is not the first to run as Windows starts but it is the starting process of (at least) the Win32 subsystem and is thus vital to Windows in all practical use. It may surprise, then, that the CSRSS executable is tiny—not even tens of kilobytes, even now when it seems that every other executable (important or not) is multi-megabyte. The substance is instead supplied by DLLs. One of these, named WINSRV.DLL, is in the early versions responsible for pretty much all the windowing functionality (which was later moved to the kernel-mode WIN32K.SYS) and was in those years the only megabyte-sized DLL in the Windows package.
One DLL, named CSRSRV.DLL, is loaded because CSRSS imports the CSRSRV function named CsrServerInitialization. This receives the CSRSS command line, parsed into arguments in the style of a C Run-Time (CRT) main function. Among these arguments can be multiple occurrences of one that starts with ServerDLL. Each names what is here referred to as a server DLL.
Much as services can be packaged together in one executable file, so may server DLLs be hosted in a shared DLL module. A server DLL, then, is not exactly a DLL but a logical unit within a DLL. Server DLLs that are hosted in the same module are distinguished by their initialiser, this being the function that the hosting module must export by name for CSRSRV to call for initialising the corresponding server DLL. The hosting module, initialiser and a 0-based index (to be described shortly) are all specified in the ServerDLL command-line argument:
The module starts immediately after the first equals sign and runs up to but not including either a colon or comma, whichever comes first. If a colon comes first, the initialiser starts immediately after and runs up to but not including a comma. If instead a comma came first, there is no initialiser and ServerDllInitialization is used by default. The index is a signed decimal. Parsing has the rules of the documented RtlCharToInteger function, with the presumably unintended side-effects that white space is ignored before the index and non-digit characters are ignored after.
The index for each server DLL must be unique. CSRSRV has a built-in server DLL which is not specified on the command line, is initialised first and necessarily has the index zero. The index is a 0-based identifier of the server DLL within a maximum allowance that varies with the Windows version:
CSRSRV loads server DLLs in the order given on the command line. As noted already, each must export the specified or defaulted initialiser by name. The function is given one argument and returns an NTSTATUS to show its success or failure. The argument is the address of a CSR_SERVER_DLL structure for both input and output. From what the server DLL changes in this structure, CSRSRV learns about the server DLL’s future interface. Broadly speaking, this comprises notifications and API routines.
Each API routine is represented by a 32-bit API number. The high word is the 0-based index of the desired server DLL. The low word selects an API routine in the selected server DLL. Although CSRSRV learns the index for each server DLL from the CSRSS command line, such that the index is in some sense configurable, clients who want to call a server DLL’s API routine seem to have no means to learn the index except from knowing it as a well-known constant:
|2||winsrv||ConServerDllInitialization||3.10 to 6.1|
|4||winsrv||GdiServerDllInitialization||3.10 to 3.51|
|sxssrv||ServerDllInitialization||6.1 and higher|
As far as is yet known, each server DLL has kept the one well-known index through all Windows versions for which the server DLL remains in use. This does not mean, however, that clients have the luxury of stable API numbers to use in their calls to the server. The high parts stay the same, but the low parts vary significantly between versions.