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