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  class Trampoline
13  {
14  public:
15  using deleter_type = std::function<void(void* a_mem, std::size_t a_size)>;
16 
17  Trampoline() = default;
18  Trampoline(const Trampoline&) = delete;
19 
20  Trampoline(Trampoline&& a_rhs) noexcept { move_from(std::move(a_rhs)); }
21 
22  explicit Trampoline(std::string_view a_name) :
23  _name(a_name)
24  {}
25 
26  ~Trampoline() { release(); }
27 
28  Trampoline& operator=(const Trampoline&) = delete;
29 
30  Trampoline& operator=(Trampoline&& a_rhs) noexcept
31  {
32  if (this != std::addressof(a_rhs)) {
33  move_from(std::move(a_rhs));
34  }
35  return *this;
36  }
37 
38  void create(std::size_t a_size) { return create(a_size, nullptr); }
39  void create(std::size_t a_size, void* a_module);
40 
41  void set_trampoline(void* a_trampoline, std::size_t a_size, deleter_type a_deleter = {})
42  {
43  auto trampoline = static_cast<std::byte*>(a_trampoline);
44  if (trampoline) {
45  constexpr auto INT3 = static_cast<int>(0xCC);
46  std::memset(trampoline, INT3, a_size);
47  }
48 
49  release();
50 
51  _deleter = std::move(a_deleter);
52  _data = trampoline;
53  _capacity = a_size;
54  _size = 0;
55 
56  log_stats();
57  }
58 
59  [[nodiscard]] void* allocate(std::size_t a_size)
60  {
61  auto result = do_allocate(a_size);
62  log_stats();
63  return result;
64  }
65 
66 #ifdef SKSE_SUPPORT_XBYAK
67  [[nodiscard]] void* allocate(Xbyak::CodeGenerator& a_code);
68 #endif
69 
70  template <class T>
71  [[nodiscard]] T* allocate()
72  {
73  return static_cast<T*>(allocate(sizeof(T)));
74  }
75 
76  [[nodiscard]] constexpr std::size_t empty() const noexcept { return _capacity == 0; }
77  [[nodiscard]] constexpr std::size_t capacity() const noexcept { return _capacity; }
78  [[nodiscard]] constexpr std::size_t allocated_size() const noexcept { return _size; }
79  [[nodiscard]] constexpr std::size_t free_size() const noexcept { return _capacity - _size; }
80 
81  template <std::size_t N>
82  std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst)
83  {
84  std::uint8_t data = 0;
85  if constexpr (N == 5) {
86  // E9 cd
87  // JMP rel32
88  data = 0xE9;
89  } else if constexpr (N == 6) {
90  // FF /4
91  // JMP r/m64
92  data = 0x25;
93  } else {
94  static_assert(false && N, "invalid branch size");
95  }
96 
97  return write_branch<N>(a_src, a_dst, data);
98  }
99 
100  template <std::size_t N, class F>
101  std::uintptr_t write_branch(std::uintptr_t a_src, F a_dst)
102  {
103  return write_branch<N>(a_src, stl::unrestricted_cast<std::uintptr_t>(a_dst));
104  }
105 
106  template <std::size_t N>
107  std::uintptr_t write_call(std::uintptr_t a_src, std::uintptr_t a_dst)
108  {
109  std::uint8_t data = 0;
110  if constexpr (N == 5) {
111  // E8 cd
112  // CALL rel32
113  data = 0xE8;
114  } else if constexpr (N == 6) {
115  // FF /2
116  // CALL r/m64
117  data = 0x15;
118  } else {
119  static_assert(false && N, "invalid call size");
120  }
121 
122  return write_branch<N>(a_src, a_dst, data);
123  }
124 
125  template <std::size_t N, class F>
126  std::uintptr_t write_call(std::uintptr_t a_src, F a_dst)
127  {
128  return write_call<N>(a_src, stl::unrestricted_cast<std::uintptr_t>(a_dst));
129  }
130 
131  private:
132  [[nodiscard]] void* do_create(std::size_t a_size, std::uintptr_t a_address);
133  [[nodiscard]] void* do_allocate(std::size_t a_size);
134 
135  void write_5branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode);
136  void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm);
137 
138  template <std::size_t N>
139  [[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data)
140  {
141  const auto isNop = *reinterpret_cast<std::int8_t*>(a_src) == 0x90;
142  const auto disp = reinterpret_cast<std::int32_t*>(a_src + N - 4);
143  const auto nextOp = a_src + N;
144  const auto func = isNop ? 0 : nextOp + *disp;
145 
146  if constexpr (N == 5) {
147  write_5branch(a_src, a_dst, a_data);
148  } else if constexpr (N == 6) {
149  write_6branch(a_src, a_dst, a_data);
150  } else {
151  static_assert(false && N, "invalid branch size");
152  }
153 
154  return func;
155  }
156 
157  void move_from(Trampoline&& a_rhs)
158  {
159  _5branches = std::move(a_rhs._5branches);
160  _6branches = std::move(a_rhs._6branches);
161  _name = std::move(a_rhs._name);
162 
163  _deleter = std::move(a_rhs._deleter);
164 
165  _data = a_rhs._data;
166  a_rhs._data = nullptr;
167 
168  _capacity = a_rhs._capacity;
169  a_rhs._capacity = 0;
170 
171  _size = a_rhs._size;
172  a_rhs._size = 0;
173  }
174 
175  void log_stats() const;
176 
177  [[nodiscard]] bool in_range(std::ptrdiff_t a_disp) const
178  {
179  constexpr auto min = std::numeric_limits<std::int32_t>::min();
180  constexpr auto max = std::numeric_limits<std::int32_t>::max();
181 
182  return min <= a_disp && a_disp <= max;
183  }
184 
185  void release()
186  {
187  if (_data && _deleter) {
188  _deleter(_data, _capacity);
189  }
190 
191  _5branches.clear();
192  _6branches.clear();
193  _data = nullptr;
194  _capacity = 0;
195  _size = 0;
196  }
197 
198  std::map<std::uintptr_t, std::byte*> _5branches;
199  std::map<std::uintptr_t, std::byte*> _6branches;
200  std::string _name{ "Default Trampoline"sv };
201  deleter_type _deleter;
202  std::byte* _data{ nullptr };
203  std::size_t _capacity{ 0 };
204  std::size_t _size{ 0 };
205  };
206 
208 }
Definition: Trampoline.h:13
std::uintptr_t write_call(std::uintptr_t a_src, std::uintptr_t a_dst)
Definition: Trampoline.h:107
Trampoline()=default
constexpr std::size_t allocated_size() const noexcept
Definition: Trampoline.h:78
std::function< void(void *a_mem, std::size_t a_size)> deleter_type
Definition: Trampoline.h:15
T * allocate()
Definition: Trampoline.h:71
void * allocate(std::size_t a_size)
Definition: Trampoline.h:59
~Trampoline()
Definition: Trampoline.h:26
std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst)
Definition: Trampoline.h:82
Trampoline(std::string_view a_name)
Definition: Trampoline.h:22
Trampoline(const Trampoline &)=delete
void set_trampoline(void *a_trampoline, std::size_t a_size, deleter_type a_deleter={})
Definition: Trampoline.h:41
Trampoline(Trampoline &&a_rhs) noexcept
Definition: Trampoline.h:20
std::uintptr_t write_branch(std::uintptr_t a_src, F a_dst)
Definition: Trampoline.h:101
void create(std::size_t a_size)
Definition: Trampoline.h:38
Trampoline & operator=(Trampoline &&a_rhs) noexcept
Definition: Trampoline.h:30
constexpr std::size_t capacity() const noexcept
Definition: Trampoline.h:77
std::uintptr_t write_call(std::uintptr_t a_src, F a_dst)
Definition: Trampoline.h:126
constexpr std::size_t empty() const noexcept
Definition: Trampoline.h:76
constexpr std::size_t free_size() const noexcept
Definition: Trampoline.h:79
void create(std::size_t a_size, void *a_module)
Trampoline & operator=(const Trampoline &)=delete
constexpr std::uint8_t INT3
Definition: Relocation.h:172
NiColor min(const NiColor &a_lhs, const NiColor &a_rhs)
Definition: ColorUtil.h:63
NiColor max(const NiColor &a_lhs, const NiColor &a_rhs)
Definition: ColorUtil.h:71
string(const CharT(&)[N]) -> string< CharT, N - 1 >
Definition: API.h:14
Trampoline & GetTrampoline()