|
|
//
// COM.C
// Utility functions
//
// Copyright(c) Microsoft 1997-
//
#include <as16.h>
//
// PostMessageNoFail()
// This makes sure posted messages don't get lost on Win95 due to the fixed
// interrupt queue. Conveniently, USER exports PostPostedMessages for
// KERNEL to flush the queue, so we call that before calling PostMessage().
//
void PostMessageNoFail(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PostPostedMessages(); PostMessage(hwnd, uMsg, wParam, lParam); }
//
// AnsiToUni()
//
// UniToAnsi() is conveniently exported by krnl386. However, we need
// AnsiToUni() conversions also so we can make sure we end up where we
// started. This one we have to roll our own thunk for
//
int AnsiToUni ( LPSTR lpAnsi, int cchAnsi, LPWSTR lpUni, int cchUni ) { DWORD dwMask; LONG lReturn;
DebugEntry(AnsiToUni);
ASSERT(g_lpfnAnsiToUni); ASSERT(SELECTOROF(lpAnsi)); ASSERT(SELECTOROF(lpUni));
//
// Set up the mask. These are the params:
// 0 -- CodePage (CP_ACP, which is 0L)
// 1 -- Flags (0L)
// 2 -- lpAnsi POINTER
// 3 -- cchAnsi
// 4 -- lpUni POINTER
// 5 -- cchUni
//
//
dwMask = (1L << 2) | (1L << 4);
//
// Take the win16lock an extra time; this API will release it, and we
// can't yield in the middle of a GDI call.
//
_EnterWin16Lock(); lReturn = CallProcEx32W(6, dwMask, (DWORD)g_lpfnAnsiToUni, 0L, 0L, lpAnsi, (DWORD)(UINT)cchAnsi, lpUni, (DWORD)(UINT)cchUni); _LeaveWin16Lock();
DebugExitDWORD(AnsiToUni, (DWORD)lReturn); return((int)lReturn); }
//
// PATCHING CODE
//
//
// Get32BitOnlyExport16()
//
// This function gets hold of a 16:16 function address that isn't exported
// but is called via a flat thunk from the exported 32-bit version. We use
// this for GDI and USER functions that are handy.
//
// This code assumes that the 32-bit routine looks like the following:
// <DEBUG>
// 68 dwStr32 Push string
// E8 dwOffsetOut Call trace out
// <DEBUG AND RETAIL>
// B1 bIndex Put offset in thunk table into cl
// OR
// 66 B9 wIndex Put offset in thunk table into cx
//
// EB bOffset Jmp to common flat thunk routine
// OR
// 66 ED wOffset Jmp word to common flat thunk routine
// OR
// prolog of common flat thunk routine
//
BOOL GetGdi32OnlyExport ( LPSTR lpszExport32, UINT cbJmpOffset, FARPROC FAR* lplpfn16 ) { BOOL rc;
DebugEntry(GetGdi32OnlyExport);
rc = Get32BitOnlyExport(GetProcAddress32(g_hInstGdi32, lpszExport32), cbJmpOffset, FT_GdiFThkThkConnectionData, lplpfn16);
DebugExitBOOL(GetGdi32OnlyExport, rc); return(rc); }
BOOL GetUser32OnlyExport ( LPSTR lpszExport32, FARPROC FAR* lplpfn16 ) { BOOL rc;
DebugEntry(GetUser32OnlyExport);
rc = Get32BitOnlyExport(GetProcAddress32(g_hInstUser32, lpszExport32), 0, FT_UsrFThkThkConnectionData, lplpfn16);
DebugExitBOOL(GetUser32OnlyExport, rc); return(rc); }
BOOL Get32BitOnlyExport ( DWORD dwfn32, UINT cbJmpOffset, LPDWORD lpThunkTable, FARPROC FAR* lplpfn16 ) { LPBYTE lpfn32; UINT offsetThunk;
DebugEntry(Get32BitOnlyExport);
ASSERT(lplpfn16); *lplpfn16 = NULL;
//
// The thunk table pointer points to two DWORDs. The first is a
// checksum signature. The second is the pointer to the target
// function array.
//
ASSERT(!IsBadReadPtr(lpThunkTable, 2*sizeof(DWORD))); lpThunkTable = (LPDWORD)lpThunkTable[1]; ASSERT(!IsBadReadPtr(lpThunkTable, sizeof(DWORD)));
//
// Get 16:16 pointer to export
//
lpfn32 = NULL;
if (!dwfn32) { ERROR_OUT(("Missing 32-bit export")); DC_QUIT; }
lpfn32 = MapLS((LPVOID)dwfn32); if (!SELECTOROF(lpfn32)) { ERROR_OUT(("Out of selectors")); DC_QUIT; }
//
// Was a jmp offset passed in? If so, decode the instruction there.
// It should be a jmp <dword offset>. Figure out what EIP would be
// if we jumped there. That's the place the flat thunk occurs.
// Currently, only PolyPolyline needs this.
//
if (cbJmpOffset) { if (IsBadReadPtr(lpfn32, cbJmpOffset+5) || (lpfn32[cbJmpOffset] != OPCODE32_JUMP4)) { ERROR_OUT(("Can't read 32-bit export")); DC_QUIT; }
//
// Add dword at cbJmpOffset+1, add this number to (lpfn32+cbJmpOffset+
// 5), which is the EIP of the next instruction after the jump. This
// produces the EIP of the real thunk stub.
//
dwfn32 += cbJmpOffset + 5 + *(LPDWORD)(lpfn32+cbJmpOffset+1);
UnMapLS(lpfn32); lpfn32 = MapLS((LPVOID)dwfn32); if (!SELECTOROF(lpfn32)) { ERROR_OUT(("Out of selectors")); DC_QUIT; } }
//
// Verify that we can read 13 bytes. The reason this will won't go
// past the end in a legitimate case is that this thunklet is either
// followed by the large # of bytes in the common flat thunk routine,
// or by another thunklet
//
if (IsBadReadPtr(lpfn32, 13)) { ERROR_OUT(("Can't read code in 32-bit export")); DC_QUIT; }
//
// Does this have the 10-byte DEBUG prolog?
//
if (*lpfn32 == OPCODE32_PUSH) { // Yes, skip it
lpfn32 += 5;
// Make sure that next thing is a call
if (*lpfn32 != OPCODE32_CALL) { ERROR_OUT(("Can't read code in 32-bit export")); DC_QUIT; }
lpfn32 += 5; }
//
// This should either be mov cl, byte or mov cx, word
//
if (*lpfn32 == OPCODE32_MOVCL) { offsetThunk = *(lpfn32+1); } else if (*((LPWORD)lpfn32) == OPCODE32_MOVCX) { //
// NOTE: Even though this is a CX offset, the thunk code only
// looks at the low BYTE
//
offsetThunk = *(lpfn32+2); } else { ERROR_OUT(("Can't read code in 32-bit export")); DC_QUIT; }
//
// Now, can we read this value?
//
if (IsBadReadPtr(lpThunkTable+offsetThunk, sizeof(DWORD)) || IsBadCodePtr((FARPROC)lpThunkTable[offsetThunk])) { ERROR_OUT(("Can't read thunk table entry")); DC_QUIT; }
*lplpfn16 = (FARPROC)lpThunkTable[offsetThunk];
DC_EXIT_POINT: if (SELECTOROF(lpfn32)) { UnMapLS(lpfn32); } DebugExitBOOL(Get32BitOnlyExport16, (*lplpfn16 != NULL)); return(*lplpfn16 != NULL); }
//
// CreateFnPatch()
// This sets things up to be able to quickly patch/unpatch a system routine.
// The patch is not originally enabled.
//
UINT CreateFnPatch ( LPVOID lpfnToPatch, LPVOID lpfnJumpTo, LPFN_PATCH lpbPatch, UINT uCodeAlias ) { SEGINFO segInfo; UINT ib;
DebugEntry(CreateFnPatch);
ASSERT(lpbPatch->lpCodeAlias == NULL); ASSERT(lpbPatch->wSegOrg == 0);
//
// Do NOT call IsBadReadPtr() here, that will cause the segment, if
// not present, to get pulled in, and will masks problems in the debug
// build that will show up in retail.
//
// Fortunately, PrestoChangoSelector() will set the linear address and
// limit and attributes of our read/write selector properly.
//
//
// Call GetCodeInfo() to check out bit-ness of code segment. If 32-bit
// we need to use the 16-bit override opcode for a far 16:16 jump.
//
segInfo.flags = 0; GetCodeInfo(lpfnToPatch, &segInfo); if (segInfo.flags & NSUSE32) { WARNING_OUT(("Patching 32-bit code segment 0x%04x:0x%04x", SELECTOROF(lpfnToPatch), OFFSETOF(lpfnToPatch))); lpbPatch->f32Bit = TRUE; }
//
// We must fix the codeseg in linear memory, or our shadow will end up
// pointing somewhere if the original moves. PolyBezier and SetPixel
// are in moveable code segments for example.
//
// So save this away. We will fix it when the patch is enabled.
//
lpbPatch->wSegOrg = SELECTOROF(lpfnToPatch);
if (uCodeAlias) { //
// We are going to share an already allocated selector. Note that
// this only works if the code segments of the two patched functions
// are identical. We verify this by the base address in an assert
// down below.
//
lpbPatch->fSharedAlias = TRUE; } else { //
// Create a selector with read-write attributes to alias the read-only
// code function. Using the original will set the limit of our
// selector to the same as that of the codeseg, with the same
// attributes but read-write.
//
uCodeAlias = AllocSelector(SELECTOROF(lpfnToPatch)); if (!uCodeAlias) { ERROR_OUT(("CreateFnPatch: Unable to create alias selector")); DC_QUIT; } uCodeAlias = PrestoChangoSelector(SELECTOROF(lpfnToPatch), uCodeAlias); }
lpbPatch->lpCodeAlias = MAKELP(uCodeAlias, OFFSETOF(lpfnToPatch));
//
// Create the N patch bytes (jmp far16 Seg:Function) of the patch
//
ib = 0; if (lpbPatch->f32Bit) { lpbPatch->rgbPatch[ib++] = OPCODE32_16OVERRIDE; } lpbPatch->rgbPatch[ib++] = OPCODE_FARJUMP16; lpbPatch->rgbPatch[ib++] = LOBYTE(OFFSETOF(lpfnJumpTo)); lpbPatch->rgbPatch[ib++] = HIBYTE(OFFSETOF(lpfnJumpTo)); lpbPatch->rgbPatch[ib++] = LOBYTE(SELECTOROF(lpfnJumpTo)); lpbPatch->rgbPatch[ib++] = HIBYTE(SELECTOROF(lpfnJumpTo));
lpbPatch->fActive = FALSE; lpbPatch->fEnabled = FALSE; DC_EXIT_POINT: DebugExitBOOL(CreateFnPatch, uCodeAlias); return(uCodeAlias); }
//
// DestroyFnPatch()
// This frees any resources used when creating a function patch. The
// alias data selector to the codeseg for writing purposes is it.
//
void DestroyFnPatch(LPFN_PATCH lpbPatch) { DebugEntry(DestroyFnPatch);
//
// First, disable the patch if in use
//
if (lpbPatch->fActive) { TRACE_OUT(("Destroying active patch")); EnableFnPatch(lpbPatch, PATCH_DEACTIVATE); }
//
// Second, free the alias selector if we allocated one
//
if (lpbPatch->lpCodeAlias) { if (!lpbPatch->fSharedAlias) { FreeSelector(SELECTOROF(lpbPatch->lpCodeAlias)); } lpbPatch->lpCodeAlias = NULL; }
//
// Third, clear this to fine cleanup problems
//
lpbPatch->wSegOrg = 0; lpbPatch->f32Bit = FALSE;
DebugExitVOID(DestroyFnPatch); }
//
// EnableFnPatch()
// This actually patches the function to jump to our routine using the
// info saved when created.
//
// THIS ROUTINE MAY GET CALLED AT INTERRUPT TIME. YOU CAN NOT USE ANY
// EXTERNAL FUNCTION, INCLUDING DEBUG TRACING/ASSERTS.
//
#define SAFE_ASSERT(x) if (!lpbPatch->fInterruptable) { ASSERT(x); }
void EnableFnPatch(LPFN_PATCH lpbPatch, UINT flags) { UINT ib; UINT cbPatch;
SAFE_ASSERT(lpbPatch->lpCodeAlias); SAFE_ASSERT(lpbPatch->wSegOrg);
//
// Make sure the original and the alias are pointing to the same
// linear memory. We don't do this when not enabling/disabling for calls,
// only when starting/stopping the patches
//
//
// If enabling for the first time, fix BEFORE bytes are copied
//
if ((flags & ENABLE_MASK) == ENABLE_ON) { //
// We need to fix the original code segment so it won't move in
// linear memory. Note that we set the selector base of our alias
// even though several patches (not too many) may share one. The
// extra times are harmless, and this prevents us from having to order
// patches in our array in such a way that the original precedes the
// shared ones, and walking our array in opposite orders to enable or
// disable.
//
// And GlobalFix() just bumps up a lock count, so again, it's OK to do
// this multiple times.
//
//
// WE KNOW THIS CODE DOESN'T EXECUTE AT INTERRUPT TIME.
//
ASSERT(!lpbPatch->fEnabled); ASSERT(!lpbPatch->fActive);
if (!lpbPatch->fActive) { lpbPatch->fActive = TRUE;
//
// Make sure this segment gets pulled in if discarded. GlobalFix()
// fails if not, and worse we'll fault the second we try to write
// to or read from the alias. We do this by grabbing the 1st word
// from the original.
//
// GlobalFix will prevent the segment from being discarded until
// GlobalUnfix() happens.
//
ib = *(LPUINT)MAKELP(lpbPatch->wSegOrg, OFFSETOF(lpbPatch->lpCodeAlias)); GlobalFix((HGLOBAL)lpbPatch->wSegOrg); SetSelectorBase(SELECTOROF(lpbPatch->lpCodeAlias), GetSelectorBase(lpbPatch->wSegOrg)); } }
if (lpbPatch->fInterruptable) { //
// If this is for starting/stopping the patch, we have to disable
// interrupts around the byte copying. Or we could die if the
// interrupt handler happens in the middle.
//
// We don't need to do this when disabling/enabling to call through
// to the original, since we know that reflected interrupts aren't
// nested, and the interrupt will complete before we come back to
// the interrupted normal app code.
//
if (!(flags & ENABLE_FORCALL)) { _asm cli } }
SAFE_ASSERT(GetSelectorBase(SELECTOROF(lpbPatch->lpCodeAlias)) == GetSelectorBase(lpbPatch->wSegOrg));
if (lpbPatch->f32Bit) { cbPatch = CB_PATCHBYTES32; } else { cbPatch = CB_PATCHBYTES16; }
if (flags & ENABLE_ON) { SAFE_ASSERT(lpbPatch->fActive); SAFE_ASSERT(!lpbPatch->fEnabled);
if (!lpbPatch->fEnabled) { //
// Save the function's original first N bytes, and copy the jump far16
// patch in.
//
for (ib = 0; ib < cbPatch; ib++) { lpbPatch->rgbOrg[ib] = lpbPatch->lpCodeAlias[ib]; lpbPatch->lpCodeAlias[ib] = lpbPatch->rgbPatch[ib]; }
lpbPatch->fEnabled = TRUE; } } else { SAFE_ASSERT(lpbPatch->fActive); SAFE_ASSERT(lpbPatch->fEnabled);
if (lpbPatch->fEnabled) { //
// Put back the function's original first N bytes
//
for (ib = 0; ib < cbPatch; ib++) { lpbPatch->lpCodeAlias[ib] = lpbPatch->rgbOrg[ib]; }
lpbPatch->fEnabled = FALSE; } }
SAFE_ASSERT(GetSelectorBase(SELECTOROF(lpbPatch->lpCodeAlias)) == GetSelectorBase(lpbPatch->wSegOrg));
if (lpbPatch->fInterruptable) { //
// Reenable interrupts
//
if (!(flags & ENABLE_FORCALL)) { _asm sti }
}
//
// If really stopping, unfix AFTER the bytes have been copied. This will
// bump down a lock count, so when all patches are disabled, the original
// code segment will be able to move.
//
if ((flags & ENABLE_MASK) == ENABLE_OFF) { //
// WE KNOW THIS CODE DOESN'T EXECUTE AT INTERRUPT TIME.
//
ASSERT(!lpbPatch->fEnabled); ASSERT(lpbPatch->fActive);
if (lpbPatch->fActive) { lpbPatch->fActive = FALSE; GlobalUnfix((HGLOBAL)lpbPatch->wSegOrg); } } }
//
// LIST MANIPULATION ROUTINES
//
//
// COM_BasedListInsertBefore(...)
//
// See com.h for description.
//
void COM_BasedListInsertBefore(PBASEDLIST pExisting, PBASEDLIST pNew) { PBASEDLIST pTemp;
DebugEntry(COM_BasedListInsertBefore);
//
// Check for bad parameters.
//
ASSERT((pNew != NULL)); ASSERT((pExisting != NULL));
//
// Find the item before pExisting:
//
pTemp = COM_BasedPrevListField(pExisting); ASSERT((pTemp != NULL));
TRACE_OUT(("Inserting item at %#lx into list between %#lx and %#lx", pNew, pTemp, pExisting));
//
// Set its <next> field to point to the new item
//
pTemp->next = PTRBASE_TO_OFFSET(pNew, pTemp); pNew->prev = PTRBASE_TO_OFFSET(pTemp, pNew);
//
// Set <prev> field of pExisting to point to new item:
//
pExisting->prev = PTRBASE_TO_OFFSET(pNew, pExisting); pNew->next = PTRBASE_TO_OFFSET(pExisting, pNew);
DebugExitVOID(COM_BasedListInsertBefore); } // COM_BasedListInsertBefore
//
// COM_BasedListInsertAfter(...)
//
// See com.h for description.
//
void COM_BasedListInsertAfter(PBASEDLIST pExisting, PBASEDLIST pNew) { PBASEDLIST pTemp;
DebugEntry(COM_BasedListInsertAfter);
//
// Check for bad parameters.
//
ASSERT((pNew != NULL)); ASSERT((pExisting != NULL));
//
// Find the item after pExisting:
//
pTemp = COM_BasedNextListField(pExisting); ASSERT((pTemp != NULL));
TRACE_OUT(("Inserting item at %#lx into list between %#lx and %#lx", pNew, pExisting, pTemp));
//
// Set its <prev> field to point to the new item
//
pTemp->prev = PTRBASE_TO_OFFSET(pNew, pTemp); pNew->next = PTRBASE_TO_OFFSET(pTemp, pNew);
//
// Set <next> field of pExisting to point to new item:
//
pExisting->next = PTRBASE_TO_OFFSET(pNew, pExisting); pNew->prev = PTRBASE_TO_OFFSET(pExisting, pNew);
DebugExitVOID(COM_BasedListInsertAfter); } // COM_BasedListInsertAfter
//
// COM_BasedListRemove(...)
//
// See com.h for description.
//
void COM_BasedListRemove(PBASEDLIST pListItem) { PBASEDLIST pNext = NULL; PBASEDLIST pPrev = NULL;
DebugEntry(COM_BasedListRemove);
//
// Check for bad parameters.
//
ASSERT((pListItem != NULL));
pPrev = COM_BasedPrevListField(pListItem); pNext = COM_BasedNextListField(pListItem);
ASSERT((pPrev != NULL)); ASSERT((pNext != NULL));
TRACE_OUT(("Removing item at %#lx from list", pListItem));
pPrev->next = PTRBASE_TO_OFFSET(pNext, pPrev); pNext->prev = PTRBASE_TO_OFFSET(pPrev, pNext);
DebugExitVOID(COM_BasedListRemove); } // COM_BasedListRemove
//
// NOTE:
// Because this is small model 16-bit code, NULL (which is 0) gets turned
// into ds:0 when casting to void FAR*. Therefore we use our own FAR_NULL
// define, which is 0:0.
//
void FAR * COM_BasedListNext( PBASEDLIST pHead, void FAR * pEntry, UINT nOffset ) { PBASEDLIST p;
ASSERT(pHead != NULL); ASSERT(pEntry != NULL);
p = COM_BasedNextListField(COM_BasedStructToField(pEntry, nOffset)); return ((p == pHead) ? FAR_NULL : COM_BasedFieldToStruct(p, nOffset)); }
void FAR * COM_BasedListPrev ( PBASEDLIST pHead, void FAR * pEntry, UINT nOffset ) { PBASEDLIST p;
ASSERT(pHead != NULL); ASSERT(pEntry != NULL);
p = COM_BasedPrevListField(COM_BasedStructToField(pEntry, nOffset)); return ((p == pHead) ? FAR_NULL : COM_BasedFieldToStruct(p, nOffset)); }
void FAR * COM_BasedListFirst ( PBASEDLIST pHead, UINT nOffset ) { return (COM_BasedListIsEmpty(pHead) ? FAR_NULL : COM_BasedFieldToStruct(COM_BasedNextListField(pHead), nOffset)); }
void FAR * COM_BasedListLast ( PBASEDLIST pHead, UINT nOffset ) { return (COM_BasedListIsEmpty(pHead) ? FAR_NULL : COM_BasedFieldToStruct(COM_BasedPrevListField(pHead), nOffset)); }
|