CommonLibSSE (Parapets fork)
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  [[nodiscard]] std::int32_t make_disp(std::uintptr_t a_rip, std::uintptr_t a_dst)
182  {
183 #pragma pack(push, 1)
184  // FF /4
185  // JMP r/m64
186  struct TrampolineAssembly
187  {
188  // jmp [rip]
189  std::uint8_t jmp; // 0 - 0xFF
190  std::uint8_t modrm; // 1 - 0x25
191  std::int32_t disp; // 2 - 0x00000000
192  std::uint64_t addr; // 6 - [rip]
193  };
194  static_assert(offsetof(TrampolineAssembly, jmp) == 0x0);
195  static_assert(offsetof(TrampolineAssembly, modrm) == 0x1);
196  static_assert(offsetof(TrampolineAssembly, disp) == 0x2);
197  static_assert(offsetof(TrampolineAssembly, addr) == 0x6);
198  static_assert(sizeof(TrampolineAssembly) == 0xE);
199 #pragma pack(pop)
200 
201  std::ptrdiff_t disp =
202  reinterpret_cast<const std::byte*>(a_dst) -
203  reinterpret_cast<const std::byte*>(a_rip);
204 
205  if (!std::in_range<std::int32_t>(disp)) {
206  TrampolineAssembly* mem = nullptr;
207  if (const auto it = _5branches.find(a_dst); it != _5branches.end()) {
208  mem = reinterpret_cast<TrampolineAssembly*>(it->second);
209  } else {
210  mem = allocate<TrampolineAssembly>();
211  _5branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
212  }
213 
214  disp =
215  reinterpret_cast<const std::byte*>(mem) -
216  reinterpret_cast<const std::byte*>(a_rip);
217 
218  if (!std::in_range<std::int32_t>(disp)) { // the trampoline should already be in range, so this should never happen
219  stl::report_and_fail("displacement is out of range"sv);
220  }
221 
222  *mem = {
223  .jmp = static_cast<std::uint8_t>(0xFF),
224  .modrm = static_cast<std::uint8_t>(0x25),
225  .disp = static_cast<std::int32_t>(0),
226  .addr = static_cast<std::uint64_t>(a_dst),
227  };
228  }
229 
230  return static_cast<std::int32_t>(disp);
231  }
232 
233  private:
234  [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address);
235 
236  [[nodiscard]] void* do_allocate(std::size_t a_size)
237  {
238  if (a_size > free_size()) {
239  stl::report_and_fail("Failed to handle allocation request"sv);
240  }
241 
242  auto mem = _data + _size;
243  _size += a_size;
244 
245  return mem;
246  }
247 
248  void write_5branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode)
249  {
250 #pragma pack(push, 1)
251  struct SrcAssembly
252  {
253  // jmp/call [rip + imm32]
254  std::uint8_t opcode; // 0 - 0xE9/0xE8
255  std::int32_t disp; // 1
256  };
257  static_assert(offsetof(SrcAssembly, opcode) == 0x0);
258  static_assert(offsetof(SrcAssembly, disp) == 0x1);
259  static_assert(sizeof(SrcAssembly) == 0x5);
260 #pragma pack(pop)
261 
262  const std::int32_t disp = make_disp(a_src + sizeof(SrcAssembly), a_dst);
263 
264  SrcAssembly assembly{
265  .opcode = a_opcode,
266  .disp = disp,
267  };
268  REL::safe_write(a_src, &assembly, sizeof(assembly));
269  }
270 
271  void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm)
272  {
273 #pragma pack(push, 1)
274  struct Assembly
275  {
276  // jmp/call [rip + imm32]
277  std::uint8_t opcode; // 0 - 0xFF
278  std::uint8_t modrm; // 1 - 0x25/0x15
279  std::int32_t disp; // 2
280  };
281  static_assert(offsetof(Assembly, opcode) == 0x0);
282  static_assert(offsetof(Assembly, modrm) == 0x1);
283  static_assert(offsetof(Assembly, disp) == 0x2);
284  static_assert(sizeof(Assembly) == 0x6);
285 #pragma pack(pop)
286 
287  std::uintptr_t* mem = nullptr;
288  if (const auto it = _6branches.find(a_dst); it != _6branches.end()) {
289  mem = reinterpret_cast<std::uintptr_t*>(it->second);
290  } else {
291  mem = allocate<std::uintptr_t>();
292  _6branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
293  }
294 
295  const auto disp =
296  reinterpret_cast<const std::byte*>(mem) -
297  reinterpret_cast<const std::byte*>(a_src + sizeof(Assembly));
298  if (!std::in_range<std::int32_t>(disp)) { // the trampoline should already be in range, so this should never happen
299  stl::report_and_fail("displacement is out of range"sv);
300  }
301 
302  Assembly assembly{
303  .opcode = static_cast<std::uint8_t>(0xFF),
304  .modrm = a_modrm,
305  .disp = static_cast<std::int32_t>(disp),
306  };
307  REL::safe_write(a_src, &assembly, sizeof(assembly));
308 
309  *mem = a_dst;
310  }
311 
312  template <std::size_t N>
313  [[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data)
314  {
315  const auto disp = reinterpret_cast<std::int32_t*>(a_src + N - 4);
316  const auto nextOp = a_src + N;
317  const auto func = nextOp + *disp;
318 
319  if constexpr (N == 5) {
320  write_5branch(a_src, a_dst, a_data);
321  } else if constexpr (N == 6) {
322  write_6branch(a_src, a_dst, a_data);
323  } else {
324  static_assert(false && N, "invalid branch size");
325  }
326 
327  return func;
328  }
329 
330  void move_from(Trampoline&& a_rhs)
331  {
332  _5branches = std::move(a_rhs._5branches);
333  _6branches = std::move(a_rhs._6branches);
334  _name = std::move(a_rhs._name);
335 
336  _deleter = std::move(a_rhs._deleter);
337 
338  _data = a_rhs._data;
339  a_rhs._data = nullptr;
340 
341  _capacity = a_rhs._capacity;
342  a_rhs._capacity = 0;
343 
344  _size = a_rhs._size;
345  a_rhs._size = 0;
346  }
347 
348  void log_stats() const;
349 
350  void release()
351  {
352  if (_data && _deleter) {
353  _deleter(_data, _capacity);
354  }
355 
356  _5branches.clear();
357  _6branches.clear();
358  _data = nullptr;
359  _capacity = 0;
360  _size = 0;
361  }
362 
363  std::map<std::uintptr_t, std::byte*> _5branches;
364  std::map<std::uintptr_t, std::byte*> _6branches;
365  std::string _name{ "Default Trampoline"sv };
366  deleter_type _deleter;
367  std::byte* _data{ nullptr };
368  std::size_t _capacity{ 0 };
369  std::size_t _size{ 0 };
370  };
371 }
constexpr Segment segment(Segment::Name a_segment) const noexcept
Definition: Relocation.h:442
static Module & get()
Definition: Relocation.h:518
void * pointer() const noexcept
Definition: Relocation.h:413
@ textx
Definition: Relocation.h:390
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::int32_t make_disp(std::uintptr_t a_rip, std::uintptr_t a_dst)
Definition: Trampoline.h:181
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:621
Definition: API.h:14