Skip to content

Commit 3242436

Browse files
committed
Darwin: Implement Mach exceptions ports instead of signals
Credit goes to the Julia language and Dolphin emulator for their free and open source implementations
1 parent 43e073a commit 3242436

File tree

1 file changed

+197
-1
lines changed

1 file changed

+197
-1
lines changed

common/Darwin/DarwinMisc.cpp

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "common/Threading.h"
1212
#include "common/WindowInfo.h"
1313
#include "common/HostSys.h"
14+
#include "fmt/format.h"
1415

1516
#include <csignal>
1617
#include <cstring>
@@ -19,12 +20,15 @@
1920
#include <sys/mman.h>
2021
#include <sys/types.h>
2122
#include <sys/sysctl.h>
23+
#include <thread>
2224
#include <time.h>
2325
#include <mach/mach_init.h>
2426
#include <mach/mach_port.h>
2527
#include <mach/mach_time.h>
2628
#include <mach/mach_vm.h>
29+
#include <mach/message.h>
2730
#include <mach/task.h>
31+
#include <mach/thread_state.h>
2832
#include <mach/vm_map.h>
2933
#include <mutex>
3034
#include <ApplicationServices/ApplicationServices.h>
@@ -294,7 +298,7 @@ static CPUInfo CalcCPUInfo()
294298
std::vector<DarwinMisc::CPUClass> classes = DarwinMisc::GetCPUClasses();
295299
out.num_clusters = static_cast<u32>(classes.size());
296300
out.num_big_cores = classes.empty() ? 0 : classes[0].num_physical;
297-
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
301+
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
298302
out.num_small_cores = 0;
299303
for (std::size_t i = 1; i < classes.size(); i++)
300304
{
@@ -568,15 +572,206 @@ void HostSys::EndCodeWrite()
568572

569573
#endif // _M_ARM64
570574

575+
#define USE_MACH_EXCEPTION_PORTS
576+
571577
namespace PageFaultHandler
572578
{
579+
#ifdef USE_MACH_EXCEPTION_PORTS
580+
static void SignalHandler(mach_port_t port);
581+
#else
573582
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
583+
#endif
574584

575585
static std::recursive_mutex s_exception_handler_mutex;
576586
static bool s_in_exception_handler = false;
577587
static bool s_installed = false;
578588
} // namespace PageFaultHandler
579589

590+
#ifdef USE_MACH_EXCEPTION_PORTS
591+
592+
#if defined(_M_X86)
593+
#define THREAD_STATE64_COUNT x86_THREAD_STATE64_COUNT
594+
#define THREAD_STATE64 x86_THREAD_STATE64
595+
#define thread_state64_t x86_thread_state64_t
596+
#elif defined(_M_ARM64)
597+
#define THREAD_STATE64_COUNT ARM_THREAD_STATE64_COUNT
598+
#define THREAD_STATE64 ARM_THREAD_STATE64
599+
#define thread_state64_t arm_thread_state64_t
600+
#else
601+
#error Unknown Darwin Platform
602+
#endif
603+
604+
void PageFaultHandler::SignalHandler(mach_port_t port)
605+
{
606+
Threading::SetNameOfCurrentThread("Mach Exception Thread");
607+
608+
#pragma pack(4)
609+
struct
610+
{
611+
mach_msg_header_t Head;
612+
NDR_record_t NDR;
613+
exception_type_t exception;
614+
mach_msg_type_number_t codeCnt;
615+
int64_t code[2];
616+
int flavor;
617+
mach_msg_type_number_t old_stateCnt;
618+
natural_t old_state[THREAD_STATE64_COUNT];
619+
mach_msg_trailer_t trailer;
620+
} msg_in;
621+
622+
struct
623+
{
624+
mach_msg_header_t Head;
625+
NDR_record_t NDR;
626+
kern_return_t RetCode;
627+
int flavor;
628+
mach_msg_type_number_t new_stateCnt;
629+
natural_t new_state[THREAD_STATE64_COUNT];
630+
} msg_out;
631+
#pragma pack()
632+
memset(&msg_in, 0xee, sizeof(msg_in));
633+
memset(&msg_out, 0xee, sizeof(msg_out));
634+
mach_msg_size_t send_size = 0;
635+
mach_msg_option_t option = MACH_RCV_MSG;
636+
while (true)
637+
{
638+
kern_return_t r;
639+
if ((r = mach_msg_overwrite(&msg_out.Head, option, send_size, sizeof(msg_in), port,
640+
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg_in.Head, 0)))
641+
{
642+
pxFail(fmt::format("CRITICAL: mach_msg_overwrite: {:x}", r).c_str());
643+
}
644+
645+
if (msg_in.Head.msgh_id == MACH_NOTIFY_NO_SENDERS)
646+
{
647+
// the other thread exited
648+
mach_port_deallocate(mach_task_self(), port);
649+
return;
650+
}
651+
652+
if (msg_in.Head.msgh_id != 2406)
653+
{
654+
pxFailRel("unknown message received");
655+
return;
656+
}
657+
658+
if (msg_in.flavor != THREAD_STATE64)
659+
{
660+
pxFailRel(fmt::format("unknown flavour {}, expected {}", msg_in.flavor, THREAD_STATE64).c_str());
661+
return;
662+
}
663+
664+
s_exception_handler_mutex.lock();
665+
thread_state64_t* state = (thread_state64_t*)msg_in.old_state;
666+
667+
HandlerResult result = HandlerResult::ExecuteNextHandler;
668+
if (!s_in_exception_handler)
669+
{
670+
s_in_exception_handler = true;
671+
672+
#ifdef _M_ARM64
673+
result = HandlePageFault((void*)state->__pc, reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
674+
#else
675+
result = HandlePageFault((void*)state->__rip, reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
676+
#endif
677+
s_in_exception_handler = false;
678+
}
679+
680+
// Set up the reply.
681+
msg_out.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg_in.Head.msgh_bits), 0);
682+
msg_out.Head.msgh_remote_port = msg_in.Head.msgh_remote_port;
683+
msg_out.Head.msgh_local_port = MACH_PORT_NULL;
684+
msg_out.Head.msgh_id = msg_in.Head.msgh_id + 100;
685+
msg_out.NDR = msg_in.NDR;
686+
687+
if (result != HandlerResult::ContinueExecution) // cooked
688+
{
689+
msg_out.RetCode = KERN_FAILURE;
690+
msg_out.flavor = 0;
691+
msg_out.new_stateCnt = 0;
692+
693+
// The crash handler on macOS or Linux doesn't use context passed to it
694+
// Stubbing it here is fine
695+
CrashHandler::CrashSignalHandler(-1, nullptr, nullptr);
696+
697+
pxFailRel("CrashSignalHandler returned when it should have terminated us!");
698+
}
699+
else
700+
{
701+
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV)
702+
msg_out.RetCode = KERN_SUCCESS;
703+
msg_out.flavor = THREAD_STATE64;
704+
msg_out.new_stateCnt = THREAD_STATE64_COUNT;
705+
memcpy(msg_out.new_state, msg_in.old_state, THREAD_STATE64_COUNT * sizeof(natural_t));
706+
}
707+
708+
msg_out.Head.msgh_size =
709+
offsetof(__typeof__(msg_out), new_state) + msg_out.new_stateCnt * sizeof(natural_t);
710+
send_size = msg_out.Head.msgh_size;
711+
option |= MACH_SEND_MSG;
712+
713+
s_exception_handler_mutex.unlock();
714+
}
715+
}
716+
717+
bool PageFaultHandler::Install(Error* error)
718+
{
719+
exception_mask_t masks[EXC_TYPES_COUNT];
720+
mach_port_t ports[EXC_TYPES_COUNT];
721+
exception_behavior_t behaviors[EXC_TYPES_COUNT];
722+
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
723+
mach_msg_type_number_t count = EXC_TYPES_COUNT;
724+
725+
kern_return_t r = task_get_exception_ports(mach_task_self(), EXC_MASK_ALL,
726+
masks, &count, ports, behaviors, flavors);
727+
728+
mach_port_t port;
729+
if ((r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)))
730+
{
731+
pxFailRel(fmt::format("mach_port_allocate: {:x}", r).c_str());
732+
return false;
733+
}
734+
735+
std::thread sig_thread(PageFaultHandler::SignalHandler, port);
736+
sig_thread.detach();
737+
738+
if ((r = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND)))
739+
{
740+
mach_port_deallocate(mach_task_self(), port);
741+
pxFailRel(fmt::format("mach_port_insert_right: {:x}", r).c_str());
742+
return false;
743+
}
744+
745+
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
746+
747+
if ((r = thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, THREAD_STATE64)))
748+
{
749+
mach_port_deallocate(mach_task_self(), port);
750+
pxFailRel(fmt::format("thread_set_exception_ports: {:x}", r).c_str());
751+
return false;
752+
}
753+
754+
if ((r = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1)))
755+
{
756+
mach_port_deallocate(mach_task_self(), port);
757+
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
758+
return false;
759+
}
760+
761+
mach_port_t previous;
762+
if ((r = mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS, 0, port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)))
763+
{
764+
mach_port_deallocate(mach_task_self(), port);
765+
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
766+
return false;
767+
}
768+
769+
s_installed = true;
770+
return true;
771+
}
772+
773+
#else
774+
580775
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
581776
{
582777
#if defined(_M_X86)
@@ -644,3 +839,4 @@ bool PageFaultHandler::Install(Error* error)
644839
s_installed = true;
645840
return true;
646841
}
842+
#endif

0 commit comments

Comments
 (0)