Hi Quinn — following up from DTS case 22070584.
I'm working on a Windows compatibility runtime (Wine plus a CPU translator) that runs natively on Apple Silicon. 64-bit x86 Windows programs work fine. 32-bit ones don't, because they need address space in the low 4 GB: guest pointers are 32-bit, and some Windows structures sit at fixed addresses like 0x7ffe0000 that programs read directly.
On arm64 I can't get anything down there:
task_info(TASK_VM_INFO) -> min_address 0x100ea0000 mmap(0x7ffe0000, MAP_FIXED) -> ENOMEM mach_vm_allocate(0x7ffe0000, VM_FLAGS_FIXED) -> KERN_INVALID_ADDRESS
There's also nothing below 4 GB to remove: mach_vm_region finds no entry there at all, and mach_vm_deallocate(0, 4 GB) returns KERN_SUCCESS without changing anything. Building with a smaller __PAGEZERO doesn't help either — every size I tried (0x1000, 0x4000, 0x10000, 0x100000, 0x1000000, 0x10000000, 0x80000000) gets SIGKILLed before main, with no crash report. Ad-hoc signing, the hardened runtime and -no_pie made no difference.
I did notice /usr/libexec/rosetta/runtime is arm64 with no __PAGEZERO segment at all and __TEXT at vmaddr 0, so the kernel can clearly do this, at least for platform binaries.
Is there a supported way for a third-party arm64 process to map below 4 GB — an entitlement, a spawn attribute, something I've missed? If the answer is no, that's fine, I'd just like to know so I can stop looking and plan around it.
I have two small test programs that print all of the above if they'd be useful.
I sat down to play with this today and I want to share some results.
Using Xcode 27.0 on macOS 26.6.1, I created a new app target and then gutted all the app bits, à la Signing a daemon with a restricted entitlement.
I then added a main.swift with this code:
import Darwin
func main() {
print("Hello Cruel World!")
var addr: vm_address_t = 0x7ffe0000
// Annoyingly, using `vm_page_size` makes Swift concurrency unhappy, so
// let’s get this from BSD.
let pageSize = vm_size_t(bitPattern: Int(getpagesize()))
print("page size: 0x\(String(pageSize, radix: 16))")
let kr = vm_allocate(mach_task_self_, &addr, pageSize, VM_FLAGS_FIXED)
if kr == KERN_SUCCESS {
print("did allocate, addr: 0x\(String(addr, radix: 16))")
} else {
print("did not allocate, kr: \(kr)")
}
if false {
print("will pause, pid: \(getpid())")
pause()
print("did pause")
}
}
main()
On running that, I saw this:
Hello Cruel World!
page size: 0x4000
did not allocate, kr: 1
where 1 is KERN_INVALID_ADDRESS. So far, so expected.
To get this to work I had to change two things. First, in Signing & Capabilities I added the Cross-architecture Compatibility Framework capability [1]. This added the com.apple.developer.cross-architecture-support entitlement. Xcode’s automatic code signing kicked in and generated a provisioning profile that authorises my app to claim that entitlement.
When I ran that version the allocation failed with error 3, or KERN_NO_SPACE. Which brings me to the second change: adding an explicit zero page segment. Specifically, I edited the Other Linker Flags build setting to look like this:
-Xlinker
-pagezero_size
-Xlinker
0x4000
$(inherited)
And with change, the allocation works:
Hello Cruel World!
page size: 0x4000
did allocate, addr: 0x7ffe0000
Yay!
I think that’s all you need, but lemme know if you hit any snags.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] No docs for this yet, alas (r. 188189100).