Windows IT Pro
Windows IT Library
  - Advertise        
Windows IT Pro Logo

  Home  |   Books  |   Chapters  |   Topics  |   Authors  |   Book Reviews  |   Whitepapers  |   About Us  |   Contact Us  |   ITTV  |   IT Jobs

search for  on    power search   help
 






Local Procedure Call
View the book table of contents
Author: Prasad Dabak
Milind Borate
Sandeep Phadke
Published: October 1999
Copyright: 1999
Publisher: M&T Books
 


int server()

{

static HANDLE EventPairHandle;

HANDLE ClientEventPairHandle;


OBJECT_ATTRIBUTES ObjectAttr; UNICODE_STRING uString; DWORD ClientPid, ClientTid; HANDLE ClientProcessHandle, ClientThreadHandle; DWORD OpenThreadParam[2]; int rc;
memset(&ObjectAttr, 0, sizeof(ObjectAttr)); ObjectAttr.Length = sizeof(ObjectAttr); RtlInitUnicodeString(&uString, EVENTPAIRNAME); ObjectAttr.ObjectName = &uString; rc = NtCreateEventPair( &EventPairHandle, STANDARD_RIGHTS_ALL, &ObjectAttr); if (rc == 0) { printf("EventPairHandle=%x\n", EventPairHandle); } else { printf("Unable to create event pair, rc=%x\n", rc); return -1; }
rc = ZwSetInformationThread( GetCurrentThread(), 8, &EventPairHandle, 4);
if (rc != 0) { printf("NtSetInformationThread failed for " "the server, rc=%x\n", rc); return -1; }
printf("Enter pid and tid of the client : "); scanf("%d%d", &ClientPid, &ClientTid);
ClientProcessHandle = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ClientPid); if (ClientProcessHandle == NULL) { rc = GetLastError(); printf("Unable to open handle to process, rc=%x\n", rc); return -1; }
memset(&ObjectAttr, 0, sizeof(ObjectAttr)); ObjectAttr.Length = sizeof(ObjectAttr); OpenThreadParam[0] = ClientPid; OpenThreadParam[1] = ClientTid;
rc = NtOpenThread( &ClientThreadHandle, THREAD_ALL_ACCESS, &ObjectAttr, OpenThreadParam); if (rc != 0) { printf("NtOpenThread failed, rc=%x\n", rc); return -1; } printf("ClientProcessHandle = %x\n", ClientProcessHandle); printf("ClientThreadHandle = %x\n", ClientThreadHandle);
rc = DuplicateHandle( GetCurrentProcess(), EventPairHandle, ClientProcessHandle, &ClientEventPairHandle, 0, FALSE, DUPLICATE_SAME_ACCESS); if (rc == FALSE) { rc = GetLastError(); printf("DuplicateHandle failed, rc=%x\n", rc); return -1; }
printf("Client EventPair handle = %x\n", ClientEventPairHandle);
rc = ZwSetInformationThread( ClientThreadHandle, 8, &EventPairHandle, 4);
if (rc != 0) { printf("NtSetInformationThread failed for " "the client, rc=%x\n", rc); return -1; }
while (1) { DWORD ret_val;
_asm int 2Ch _asm mov ret_val, eax
if (ret_val != 0) { printf("int 2C returned error, rc=%x\n", ret_val); } else { printf("int 2C returned\n"); } getchar(); }
return 0; }
The server() function creates a named event pair object. It receives a handle to the newly created event pair upon successful creation of the object. Next, it establishes an association between the event pair and the server thread–the current thread. The server uses the ZwSetInformationThread() function to associate the event pair with the thread. This function is documented in the Windows NT DDK, but you can also call it from a user-mode, nondriver application. The prototype for this function looks like:
NTSTATUS

ZwSetInformationThread(

HANDLE ThreadHandle,

THREADINFOCLASS ThreadInformationClass,

PVOID ThreadInformation,

ULONG ThreadInformationLength);
As described earlier, each thread points to the associated event pair object, and the INT 2BH/INT 2CH issued by a thread operates on the associated event pair object. The operating system stores the pointer of the associated event pair in the Thread Environment Block for the thread, and you can set it using the ZwSetInformationThread() function. The ThreadInformationClass for the event pair pointer is 8. The actual information to set is the handle of the event pair object. We pass 4 as the ThreadInformationLength parameter because it represents the size of a handle in Windows NT.

The server needs to associate the event pair with the client thread. But this is not as simple as setting up the association for the current thread. First, the server gets a hold of handles to the client process and the client thread. For this, it needs the client’s process ID and thread ID, which input from the user. The function uses the OpenProcess() API function to get a handle to the client process.

Note: The server process should have security rights to open the client process.

The function uses an undocumented system call–namely, NtOpenThread()–to get a handle to the client thread. The NtOpenThread() system call returns a thread handle given the process ID and the thread ID. Next, the server duplicates the event pair handle in the client process’s context. It uses the DuplicateHandle() API function to achieve this. The server needs the process handle to get the duplicate event pair handle and the thread handle to associate the event pair and the thread. The ZwSetInformationThread() function is called again, this time with the client thread handle, to associate the event pair with the client thread. The function requires the event pair handle to exist in the context of the process that owns the thread. That is the reason we duplicated the handle in the context of the client process.

After setting up the Quick LPC channel, the server can now accept requests from the client. It goes into a loop, blocking in the INT 2CH, and indicating it to the user whenever it gets a request from the client. The server waits for a keystroke and then issues INT 2CH. This causes the server thread to suspend and the client thread to release for execution. We use inline assembly to issue the software interrupt. Note that the interrupt routine, for interrupt 0x2C, stores the return value in the EAX register.
int client()

{

printf("Client Process id = %d\n",

GetCurrentProcessId());

printf("Client Thread id = %d\n",

GetCurrentThreadId());

getchar();

while (1) {

DWORD ret_val;


_asm int 2Bh _asm mov ret_val, eax if (ret_val != 0) { printf("int 2B returned error, rc=%x\n", ret_val); } else { printf("int 2B returned\n"); } getchar(); }
return 0; }
The client() function proves much simpler in comparison to the server() function because the entire Quick LPC initialization is done by the server. The client just provides the process ID and the thread ID for input to the server. After the initialization is complete, the server waits for a client request in INT 2CH. You should indicate the end of initialization to the client by a keystroke. After receiving the keystroke, the client issues a INT 2BH, releasing the server thread for execution. Now, the client blocks and is rescheduled only when the server issues INT 2CH. The client waits for a keystroke from the user before issuing another INT 2BH.

We use inline assembly to issue the software interrupt. Note that the interrupt routine, for interrupt 0x2B, stores the return value in the EAX register.
main(int argc, char **argv)

{

	int rc;

if (argc == 1) {

rc = server();

} else {

rc = client();

}

return rc;

}
The main function in this sample program represents the control center. It calls the server() function if you invoke the program without any parameters; otherwise, it calls the client() function.

Enhancements to the Sample Program
The sample program, presented in the previous section, can handle a single client. But the user must supply the client’s process ID and thread ID to the server. You can overcome these deficiencies by making use of the port LPC for establishing the Quick LPC. The server can create a port and wait for requests on the port.

Whenever a client starts, it connects to the server over the port and sends a LPC request containing its process ID and thread ID. The server, upon receiving the request, initializes an event pair object and creates a new thread to handle the new client. A shared section also needs to be created and mapped in the server address space, as well as the client address space. The server can do it explicitly, or it can use the shared-section LPC so that the client creates the section and the system itself takes care of the mapping.

After setting up the communication channel like this, the main server thread sends a reply message to the client indicating that everything is set up. Now, the main server thread can freely accept more connection requests from clients. The newly created thread waits for the client requests by issuing INT 2CH. After the Quick LPC channel is established, the client can copy the parameters to the shared area and issue INT 2BH whenever it needs to invoke some service from the server.

As a result of the software interrupt, the server thread is scheduled for execution. The server thread reads the parameters from the shared area, processes the request, copies the results to the shared area, and invokes INT 2CH. The software interrupt causes the server thread to sleep, and the client thread is scheduled for execution. This continues until the client thread closes the port handle or dies. Now, the main server thread gets a LPC_HANDLE_CLOSED message over the port. Upon receiving the message, the main thread releases all resources allocated for the client; in other words, it destroys the shared-section mapping, kills the thread handling the particular client, destroys the event pair handle, and so on.

The sample program presented in the previous section works for console applications under Windows NT 3.51. The program does not work for GUI applications because the Win32 subsystem also sets the event pair handle in the Thread Environment Block (TEB), overwriting the event pair handle set by our program. The Win32 subsystem sets the event pair handle in the TEB when the thread makes the first GUI call. One fact in our favor is that the event pair handle is maintained per thread. Therefore, you can work around this problem very easily by having a separate client thread to communicate with the server. The other threads in the application can consist of GUI threads, accessing the GUI functions offered by the Win32 subsystem and using the Quick LPC to talk to the Win32 subsystem. You should take care only that the thread, using the Quick LPC to talk to your own server, does not make any GUI calls.

Note: Our sample program does not work in Windows NT 4.0 because the interrupt 0x2B serves a different purpose.
As you know, the Win32 subsystem functionality moves entirely into the kernel-mode driver, namely, WIN32K.SYS, in Windows NT 4.0. The Win32 GUI calls also process as system calls in Windows NT 4.0. Therefore, the Win32 subsystem no longer needs the Quick LPC interface, also negating the requirement of interrupts 0x2C and 0x2B.

We already saw that the functions KiSetLowWaitHighThread() and KiSetHighWaitLowThread() are not directly callable from the user land. Being unable to use interrupt 0x2B means that a way to access these functions from the user land is blocked. There is another way though. A pair of kernel functions, namely, NtSetLowWaitHighThread() and NtSetHighWaitLowThread(), can perform the same job. You can get to these functions using a pair of system calls that invoke these functions. These system calls don’t accept any parameters since the two functions operate on the event pair pointed to by the TEB of the calling thread. Surprisingly, the corresponding functions in the NTDLL.DLL don’t invoke these system services. Instead, these functions invoke the interrupts 0x2B and 0x2C.

Note: Surprisingly, the Win32 subsystem, under Windows NT 3.51, does not call the NTDLL.DLL functions. It invokes the interrupts 0x2B and 0x2C directly. Performance seems the most likely reason behind this “bypassing” act. First, the system call interface is bypassed. The overheads of system call setup—that is, indexing the system call ID to find out the number of parameters and the kernel function to be invoked—might prove unacceptable. Hence, we find the two functions in question by going out of the way and invoking the special interrupts instead of using the normal system call interface interrupt 0x2E. Of course, this required modifying the kernel to handle the two new software interrupts. We still don’t understand why the Win32 subsystem bypasses the NTDLL.DLL functions.

You cannot use these functions to access the Quick LPC on Windows NT 4.0. Obviously, you need to implement the system call invocation yourself; it’s fairly easy, though. On Windows NT 4.0, you need to change the INT 2Bh instruction to the following sequence of instructions that invoke the NtSetHighWaitLowThread() system call:
MOV EAX, A0h

LEA EDX, [ESP + 4]

INT 2Eh
You cannot use INT 2CH, under Windows NT 4.0, even though the interrupt handler for it remains there in place. (You would expect both the interrupt handlers to be extinct if the Win32 subsystem no longer requires them, wouldn’t you?) This is because the interrupt handler returns a STATUS_NO_EVENT_PAIR error even if the TEB of the calling thread points to a proper event pair. Therefore, you need to use a corresponding system call to achieve the same effect as the KiSetLowWaitHighThread() function. You can replace the INT 2CH instruction with the following instructions that invoke the NtSetLowWaitHighThread() system call:
MOV EAX, ABh

LEA EDX, [ESP + 4]

INT 2Eh
The system call interface exists and can function even under Windows NT 3.51. You might choose to use the same interface for the two versions of Windows NT so that the same code works on both versions. OK! It’s not so straightforward because the service IDs changed from Windows NT 3.51 to Windows NT 4.0. In Windows NT 3.51, the service ID for the NtSetLowWaitHighThread() system call is 0xA3, and the NtSetHighWaitLowThread() system call is 0x98.


SUMMARY

A local procedure call (LPC) is the communication mechanism used by Windows NT subsystems. In this chapter, we gave you a brief introduction to subsystems followed by a detailed discussion on the undocumented LPC mechanism.

There are three types of LPC. The short message LPC passes small messages up to 304 bytes in length. The shared section LPC uses shared memory and passes larger messages. Both the short message LPC and the shared section LPC are based on a kernel object called port. The functions to manipulate ports are not documented. In this chapter, we documented the parameters and use of these functions with demonstration programs.

The Quick LPC, the fastest form of LPC, is used exclusively by the Win32 subsystem. The Quick LPC proves faster because it ensures controlled scheduling of the client and server thread. In contrast with the other two forms of LPC, the Quick LPC requires a dedicated server thread per client thread. The Quick LPC mechanism uses another kernel object–the event pair. The context switches between the client thread and the corresponding dedicated server thread are optimized using the event pair object.



Page: 1, 2, 3, 4, 5, 6




Windows IT Pro Home Register FAQ for Windows WinInfo News
Europe Edition About Us Contact Us/Customer Service Media Kit Affiliates / Licensing  
SQL Server Magazine Office & SharePoint Pro Windows Dev Pro IT Job Hound ITTV
IT Library Technology Resource Directory Connected Home Windows Excavator Windows SuperSite 
 
 Windows IT Pro is a Division of Penton Media Inc.
 Copyright © 2008 Penton Media, Inc., All rights reserved. Terms and Use | Privacy Statement | Reprints and Licensing