CommonLibSSE (powerof3)
Trampoline.h
Go to the documentation of this file.
1 #pragma once
2 
3 #if defined(SKSE_SUPPORT_XBYAK)
4 namespace Xbyak
5 {
6  class CodeGenerator;
7 }
8 #endif
9 
10 namespace SKSE
11 {
12  namespace detail
13  {
14  [[nodiscard]] constexpr std::size_t roundup(std::size_t a_number, std::size_t a_multiple) noexcept
15  {
16  if (a_multiple == 0) {
17  return 0;
18  }
19 
20  const auto remainder = a_number % a_multiple;
21  return remainder == 0 ?
22  a_number :
23  a_number + a_multiple - remainder;
24  }
25 
26  [[nodiscard]] constexpr std::size_t rounddown(std::size_t a_number, std::size_t a_multiple) noexcept
27  {
28  if (a_multiple == 0) {
29  return 0;
30  }
31 
32  const auto remainder = a_number % a_multiple;
33  return remainder == 0 ?
34  a_number :
35  a_number - remainder;
36  }
37  }
38 
39  class Trampoline
40  {
41  public:
42  using deleter_type = std::function<void(void* a_mem, std::size_t a_size)>;
43 
44  Trampoline() = default;
45  Trampoline(const Trampoline&) = delete;
46 
47  Trampoline(Trampoline&& a_rhs) { move_from(std::move(a_rhs)); }
48 
49  explicit Trampoline(std::string_view a_name) :
50  _name(a_name)
51  {}
52 
53  ~Trampoline() { release(); }
54 
55  Trampoline& operator=(const Trampoline&) = delete;
56 
58  {
59  if (this != std::addressof(a_rhs)) {
60  move_from(std::move(a_rhs));
61  }
62  return *this;
63  }
64 
65  void create(std::size_t a_size) { return create(a_size, nullptr); }
66 
67  void create(std::size_t a_size, void* a_module)
68  {
69  if (a_size == 0) {
70  stl::report_and_fail("cannot create a trampoline with a zero size"sv);
71  }
72 
73  if (!a_module) {
74  const auto text = REL::Module::get().segment(REL::Segment::textx);
75  a_module = text.pointer<std::byte>() + text.size();
76  }
77 
78  auto mem = do_create(a_size, reinterpret_cast<std::uintptr_t>(a_module));
79  if (!mem) {
80  stl::report_and_fail("failed to create trampoline"sv);
81  }
82 
83  set_trampoline(mem, a_size,
84  [](void* a_mem, std::size_t) {
86  });
87  }
88 
89  void set_trampoline(void* a_trampoline, std::size_t a_size) { set_trampoline(a_trampoline, a_size, {}); }
90 
91  void set_trampoline(void* a_trampoline, std::size_t a_size, deleter_type a_deleter)
92  {
93  auto trampoline = static_cast<std::byte*>(a_trampoline);
94  if (trampoline) {
95  constexpr auto INT3 = static_cast<int>(0xCC);
96  std::memset(trampoline, INT3, a_size);
97  }
98 
99  release();
100 
101  _deleter = std::move(a_deleter);
102  _data = trampoline;
103  _capacity = a_size;
104  _size = 0;
105 
106  log_stats();
107  }
108 
109  [[nodiscard]] void* allocate(std::size_t a_size)
110  {
111  auto result = do_allocate(a_size);
112  log_stats();
113  return result;
114  }
115 
116 #ifdef SKSE_SUPPORT_XBYAK
117  [[nodiscard]] void* allocate(Xbyak::CodeGenerator& a_code);
118 #endif
119 
120  template <class T>
121  [[nodiscard]] T* allocate()
122  {
123  return static_cast<T*>(allocate(sizeof(T)));
124  }
125 
126  [[nodiscard]] constexpr std::size_t empty() const noexcept { return _capacity == 0; }
127  [[nodiscard]] constexpr std::size_t capacity() const noexcept { return _capacity; }
128  [[nodiscard]] constexpr std::size_t allocated_size() const noexcept { return _size; }
129  [[nodiscard]] constexpr std::size_t free_size() const noexcept { return _capacity - _size; }
130 
131  template <std::size_t N>
132  std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst)
133  {
134  std::uint8_t data = 0;
135  if constexpr (N == 5) {
136  // E9 cd
137  // JMP rel32
138  data = 0xE9;
139  } else if constexpr (N == 6) {
140  // FF /4
141  // JMP r/m64
142  data = 0x25;
143  } else {
144  static_assert(false && N, "invalid branch size");
145  }
146 
147  return write_branch<N>(a_src, a_dst, data);
148  }
149 
150  template <std::size_t N, class F>
151  std::uintptr_t write_branch(std::uintptr_t a_src, F a_dst)
152  {
153  return write_branch<N>(a_src, stl::unrestricted_cast<std::uintptr_t>(a_dst));
154  }
155 
156  template <std::size_t N>
157  std::uintptr_t write_call(std::uintptr_t a_src, std::uintptr_t a_dst)
158  {
159  std::uint8_t data = 0;
160  if constexpr (N == 5) {
161  // E8 cd
162  // CALL rel32
163  data = 0xE8;
164  } else if constexpr (N == 6) {
165  // FF /2
166  // CALL r/m64
167  data = 0x15;
168  } else {
169  static_assert(false && N, "invalid call size");
170  }
171 
172  return write_branch<N>(a_src, a_dst, data);
173  }
174 
175  template <std::size_t N, class F>
176  std::uintptr_t write_call(std::uintptr_t a_src, F a_dst)
177  {
178  return write_call<N>(a_src, stl::unrestricted_cast<std::uintptr_t>(a_dst));
179  }
180 
181  private:
182  [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address);
183 
184  [[nodiscard]] void* do_allocate(std::size_t a_size)
185  {
186  if (a_size > free_size()) {
187  stl::report_and_fail("Failed to handle allocation request"sv);
188  }
189 
190  auto mem = _data + _size;
191  _size += a_size;
192 
193  return mem;
194  }
195 
196  void write_5branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode)
197  {
198 #pragma pack(push, 1)
199  struct SrcAssembly
200  {
201  // jmp/call [rip + imm32]
202  std::uint8_t opcode; // 0 - 0xE9/0xE8
203  std::int32_t disp; // 1
204  };
205  static_assert(offsetof(SrcAssembly, opcode) == 0x0);
206  static_assert(offsetof(SrcAssembly, disp) == 0x1);
207  static_assert(sizeof(SrcAssembly) == 0x5);
208 
209  // FF /4
210  // JMP r/m64
211  struct TrampolineAssembly
212  {
213  // jmp [rip]
214  std::uint8_t jmp; // 0 - 0xFF
215  std::uint8_t modrm; // 1 - 0x25
216  std::int32_t disp; // 2 - 0x00000000
217  std::uint64_t addr; // 6 - [rip]
218  };
219  static_assert(offsetof(TrampolineAssembly, jmp) == 0x0);
220  static_assert(offsetof(TrampolineAssembly, modrm) == 0x1);
221  static_assert(offsetof(TrampolineAssembly, disp) == 0x2);
222  static_assert(offsetof(TrampolineAssembly, addr) == 0x6);
223  static_assert(sizeof(TrampolineAssembly) == 0xE);
224 #pragma pack(pop)
225 
226  TrampolineAssembly* mem = nullptr;
227  if (const auto it = _5branches.find(a_dst); it != _5branches.end()) {
228  mem = reinterpret_cast<TrampolineAssembly*>(it->second);
229  } else {
230  mem = allocate<TrampolineAssembly>();
231  _5branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
232  }
233 
234  const auto disp =
235  reinterpret_cast<const std::byte*>(mem) -
236  reinterpret_cast<const std::byte*>(a_src + sizeof(SrcAssembly));
237  if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen
238  stl::report_and_fail("displacement is out of range"sv);
239  }
240 
241  SrcAssembly assembly;
242  assembly.opcode = a_opcode;
243  assembly.disp = static_cast<std::int32_t>(disp);
244  REL::safe_write(a_src, &assembly, sizeof(assembly));
245 
246  mem->jmp = static_cast<std::uint8_t>(0xFF);
247  mem->modrm = static_cast<std::uint8_t>(0x25);
248  mem->disp = static_cast<std::int32_t>(0);
249  mem->addr = static_cast<std::uint64_t>(a_dst);
250  }
251 
252  void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm)
253  {
254 #pragma pack(push, 1)
255  struct Assembly
256  {
257  // jmp/call [rip + imm32]
258  std::uint8_t opcode; // 0 - 0xFF
259  std::uint8_t modrm; // 1 - 0x25/0x15
260  std::int32_t disp; // 2
261  };
262  static_assert(offsetof(Assembly, opcode) == 0x0);
263  static_assert(offsetof(Assembly, modrm) == 0x1);
264  static_assert(offsetof(Assembly, disp) == 0x2);
265  static_assert(sizeof(Assembly) == 0x6);
266 #pragma pack(pop)
267 
268  std::uintptr_t* mem = nullptr;
269  if (const auto it = _6branches.find(a_dst); it != _6branches.end()) {
270  mem = reinterpret_cast<std::uintptr_t*>(it->second);
271  } else {
272  mem = allocate<std::uintptr_t>();
273  _6branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
274  }
275 
276  const auto disp =
277  reinterpret_cast<const std::byte*>(mem) -
278  reinterpret_cast<const std::byte*>(a_src + sizeof(Assembly));
279  if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen
280  stl::report_and_fail("displacement is out of range"sv);
281  }
282 
283  Assembly assembly;
284  assembly.opcode = static_cast<std::uint8_t>(0xFF);
285  assembly.modrm = a_modrm;
286  assembly.disp = static_cast<std::int32_t>(disp);
287  REL::safe_write(a_src, &assembly, sizeof(assembly));
288 
289  *mem = a_dst;
290  }
291 
292  template <std::size_t N>
293  [[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data)
294  {
295  const auto disp = reinterpret_cast<std::int32_t*>(a_src + N - 4);
296  const auto nextOp = a_src + N;
297  const auto func = nextOp + *disp;
298 
299  if constexpr (N == 5) {
300  write_5branch(a_src, a_dst, a_data);
301  } else if constexpr (N == 6) {
302  write_6branch(a_src, a_dst, a_data);
303  } else {
304  static_assert(false && N, "invalid branch size");
305  }
306 
307  return func;
308  }
309 
310  void move_from(Trampoline&& a_rhs)
311  {
312  _5branches = std::move(a_rhs._5branches);
313  _6branches = std::move(a_rhs._6branches);
314  _name = std::move(a_rhs._name);
315 
316  _deleter = std::move(a_rhs._deleter);
317 
318  _data = a_rhs._data;
319  a_rhs._data = nullptr;
320 
321  _capacity = a_rhs._capacity;
322  a_rhs._capacity = 0;
323 
324  _size = a_rhs._size;
325  a_rhs._size = 0;
326  }
327 
328  void log_stats() const;
329 
330  [[nodiscard]] bool in_range(std::ptrdiff_t a_disp) const
331  {
332  constexpr auto min = std::numeric_limits<std::int32_t>::min();
333  constexpr auto max = std::numeric_limits<std::int32_t>::max();
334 
335  return min <= a_disp && a_disp <= max;
336  }
337 
338  void release()
339  {
340  if (_data && _deleter) {
341  _deleter(_data, _capacity);
342  }
343 
344  _5branches.clear();
345  _6branches.clear();
346  _data = nullptr;
347  _capacity = 0;
348  _size = 0;
349  }
350 
351  std::map<std::uintptr_t, std::byte*> _5branches;
352  std::map<std::uintptr_t, std::byte*> _6branches;
353  std::string _name{ "Default Trampoline"sv };
354  deleter_type _deleter;
355  std::byte* _data{ nullptr };
356  std::size_t _capacity{ 0 };
357  std::size_t _size{ 0 };
358  };
359 }
static Module & get()
Definition: Relocation.h:421
Segment segment(Segment::Name a_segment) const noexcept
Definition: Relocation.h:431
void * pointer() const noexcept
Definition: Relocation.h:404
@ textx
Definition: Relocation.h:381
Definition: Trampoline.h:40
Trampoline & operator=(Trampoline &&a_rhs)
Definition: Trampoline.h:57
std::uintptr_t write_call(std::uintptr_t a_src, std::uintptr_t a_dst)
Definition: Trampoline.h:157
Trampoline()=default
constexpr std::size_t allocated_size() const noexcept
Definition: Trampoline.h:128
std::function< void(void *a_mem, std::size_t a_size)> deleter_type
Definition: Trampoline.h:42
T * allocate()
Definition: Trampoline.h:121
void * allocate(std::size_t a_size)
Definition: Trampoline.h:109
~Trampoline()
Definition: Trampoline.h:53
std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst)
Definition: Trampoline.h:132
Trampoline(std::string_view a_name)
Definition: Trampoline.h:49
Trampoline(const Trampoline &)=delete
std::uintptr_t write_branch(std::uintptr_t a_src, F a_dst)
Definition: Trampoline.h:151
void create(std::size_t a_size)
Definition: Trampoline.h:65
Trampoline(Trampoline &&a_rhs)
Definition: Trampoline.h:47
void set_trampoline(void *a_trampoline, std::size_t a_size)
Definition: Trampoline.h:89
constexpr std::size_t capacity() const noexcept
Definition: Trampoline.h:127
std::uintptr_t write_call(std::uintptr_t a_src, F a_dst)
Definition: Trampoline.h:176
constexpr std::size_t empty() const noexcept
Definition: Trampoline.h:126
constexpr std::size_t free_size() const noexcept
Definition: Trampoline.h:129
void create(std::size_t a_size, void *a_module)
Definition: Trampoline.h:67
Trampoline & operator=(const Trampoline &)=delete
void set_trampoline(void *a_trampoline, std::size_t a_size, deleter_type a_deleter)
Definition: Trampoline.h:91
constexpr std::uint8_t INT3
Definition: Relocation.h:198
void safe_write(std::uintptr_t a_dst, const void *a_src, std::size_t a_count)
Definition: Relocation.h:218
constexpr auto MEM_RELEASE
Definition: WinAPI.h:10
bool VirtualFree(void *a_address, std::size_t a_size, std::uint32_t a_freeType) noexcept
constexpr std::size_t rounddown(std::size_t a_number, std::size_t a_multiple) noexcept
Definition: Trampoline.h:26
constexpr std::size_t roundup(std::size_t a_number, std::size_t a_multiple) noexcept
Definition: Trampoline.h:14
string(const CharT(&)[N]) -> string< CharT, N - 1 >
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:579
Definition: API.h:14