ld64.lld doesn't seem to support arm64e
Opened this issue · 10 comments
$ echo "int main() { return 0; }" > foo.c
$ clang --target=arm64e-apple-darwin -o foo foo.c -isysroot /path/to/MacOSX14.2.sdk -fuse-ld=lld
$ file foo
foo: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
When using plain ld64:
$ clang --target=arm64e-apple-darwin -o foo foo.c -isysroot /path/to/MacOSX14.2.sdk
$ file foo
foo: Mach-O 64-bit arm64e 00) executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
@llvm/issue-subscribers-lld-macho
Author: Mike Hommey (glandium)
For the record, isArchABICompatible, compatWithTargetArch and createTargetInfo all ignore the cpuSubtype from the target.
@BertalanD feel free to work on this. We needed to clarify a couple more things and have not started to work on this or have time allocated to start. Could you mention us in future changes you make around arm64e support to keep track of it? Thanks.
I put up my WIP branch. This won't ever become a PR, I'm just trying to make things work with the least amount of refactoring/easiest hacks, and once I know the full scope of the required changes, we can think about refactoring code and doing it the proper way.
We'll need these changes:
-
Reloc
needs to store either a 64-bit addend (other architectures) or a 32-bit addend + authentication data (arm64e). Will need to use aunion
if we wish to keepReloc
's size down. - Support for new pointer formats (ld64's source says these are used for macOS 12+; Chrome doesn't target older versions, so I'd say forget about the legacy format for now):
dyld_chained_ptr_arm64e_rebase
,dyld_chained_ptr_arm64e_auth_rebase
,dyld_chained_ptr_arm64e_bind24
,dyld_chained_ptr_arm64e_auth_bind24
-
ChainedFixupsSection::addBinding
needs to know whether it's an authenticated fixup, as only non-authenticated binds can store an addend in-line (low prio, just an optimization) - We can't call
writeChainedRebase
directly fromConcatInputSection::writeTo
, as that would drop auth data. - split the GOT:
__stubs
loads pointers from__auth_got
(signed with div=0x0000 ad=0 key=IA), address-taken globals/GOT relocations reference unsigned data in__got
. - Personality functions need to be signed according to ld64 (?)
-
__thread_vars
should sign__tlv_bootstrap
pointers with div=0x0000 ad=0 key=IA even though they're emitted as non-signed by the assembler. - Logic to set
CPU_SUBTYPE_ARM64E_PTRAUTH_VERSION
. macOS 15 and 26 beta refuse to load the binary if the ABI version is not 1. - Range extension thunks,
objc_msgSend
stubs etc. should use signed pointers. - Update ICF to handle
ARM64_RELOC_AUTHENTICATED_POINTER
(even__cfstring
contains authenticated pointers)
Stay tuned for an in-depth gist with my findings about arm64e.
@BertalanD Thanks so much for looking into this! I did some investigation into adding arm64e support as well and I've overlapped in a lot of areas you've talked about. I will upload my changes into a branch and provide the link shortly.
I've additionally been working off of swiftlang#8946 which is a bit dated but includes the same compiler flags that Xcode's Clang has, namely -fptrauth-objc-isa-mode=sign-and-strip
& -fptrauth-abi-version=0
.
I am new to LLD so full-disclosure my patch is pretty vibe-coded, but I was able to get pretty good parity with Xcode strictly through my test cases. I also have been trying to use a small test iOS app to get parity but the fixups are still quite different (possibly due to missing swiftlang#8946)
@oskarwirga part of that PR seems to be already in upstream LLVM. Have you identified specific commits that are missing?
I put up my WIP branch. This won't ever become a PR, I'm just trying to make things work with the least amount of refactoring/easiest hacks, and once I know the full scope of the required changes, we can think about refactoring code and doing it the proper way.
I put up my own WIP branch and we have a lot of similarities! To minimize the cognitive burden of two distinct branches, here are the details that are relevant to your 10 proposed changes:
- I stored the AuthInfo (diversity, key, ad) directly in Reloc
- I did not use
dyld_chained_ptr_arm64e_bind24
ordyld_chained_ptr_arm64e_bind24
, opting for the non-24'd variants. - I added a
forceOutline
parameter toChainedFixupsSection::addBinding
- I called
writeChainedRebase
directly fromConcatInputSection::writeTo
by adding a way to get authdata from the relocation. - I split the got and added the authgot from the stubs section.
- I don't know what personality functions are (yet!) so I don't think I ported those :P
- Didn't touch any TLV stuff yet
- I just hardcoded this by adding a
LCSourceVersion
class to the writer. - I am not sure what range extension thunks are either, but I imagine getting Clang to sign
objc_msgSend
may be part of swiftlang#8946 as - I don't know what ICF is but FWIW for my test app I see that there are authenticated pointers in these sections:
__objc_const
,__objc_data
,__thread_vars
(__tlv_bootstrap
as you mentioned),__auth_got
,__auth_ptr
,__cfstring
,__const
@oskarwirga part of that PR seems to be already in upstream LLVM. Have you identified specific commits that are missing?
This commit in particular contains the feature hidden behind -fptrauth-objc-isa-mode=sign-and-strip
which adds ptr auth intrinsics to the ObjC IsA data pointer: swiftlang@921369f
I was seeing differing fixups because Apple's Xcode Clang automatically adds that flag when targeting arm64e.
EDIT:
Looks like swiftlang@a42c9e5 and swiftlang@2222706 are also missing but it also looks like some parts made it in in some form, its difficult for me to tell.
Thank you so much for posting the branch! Looks like our changes are indeed very similar :)
I apologize I didn't have time today to work on this, so I don't have any well-thought-out comments at the moment. I do want to iterate on the design before we start merging our changes in order to minimize the arm64e-dependent code paths in the architecture-neutral parts of the code. I'll post my ideas here so we can discuss them.
We'll also need proper lit tests for all of this, which means we'll need to be able to dump the authenticated fixups with llvm-otool
(see e.g. this diff from the original chained fixups implementation). As I'm familiar with this part of the code base, I'd prefer to do this myself.
Also I'd suggest we don't worry about Objective-C right now (especially not with upstream clang...).