@@ -110,9 +110,10 @@ struct MHOOKS_TRAMPOLINE {
110110 // in the original location
111111 BYTE codeUntouched[MHOOKS_MAX_CODE_BYTES]; // placeholder for unmodified original code
112112 // (we patch IP-relative addressing)
113+ MHOOKS_TRAMPOLINE* pPrevTrampoline; // When in the free list, thess are pointers to the prev and next entry.
114+ MHOOKS_TRAMPOLINE* pNextTrampoline; // When not in the free list, this is a pointer to the prev and next trampoline in use.
113115};
114116
115-
116117// =========================================================================
117118// The patch data structures - store info about rip-relative instructions
118119// during hook placement
@@ -134,26 +135,28 @@ struct MHOOKS_PATCHDATA
134135// Global vars
135136static BOOL g_bVarsInitialized = FALSE ;
136137static CRITICAL_SECTION g_cs;
137- static MHOOKS_TRAMPOLINE* g_pHooks[MHOOKS_MAX_SUPPORTED_HOOKS];
138+ static MHOOKS_TRAMPOLINE* g_pHooks = NULL ;
139+ static MHOOKS_TRAMPOLINE* g_pFreeList = NULL ;
138140static DWORD g_nHooksInUse = 0 ;
139141static HANDLE* g_hThreadHandles = NULL ;
140142static DWORD g_nThreadHandles = 0 ;
141143#define MHOOK_JMPSIZE 5
144+ #define MHOOK_MINALLOCSIZE 4096
142145
143146// =========================================================================
144147// Toolhelp defintions so the functions can be dynamically bound to
145148typedef HANDLE (WINAPI * _CreateToolhelp32Snapshot)(
146- DWORD dwFlags,
149+ DWORD dwFlags,
147150 DWORD th32ProcessID
148151 );
149152
150153typedef BOOL (WINAPI * _Thread32First)(
151- HANDLE hSnapshot,
154+ HANDLE hSnapshot,
152155 LPTHREADENTRY32 lpte
153156 );
154157
155158typedef BOOL (WINAPI * _Thread32Next)(
156- HANDLE hSnapshot,
159+ HANDLE hSnapshot,
157160 LPTHREADENTRY32 lpte
158161 );
159162
@@ -163,11 +166,48 @@ _CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapsho
163166_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle(L" kernel32" ), " Thread32First" );
164167_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle(L" kernel32" ), " Thread32Next" );
165168
169+ // =========================================================================
170+ // Internal function:
171+ //
172+ // Remove the trampoline from the specified list, updating the head pointer
173+ // if necessary.
174+ // =========================================================================
175+ static VOID ListRemove (MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) {
176+ if (pNode->pPrevTrampoline ) {
177+ pNode->pPrevTrampoline ->pNextTrampoline = pNode->pNextTrampoline ;
178+ }
179+
180+ if (pNode->pNextTrampoline ) {
181+ pNode->pNextTrampoline ->pPrevTrampoline = pNode->pPrevTrampoline ;
182+ }
183+
184+ if ((*pListHead) == pNode) {
185+ (*pListHead) = pNode->pNextTrampoline ;
186+ assert ((*pListHead)->pPrevTrampoline == NULL );
187+ }
188+
189+ pNode->pPrevTrampoline = NULL ;
190+ pNode->pNextTrampoline = NULL ;
191+ }
192+
193+ // =========================================================================
194+ // Internal function:
195+ //
196+ // Prepend the trampoline from the specified list and update the head pointer.
197+ // =========================================================================
198+ static VOID ListPrepend (MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) {
199+ pNode->pPrevTrampoline = NULL ;
200+ pNode->pNextTrampoline = (*pListHead);
201+ if ((*pListHead)) {
202+ (*pListHead)->pPrevTrampoline = pNode;
203+ }
204+ (*pListHead) = pNode;
205+ }
206+
166207// =========================================================================
167208static VOID EnterCritSec () {
168209 if (!g_bVarsInitialized) {
169210 InitializeCriticalSection (&g_cs);
170- ZeroMemory (g_pHooks, sizeof (g_pHooks));
171211 g_bVarsInitialized = TRUE ;
172212 }
173213 EnterCriticalSection (&g_cs);
@@ -265,66 +305,128 @@ static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) {
265305 return pbCode;
266306}
267307
308+
268309// =========================================================================
269310// Internal function:
270311//
271- // Will try to allocate the trampoline structure within 2 gigabytes of
272- // the target function.
312+ // Round down to the next multiple of rndDown
273313// =========================================================================
274- static MHOOKS_TRAMPOLINE* TrampolineAlloc (PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
314+ static size_t RoundDown (size_t addr, size_t rndDown)
315+ {
316+ return (addr / rndDown) * rndDown;
317+ }
275318
276- MHOOKS_TRAMPOLINE* pTrampoline = NULL ;
319+ // =========================================================================
320+ // Internal function:
321+ //
322+ // Will attempt allocate a block of memory within the specified range, as
323+ // near as possible to the specified function.
324+ // =========================================================================
325+ static MHOOKS_TRAMPOLINE* BlockAlloc (PBYTE pSystemFunction, PBYTE pbLower, PBYTE pbUpper) {
326+ SYSTEM_INFO sSysInfo = {0 };
327+ ::GetSystemInfo (&sSysInfo );
328+
329+ // Always allocate in bulk, in case the system actually has a smaller allocation granularity than MINALLOCSIZE.
330+ const ptrdiff_t cAllocSize = max (sSysInfo .dwAllocationGranularity , MHOOK_MINALLOCSIZE);
331+
332+ MHOOKS_TRAMPOLINE* pRetVal = NULL ;
333+ PBYTE pModuleGuess = (PBYTE) RoundDown ((size_t )pSystemFunction, cAllocSize);
334+ int loopCount = 0 ;
335+ for (PBYTE pbAlloc = pModuleGuess; pbLower < pbAlloc && pbAlloc < pbUpper; ++loopCount) {
336+ // determine current state
337+ MEMORY_BASIC_INFORMATION mbi;
338+ ODPRINTF ((L" mhooks: BlockAlloc: Looking at address %p" , pbAlloc));
339+ if (!VirtualQuery (pbAlloc, &mbi, sizeof (mbi)))
340+ break ;
341+ // free & large enough?
342+ if (mbi.State == MEM_FREE && mbi.RegionSize >= (unsigned )cAllocSize) {
343+ // and then try to allocate it
344+ pRetVal = (MHOOKS_TRAMPOLINE*) VirtualAlloc (pbAlloc, cAllocSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
345+ if (pRetVal) {
346+ size_t trampolineCount = cAllocSize / sizeof (MHOOKS_TRAMPOLINE);
347+ ODPRINTF ((L" mhooks: BlockAlloc: Allocated block at %p as %d trampolines" , pRetVal, trampolineCount));
348+
349+ pRetVal[0 ].pPrevTrampoline = NULL ;
350+ pRetVal[0 ].pNextTrampoline = &pRetVal[1 ];
351+
352+ // prepare them by having them point down the line at the next entry.
353+ for (size_t s = 1 ; s < trampolineCount; ++s) {
354+ pRetVal[s].pPrevTrampoline = &pRetVal[s - 1 ];
355+ pRetVal[s].pNextTrampoline = &pRetVal[s + 1 ];
356+ }
277357
278- // do we have room to store this guy?
279- if (g_nHooksInUse < MHOOKS_MAX_SUPPORTED_HOOKS) {
280-
281- // determine lower and upper bounds for the allocation locations.
282- // in the basic scenario this is +/- 2GB but IP-relative instructions
283- // found in the original code may require a smaller window.
284- PBYTE pLower = pSystemFunction + nLimitUp;
285- pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
286- (PBYTE)(0x1 ) : (PBYTE)(pLower - (PBYTE)0x7fff0000 );
287- PBYTE pUpper = pSystemFunction + nLimitDown;
288- pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
289- (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000 ) : (PBYTE)(DWORD_PTR)0xfffffffffff80000 ;
290- ODPRINTF ((L" mhooks: TrampolineAlloc: Allocating for %p between %p and %p" , pSystemFunction, pLower, pUpper));
291-
292- SYSTEM_INFO sSysInfo = {0 };
293- ::GetSystemInfo (&sSysInfo );
294-
295- // go through the available memory blocks and try to allocate a chunk for us
296- for (PBYTE pbAlloc = pLower; pbAlloc < pUpper;) {
297- // determine current state
298- MEMORY_BASIC_INFORMATION mbi;
299- ODPRINTF ((L" mhooks: TrampolineAlloc: Looking at address %p" , pbAlloc));
300- if (!VirtualQuery (pbAlloc, &mbi, sizeof (mbi)))
358+ // last entry points to the current head of the free list
359+ pRetVal[trampolineCount - 1 ].pNextTrampoline = g_pFreeList;
301360 break ;
302- // free & large enough?
303- if (mbi.State == MEM_FREE && mbi.RegionSize >= sizeof (MHOOKS_TRAMPOLINE) && mbi.RegionSize >= sSysInfo .dwAllocationGranularity ) {
304- // yes, align the pointer to the 64K boundary first
305- pbAlloc = (PBYTE)(ULONG_PTR ((ULONG_PTR (pbAlloc) + (sSysInfo .dwAllocationGranularity -1 )) / sSysInfo .dwAllocationGranularity ) * sSysInfo .dwAllocationGranularity );
306- // and then try to allocate it
307- pTrampoline = (MHOOKS_TRAMPOLINE*)VirtualAlloc (pbAlloc, sizeof (MHOOKS_TRAMPOLINE), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READ);
308- if (pTrampoline) {
309- ODPRINTF ((L" mhooks: TrampolineAlloc: Allocated block at %p as the trampoline" , pTrampoline));
310- break ;
311- }
312361 }
313- // continue the search
314- pbAlloc = (PBYTE)mbi.BaseAddress + mbi.RegionSize ;
315362 }
363+
364+ // This is a spiral, should be -1, 1, -2, 2, -3, 3, etc. (* cAllocSize)
365+ ptrdiff_t bytesToOffset = (cAllocSize * (loopCount + 1 ) * ((loopCount % 2 == 0 ) ? -1 : 1 ));
366+ pbAlloc = pbAlloc + bytesToOffset;
367+ }
368+
369+ return pRetVal;
370+ }
316371
317- // found and allocated a trampoline?
318- if (pTrampoline) {
319- // put it into our list so we know we'll have to free it
320- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
321- if (g_pHooks[i] == NULL ) {
322- g_pHooks[i] = pTrampoline;
323- g_nHooksInUse++;
324- break ;
325- }
326- }
372+ // =========================================================================
373+ // Internal function:
374+ //
375+ // Will try to allocate a big block of memory inside the required range.
376+ // =========================================================================
377+ static MHOOKS_TRAMPOLINE* FindTrampolineInRange (PBYTE pLower, PBYTE pUpper) {
378+ if (!g_pFreeList) {
379+ return NULL ;
380+ }
381+
382+ // This is a standard free list, except we're doubly linked to deal with soem return shenanigans.
383+ MHOOKS_TRAMPOLINE* curEntry = g_pFreeList;
384+ while (curEntry) {
385+ if ((MHOOKS_TRAMPOLINE*) pLower < curEntry && curEntry < (MHOOKS_TRAMPOLINE*) pUpper) {
386+ ListRemove (&g_pFreeList, curEntry);
387+
388+ return curEntry;
327389 }
390+
391+ curEntry = curEntry->pNextTrampoline ;
392+ }
393+
394+ return NULL ;
395+ }
396+
397+ // =========================================================================
398+ // Internal function:
399+ //
400+ // Will try to allocate the trampoline structure within 2 gigabytes of
401+ // the target function.
402+ // =========================================================================
403+ static MHOOKS_TRAMPOLINE* TrampolineAlloc (PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
404+
405+ MHOOKS_TRAMPOLINE* pTrampoline = NULL ;
406+
407+ // determine lower and upper bounds for the allocation locations.
408+ // in the basic scenario this is +/- 2GB but IP-relative instructions
409+ // found in the original code may require a smaller window.
410+ PBYTE pLower = pSystemFunction + nLimitUp;
411+ pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
412+ (PBYTE)(0x1 ) : (PBYTE)(pLower - (PBYTE)0x7fff0000 );
413+ PBYTE pUpper = pSystemFunction + nLimitDown;
414+ pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
415+ (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000 ) : (PBYTE)(DWORD_PTR)0xfffffffffff80000 ;
416+ ODPRINTF ((L" mhooks: TrampolineAlloc: Allocating for %p between %p and %p" , pSystemFunction, pLower, pUpper));
417+
418+ // try to find a trampoline in the specified range
419+ pTrampoline = FindTrampolineInRange (pLower, pUpper);
420+ if (!pTrampoline) {
421+ // if it we can't find it, then we need to allocate a new block and
422+ // try again. Just fail if that doesn't work
423+ g_pFreeList = BlockAlloc (pSystemFunction, pLower, pUpper);
424+ pTrampoline = FindTrampolineInRange (pLower, pUpper);
425+ }
426+
427+ // found and allocated a trampoline?
428+ if (pTrampoline) {
429+ ListPrepend (&g_pHooks, pTrampoline);
328430 }
329431
330432 return pTrampoline;
@@ -336,12 +438,16 @@ static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S
336438// Return the internal trampoline structure that belongs to a hooked function.
337439// =========================================================================
338440static MHOOKS_TRAMPOLINE* TrampolineGet (PBYTE pHookedFunction) {
339- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
340- if (g_pHooks[i]) {
341- if (g_pHooks[i]->codeTrampoline == pHookedFunction)
342- return g_pHooks[i];
441+ MHOOKS_TRAMPOLINE* pCurrent = g_pHooks;
442+
443+ while (pCurrent) {
444+ if (pCurrent->pHookFunction == pHookedFunction) {
445+ return pCurrent;
343446 }
447+
448+ pCurrent = pCurrent->pNextTrampoline ;
344449 }
450+
345451 return NULL ;
346452}
347453
@@ -351,20 +457,17 @@ static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
351457// Free a trampoline structure.
352458// =========================================================================
353459static VOID TrampolineFree (MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) {
354- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
355- if (g_pHooks[i] == pTrampoline) {
356- g_pHooks[i] = NULL ;
357- // It might be OK to call VirtualFree, but quite possibly it isn't:
358- // If a thread has some of our trampoline code on its stack
359- // and we yank the region from underneath it then it will
360- // surely crash upon returning. So instead of freeing the
361- // memory we just let it leak. Ugly, but safe.
362- if (bNeverUsed)
363- VirtualFree (pTrampoline, 0 , MEM_RELEASE);
364- g_nHooksInUse--;
365- break ;
366- }
460+ ListRemove (&g_pHooks, pTrampoline);
461+
462+ // If a thread could feasinbly have some of our trampoline code
463+ // on its stack and we yank the region from underneath it then it will
464+ // surely crash upon returning. So instead of freeing the
465+ // memory we just let it leak. Ugly, but safe.
466+ if (bNeverUsed) {
467+ ListPrepend (&g_pFreeList, pTrampoline);
367468 }
469+
470+ g_nHooksInUse--;
368471}
369472
370473// =========================================================================
0 commit comments