This is an archive of the discontinued LLVM Phabricator instance.

Prevent out of range fixup encoding on AArch64
ClosedPublic

Authored by dhoekwater on Jun 13 2023, 10:56 AM.

Details

Summary

The range of a 21-bit signed integer is [-1048576, 1048575],
not [-2097152, 2097151].

Diff Detail

Event Timeline

dhoekwater created this revision.Jun 13 2023, 10:56 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 13 2023, 10:56 AM
dhoekwater requested review of this revision.Jun 13 2023, 10:56 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 13 2023, 10:56 AM
This revision is now accepted and ready to land.Jun 13 2023, 1:24 PM
MaskRay added inline comments.Jun 13 2023, 1:25 PM
llvm/test/MC/AArch64/fixup-out-of-range-edge.s
10

.space 1<<20 is 1MiB (acceptable, but best not to introduce too many tests like this) and .space (1<<27) below needs 128MiB. The large object file makes test slow and won't work on machines with limited disk space. I'd like to hear from other reviewers.

efriedma added inline comments.Jun 13 2023, 2:08 PM
llvm/test/MC/AArch64/fixup-out-of-range-edge.s
10

I get the concern in general, but I don't think this test actually does anything expensive? It should error out before the point we actually try to expand ".space" to raw bits.

efriedma added inline comments.Jun 13 2023, 2:17 PM
llvm/test/MC/AArch64/fixup-out-of-range-edge.s
10

Oh, hmm, I guess we don't actually abort emitting the object file even if there's an error. So that could get a bit expensive. Only in terms of time, though; it doesn't actually seem to use much memory. (And disk space is irrelevant if we're outputting to /dev/null.)

MaskRay accepted this revision.EditedJun 13 2023, 2:31 PM

This is still going to be a bit slow. I think we should use the following scheme to minimize the number of large .space as well as testing the real range.

  FileCheck ... --implicit-check-not=error:

  .p2align 2
# CHECK: [[#@LINE+1]]:3: error: fixup value out of range
  adr x0, adr1-8
  adr x0, adr1-8
adr0:
  .space 1<<20
adr1:
  adr x0, adr0
# CHECK: [[#@LINE+1]]:3: error: fixup value out of range
  adr x0, adr0

Maybe we could add a shortcut to allow testing this sort of thing without trying to pump megabytes of data into the ostream, so we don't have to worry about the compile-time. Might be sufficient to just add a codepath to llvm-mc to force all the output to a raw_null_ostream.

Funny side-note, while experimenting, I found that we don't emit an error if the distance is greater than 4GB. Probably accidentally truncating an offset somewhere.

Code changes LGTM, if you're aiming for completeness, a test for the pc-relative ldr would also be good.

llvm/test/MC/AArch64/fixup-out-of-range-edge.s
62

The fixup fixup_aarch64_ldr_pcrel_imm19 is also affected. May be worth a test case for that as well. I think that would look like

ldr x0, ldr_low_range

Funny side-note, while experimenting, I found that we don't emit an error if the distance is greater than 4GB. Probably accidentally truncating an offset somewhere.

I actually did consider adding a check to ADRP to ensure the distance isn't greater than 4GB (because it only has 21 offset bits to encode the page), but I couldn't quite figure out how to expose that behavior here. Should I add the check? If so, what triple do I have to use to get this function to be called for ADRP fixups?

adr x0, adr1-8

I didn't realize that adding an offset to the destination label like that was valid assembly. In that case, let's not emit any .spaces at all and just use custom offsets. I'll push an updated change in a little bit.

# CHECK: [[#@LINE+1]]:3: error: fixup value out of range
adr_self:
  adr x0, adr_self-(1<<20)-1
dhoekwater marked 4 inline comments as done.

Switch to use manual offsets to reduce runtime

llvm/test/MC/AArch64/fixup-out-of-range-edge.s
10

Replaced with manual offsets, so we don't need to emit any space at all.

62

Done.

Funny side-note, while experimenting, I found that we don't emit an error if the distance is greater than 4GB. Probably accidentally truncating an offset somewhere.

I actually did consider adding a check to ADRP to ensure the distance isn't greater than 4GB (because it only has 21 offset bits to encode the page), but I couldn't quite figure out how to expose that behavior here. Should I add the check? If so, what triple do I have to use to get this function to be called for ADRP fixups?

I saw the 4GB issue with other relocations. Actually, I didn't even try adrp. I don't think we ever use this codepath for adrp.

I saw the 4GB issue with other relocations. Actually, I didn't even try adrp. I don't think we ever use this codepath for adrp.

Oh, interesting! I can't recreate that behavior by manually setting large offsets in this test. What assembly did you use to expose that?

We do use this codepath for adrp, but I guess only on Windows. case AArch64::fixup_aarch64_pcrel_adrp_imm21 handles the fixup, and it's tested in llvm/test/MC/AArch64/fixup-out-of-range.s.

Rebase onto origin/main

Add a newline at the end of the file, for my sanity

I saw the 4GB issue with other relocations. Actually, I didn't even try adrp. I don't think we ever use this codepath for adrp.

Oh, interesting! I can't recreate that behavior by manually setting large offsets in this test. What assembly did you use to expose that?

printf ".Lfoo:\n.space (1<<32)\ntbz x0, #1, .Lfoo\n" | llvm-mc -triple=aarch64 -filetype=obj -o  /dev/null

We do use this codepath for adrp, but I guess only on Windows. case AArch64::fixup_aarch64_pcrel_adrp_imm21 handles the fixup, and it's tested in llvm/test/MC/AArch64/fixup-out-of-range.s.

Oh, right; in that case, the offset isn't the offset relative to the instruction; it's the offset relative to the specified symbol, which needs to be encoded in a 21-bit field. On ELF, we can encode arbitrary offsets (it'll just fail to link if the resolved address isn't close enough).

printf ".Lfoo:\n.space (1<<32)\ntbz x0, #1, .Lfoo\n" | llvm-mc -triple=aarch64 -filetype=obj -o /dev/null

This looks like it's caused by // uint32_t Offset = Layout.getFragmentOffset(DF) + Fixup.getOffset(); in MCAssembler.cpp:276, which should be replaced with a uint64_t. I can't find a good way to test it without writing .space 1 << 32, which isn't preferable for the reasons listed above. I'm actually not sure where it should be fixed/tested, as it's not AArch64-specific.

This revision was automatically updated to reflect the committed changes.

I'm actually not sure where it should be fixed/tested, as it's not AArch64-specific.

MC-level tests are all target-dependent to some extent, so all tests specify some target. We don't have a way to specify "any ELF target". Given that, just pick a target and write a test.