aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/thooklib/ttrampolinepool.h
blob: 5b44c517364eb9358e0efe1f972c2f08642e7207 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
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/>.
*/
#pragma once

#include "asmjit_sane.h"
#include <logging.h>
#include <map>
#include <vector>
#include <windows_sane.h>

#ifdef _MSC_VER
#pragma warning(disable : 4714)
#include <boost/config/compiler/visualc.hpp>
#else
#include <boost/config/compiler/gcc.hpp>
#endif
#include <boost/predef.h>
#include <boost/thread.hpp>
#include <mutex>

// #include <boost/thread/mutex.hpp>

namespace HookLib
{

///
/// trampolines are runtime-generated mini-functions that are used to call the
/// original code of a function
///
class TrampolinePool
{
public:
  /// Call initialize before you use the TrampolinePool
  static void initialize();

  static TrampolinePool& instance()
  {
    // This is a very very sensitive place so we want to keep this function as simple as
    // possible and having it inlined probably cann't hurt
    return *s_Instance;
  }

  void setBlock(bool block);

  ///
  /// store a stub without moving code from the original function. This is used in cases
  /// where the hook can be placed without overwriting logic (i.e. hot-patchable
  /// functions and when chaining hooks)
  /// \param reroute the stub function to call before the regular function (on x86 this
  /// needs to be cdecl calling convention!)
  /// \param original the original function
  /// \param returnAddress address under which the original functionality can be
  /// reached.
  ///                      for the first hook this should be (original + 2), otherwise
  ///                      the address of the next hook in the chain
  /// \return address of the created trampoline function
  ///
  LPVOID storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress);

  ///
  /// store a stub, moving part of the original function to the trampoline
  /// \param reroute the stub function to call before the regular function (on x86 this
  /// needs to be cdecl calling convention!)
  /// \param original the original function
  /// \param preambleSize number of bytes from the original function to backup. Needs to
  /// correspond to complete instructions
  /// \param rerouteOffset offset in bytes from the created trampoline to the preamble
  /// that leads back to the original code
  /// \return address of the created trampoline function
  ///
  LPVOID storeStub(LPVOID reroute, LPVOID original, size_t preambleSize,
                   size_t* rerouteOffset);

  ///
  /// store a trampoline for hot-patchable functions, where the original function
  /// is unharmed.
  /// \param reroute the reroute function
  /// \param original original function
  /// \param returnAddress address under which the original functionality can be
  /// reached.
  /// \return address of the trampoline function
  ///
  LPVOID storeTrampoline(LPVOID reroute, LPVOID original, LPVOID returnAddress);

  ///
  /// store a trampoline, copying a part of the original function to the trampoline.
  /// This is used for the case where the hooking mechanism needs to overwrite part of
  /// the function
  /// \param reroute the reroute function
  /// \param original original function
  /// \param preambleSize number of bytes from the original function to backup. Needs to
  /// correspond to complete instructions
  /// \param rerouteOffset offset in bytes from the created trampoline to the preamble
  /// that leads us back to the original code
  /// \return address of the trampoline function
  ///
  LPVOID storeTrampoline(LPVOID reroute, LPVOID original, size_t preambleSize,
                         size_t* rerouteOffset);

  ///
  /// \param addressNear used to find a trampoline buffer near the jump instruction
  /// \return retrieve address of current trampoline buffer
  ///
  LPVOID currentBufferAddress(LPVOID addressNear);

  ///
  /// \brief forces the barrier(s) for the current thread to be released
  ///
  void forceUnlockBarrier();

private:
  struct BufferList
  {
    size_t offset;
    std::vector<LPVOID> buffers;
  };

  typedef std::map<LPVOID, BufferList> BufferMap;
  static const intptr_t ADDRESS_MASK =
      0xFFFFFFFFFF000000LL;  // mask to "round" addresses to consolidate near
                             // trampolines

private:
  TrampolinePool();

  TrampolinePool& operator=(const TrampolinePool& reference);  // not implemented

  /**
   * @brief allocates a buffer with read, write and execute rights near the
   * specified adress. The purpose is that we want to be able to jump from
   * adressNear to generated code with a 5-byte jump, even on x64 systems.
   * @param addressNear the reference adress
   * @note the resulting buffer is stored in the m_Buffers map
   */
  BufferMap::iterator allocateBuffer(LPVOID addressNear);

  void addBarrier(LPVOID rerouteAddr, LPVOID original, asmjit::X86Assembler& assembler);

#if BOOST_ARCH_X86_64
  void copyCode(asmjit::X86Assembler& assembler, LPVOID source, size_t numBytes);
#endif  // BOOST_ARCH_X86_64

  BufferList& getBufferList(LPVOID address);

  LPVOID roundAddress(LPVOID address) const;

public:
  static LPVOID __stdcall barrier(LPVOID function);
  static LPVOID __stdcall release(LPVOID function);

  LPVOID barrierInt(LPVOID function);
  LPVOID releaseInt(LPVOID function);

  void addCallToStub(asmjit::X86Assembler& assembler, LPVOID original, LPVOID reroute);

private:
  ///
  /// \brief add a jump to an address outside the custom generated asm code (without
  /// modifying registers)
  /// \param assembler the assembler generator to write to
  /// \param destination destination address
  /// \note this currently generates a lot code on x64, may be overly complicated
  ///
  void addAbsoluteJump(asmjit::X86Assembler& assembler, uint64_t destination);

  DWORD determinePageSize();

private:
#if BOOST_ARCH_X86_64
  static const int SIZE_OF_JUMP = 13;
#elif BOOST_ARCH_X86_32
  static const int SIZE_OF_JUMP = 5;
#endif

  // the hook lib tries to allocate buffer close to the hooked functions,
  // for 32-bits application, this often falls on 0x40000000, which is the
  // address at which DLLs can be loaded on Windows
  //
  // some old-style DRM (e.g. Dragon Age 2) check that address and prevent
  // the game from running if it's not 0x40000000, so we give them a bit of
  // spaces before allocating our buffers
  //
  static constexpr uintptr_t MIN_ALLOC_ADDR = 0x40000000 + 0x200000;

  static TrampolinePool* s_Instance;

  bool m_FullBlock{false};

  BufferMap m_Buffers;

  typedef std::map<void*, void*> TThreadMap;
  boost::thread_specific_ptr<TThreadMap> m_ThreadGuards;

  LPVOID m_BarrierAddr;
  LPVOID m_ReleaseAddr;

  DWORD m_BufferSize = {1024};
  size_t m_SearchRange;
  uint64_t m_AddressMask;

  int m_MaxTrampolineSize;
};

}  // namespace HookLib