An important feature of a multi-tasking operating system is protection against misbehaving programs. Not only the operating system has to protect itself from such a program, but it also needs to make sure that other programs that may be running at the same time will not be affected. This is usually done at several levels, segmentation is one of the first levels of defence. On Intel CPUs, segmentation is implemented through system-level data structures known as descriptor tables.
There are global and local descriptor tables (GDT and LDT) pointed to by special system registers, GDTR and LDTR respectively. These tables regularly contain code and data segment descriptors, for both the operating system and application programs. There is also an Interrupt Descriptor Table to hold the current interrupt vectors, with IDTR register to access the table. The CPU garantees that an adventurous program will not be able to modify the content of these registers and thus to make them point elsewhere, by making the intstructions that alter the registers supervisor-privileged. Only the operating system, which naturally has such a privilege can execute LGDT, LLDT and LIDT instructions.
However, the reverse operation, getting the content of the descriptor table registers, implemented with SGDT, SLDT and SIDT instructions, is not privileged. Any user-mode program can execute them. It's important to understand that this is CPU-specific rather than platform-specific and can be easily done on anything that runs on PC, whether it's Windows 95, Windows NT or OS/2.
Having such an easy way to obtain pointers to the descriptor tables, can a program actually do something to them? This depends entirely on the operating system, any other protection mechanisms that the CPU has to offer depend on the operating system's willingness to use them. For example, an operating system can utilize the CPU paging machinery and its protection feature to make those pointers useless for application software. It can mark all the memory pages that constitute the descriptor tables as supervisor accessible, any attempt to touch any cell belonging to these pages from the user-mode program will fail. In addition, an operating system has a chance to carefully craft any relevant software interface available at application level such as DPMI. For example, when an application makes a DPMI request to set up an LDT selector in a manner that has a potential to affect system memory (such as descriptot table memory), and operating system can easily refuse such request.
As it turns out, none of the above techniques is used on Windows platforms based on the VxD-kernal model. This is demonstrated by the C language function below, which returns a newly created USE16 data selector that points directly to the current LDT. This Sldt() function works for both Win16 applications and protected mode DOS programs that use DPMI for selector manipulations.
// Construct LDT selector that points to the LDT itself! WORD Sldt (void) { // Global Descriptor Table Register typedef struct { WORD GDTRL; /* GDT limit */ DWORD GDTRB; /* GDT base */ }GDTSTRUCT, far *LPGDTSTRUCT; // Segment Descriptor layout typedef struct { WORD Seg_Desc_Limit_0_15; // Limit bits 0 - 15 WORD Seg_Desc_Base_0_15; // Base BITS 0 - 15 BYTE Seg_Desc_Base_16_23; // Base BITS 16 - 23 BYTE Seg_Desc_Access_Right; // Access right byte BYTE Seg_Desc_Gran_Byte; // Granularity, etc BYTE Seg_Desc_Base_24_31; // Base bits 24 - 31 }SEGDESCRIPTOR,*PSEGDESCTIPTOR,far *LPSEGDESCRIPTOR; GDTSTRUCT gdtstr; WORD ldt_in_gdt; UINT sel,sel1; LPSEGDESCRIPTOR gdtptr; DWORD ldtbase; DWORD ldtlimit; // SGDT and SLDT are not privileged instructions !!! _asm { sgdt gdtstr sldt ax mov ldt_in_gdt,ax } #ifndef DPMI_APP _asm mov sel,ds #endif // in case of DPMI application, AllocSelector is a wrapper // around the corresponding DPMI function sel = AllocSelector (sel); sel1 = AllocSelector (sel); //make sel to map the GDT SetSelectorBase (sel,gdtstr.GDTRB); SetSelectorLimit (sel, 0xFFFF); //points to the ldt descriptor in gdt gdtptr = MAKELP(sel,ldt_in_gdt & SELECTOR_MASK); // can't use ldt_in_gdt selector directly, because it has DPL 0 // map another one ldtbase = MAKEBASE(gdtptr->Seg_Desc_Base_0_15, \ gdtptr->Seg_Desc_Base_16_23, \ gdtptr->Seg_Desc_Base_24_31); ldtlimit = MAKELIMIT(gdtptr->Seg_Desc_Limit_0_15,0); SetSelectorBase(sel1, ldtbase); SetSelectorLimit(sel1, ldtlimit); FreeSelector(sel); return sel1; } |
First, Sldt() uses SGDT and SLDT instructions to get hold on the important characteristics of the descriptor tables. For the GDT, the function obtains the linear pointer to the GDT base and the length of the table. For the LDT, it gets the GDT selector (called ldt_in_gdt in the code) which points to the LDT. This, in fact, could have been enough and ldt_in_gdt could have been returned to Sldt()'s caller, but it has a DPL 0 (Descriptor Privilege Level which must be at least matched by the application code selector's RPL - Requested Privilege Level) and thus is still useless for Ring 3 software. But this is easy to get around after all. Using DPMI or Win16's AllocSelector/SetSelectorBase/SetSelectorLimit API, Sldt() creates an alias selector which has exectly the same attributes but the DPL = 3. It then returns the new selector to the outside caller. With this selector, a program can easily construct usable prointers through which it can read and write to the LDT, to build system (non-segment) selectors such as gates, for example.
One typical software design problem every VxD programmer sooner or later runs into, is how to communicate data from a VxD to a user-mode software component. By far, the most popular scheme involves a VxD posting a GUI message with the 16-bit PostMessage API. There is a number of compelling reasons to prefer this scheme over its alternatives, and indeed it can be found in numerous implementation. Take a pair of the Winoldap program and SHELL VxD, for a well-known example.
Because posting a GUI message requires to have a window handle to post a message to in the first place, many people include a combination of an executable program, a DLL and a VxD into their packages. And because very often a window handle is only needed for IPCs, but doesn't have to represent any GUI component visible to the user, such windows are often declared hidden.
It's said that there is always a better way. Every Windows system comes with an existing invisible window, called the desktop window, and like any other window, this one receives and processes messages. There is nothing else needed to know about this window, to use it for your inter-ring communications. And you can place your entire Ring 3 code into a DLL, if a particular application doesn't have its own GUI. You do this with the simple and well known windows subclassing procedure:
HWND desktop; // Desktop window handle char my_message[] = "WM_VXD"; // Your private message name UINT vxdmsg; // Numeric value of your private message FARPROC original_MsgHandler; // First, register your custom GUI message, this gets you its numeric value // In Windows 3.x, you'll need to inform your VxD about this value // In Windows 95, a VxD can find it using the same API at appy-time vxdmsg = RegisterWindowMessage (my_message); // Now, get desktop window and subclass it // Again, in Windows 3.x you'll have to give your VxD the HWND value desktop = GetDesktopWindow (); original_MsgHandler = (FARPROC)GetWindowLong(desktop,GWL_WNDPROC); SubclassWindow(desktop,VxDMsgProc); // Your subclass procedure might look something like this long WINAPI _export VxDMsgProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == vxdmsg) { ... do your processing here, call back VxD, etc. ... return (oL); // consume the message } // reflect all other messages back to the desktop return CallWindowProc (original_MsgHandler, hwnd, msg, wparam, lparam); } |
This trick works equally well on Windows 3.x and Windows 95. The latter offers some additional benefits to the VxD part in the face of appy-time services. With these, a VxD doesn't need any GUI-specific information from your DLL such as the new message ID because it can easily find it. Nor does a VxD need you to tell the address of PostMessage function, as this is known to SHELL VxD as well.
This page has been visited times since Mar-24-1998