88#include < sys/mman.h>
99#include < unistd.h>
1010#include < cstring>
11+ #include < fstream>
1112
1213CFunctionHook::CFunctionHook (HANDLE owner, void * source, void * destination) {
1314 m_pSource = source;
@@ -64,10 +65,14 @@ CFunctionHook::SInstructionProbe CFunctionHook::probeMinimumJumpSize(void* start
6465}
6566
6667CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls (const SInstructionProbe& probe) {
68+ SAssembly returns;
69+
6770 // analyze the code and fix what we know how to.
6871 uint64_t currentAddress = (uint64_t )m_pSource;
6972 // actually newline + 1
70- size_t lastAsmNewline = 0 ;
73+ size_t lastAsmNewline = 0 ;
74+ // needle for destination binary
75+ size_t currentDestinationOffset = 0 ;
7176 std::string assemblyBuilder;
7277 for (auto & len : probe.insSizes ) {
7378
@@ -82,17 +87,27 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
8287 return {};
8388 const uint64_t DESTINATION = currentAddress + OFFSET + len;
8489
85- if (code.starts_with (" mov" )) {
86- // mov +0xdeadbeef(%rip), %rax
87- assemblyBuilder += std::format (" movabs $0x{:x}, {}\n " , DESTINATION, tokens[2 ]);
88- } else if (code.starts_with (" call" )) {
90+ if (code.starts_with (" call" )) {
8991 // call +0xdeadbeef(%rip)
9092 assemblyBuilder += std::format (" pushq %rax\n movabs $0x{:x}, %rax\n callq *%rax\n popq %rax\n " , DESTINATION);
93+ currentDestinationOffset += 14 ;
9194 } else if (code.starts_with (" lea" )) {
9295 // lea 0xdeadbeef(%rip), %rax
9396 assemblyBuilder += std::format (" movabs $0x{:x}, {}\n " , DESTINATION, tokens[2 ]);
97+ currentDestinationOffset += 10 ;
9498 } else {
95- return {};
99+ auto ADDREND = code.find (" (%rip)" );
100+ auto ADDRSTART = (code.substr (0 , ADDREND).find_last_of (' ' ));
101+
102+ if (ADDREND == std::string::npos || ADDRSTART == std::string::npos)
103+ return {};
104+
105+ const uint64_t PREDICTEDRIP = (uint64_t )m_pTrampolineAddr + currentDestinationOffset + len;
106+ const bool POSITIVE = DESTINATION > PREDICTEDRIP;
107+ const uint64_t NEWRIPOFFSET = POSITIVE ? DESTINATION - PREDICTEDRIP : PREDICTEDRIP - DESTINATION;
108+
109+ assemblyBuilder += std::format (" {} {}0x{:x}{}\n " , code.substr (0 , ADDRSTART), POSITIVE ? ' +' : ' -' , NEWRIPOFFSET, code.substr (ADDREND));
110+ currentDestinationOffset += len;
96111 }
97112 } else if (code.contains (" invalid" )) {
98113 std::vector<uint8_t > bytes;
@@ -101,6 +116,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
101116 if (len == 4 && bytes[0 ] == 0xF3 && bytes[1 ] == 0x0F && bytes[2 ] == 0x1E && bytes[3 ] == 0xFA ) {
102117 // F3 0F 1E FA = endbr64, udis doesn't understand that one
103118 assemblyBuilder += " endbr64\n " ;
119+ currentDestinationOffset += 4 ;
104120 } else {
105121 // raise error, unknown op
106122 std::string strBytes;
@@ -112,6 +128,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
112128 }
113129 } else {
114130 assemblyBuilder += code + " \n " ;
131+ currentDestinationOffset += len;
115132 }
116133
117134 lastAsmNewline = probe.assembly .find (" \n " , lastAsmNewline) + 1 ;
@@ -131,7 +148,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
131148 }
132149
133150 std::ifstream ifs (" /tmp/hypr/.hookbinary2.o" , std::ios::binary);
134- SAssembly returns = {std::vector<char >(std::istreambuf_iterator<char >(ifs), {})};
151+ returns = {std::vector<char >(std::istreambuf_iterator<char >(ifs), {})};
135152 ifs.close ();
136153 std::filesystem::remove (" /tmp/hypr/.hookcode.asm" );
137154 std::filesystem::remove (" /tmp/hypr/.hookbinary.o" );
@@ -158,6 +175,10 @@ bool CFunctionHook::hook() {
158175 // nop
159176 static constexpr uint8_t NOP = 0x90 ;
160177
178+ // alloc trampoline
179+ const auto MAX_TRAMPOLINE_SIZE = HOOK_TRAMPOLINE_MAX_SIZE; // we will never need more.
180+ m_pTrampolineAddr = (void *)g_pFunctionHookSystem->getAddressForTrampo ();
181+
161182 // probe instructions to be trampolin'd
162183 SInstructionProbe probe;
163184 try {
@@ -174,9 +195,12 @@ bool CFunctionHook::hook() {
174195 const size_t HOOKSIZE = PROBEFIXEDASM.bytes .size ();
175196 const size_t ORIGSIZE = probe.len ;
176197
177- // alloc trampoline
178- const auto TRAMPOLINE_SIZE = sizeof (ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof (PUSH_RAX);
179- m_pTrampolineAddr = mmap (NULL , TRAMPOLINE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );
198+ const auto TRAMPOLINE_SIZE = sizeof (ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof (PUSH_RAX);
199+
200+ if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) {
201+ Debug::log (ERR, " [functionhook] failed, not enough space in trampo to alloc:\n {}" , probe.assembly );
202+ return false ;
203+ }
180204
181205 m_pOriginalBytes = malloc (ORIGSIZE);
182206 memcpy (m_pOriginalBytes, m_pSource, ORIGSIZE);
@@ -236,14 +260,11 @@ bool CFunctionHook::unhook() {
236260 // revert mprot
237261 mprotect ((uint8_t *)m_pSource - ((uint64_t )m_pSource) % sysconf (_SC_PAGE_SIZE), sysconf (_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
238262
239- // unmap
240- munmap (m_pTrampolineAddr, m_iTrampoLen);
241-
242263 // reset vars
243264 m_bActive = false ;
244265 m_iHookLen = 0 ;
245266 m_iTrampoLen = 0 ;
246- m_pTrampolineAddr = nullptr ;
267+ m_pTrampolineAddr = nullptr ; // no unmapping, it's managed by the HookSystem
247268 m_pOriginalBytes = nullptr ;
248269
249270 free (m_pOriginalBytes);
@@ -263,3 +284,108 @@ bool CHookSystem::removeHook(CFunctionHook* hook) {
263284void CHookSystem::removeAllHooksFrom (HANDLE handle) {
264285 std::erase_if (m_vHooks, [&](const auto & other) { return other->m_pOwner == handle; });
265286}
287+
288+ static uintptr_t seekNewPageAddr () {
289+ const uint64_t PAGESIZE_VAR = sysconf (_SC_PAGE_SIZE);
290+ auto MAPS = std::ifstream (" /proc/self/maps" );
291+
292+ uint64_t lastStart = 0 , lastEnd = 0 ;
293+
294+ std::string line;
295+ while (std::getline (MAPS, line)) {
296+ CVarList props{line, 0 , ' s' , true };
297+
298+ uint64_t start = 0 , end = 0 ;
299+ if (props[0 ].empty ()) {
300+ Debug::log (WARN, " seekNewPageAddr: unexpected line in self maps" );
301+ continue ;
302+ }
303+
304+ CVarList startEnd{props[0 ], 0 , ' -' , true };
305+
306+ try {
307+ start = std::stoull (startEnd[0 ], nullptr , 16 );
308+ end = std::stoull (startEnd[1 ], nullptr , 16 );
309+ } catch (std::exception& e) {
310+ Debug::log (WARN, " seekNewPageAddr: unexpected line in self maps: {}" , line);
311+ continue ;
312+ }
313+
314+ Debug::log (LOG, " seekNewPageAddr: page 0x{:x} - 0x{:x}" , start, end);
315+
316+ if (lastStart == 0 ) {
317+ lastStart = start;
318+ lastEnd = end;
319+ continue ;
320+ }
321+
322+ if (start - lastEnd > PAGESIZE_VAR * 2 ) {
323+ Debug::log (LOG, " seekNewPageAddr: found gap: 0x{:x}-0x{:x} ({} bytes)" , lastEnd, start, start - lastEnd);
324+ MAPS.close ();
325+ return lastEnd;
326+ }
327+
328+ lastStart = start;
329+ lastEnd = end;
330+ }
331+
332+ MAPS.close ();
333+ return 0 ;
334+ }
335+
336+ uint64_t CHookSystem::getAddressForTrampo () {
337+ // yes, technically this creates a memory leak of 64B every hook creation. But I don't care.
338+ // tracking all the users of the memory would be painful.
339+ // Nobody will hook 100k times, and even if, that's only 640kB. Nothing.
340+
341+ SAllocatedPage* page = nullptr ;
342+ for (auto & p : pages) {
343+ if (p.used + HOOK_TRAMPOLINE_MAX_SIZE > p.len )
344+ continue ;
345+
346+ page = &p;
347+ break ;
348+ }
349+
350+ if (!page)
351+ page = &pages.emplace_back ();
352+
353+ if (!page->addr ) {
354+ // allocate it
355+ Debug::log (LOG, " getAddressForTrampo: Allocating new page for hooks" );
356+ const uint64_t PAGESIZE_VAR = sysconf (_SC_PAGE_SIZE);
357+ const auto BASEPAGEADDR = seekNewPageAddr ();
358+ for (int attempt = 0 ; attempt < 2 ; ++attempt) {
359+ for (int i = 2 ; i >= 0 ; --i) {
360+ const auto PAGEADDR = BASEPAGEADDR + i * PAGESIZE_VAR;
361+
362+ page->addr = (uint64_t )mmap ((void *)PAGEADDR, PAGESIZE_VAR, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );
363+ page->len = PAGESIZE_VAR;
364+ page->used = 0 ;
365+
366+ Debug::log (LOG, " Attempted to allocate 0x{:x}, got 0x{:x}" , PAGEADDR, page->addr );
367+
368+ if (page->addr == (uint64_t )MAP_FAILED)
369+ continue ;
370+ if (page->addr != PAGEADDR && attempt == 0 ) {
371+ munmap ((void *)page->addr , PAGESIZE_VAR);
372+ page->addr = 0 ;
373+ page->len = 0 ;
374+ continue ;
375+ }
376+
377+ break ;
378+ }
379+ if (page->addr )
380+ break ;
381+ }
382+ }
383+
384+ const auto ADDRFORCONSUMER = page->addr + page->used ;
385+
386+ page->used += HOOK_TRAMPOLINE_MAX_SIZE;
387+
388+ Debug::log (LOG, " getAddressForTrampo: Returning addr 0x{:x} for page at 0x{:x}" , ADDRFORCONSUMER, page->addr );
389+
390+ return ADDRFORCONSUMER;
391+ }
0 commit comments