/* Trampolines * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as a special exception, the author gives permission to * link the code of this program with the Half-Life Game Engine ("HL * Engine") and Modified Game Libraries ("MODs") developed by Valve, * L.L.C ("Valve"). You must obey the GNU General Public License in all * respects for all of the code used other than the HL Engine and MODs * from Valve. If you modify this file, you may extend this exception * to your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. */ #ifndef TRAMPOLINES_H #define TRAMPOLINES_H #ifndef NDEBUG #define TPRINT(msg) printf msg #else #define TPRINT(msg) /* nothing */ #endif #if defined _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN #if _MSC_VER >= 1400 #ifdef offsetof #undef offsetof #endif // offsetof #endif // _MSC_VER >= 1400 #include #elif defined(__linux__) || defined(__APPLE__) #include #if defined (__linux__) #include #endif #endif #include // size_t #include // memcpy #include // memalign #include #include namespace Trampolines { /** * List of x86 bytecodes for creating * basic trampolines at runtime. * - * These are defined here so that, should * the need ever arise, this can be ported * to other architectures fairly painlessly */ namespace Bytecode { /** * Prologue for a function */ const unsigned char codePrologue[] = { 0x55, // push ebp 0x89, 0xE5, // mov ebp, esp }; /** * Align stack on 16 byte boundary */ const unsigned char codeAlignStack16[] = { 0x83, 0xE4, 0xF0, // and esp, 0xFFFFFFF0 }; /** * Allocate stack space (8-bit) by adding to ESP */ const unsigned char codeAllocStack[] = { 0x83, 0xEC, 0xFF, // sub esp, 0xFF }; /** * Offset of codeAllocStack to modify at runtime * to contain amount of stack space to allocate. */ const unsigned int codeAllocStackReplace = 2; /** * Takes a paramter from the trampoline's stack * and pushes it onto the target's stack. */ const unsigned char codePushParam[] = { 0xFF, 0x75, 0xFF // pushl [ebp+0xFF] }; /** * Offset of codePushParam to modify at runtime * that contains the stack offset */ const unsigned int codePushParamReplace = 2; /** * Takes the "this" pointer from the trampoline and * pushes it onto the target's stack. */ const unsigned char codePushThis[] = { #if defined(_WIN32) 0x51 // push ecx #elif defined(__linux__) || defined(__APPLE__) 0xFF, 0x75, 0x04 // pushl [ebp+0x08h] #endif }; #if defined(__linux__) || defined(__APPLE__) const int codePushThisReplace = 2; #endif /** * Pushes a raw number onto the target's stack */ const unsigned char codePushID[] = { 0x68, 0xDE, 0xFA, 0xAD, 0xDE // push DEADFADEh }; /** * Offset of codePushID to modify at runtime * to contain the number to push */ const unsigned int codePushIDReplace = 1; /** * Call our procedure */ const unsigned char codeCall[] = { 0xB8, 0xDE, 0xFA, 0xAD, 0xDE,// mov eax, DEADFADEh 0xFF, 0xD0 // call eax }; /** * Offset of codeCall to modify at runtime * to contain the pointer to the function */ const unsigned int codeCallReplace = 1; /** * Adds to ESP, freeing up stack space */ const unsigned char codeFreeStack[] = { 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF// add esp REPLACEME }; /** * Offset of codeFreeStack to modify at runtime * to contain how much data to free */ const unsigned int codeFreeStackReplace = 2; /** * Epilogue of a simple function */ const unsigned char codeEpilogue[] = { 0x89, 0xEC, // mov esp, ebp 0x5D, // pop ebp 0xC3 // ret }; const unsigned char codeEpilogueN[] = { 0x89, 0xEC, // mov esp, ebp 0x5D, // pop ebp 0xC2, 0xCD, 0xAB // retn 0xABCD }; const int codeEpilogueNReplace = 4; const unsigned char codeBreakpoint[] = { 0xCC // int 3 }; } /** * Our actual maker of the trampolines!!@$ * I've no idea why I made this a class and not a namespace * Oh well! */ class TrampolineMaker { private: unsigned char *m_buffer; // the actual buffer containing the code int m_size; // size of the buffer int m_mystack; // stack for the trampoline itself int m_calledstack; // stack for the target function int m_paramstart; int m_thiscall; int m_maxsize; /** * Adds data to the buffer * data must be pre-formatted before hand! */ void Append(const unsigned char *src, size_t size) { int orig=m_size; m_size+=size; if (m_buffer==NULL) { m_maxsize=512; m_buffer=(unsigned char *)malloc(m_maxsize); } else if (m_size > m_maxsize) { m_maxsize = m_size + 512; m_buffer=(unsigned char *)realloc(m_buffer,m_maxsize); } unsigned char *dat=m_buffer+orig; // point dat to the end of the prewritten while (origPrologue(); m_thiscall=1; }; /** * Epilogue for a function pops registers but does not free any more of the stack! */ void Epilogue() { Append(&::Trampolines::Bytecode::codeEpilogue[0],sizeof(::Trampolines::Bytecode::codeEpilogue)); }; /** * Epilogue that also frees it's estimated stack usage. Useful for stdcall/thiscall/fastcall. */ void EpilogueAndFree() { this->Epilogue(m_mystack); }; /** * Epilogue. Pops registers, and frees given amount of data from the stack. * * @param howmuch How many bytes to free from the stack. */ void Epilogue(int howmuch) { unsigned char code[sizeof(::Trampolines::Bytecode::codeEpilogueN)]; memcpy(&code[0],&::Trampolines::Bytecode::codeEpilogueN[0],sizeof(::Trampolines::Bytecode::codeEpilogueN)); unsigned char *c=&code[0]; union { int i; unsigned char b[4]; } bi; bi.i=howmuch; c+=::Trampolines::Bytecode::codeEpilogueNReplace; *c++=bi.b[0]; *c++=bi.b[1]; Append(&code[0],sizeof(::Trampolines::Bytecode::codeEpilogueN)); }; /** * Aligns stack on 16 byte boundary for functions that use aligned SSE instructions. * This also allocates extra stack space to allow the specified number of slots to be used * for function paramaters that will be pushed onto the stack. */ void AlignStack16(int slots) { const size_t stackNeeded = slots * sizeof(void *); const size_t stackReserve = ke::Align(stackNeeded, 16); const size_t stackExtra = stackReserve - stackNeeded; // Stack space should fit in a byte assert(stackExtra <= 0xFF); const size_t codeAlignStackSize = sizeof(::Trampolines::Bytecode::codeAlignStack16); const size_t codeAllocStackSize = sizeof(::Trampolines::Bytecode::codeAllocStack); unsigned char code[codeAlignStackSize + codeAllocStackSize]; memcpy(&code[0], &::Trampolines::Bytecode::codeAlignStack16[0], codeAlignStackSize); if (stackExtra > 0) { unsigned char *c = &code[codeAlignStackSize]; memcpy(c, &::Trampolines::Bytecode::codeAllocStack[0], codeAllocStackSize); c += ::Trampolines::Bytecode::codeAllocStackReplace; *c = (unsigned char)stackExtra; Append(&code[0], codeAlignStackSize + codeAllocStackSize); } else { Append(&code[0], codeAlignStackSize); } } /** * Pushes the "this" pointer onto the callee stack. Pushes ECX for MSVC, and param0 on GCC. */ void PushThis() { if (!m_thiscall) { return; } unsigned char code[sizeof(::Trampolines::Bytecode::codePushThis)]; memcpy(&code[0],&::Trampolines::Bytecode::codePushThis[0],sizeof(::Trampolines::Bytecode::codePushThis)); #if defined(__linux__) || defined(__APPLE__) unsigned char *c=&code[0]; union { int i; unsigned char b[4]; } bi; bi.i=m_paramstart+8; c+=::Trampolines::Bytecode::codePushThisReplace; *c++=bi.b[0]; #endif Append(&code[0],sizeof(::Trampolines::Bytecode::codePushThis)); #if defined(__linux__) || defined(__APPLE__) m_mystack+=4; #endif m_calledstack+=4; }; /** * Frees what is estimated as the stack usage of the trampoline. */ void FreeMyStack(void) { this->FreeStack(m_mystack); }; /** * Frees the estimated stack usage of the callee. */ void FreeTargetStack(void) { this->FreeStack(m_calledstack); }; /** * Frees the estimated stack usage of the callee and the trampoline. */ void FreeBothStacks(void) { this->FreeStack(m_calledstack + m_mystack); }; /** * Frees a given amount of bytes from the stack. * * @param howmuch How many bytes to free. */ void FreeStack(int howmuch) { unsigned char code[sizeof(::Trampolines::Bytecode::codeFreeStack)]; memcpy(&code[0],&::Trampolines::Bytecode::codeFreeStack[0],sizeof(::Trampolines::Bytecode::codeFreeStack)); unsigned char *c=&code[0]; union { int i; unsigned char b[4]; } bi; bi.i=howmuch; c+=::Trampolines::Bytecode::codeFreeStackReplace; *c++=bi.b[0]; *c++=bi.b[1]; *c++=bi.b[2]; *c++=bi.b[3]; Append(&code[0],sizeof(::Trampolines::Bytecode::codeFreeStack)); }; /** * Pushes a raw number onto the callee stack. * * @param Number The number to push onto the callee stack. */ void PushNum(int Number) { unsigned char code[sizeof(::Trampolines::Bytecode::codePushID)]; memcpy(&code[0],&::Trampolines::Bytecode::codePushID[0],sizeof(::Trampolines::Bytecode::codePushID)); unsigned char *c=&code[0]; union { int i; unsigned char b[4]; } bi; bi.i=Number; c+=::Trampolines::Bytecode::codePushIDReplace; *c++=bi.b[0]; *c++=bi.b[1]; *c++=bi.b[2]; *c++=bi.b[3]; Append(&code[0],sizeof(::Trampolines::Bytecode::codePushID)); m_calledstack+=4; // increase auto detected stack size }; /** * Takes a parameter passed on the trampoline's stack and inserts it into the callee's stack. * * @param which The parameter number to push. 1-based. "thiscall" trampolines automatically compensate for the off-number on GCC. */ void PushParam(int which) { #if defined(__linux__) || defined(__APPLE__) if (m_thiscall) { which++; } #endif which=which*4; which+=m_paramstart+4; unsigned char value=which; unsigned char code[sizeof(::Trampolines::Bytecode::codePushParam)]; memcpy(&code[0],&::Trampolines::Bytecode::codePushParam[0],sizeof(::Trampolines::Bytecode::codePushParam)); unsigned char *c=&code[0]; c+=::Trampolines::Bytecode::codePushParamReplace; *c=value; Append(&code[0],sizeof(::Trampolines::Bytecode::codePushParam)); m_calledstack+=4; // increase auto detected stack size m_mystack+=4; }; /** * Insert a function to call into the trampoline. * * @param ptr The function to call, cast to void*. */ void Call(void *ptr) { unsigned char code[sizeof(::Trampolines::Bytecode::codeCall)]; memcpy(&code[0],&::Trampolines::Bytecode::codeCall[0],sizeof(::Trampolines::Bytecode::codeCall)); unsigned char *c=&code[0]; union { void *p; unsigned char b[4]; } bp; bp.p=ptr; c+=::Trampolines::Bytecode::codeCallReplace; *c++=bp.b[0]; *c++=bp.b[1]; *c++=bp.b[2]; *c++=bp.b[3]; Append(&code[0],sizeof(::Trampolines::Bytecode::codeCall)); }; /** * Finalizes the trampoline. Do not try to modify it after this. * * @param size A pointer to retrieve the size of the trampoline. Ignored if set to NULL. * @return The trampoline pointer, cast to void*. */ void *Finish(int *size) { //void *ret=(void *)m_buffer; if (size) { *size=m_size; } // Reallocate with proper flags #if defined(_WIN32) void *ret=VirtualAlloc(NULL, m_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); #elif defined(__GNUC__) # if defined(__APPLE__) void *ret = valloc(m_size); # else void *ret=memalign(sysconf(_SC_PAGESIZE), m_size); # endif mprotect(ret,m_size,PROT_READ|PROT_WRITE|PROT_EXEC); #endif memcpy(ret, m_buffer, m_size); m_size=0; free(m_buffer); m_buffer=NULL; // so we don't accidentally rewrite! m_mystack=0; m_calledstack=0; m_maxsize=512; return ret; }; }; }; /** * Utility to make a generic trampoline. */ inline void *CreateGenericTrampoline(bool thiscall, bool voidcall, bool retbuf, int paramcount, void *extraptr, void *callee) { Trampolines::TrampolineMaker tramp; if (thiscall) { tramp.ThisPrologue(); tramp.AlignStack16(paramcount + 2); // Param count + this ptr + extra ptr } else { tramp.Prologue(); tramp.AlignStack16(paramcount + 1); // Param count + extra ptr } while (paramcount) { tramp.PushParam(paramcount--); } if (thiscall) { tramp.PushThis(); } tramp.PushNum(reinterpret_cast(extraptr)); tramp.Call(callee); tramp.FreeTargetStack(); #if defined(_WIN32) tramp.EpilogueAndFree(); #elif defined(__linux__) || defined(__APPLE__) if (retbuf) { tramp.Epilogue(4); } else { tramp.Epilogue(); } #endif return tramp.Finish(NULL); }; #endif // TRAMPOLINEMANAGER_H