CommonLibSSE (Parapets fork)
Loading...
Searching...
No Matches
Trampoline.h
Go to the documentation of this file.
1#pragma once
2
3#if defined(SKSE_SUPPORT_XBYAK)
4namespace Xbyak
5{
6 class CodeGenerator;
7}
8#endif
9
10namespace 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
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
@ textx
Definition: Relocation.h:381
void * pointer() const noexcept
Definition: Relocation.h:404
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
T * allocate()
Definition: Trampoline.h:121
std::function< void(void *a_mem, std::size_t a_size)> deleter_type
Definition: Trampoline.h:42
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 & operator=(const Trampoline &)=delete
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
void set_trampoline(void *a_trampoline, std::size_t a_size, deleter_type a_deleter)
Definition: Trampoline.h:91
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
void report_and_fail(std::string_view a_msg, std::source_location a_loc=std::source_location::current())
Definition: PCH.h:580
Definition: API.h:14