|
11 | 11 | #include "common/Threading.h" |
12 | 12 | #include "common/WindowInfo.h" |
13 | 13 | #include "common/HostSys.h" |
| 14 | +#include "fmt/format.h" |
14 | 15 |
|
15 | 16 | #include <csignal> |
16 | 17 | #include <cstring> |
|
19 | 20 | #include <sys/mman.h> |
20 | 21 | #include <sys/types.h> |
21 | 22 | #include <sys/sysctl.h> |
| 23 | +#include <thread> |
22 | 24 | #include <time.h> |
23 | 25 | #include <mach/mach_init.h> |
24 | 26 | #include <mach/mach_port.h> |
25 | 27 | #include <mach/mach_time.h> |
26 | 28 | #include <mach/mach_vm.h> |
| 29 | +#include <mach/message.h> |
27 | 30 | #include <mach/task.h> |
| 31 | +#include <mach/thread_state.h> |
28 | 32 | #include <mach/vm_map.h> |
29 | 33 | #include <mutex> |
30 | 34 | #include <ApplicationServices/ApplicationServices.h> |
@@ -294,7 +298,7 @@ static CPUInfo CalcCPUInfo() |
294 | 298 | std::vector<DarwinMisc::CPUClass> classes = DarwinMisc::GetCPUClasses(); |
295 | 299 | out.num_clusters = static_cast<u32>(classes.size()); |
296 | 300 | 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; |
298 | 302 | out.num_small_cores = 0; |
299 | 303 | for (std::size_t i = 1; i < classes.size(); i++) |
300 | 304 | { |
@@ -568,15 +572,206 @@ void HostSys::EndCodeWrite() |
568 | 572 |
|
569 | 573 | #endif // _M_ARM64 |
570 | 574 |
|
| 575 | +#define USE_MACH_EXCEPTION_PORTS |
| 576 | + |
571 | 577 | namespace PageFaultHandler |
572 | 578 | { |
| 579 | +#ifdef USE_MACH_EXCEPTION_PORTS |
| 580 | + static void SignalHandler(mach_port_t port); |
| 581 | +#else |
573 | 582 | static void SignalHandler(int sig, siginfo_t* info, void* ctx); |
| 583 | +#endif |
574 | 584 |
|
575 | 585 | static std::recursive_mutex s_exception_handler_mutex; |
576 | 586 | static bool s_in_exception_handler = false; |
577 | 587 | static bool s_installed = false; |
578 | 588 | } // namespace PageFaultHandler |
579 | 589 |
|
| 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 | + |
580 | 775 | void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx) |
581 | 776 | { |
582 | 777 | #if defined(_M_X86) |
@@ -644,3 +839,4 @@ bool PageFaultHandler::Install(Error* error) |
644 | 839 | s_installed = true; |
645 | 840 | return true; |
646 | 841 | } |
| 842 | +#endif |
0 commit comments