This is an archive of the discontinued LLVM Phabricator instance.

[MC][AArch64] Restrict use of signed relocation operators on MOV[NZK]
Needs ReviewPublic

Authored by peter.smith on Aug 7 2019, 3:54 AM.

Details

Summary

In D64466 we permitted all of the relocation specifiers on any of MOV[NZK]. I think that this was the right thing to do for the unsigned relocation specifiers such as :abs_g*[_nc]:, however some research on how linker's such as ld.bfd and ld.gold handle the signed relocation specifiers makes me think that we should keep the restrictions on the signed specifiers such as :tprel_g*[_nc]:

This change adds back the distinction between movk and movnz, and permits the signed checking relocation specifiers on movz and non-checking on movk. The unsigned relocation specifiers can still be allowed on both. I've added tests for the signed relocation specifiers.

When the result of the operation is positive it doesn't matter whether the
sequence:

movz x0, #:abs_g3:foo
movk x0, #:abs_g2_nc:foo
movk x0, #:abs_g1_nc:foo
movk x0, #:abs_g0_nc:foo

or

movz x0, #:abs_g0_nc:foo
movk x0, #:abs_g1_nc:foo
movk x0, #:abs_g2_nc:foo
movk x0, #:abs_g3:foo

is used as the movz never needs to be transformed into a movn so all the relocations apply correctly on all AArch64 linkers.

When the result of the operation is signed a linker must transform the movz into a movn if the result is negative, and a movn to a movz if the result is positive.
For example:

movz x0, #:prel_g3:foo    // Linker transforms to movn if result negative
movk x0, #:prel_g2_nc:foo
movk x0, #:prel_g1_nc:foo
movk x0, #:prel_g0_nc:foo

The way the ABI is written with the checking forms only permitted on mov[nz] and the non-checking form on movk a linker doesn't need to
disassemble the binary to know that if the result of a checking relocation is negative, it needs to output a movn, and a movz otherwise. The ld.bfd and gold linkers have taken advantage of this and for the signed movw relocations such as R_AARCH64_TLSLE_TPREL_G2 it will write a movz if the result is positive and a movn otherwise, so if this relocation is applied to a movk (which the linker is not expecting) it will overwrite it with the wrong instruction. Similarly if R_AARCH64_TLSLE_TPREL_G0_NC is
applied to a mov[nz] (which the linker is not expecting) it will never change the instruction as it is expecting a movk.

It is true that if a linker disassembles the instruction to find out if it is a mov[zn] or movk and alter its behaviour dependent on the instruction then it doesn't matter whether the checked or non-checked relocation is applied (LLD will do this). However given that the signed case will break on ld.bfd and ld.gold I think we should be conservative in permitting the signed relocation specifiers.

Diff Detail

Event Timeline

peter.smith created this revision.Aug 7 2019, 3:54 AM

I will also need to fix up the LLD test/ELF/aarch64-reloc.s as this is the only place where the signed relocations are used with _nc on mov[nz] and the checked on movk. A simple change that works is:

 .section .R_AARCH64_MOVW_PREL,"ax",@progbits
    movz x1, #:prel_g0:.+1
-   movz x1, #:prel_g0_nc:.-1
-   movk x1, #:prel_g0:.+1
+   movn x1, #:prel_g0:.-1
+   movk x1, #:prel_g0_nc:.+1
    movk x1, #:prel_g0_nc:.-1
    movz x2, #:prel_g1:.+0x20000
-   movz x2, #:prel_g1_nc:.-0x20000
-   movk x2, #:prel_g1:.+0x20000
+   movn x2, #:prel_g1:.-0x20000
+   movk x2, #:prel_g1_nc:.+0x20000
    movk x2, #:prel_g1_nc:.-0x20000
    movz x3, #:prel_g2:.+0x300000000
-   movz x3, #:prel_g2_nc:.-0x300000000
-   movk x3, #:prel_g2:.+0x300000000
+   movn x3, #:prel_g2:.-0x300000000
+   movk x3, #:prel_g2_nc:.+0x300000000
    movk x3, #:prel_g2_nc:.-0x300000000
    movz x3, #:prel_g2:.+0x300000000
    movz x4, #:prel_g3:.+0x4000000000000
    movz x4, #:prel_g3:.-0x4000000000000
-   movk x4, #:prel_g3:.+0x4000000000000
-   movk x4, #:prel_g3:.-0x4000000000000
 
 # CHECK: Disassembly of section .R_AARCH64_MOVW_PREL:
 # CHECK-EMPTY:
@@ -253,5 +251,3 @@ movz1:
 ## 1125899906842624 = 0x4000000000000
 # CHECK-NEXT: 2100d0: 84 00 e0 d2  mov x4, #1125899906842624
 # CHECK-NEXT: 2100d4: 84 ff ff d2  mov x4, #-1125899906842624
-# CHECK-NEXT: 2100d8: 84 00 e0 f2  movk        x4, #4, lsl #48
-# CHECK-NEXT: 2100dc: 84 ff ff f2  movk        x4, #65532, lsl #48

This doesn't test the LLD specific parts that can handle those cases, I can write a YAML test for that separately.

pcc added a comment.Aug 7 2019, 10:23 AM

MHO: The assembler is a low enough level component that the user can be presumed to know what they're doing, regardless of linker limitations. So I would prefer not to do this. If we do anything about this, we should document the limitations of the GNU linkers somewhere.

lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
965

We produce MOVK with PREL_G3 when the tagged-globals target feature is enabled, so we should allow this combination at least.

peter.smith marked an inline comment as done.Aug 8 2019, 6:17 AM
In D65857#1619366, @pcc wrote:

MHO: The assembler is a low enough level component that the user can be presumed to know what they're doing, regardless of linker limitations. So I would prefer not to do this. If we do anything about this, we should document the limitations of the GNU linkers somewhere.

It is a tricky balance, we don't want to rule out a reasonable use case, but ideally we want to detect problems as soon as possible. I think what I have here may be overly strict as if you happen to know that the result of a signed operation is positive then this can work, or if you happen to know you are linking with LLD.

One possible compromise is some kind of strict mode, something like --strict-movw-relocs that people could enable to restrict the relocations to a GNU compatible subset for those that need it. The command line option could also act as a kind of documentation that is a bit more visible. Any thoughts?

lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
965

I hadn't realised that this was actively being used. Are you doing something like:

movz x0, :prel_g0_nc: foo
movk x0, :prel_g1_nc: foo
movk x0, :prel_g2_nc: foo
movk x0, :prel_g3: foo

This will work in BFD and gold if the result is positive as bit 30 is already 1 in a MOVK so orring with 1 won't change it, however if the result is negative then bfd/gold will clear bit 30, which will result in an OPC of 0 1 which is MOV <register> , this will likely result in a silent failure. Similarly as VK_PREL_G* is not present in fixMOVZ it won't get altered to a MOVN so the non-checking g0 won't alter it.

If you are happy with those restrictions, for example the offset from code to data in LD.bfd with the default linker script is always going to be positive, or are willing to only support the feature on LLD, then we should support it. I'm a bit nervous about fixMOVZ though.

pcc added a comment.Aug 8 2019, 9:43 AM
In D65857#1619366, @pcc wrote:

MHO: The assembler is a low enough level component that the user can be presumed to know what they're doing, regardless of linker limitations. So I would prefer not to do this. If we do anything about this, we should document the limitations of the GNU linkers somewhere.

It is a tricky balance, we don't want to rule out a reasonable use case, but ideally we want to detect problems as soon as possible. I think what I have here may be overly strict as if you happen to know that the result of a signed operation is positive then this can work, or if you happen to know you are linking with LLD.

One possible compromise is some kind of strict mode, something like --strict-movw-relocs that people could enable to restrict the relocations to a GNU compatible subset for those that need it. The command line option could also act as a kind of documentation that is a bit more visible. Any thoughts?

Sure, that would be fine with me.

lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
965

What I am doing is (see D65364 for more information):

adrp x0, :pg_hi21_nc:global
movk x0, #:prel_g3:global+4294967296
add x0, x0, :lo12:global

The second instruction is taking the pointer tag (i.e. a TBI pointer tag with bits 56-63 potentially set) from the address of global and combining it into x0. Note that the tagged-globals feature is only activated when passing -fsanitize=hwaddress.

It's possible for this offset to be "negative" in the sense that the pointer tag may be >= 0x80. From reading the ld.bfd source code it does appear that the linker will mangle the instruction in this case. On Android we currently require the use of lld to be able to use HWASAN since we use old versions of the GNU linkers that don't support the PREL relocations and are unlikely to upgrade them, so I think we are safe to use all 8 bits of the tag space. We may need to restrict the tag entropy to 7 bits on other operating systems in order to work around the GNU linker issue.

I'm a bit nervous about fixMOVZ though.

It looks like fixMOVZ is only called for actual MOVZ instructions, unless I'm missing something.
http://llvm-cs.pcc.me.uk/gen/lib/Target/AArch64/AArch64GenMCCodeEmitter.inc#9526

peter.smith marked an inline comment as done.Aug 8 2019, 10:01 AM

Thanks for getting back to me, I'll look into making a strict mode. I'll also mention the use case you've identified with movk to my colleague from our GCC team, it is a pity that we don't have a R_AARCH64_PREL_G3_NC for the movk as although not ideal, as it is skipping an overflow check, it would permit GNU ld to be extended whilst retaining the property that you don't need to disassemble the binary to know how to evaluate a relocation.

lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
965

For the movz case I was worried about:

movz x0, :prel_g0_nc: foo
movk x0, :prel_g1_nc: foo
movk x0, :prel_g2_nc: foo
movk x0, :prel_g3: foo

which wouldn't work on BFD if the offset were always positive and fixMOVZ turned movz into movn, but as you aren't using movz then it doesn't matter.

pcc added a comment.Feb 16 2023, 3:47 PM

Thanks for getting back to me, I'll look into making a strict mode. I'll also mention the use case you've identified with movk to my colleague from our GCC team, it is a pity that we don't have a R_AARCH64_PREL_G3_NC for the movk as although not ideal, as it is skipping an overflow check, it would permit GNU ld to be extended whilst retaining the property that you don't need to disassemble the binary to know how to evaluate a relocation.

Did you ever get a chance to mention this to the GCC team? I've heard that binutils is still relocating MOVK into an illegal instruction.

Herald added a project: Restricted Project. · View Herald TranscriptFeb 16 2023, 3:47 PM