@@ -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);
@@ -250,66 +290,128 @@ static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) {
250290 return pbCode;
251291}
252292
293+
253294// =========================================================================
254295// Internal function:
255296//
256- // Will try to allocate the trampoline structure within 2 gigabytes of
257- // the target function.
297+ // Round down to the next multiple of rndDown
258298// =========================================================================
259- static MHOOKS_TRAMPOLINE* TrampolineAlloc (PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
299+ static size_t RoundDown (size_t addr, size_t rndDown)
300+ {
301+ return (addr / rndDown) * rndDown;
302+ }
260303
261- MHOOKS_TRAMPOLINE* pTrampoline = NULL ;
304+ // =========================================================================
305+ // Internal function:
306+ //
307+ // Will attempt allocate a block of memory within the specified range, as
308+ // near as possible to the specified function.
309+ // =========================================================================
310+ static MHOOKS_TRAMPOLINE* BlockAlloc (PBYTE pSystemFunction, PBYTE pbLower, PBYTE pbUpper) {
311+ SYSTEM_INFO sSysInfo = {0 };
312+ ::GetSystemInfo (&sSysInfo );
313+
314+ // Always allocate in bulk, in case the system actually has a smaller allocation granularity than MINALLOCSIZE.
315+ const ptrdiff_t cAllocSize = max (sSysInfo .dwAllocationGranularity , MHOOK_MINALLOCSIZE);
316+
317+ MHOOKS_TRAMPOLINE* pRetVal = NULL ;
318+ PBYTE pModuleGuess = (PBYTE) RoundDown ((size_t )pSystemFunction, cAllocSize);
319+ int loopCount = 0 ;
320+ for (PBYTE pbAlloc = pModuleGuess; pbLower < pbAlloc && pbAlloc < pbUpper; ++loopCount) {
321+ // determine current state
322+ MEMORY_BASIC_INFORMATION mbi;
323+ ODPRINTF ((L" mhooks: BlockAlloc: Looking at address %p" , pbAlloc));
324+ if (!VirtualQuery (pbAlloc, &mbi, sizeof (mbi)))
325+ break ;
326+ // free & large enough?
327+ if (mbi.State == MEM_FREE && mbi.RegionSize >= (unsigned )cAllocSize) {
328+ // and then try to allocate it
329+ pRetVal = (MHOOKS_TRAMPOLINE*) VirtualAlloc (pbAlloc, cAllocSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
330+ if (pRetVal) {
331+ size_t trampolineCount = cAllocSize / sizeof (MHOOKS_TRAMPOLINE);
332+ ODPRINTF ((L" mhooks: BlockAlloc: Allocated block at %p as %d trampolines" , pRetVal, trampolineCount));
333+
334+ pRetVal[0 ].pPrevTrampoline = NULL ;
335+ pRetVal[0 ].pNextTrampoline = &pRetVal[1 ];
336+
337+ // prepare them by having them point down the line at the next entry.
338+ for (size_t s = 1 ; s < trampolineCount; ++s) {
339+ pRetVal[s].pPrevTrampoline = &pRetVal[s - 1 ];
340+ pRetVal[s].pNextTrampoline = &pRetVal[s + 1 ];
341+ }
262342
263- // do we have room to store this guy?
264- if (g_nHooksInUse < MHOOKS_MAX_SUPPORTED_HOOKS) {
265-
266- // determine lower and upper bounds for the allocation locations.
267- // in the basic scenario this is +/- 2GB but IP-relative instructions
268- // found in the original code may require a smaller window.
269- PBYTE pLower = pSystemFunction + nLimitUp;
270- pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
271- (PBYTE)(0x1 ) : (PBYTE)(pLower - (PBYTE)0x7fff0000 );
272- PBYTE pUpper = pSystemFunction + nLimitDown;
273- pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
274- (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000 ) : (PBYTE)(DWORD_PTR)0xfffffffffff80000 ;
275- ODPRINTF ((L" mhooks: TrampolineAlloc: Allocating for %p between %p and %p" , pSystemFunction, pLower, pUpper));
276-
277- SYSTEM_INFO sSysInfo = {0 };
278- ::GetSystemInfo (&sSysInfo );
279-
280- // go through the available memory blocks and try to allocate a chunk for us
281- for (PBYTE pbAlloc = pLower; pbAlloc < pUpper;) {
282- // determine current state
283- MEMORY_BASIC_INFORMATION mbi;
284- ODPRINTF ((L" mhooks: TrampolineAlloc: Looking at address %p" , pbAlloc));
285- if (!VirtualQuery (pbAlloc, &mbi, sizeof (mbi)))
343+ // last entry points to the current head of the free list
344+ pRetVal[trampolineCount - 1 ].pNextTrampoline = g_pFreeList;
286345 break ;
287- // free & large enough?
288- if (mbi.State == MEM_FREE && mbi.RegionSize >= sizeof (MHOOKS_TRAMPOLINE) && mbi.RegionSize >= sSysInfo .dwAllocationGranularity ) {
289- // yes, align the pointer to the 64K boundary first
290- pbAlloc = (PBYTE)(ULONG_PTR ((ULONG_PTR (pbAlloc) + (sSysInfo .dwAllocationGranularity -1 )) / sSysInfo .dwAllocationGranularity ) * sSysInfo .dwAllocationGranularity );
291- // and then try to allocate it
292- pTrampoline = (MHOOKS_TRAMPOLINE*)VirtualAlloc (pbAlloc, sizeof (MHOOKS_TRAMPOLINE), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READ);
293- if (pTrampoline) {
294- ODPRINTF ((L" mhooks: TrampolineAlloc: Allocated block at %p as the trampoline" , pTrampoline));
295- break ;
296- }
297346 }
298- // continue the search
299- pbAlloc = (PBYTE)mbi.BaseAddress + mbi.RegionSize ;
300347 }
348+
349+ // This is a spiral, should be -1, 1, -2, 2, -3, 3, etc. (* cAllocSize)
350+ ptrdiff_t bytesToOffset = (cAllocSize * (loopCount + 1 ) * ((loopCount % 2 == 0 ) ? -1 : 1 ));
351+ pbAlloc = pbAlloc + bytesToOffset;
352+ }
353+
354+ return pRetVal;
355+ }
301356
302- // found and allocated a trampoline?
303- if (pTrampoline) {
304- // put it into our list so we know we'll have to free it
305- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
306- if (g_pHooks[i] == NULL ) {
307- g_pHooks[i] = pTrampoline;
308- g_nHooksInUse++;
309- break ;
310- }
311- }
357+ // =========================================================================
358+ // Internal function:
359+ //
360+ // Will try to allocate a big block of memory inside the required range.
361+ // =========================================================================
362+ static MHOOKS_TRAMPOLINE* FindTrampolineInRange (PBYTE pLower, PBYTE pUpper) {
363+ if (!g_pFreeList) {
364+ return NULL ;
365+ }
366+
367+ // This is a standard free list, except we're doubly linked to deal with soem return shenanigans.
368+ MHOOKS_TRAMPOLINE* curEntry = g_pFreeList;
369+ while (curEntry) {
370+ if ((MHOOKS_TRAMPOLINE*) pLower < curEntry && curEntry < (MHOOKS_TRAMPOLINE*) pUpper) {
371+ ListRemove (&g_pFreeList, curEntry);
372+
373+ return curEntry;
312374 }
375+
376+ curEntry = curEntry->pNextTrampoline ;
377+ }
378+
379+ return NULL ;
380+ }
381+
382+ // =========================================================================
383+ // Internal function:
384+ //
385+ // Will try to allocate the trampoline structure within 2 gigabytes of
386+ // the target function.
387+ // =========================================================================
388+ static MHOOKS_TRAMPOLINE* TrampolineAlloc (PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
389+
390+ MHOOKS_TRAMPOLINE* pTrampoline = NULL ;
391+
392+ // determine lower and upper bounds for the allocation locations.
393+ // in the basic scenario this is +/- 2GB but IP-relative instructions
394+ // found in the original code may require a smaller window.
395+ PBYTE pLower = pSystemFunction + nLimitUp;
396+ pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
397+ (PBYTE)(0x1 ) : (PBYTE)(pLower - (PBYTE)0x7fff0000 );
398+ PBYTE pUpper = pSystemFunction + nLimitDown;
399+ pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
400+ (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000 ) : (PBYTE)(DWORD_PTR)0xfffffffffff80000 ;
401+ ODPRINTF ((L" mhooks: TrampolineAlloc: Allocating for %p between %p and %p" , pSystemFunction, pLower, pUpper));
402+
403+ // try to find a trampoline in the specified range
404+ pTrampoline = FindTrampolineInRange (pLower, pUpper);
405+ if (!pTrampoline) {
406+ // if it we can't find it, then we need to allocate a new block and
407+ // try again. Just fail if that doesn't work
408+ g_pFreeList = BlockAlloc (pSystemFunction, pLower, pUpper);
409+ pTrampoline = FindTrampolineInRange (pLower, pUpper);
410+ }
411+
412+ // found and allocated a trampoline?
413+ if (pTrampoline) {
414+ ListPrepend (&g_pHooks, pTrampoline);
313415 }
314416
315417 return pTrampoline;
@@ -321,12 +423,16 @@ static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S
321423// Return the internal trampoline structure that belongs to a hooked function.
322424// =========================================================================
323425static MHOOKS_TRAMPOLINE* TrampolineGet (PBYTE pHookedFunction) {
324- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
325- if (g_pHooks[i]) {
326- if (g_pHooks[i]->codeTrampoline == pHookedFunction)
327- return g_pHooks[i];
426+ MHOOKS_TRAMPOLINE* pCurrent = g_pHooks;
427+
428+ while (pCurrent) {
429+ if (pCurrent->pHookFunction == pHookedFunction) {
430+ return pCurrent;
328431 }
432+
433+ pCurrent = pCurrent->pNextTrampoline ;
329434 }
435+
330436 return NULL ;
331437}
332438
@@ -336,20 +442,17 @@ static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
336442// Free a trampoline structure.
337443// =========================================================================
338444static VOID TrampolineFree (MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) {
339- for (DWORD i=0 ; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
340- if (g_pHooks[i] == pTrampoline) {
341- g_pHooks[i] = NULL ;
342- // It might be OK to call VirtualFree, but quite possibly it isn't:
343- // If a thread has some of our trampoline code on its stack
344- // and we yank the region from underneath it then it will
345- // surely crash upon returning. So instead of freeing the
346- // memory we just let it leak. Ugly, but safe.
347- if (bNeverUsed)
348- VirtualFree (pTrampoline, 0 , MEM_RELEASE);
349- g_nHooksInUse--;
350- break ;
351- }
445+ ListRemove (&g_pHooks, pTrampoline);
446+
447+ // If a thread could feasinbly have some of our trampoline code
448+ // on its stack and we yank the region from underneath it then it will
449+ // surely crash upon returning. So instead of freeing the
450+ // memory we just let it leak. Ugly, but safe.
451+ if (bNeverUsed) {
452+ ListPrepend (&g_pFreeList, pTrampoline);
352453 }
454+
455+ g_nHooksInUse--;
353456}
354457
355458// =========================================================================
0 commit comments