aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/thooklib/ttrampolinepool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/src/thooklib/ttrampolinepool.cpp')
-rw-r--r--libs/usvfs/src/thooklib/ttrampolinepool.cpp601
1 files changed, 0 insertions, 601 deletions
diff --git a/libs/usvfs/src/thooklib/ttrampolinepool.cpp b/libs/usvfs/src/thooklib/ttrampolinepool.cpp
deleted file mode 100644
index 3b6a51f..0000000
--- a/libs/usvfs/src/thooklib/ttrampolinepool.cpp
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
-Userspace Virtual Filesystem
-
-Copyright (C) 2015 Sebastian Herbord. All rights reserved.
-
-This file is part of usvfs.
-
-usvfs 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 3 of the License, or
-(at your option) any later version.
-
-usvfs 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 usvfs. If not, see <http://www.gnu.org/licenses/>.
-*/
-#include "ttrampolinepool.h"
-#include <addrtools.h>
-#include <shmlogger.h>
-// #include <boost/thread/lock_guard.hpp>
-#include "udis86wrapper.h"
-
-using namespace asmjit;
-#if BOOST_ARCH_X86_64
-using namespace x86;
-#elif BOOST_ARCH_X86_32
-using namespace asmjit::x86;
-#endif
-
-using namespace usvfs::shared;
-
-namespace HookLib
-{
-
-TrampolinePool* TrampolinePool::s_Instance = nullptr;
-
-TrampolinePool::TrampolinePool() : m_MaxTrampolineSize(sizeof(LPVOID))
-{
- m_BarrierAddr = &TrampolinePool::barrier;
- m_ReleaseAddr = &TrampolinePool::release;
-
- SYSTEM_INFO sysInfo;
- ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO));
- GetSystemInfo(&sysInfo);
- m_BufferSize = sysInfo.dwPageSize;
-
- // if search range = ffffff then addressmask = ffffffffff000000
- // => all jumps between xxxxxxxxxx000000 and xxxxxxxxxxffffff will use the same buffer
- // for trampolines which is guaranteed to be in that range
- // TODO it should be valid to use 2 ^ 32 as the search range to increase our chances
- // of finding a memory block we can reserve but then there is a problem with
- // converting negative jump distances to 32 bit I didn't understand. Everything
- // up to 2 ^ 31 seems to be fine though
- m_SearchRange = static_cast<size_t>(pow(2, 30)) - 1;
- m_AddressMask = std::numeric_limits<uint64_t>::max() - m_SearchRange;
-}
-
-// static
-void TrampolinePool::initialize()
-{
- if (!s_Instance)
- s_Instance = new TrampolinePool();
-}
-
-void TrampolinePool::setBlock(bool block)
-{
- m_FullBlock = block;
- if (m_ThreadGuards.get() == nullptr) {
- m_ThreadGuards.reset(new TThreadMap());
- }
-}
-
-#if BOOST_ARCH_X86_64
-// push all registers (except rax) and flags to the stack
-static void pushAll(X86Assembler& assembler)
-{
- assembler.pushf();
- assembler.push(rcx);
- assembler.push(rdx);
- assembler.push(rbx);
- assembler.push(rbp);
- assembler.push(rsi);
- assembler.push(rdi);
- assembler.push(r8);
- assembler.push(r9);
- assembler.push(r10);
- assembler.push(r11);
- assembler.push(r12);
- assembler.push(r13);
- assembler.push(r14);
- assembler.push(r15);
-}
-
-// pop all registers (except rax) and flags from stack
-static void popAll(X86Assembler& assembler)
-{
- assembler.pop(r15);
- assembler.pop(r14);
- assembler.pop(r13);
- assembler.pop(r12);
- assembler.pop(r11);
- assembler.pop(r10);
- assembler.pop(r9);
- assembler.pop(r8);
- assembler.pop(rdi);
- assembler.pop(rsi);
- assembler.pop(rbp);
- assembler.pop(rbx);
- assembler.pop(rdx);
- assembler.pop(rcx);
- assembler.popf();
-}
-#endif // BOOST_ARCH_X86_64
-
-void TrampolinePool::addBarrier(LPVOID rerouteAddr, LPVOID original,
- X86Assembler& assembler)
-{
- Label skipLabel = assembler.newLabel();
-
-#if BOOST_ARCH_X86_64
- pushAll(assembler);
- assembler.mov(rcx,
- imm(reinterpret_cast<int64_t>(
- original))); // set call parameter for call to barrier function
- assembler.mov(rax, imm((intptr_t)(void*)barrier));
- assembler.sub(rsp, 32);
- assembler.call(rax);
- assembler.add(rsp, 32);
- popAll(assembler);
- // test barrier
- assembler.cmp(rax, 0); // test if the barrier is locked
- assembler.jz(skipLabel); // skip if barrier was locked
-
- // call replacement function
- // for this call no registers are saved. the called function is a compiled function so
- // it should correctly save non-volatile registers, and the caller can't expect the
- // volatile ones to remain valid
- assembler.pop(r10);
- assembler.mov(dword_ptr(rax), r10); // store that return address to the variable
- // supplied by the barrier function
- assembler.mov(rax, imm((intptr_t)(LPVOID)rerouteAddr));
- assembler.call(rax);
- assembler.push(rax); // save away result
-
- // open the barrier again
- pushAll(assembler);
- assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original)));
- assembler.mov(rax, imm((intptr_t)(void*)release));
- assembler.sub(rsp, 32);
- assembler.call(rax);
- assembler.add(rsp, 32);
- popAll(assembler);
- assembler.pop(r10); // get the result from the replacement function to a register
- assembler.push(rax); // push the original return address back on the stack
- assembler.mov(rax, r10); // move result of actual call to rax
- assembler.ret(); // return, using the original return address
-#else // BOOST_ARCH_X86_64
- assembler.push(imm(void_ptr_cast<int32_t>(
- original))); // push original function, as parameter to barrier
- assembler.mov(ecx, (Ptr) static_cast<void*>(TrampolinePool::barrier));
- assembler.call(ecx); // call barrier function
- assembler.cmp(eax, 0);
- assembler.jz(skipLabel); // if barrier is locked, jump to end of function
-
- // case a: we got through the barrier
- assembler.pop(ecx); // pop the return address into ecx
- assembler.mov(dword_ptr(eax), ecx); // store that return address in the variable
- // supplied by the barrier function
-
- assembler.mov(eax, (Ptr) static_cast<void*>(rerouteAddr));
- assembler.call(eax); // call replacement function (pointer was stored in front of the
- // trampoline) (this function gets the parameters that were on
- // the stack already and cleans
- // them up itself (stdcall convention))
- assembler.push(eax); // save away result
- assembler.push(imm(void_ptr_cast<int32_t>(original))); // open the barrier again
- assembler.mov(eax, (Ptr) static_cast<void*>(TrampolinePool::release));
- assembler.call(eax);
- assembler.pop(ecx); // pop the result from the actual call to ecx
- assembler.push(eax); // push the original return address (returned by
- // TTrampolinePool::release) back on the stack
- assembler.mov(eax, ecx); // move result of actual call to eax
- assembler.ret(); // return, using the original return address
-#endif // BOOST_ARCH_X86_64
-
- assembler.bind(skipLabel);
-}
-
-LPVOID TrampolinePool::roundAddress(LPVOID address) const
-{
- return reinterpret_cast<LPVOID>(reinterpret_cast<intptr_t>(address) & m_AddressMask);
-}
-
-TrampolinePool::BufferList& TrampolinePool::getBufferList(LPVOID address)
-{
- LPVOID rounded = roundAddress(address);
- auto iter = m_Buffers.find(rounded);
- if (iter == m_Buffers.end()) {
- BufferList newBufList = {0, std::vector<LPVOID>()};
- m_Buffers[rounded] = newBufList;
- iter = allocateBuffer(address);
- }
- return iter->second;
-}
-
-LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress)
-{
- BufferList& bufferList = getBufferList(original);
- // first test to increase likelyhood we don't have to reallocate later
- if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
- allocateBuffer(original);
- }
-
- LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
-
- // ??? write address of reroute to trampoline and move past the address
- *reinterpret_cast<LPVOID*>(spot) = reroute;
- // coverity[suspicious_sizeof]
- spot = AddrAdd(spot, sizeof(LPVOID));
- bufferList.offset += sizeof(LPVOID);
-
- JitRuntime runtime;
-#if BOOST_ARCH_X86_64
- X86Assembler assembler(&runtime);
-#else
- X86Assembler assembler(&runtime);
-#endif
- addCallToStub(assembler, original, reroute);
- addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(returnAddress));
-
- size_t codeSize = assembler.getCodeSize();
-
- m_MaxTrampolineSize =
- std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
-
- // final test to see if we can store the trampoline in the buffer
- if ((bufferList.offset + codeSize) > m_BufferSize) {
- // can't place function in buffer, allocate another and try again
- allocateBuffer(original);
- // we could relocate the code and the data but this is simpler
- return storeStub(reroute, original, returnAddress);
- }
-
- // adjust relative jumps for move to buffer
- codeSize = assembler.relocCode(spot);
-
- uint8_t* code = assembler.getBuffer();
- memcpy(spot, code, codeSize);
-
- bufferList.offset += codeSize;
-
- return spot;
-}
-
-LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original,
- LPVOID returnAddress)
-{
- BufferList& bufferList = getBufferList(original);
- // first test to increase likelyhood we don't have to reallocate later
- if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
- allocateBuffer(original);
- }
-
- LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
-
- *reinterpret_cast<LPVOID*>(spot) = reroute;
- // coverity[suspicious_sizeof]
- spot = AddrAdd(spot, sizeof(LPVOID));
- bufferList.offset += sizeof(LPVOID);
-
- JitRuntime runtime;
- X86Assembler assembler(&runtime);
- addBarrier(reroute, original, assembler);
-#if BOOST_ARCH_X86_64
- assembler.mov(rax, imm((intptr_t)(void*)(returnAddress)));
- assembler.jmp(rax);
-#else
- assembler.mov(eax, imm((intptr_t)(void*)(returnAddress)));
- assembler.jmp(eax);
-#endif
- size_t codeSize = assembler.getCodeSize();
-
- m_MaxTrampolineSize =
- std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
-
- // final test to see if we can store the trampoline in the buffer
- if ((bufferList.offset + codeSize) > m_BufferSize) {
- // can't place function in buffer, allocate another and try again
- allocateBuffer(original);
- // we could relocate the code and the data but this is simpler
- return storeTrampoline(reroute, original, returnAddress);
- }
-
- // adjust relative jumps for move to buffer
- codeSize = assembler.relocCode(spot);
-
- // copy code to buffer
- uint8_t* code = assembler.getBuffer();
- memcpy(spot, code, codeSize);
-
- bufferList.offset += codeSize;
- return spot;
-}
-
-#if BOOST_ARCH_X86_64
-void TrampolinePool::copyCode(X86Assembler& assembler, LPVOID source, size_t numBytes)
-{
- static UDis86Wrapper disasm;
-
- disasm.setInputBuffer(static_cast<const uint8_t*>(source), numBytes);
-
- size_t offset = 0;
-
- while (ud_disassemble(disasm) != 0) {
- // rewrite relative jumps, blind copy everything else
-
- offset += ud_insn_len(disasm);
-
- // WARNING: doesn't support conditional jumps
- if ((ud_insn_mnemonic(disasm) == UD_Ijmp) &&
- (ud_insn_opr(disasm, 0)->type == UD_OP_JIMM)) {
- uintptr_t dest = disasm.jumpTarget();
- assembler.mov(rax, imm(static_cast<uint64_t>(dest)));
- assembler.jmp(rax);
- } else {
- assembler.embed(ud_insn_ptr(&disasm.obj()), ud_insn_len(&disasm.obj()));
- // assembler.data();
- }
- }
-}
-#endif
-
-void TrampolinePool::addCallToStub(X86Assembler& assembler, LPVOID original,
- LPVOID reroute)
-{
-#if BOOST_ARCH_X86_64
- pushAll(assembler);
- assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original)));
- assembler.mov(rax, imm((intptr_t)(LPVOID)reroute));
- assembler.sub(rsp, 32);
- assembler.call(rax);
- assembler.add(rsp, 32);
- popAll(assembler);
-#else // BOOST_ARCH_X86_64
- assembler.push(reinterpret_cast<int64_t>(original));
- assembler.mov(ecx, imm((intptr_t)(LPVOID)reroute));
- assembler.call(ecx);
- assembler.pop(ecx); // remove argument from stack
-#endif // BOOST_ARCH_X86_64
-}
-
-void TrampolinePool::addAbsoluteJump(X86Assembler& assembler, uint64_t destination)
-{
-#if BOOST_ARCH_X86_64
- assembler.push(rax);
- assembler.push(rax);
- assembler.mov(rax, imm(destination));
- assembler.mov(ptr(rsp, 8), rax);
- assembler.pop(rax);
- assembler.ret();
-#else // BOOST_ARCH_X86_64
- assembler.push(imm(destination));
- assembler.ret();
-#endif // BOOST_ARCH_X86_64
-}
-
-LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, size_t preambleSize,
- size_t* rerouteOffset)
-{
- BufferList& bufferList = getBufferList(original);
- // first test to increase likelyhood we don't have to reallocate later
- if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
- allocateBuffer(original);
- }
-
- LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
-
- *reinterpret_cast<LPVOID*>(spot) = reroute;
- // coverity[suspicious_sizeof]
- spot = AddrAdd(spot, sizeof(LPVOID));
- bufferList.offset += sizeof(LPVOID);
-
- JitRuntime runtime;
- X86Assembler assembler(&runtime);
- addCallToStub(assembler, original, reroute);
-#if BOOST_ARCH_X86_64
- // insert backup code
- *rerouteOffset = assembler.getCodeSize();
- copyCode(assembler, original, preambleSize);
-#else // BOOST_ARCH_X86_64
- assembler.embed(original, preambleSize);
-#endif // BOOST_ARCH_X86_64
- addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize);
-
- // adjust relative jumps for move to buffer
- size_t codeSize = assembler.getCodeSize();
-
- m_MaxTrampolineSize =
- std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
-
- // final test to see if we can store the trampoline in the buffer
- if ((bufferList.offset + codeSize) > m_BufferSize) {
- // can't place function in buffer, allocate another and try again
- allocateBuffer(original);
- // we could relocate the code and the data but this is simpler
- return storeStub(reroute, original, preambleSize, rerouteOffset);
- }
-
- // copy code to buffer
- codeSize = assembler.relocCode(spot);
-
- bufferList.offset += preambleSize + codeSize;
- return spot;
-}
-
-LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original,
- size_t preambleSize, size_t* rerouteOffset)
-{
- BufferList& bufferList = getBufferList(original);
- // first test to increase likelyhood we don't have to reallocate later
- if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
- allocateBuffer(original);
- }
-
- LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
-
- *reinterpret_cast<LPVOID*>(spot) = reroute;
- // coverity[suspicious_sizeof]
- spot = AddrAdd(spot, sizeof(LPVOID));
- bufferList.offset += sizeof(LPVOID);
-
- JitRuntime runtime;
- X86Assembler assembler(&runtime);
- addBarrier(reroute, original, assembler);
- // insert backup code
- *rerouteOffset = assembler.getCodeSize();
- assembler.embed(original, static_cast<uint32_t>(preambleSize));
- addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize);
-
- // adjust relative jumps for move to buffer
- size_t codeSize = assembler.getCodeSize();
-
- m_MaxTrampolineSize =
- std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
-
- // TODO this does not take into account that the code size may technically change
- // after relocation in which case the following test may determine the code fits into
- // the buffer when it really doesnt't. asmjit doesn't seem to provide a way to adjust
- // jumps without actually moving the code though
-
- // final test to see if we can store the trampoline in the buffer
- if ((bufferList.offset + codeSize) > m_BufferSize) {
- // can't place function in buffer, allocate another and try again
- allocateBuffer(original);
- // we could relocate the code and the data but this is simpler
- return storeTrampoline(reroute, original, preambleSize, rerouteOffset);
- }
-
- // copy code to buffer
- codeSize = static_cast<size_t>(assembler.relocCode(spot));
-
- bufferList.offset += preambleSize + codeSize;
-
- return spot;
-}
-
-LPVOID TrampolinePool::currentBufferAddress(LPVOID addressNear)
-{
- LPVOID rounded = roundAddress(addressNear);
- auto lookupAddress = m_Buffers.find(rounded);
-
- if (lookupAddress == m_Buffers.end()) {
- lookupAddress = m_Buffers.insert(std::make_pair(rounded, BufferList())).first;
- }
- if (lookupAddress->second.buffers.size() == 0) {
- allocateBuffer(addressNear);
- }
-
- LPVOID res = *(lookupAddress->second.buffers.rbegin());
- return res;
-}
-
-void TrampolinePool::forceUnlockBarrier()
-{
- if (m_ThreadGuards.get() != nullptr) {
- for (auto funcId : *m_ThreadGuards) {
- (*m_ThreadGuards)[funcId.first] = nullptr;
- }
- } // else no barriers to unlock
-}
-
-TrampolinePool::BufferMap::iterator TrampolinePool::allocateBuffer(LPVOID addressNear)
-{
- // allocate a buffer that we can write to and that is executable
- SYSTEM_INFO sysInfo;
- ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO));
- GetSystemInfo(&sysInfo);
-
- LPVOID rounded = roundAddress(addressNear);
- auto iter = m_Buffers.find(rounded);
- uintptr_t lowerEnd = reinterpret_cast<uintptr_t>(rounded);
- if (iter->second.buffers.size() > 0) {
- // start searching were we last found a buffer
- lowerEnd = reinterpret_cast<uintptr_t>(*iter->second.buffers.rbegin()) +
- sysInfo.dwPageSize;
- }
-
- uintptr_t start =
- std::max(std::max(lowerEnd, MIN_ALLOC_ADDR),
- reinterpret_cast<uintptr_t>(sysInfo.lpMinimumApplicationAddress));
- uintptr_t upperEnd = reinterpret_cast<uintptr_t>(rounded) + m_SearchRange;
- uintptr_t end = std::min(
- upperEnd, reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress));
-
- LPVOID buffer = nullptr;
- for (uintptr_t cur = start; cur < end; cur += sysInfo.dwPageSize) {
- buffer = VirtualAlloc(reinterpret_cast<LPVOID>(cur), m_BufferSize,
- MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
- if (buffer != nullptr) {
- break;
- }
- }
- if (buffer == nullptr) {
- throw std::runtime_error("failed to allocate buffer in range");
- }
-
- // the caller must have looked up the bufferlist in order to determine that a
- // buffer has to be allocated
- assert(iter != m_Buffers.end());
-
- iter->second.offset = 0;
- iter->second.buffers.push_back(buffer);
- spdlog::get("usvfs")->debug(
- "allocated trampoline buffer for jumps between {0:p} and {1:x} at {2:p}"
- "(size {3})",
- rounded, (reinterpret_cast<uintptr_t>(rounded) + m_SearchRange), buffer,
- m_BufferSize);
- return iter;
-}
-
-LPVOID TrampolinePool::barrier(LPVOID function)
-{
- DWORD err = GetLastError();
- LPVOID res = instance().barrierInt(function);
- SetLastError(err);
- return res;
-}
-
-LPVOID TrampolinePool::release(LPVOID function)
-{
- DWORD err = GetLastError();
- LPVOID res = instance().releaseInt(function);
- SetLastError(err);
- return res;
-}
-
-LPVOID TrampolinePool::barrierInt(LPVOID func)
-{
- if (m_FullBlock) {
- return nullptr;
- }
-
- if (m_ThreadGuards.get() == nullptr) {
- m_ThreadGuards.reset(new TThreadMap());
- }
-
- auto iter = m_ThreadGuards->find(func);
- if ((iter == m_ThreadGuards->end()) || (iter->second == nullptr)) {
- (*m_ThreadGuards)[func] = reinterpret_cast<LPVOID>(1);
- return &(*m_ThreadGuards)[func];
- } else {
- return nullptr;
- }
-}
-
-LPVOID TrampolinePool::releaseInt(LPVOID func)
-{
- DWORD lastError = GetLastError();
- if (m_ThreadGuards.get() == nullptr) {
- m_ThreadGuards.reset(new TThreadMap());
- }
-
- auto iter = m_ThreadGuards->find(func);
- if (iter == m_ThreadGuards->end()) {
- spdlog::get("hooks")->error("failed to release barrier for func {}", func);
- ::SetLastError(lastError);
- return nullptr;
- }
-
- LPVOID res = (*m_ThreadGuards)[func];
- (*m_ThreadGuards)[func] = nullptr;
-
- ::SetLastError(lastError);
- return res;
-}
-
-} // namespace HookLib