1 /* This file is part of the dynarmic project.
2 * Copyright (c) 2019 MerryMage
3 * SPDX-License-Identifier: 0BSD
4 */
5
6 #include "backend/x64/exception_handler.h"
7
8 #include <mach/mach.h>
9 #include <mach/message.h>
10
11 #include <cstring>
12 #include <functional>
13 #include <memory>
14 #include <mutex>
15 #include <thread>
16 #include <vector>
17
18 #include <fmt/format.h>
19
20 #include "backend/x64/block_of_code.h"
21 #include "common/assert.h"
22 #include "common/cast_util.h"
23 #include "common/common_types.h"
24
25 #define mig_external extern "C"
26 #include "backend/x64/mig/mach_exc_server.h"
27
28 namespace Dynarmic::Backend::X64 {
29
30 namespace {
31
32 struct CodeBlockInfo {
33 u64 code_begin, code_end;
34 std::function<FakeCall(u64)> cb;
35 };
36
37 struct MachMessage {
38 mach_msg_header_t head;
39 char data[2048]; ///< Arbitrary size
40 };
41
42 class MachHandler final {
43 public:
44 MachHandler();
45 ~MachHandler();
46
47 kern_return_t HandleRequest(x86_thread_state64_t* thread_state);
48
49 void AddCodeBlock(CodeBlockInfo info);
50 void RemoveCodeBlock(u64 rip);
51
52 private:
FindCodeBlockInfo(u64 rip)53 auto FindCodeBlockInfo(u64 rip) {
54 return std::find_if(code_block_infos.begin(), code_block_infos.end(), [&](const auto& x) { return x.code_begin <= rip && x.code_end > rip; });
55 }
56
57 std::vector<CodeBlockInfo> code_block_infos;
58 std::mutex code_block_infos_mutex;
59
60 std::thread thread;
61 mach_port_t server_port;
62
63 void MessagePump();
64 };
65
MachHandler()66 MachHandler::MachHandler() {
67 #define KCHECK(x) ASSERT_MSG((x) == KERN_SUCCESS, "dynarmic: macOS MachHandler: init failure at {}", #x)
68
69 KCHECK(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port));
70 KCHECK(mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND));
71 KCHECK(task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, server_port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, x86_THREAD_STATE64));
72
73 // The below doesn't actually work, and I'm not sure why; since this doesn't work we'll have a spurious error message upon shutdown.
74 mach_port_t prev;
75 KCHECK(mach_port_request_notification(mach_task_self(), server_port, MACH_NOTIFY_PORT_DESTROYED, 0, server_port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev));
76
77 #undef KCHECK
78
79 thread = std::thread(&MachHandler::MessagePump, this);
80 }
81
~MachHandler()82 MachHandler::~MachHandler() {
83 mach_port_destroy(mach_task_self(), server_port);
84 thread.join();
85 }
86
MessagePump()87 void MachHandler::MessagePump() {
88 mach_msg_return_t mr;
89 MachMessage request;
90 MachMessage reply;
91
92 while (true) {
93 mr = mach_msg(&request.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(request), server_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
94 if (mr != MACH_MSG_SUCCESS) {
95 fmt::print(stderr, "dynarmic: macOS MachHandler: Failed to receive mach message. error: {:#08x} ({})\n", mr, mach_error_string(mr));
96 return;
97 }
98
99 if (!mach_exc_server(&request.head, &reply.head)) {
100 fmt::print(stderr, "dynarmic: macOS MachHandler: Unexpected mach message\n");
101 return;
102 }
103
104 mr = mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
105 if (mr != MACH_MSG_SUCCESS){
106 fmt::print(stderr, "dynarmic: macOS MachHandler: Failed to send mach message. error: {:#08x} ({})\n", mr, mach_error_string(mr));
107 return;
108 }
109 }
110 }
111
HandleRequest(x86_thread_state64_t * ts)112 kern_return_t MachHandler::HandleRequest(x86_thread_state64_t* ts) {
113 std::lock_guard<std::mutex> guard(code_block_infos_mutex);
114
115 const auto iter = FindCodeBlockInfo(ts->__rip);
116 if (iter == code_block_infos.end()) {
117 fmt::print(stderr, "dynarmic: macOS MachHandler: Exception was not in registered code blocks (rip {:#016x})\n", ts->__rip);
118 return KERN_FAILURE;
119 }
120
121 FakeCall fc = iter->cb(ts->__rip);
122
123 ts->__rsp -= sizeof(u64);
124 *Common::BitCast<u64*>(ts->__rsp) = fc.ret_rip;
125 ts->__rip = fc.call_rip;
126
127 return KERN_SUCCESS;
128 }
129
AddCodeBlock(CodeBlockInfo cbi)130 void MachHandler::AddCodeBlock(CodeBlockInfo cbi) {
131 std::lock_guard<std::mutex> guard(code_block_infos_mutex);
132 if (auto iter = FindCodeBlockInfo(cbi.code_begin); iter != code_block_infos.end()) {
133 code_block_infos.erase(iter);
134 }
135 code_block_infos.push_back(cbi);
136 }
137
RemoveCodeBlock(u64 rip)138 void MachHandler::RemoveCodeBlock(u64 rip) {
139 std::lock_guard<std::mutex> guard(code_block_infos_mutex);
140 const auto iter = FindCodeBlockInfo(rip);
141 if (iter == code_block_infos.end()) {
142 return;
143 }
144 code_block_infos.erase(iter);
145 }
146
147 MachHandler mach_handler;
148
149 } // anonymous namespace
150
catch_mach_exception_raise(mach_port_t,mach_port_t,mach_port_t,exception_type_t,mach_exception_data_t,mach_msg_type_number_t)151 mig_external kern_return_t catch_mach_exception_raise(mach_port_t, mach_port_t, mach_port_t, exception_type_t, mach_exception_data_t, mach_msg_type_number_t) {
152 fmt::print(stderr, "dynarmic: Unexpected mach message: mach_exception_raise\n");
153 return KERN_FAILURE;
154 }
155
catch_mach_exception_raise_state_identity(mach_port_t,mach_port_t,mach_port_t,exception_type_t,mach_exception_data_t,mach_msg_type_number_t,int *,thread_state_t,mach_msg_type_number_t,thread_state_t,mach_msg_type_number_t *)156 mig_external kern_return_t catch_mach_exception_raise_state_identity(mach_port_t, mach_port_t, mach_port_t, exception_type_t, mach_exception_data_t, mach_msg_type_number_t, int*, thread_state_t, mach_msg_type_number_t, thread_state_t, mach_msg_type_number_t*) {
157 fmt::print(stderr, "dynarmic: Unexpected mach message: mach_exception_raise_state_identity\n");
158 return KERN_FAILURE;
159 }
160
catch_mach_exception_raise_state(mach_port_t,exception_type_t exception,const mach_exception_data_t,mach_msg_type_number_t,int * flavor,const thread_state_t old_state,mach_msg_type_number_t old_stateCnt,thread_state_t new_state,mach_msg_type_number_t * new_stateCnt)161 mig_external kern_return_t catch_mach_exception_raise_state(
162 mach_port_t /*exception_port*/,
163 exception_type_t exception,
164 const mach_exception_data_t /*code*/, // code[0] is as per kern_return.h, code[1] is rip.
165 mach_msg_type_number_t /*codeCnt*/,
166 int* flavor,
167 const thread_state_t old_state,
168 mach_msg_type_number_t old_stateCnt,
169 thread_state_t new_state,
170 mach_msg_type_number_t* new_stateCnt
171 ) {
172 if (!flavor || !new_stateCnt) {
173 fmt::print(stderr, "dynarmic: catch_mach_exception_raise_state: Invalid arguments.\n");
174 return KERN_INVALID_ARGUMENT;
175 }
176 if (*flavor != x86_THREAD_STATE64 || old_stateCnt != x86_THREAD_STATE64_COUNT || *new_stateCnt < x86_THREAD_STATE64_COUNT) {
177 fmt::print(stderr, "dynarmic: catch_mach_exception_raise_state: Unexpected flavor.\n");
178 return KERN_INVALID_ARGUMENT;
179 }
180 if (exception != EXC_BAD_ACCESS) {
181 fmt::print(stderr, "dynarmic: catch_mach_exception_raise_state: Unexpected exception type.\n");
182 return KERN_FAILURE;
183 }
184
185 x86_thread_state64_t* ts = reinterpret_cast<x86_thread_state64_t*>(new_state);
186 std::memcpy(ts, reinterpret_cast<const x86_thread_state64_t*>(old_state), sizeof(x86_thread_state64_t));
187 *new_stateCnt = x86_THREAD_STATE64_COUNT;
188
189 return mach_handler.HandleRequest(ts);
190 }
191
192 struct ExceptionHandler::Impl final {
ImplDynarmic::Backend::X64::ExceptionHandler::Impl193 Impl(BlockOfCode& code)
194 : code_begin(Common::BitCast<u64>(code.getCode()))
195 , code_end(code_begin + code.GetTotalCodeSize())
196 {}
197
SetCallbackDynarmic::Backend::X64::ExceptionHandler::Impl198 void SetCallback(std::function<FakeCall(u64)> cb) {
199 CodeBlockInfo cbi;
200 cbi.code_begin = code_begin;
201 cbi.code_end = code_end;
202 cbi.cb = cb;
203 mach_handler.AddCodeBlock(cbi);
204 }
205
~ImplDynarmic::Backend::X64::ExceptionHandler::Impl206 ~Impl() {
207 mach_handler.RemoveCodeBlock(code_begin);
208 }
209
210 private:
211 u64 code_begin, code_end;
212 };
213
214 ExceptionHandler::ExceptionHandler() = default;
215
216 ExceptionHandler::~ExceptionHandler() = default;
217
Register(BlockOfCode & code)218 void ExceptionHandler::Register(BlockOfCode& code) {
219 impl = std::make_unique<Impl>(code);
220 }
221
SupportsFastmem() const222 bool ExceptionHandler::SupportsFastmem() const noexcept {
223 return static_cast<bool>(impl);
224 }
225
SetFastmemCallback(std::function<FakeCall (u64)> cb)226 void ExceptionHandler::SetFastmemCallback(std::function<FakeCall(u64)> cb) {
227 impl->SetCallback(cb);
228 }
229
230 } // namespace Dynarmic::Backend::X64
231