mattgallagher / CwlPreconditionTesting

A Mach exception handler that allows Swift precondition failures to be caught and tested.
ISC License
175 stars 46 forks source link

Apple Silicone Support #21

Closed mRs- closed 2 years ago

mRs- commented 3 years ago

Is it possible to support Apple Silicone?

winkelsdorf commented 3 years ago

@mattgallagher As this is used in Nimble to test for thrown Assertions, too it's currently a blocker on arm64/m1.

I tried to update the declarations as declared in /usr/include/mach/arm64/thread_status.h but still fail to get over a SIGNAL Code 0x5.

I'd be happy to help creating a PR. Do you think you would be able to support a PR with your knowledge if I create one?

Edit: I'm unaware if there's an alternative on arm64 for the rsp on x86_64. Tried with sp pointee, but that fails with SIGNAL.

amomchilov commented 3 years ago

Typo in the title/description:

Silicone is a "polymer made up of siloxane". What you're looking for is Silicon, the 14th element used in semiconductors.

(Sorry to be pedantic. I'd fix it myself, but GH issues aren't editable the way the code in the repo is)

mattgallagher commented 3 years ago

Progress here remains: no progress. The "CwlBadInstructionException.swift" file relies on hand-coded x86-64 register manipulation. The equivalent work would need to be reproduced on an M1 Mac but I don't have access to one for testing and the code doesn't run on an iOS device.

I'm happy to accept pull requests but that's the current status.

rogerluan commented 3 years ago

I'm happy to help test PRs when they're available 🙏

brennanMKE commented 2 years ago

@mattgallagher Happy to help. I have 2 M1 Macs here.

mattgallagher commented 2 years ago

@brennanMKE It's more than a little tricky since I don't really know ARM64 assembly or know the calling convention on ARM64, at all. On x64-64, I wrote this part largely through inspecting the assembly and trial and error. Theoretically, I can inspect an iOS binary and try to guess how it should work but Mach Exception handlers don't work on iOS (not without serious shenanigans). And "trial and error" coding, without direct access to the trial, is a tough ask.

I'll give it a shot this weekend but it might not be possible.

brennanMKE commented 2 years ago

Hi @mattgallagher,

I prepared this commit to get it as far as I could with my very limited knowledge.

PreconditionCatching is my sample project that I am using to work out a solution. It includes notes in the README for supporting the new arm64 arch on Apple Silicon. I've been reviewing updates for HomeBrew and Haskell to try to learn from them. I do not know if I am on the right path or not. It appears that I am down to just 2 errors. Perhaps what you need to know is in the doc below. It covers what is unique about Apple's implementation on top of the arm64 spec. The section on the Red Zone may be relevant.

Is this going in the right direction? How would I go about inspecting the assembly myself? This is all very unfamiliar to me.

mattgallagher commented 2 years ago

@brennanMKE The problems are entirely around the receiveReply function in https://github.com/mattgallagher/CwlPreconditionTesting/blob/master/Sources/CwlPreconditionTesting/CwlBadInstructionException.swift:

The purpose is to simulate the function call raiseBadInstructionException on the state of the thread that raised the Mach exception:

// Read the old thread state
var state = old_state.withMemoryRebound(to: x86_thread_state64_t.self, capacity: 1) { return $0.pointee }

// 1. Decrement the stack pointer
state.__rsp -= __uint64_t(MemoryLayout<Int>.size)

// 2. Save the old Instruction Pointer to the stack.
if let pointer = UnsafeMutablePointer<__uint64_t>(bitPattern: UInt(state.__rsp)) {
    pointer.pointee = state.__rip
} else {
    return NSNumber(value: KERN_INVALID_ARGUMENT)
}

// 3. Set the Instruction Pointer to the new function's address
var f: @convention(c) () -> Void = raiseBadInstructionException
withUnsafePointer(to: &f) {
    state.__rip = $0.withMemoryRebound(to: __uint64_t.self, capacity: 1) { return $0.pointee }
}

Steps 1, 2 and 3 here replicates the effects of an Intel x86_64 call instruction (call increments the stack pointer, saves the old instruction pointer, then sets the instruction pointer to the new address). The stack pointer and instruction pointer are specific named registers.

On ARM64, there isn't a call instruction. I think that bl (or maybe blx) is used to similar effect. I don't know exactly what they do. The required work is to confirm that bl/blx is how C function calls are handled on Apple Silicon, then confirm which registers it changes and produce an ARM64 version of this code that does the job.

mattgallagher commented 2 years ago

The best way to inspect this is by using the debugger to step through a function call "instruction by instruction" (not line by line) and view the instructions use and the changes to the registers. Or by finding documentation for the Apple Silicon C calling convention.

saagarjha commented 2 years ago

24 should do this.

mattgallagher commented 2 years ago

I've merged https://github.com/mattgallagher/CwlPreconditionTesting/pull/24 to master which should address this issue. Let me know if there are any issues and I can reopen.

svenmuennich commented 2 years ago

Awesome 🚀

@mattgallagher could you please tag a new release so that we can update this dependency accordingly?

mattgallagher commented 2 years ago

@svenmuennich I've created tag 2.1.0 with these changes and updated the Cocoapods.

rogerluan commented 2 years ago

Thanks @mattgallagher !

svenmuennich commented 2 years ago

Thank you!