This is an archive of the discontinued LLVM Phabricator instance.

[LLD][ELF][ARM] Fix ARM Exidx order for non monotonic section order
ClosedPublic

Authored by psmith on Apr 18 2020, 3:27 AM.

Details

Summary

The contents of the .ARM.exidx section must be ordered by SHF_LINK_ORDER rules. We don't need to know the precise address for this order, but we do need to know the relative order of sections. We have been using the sectionIndex for this purpose, this works when the OutputSection order has a monotonically increasing virtual address, but it is possible to write a linker script with non-monotonically increasing virtual address. For these cases we need to evaluate the base address of the OutputSection so that we can order the .ARM.exidx sections properly.

This change moves the finalisation of .ARM.exidx till after the first call to AssignAddresses. This permits us to sort on virtual address which is linker script safe. It also permits a fix for part of pr44824 where we generate .ARM.exidx section for the vector table when that table is so far away it is out of range of the .ARM.exidx section. This fix will come in a follow up patch.

I think that this also affects the standard SHF_LINK_ORDER, I think resolveSHFLinkOrder() probably needs to be moved to a similar place.

Diff Detail

Event Timeline

psmith created this revision.Apr 18 2020, 3:27 AM
MaskRay added a comment.EditedApr 18 2020, 11:10 PM

The patch looks good. I think applying it does not hurt.

// RUN: .text.1 0x80000200 : AT(0x80000200) { *(.text.1) } \
// RUN: .text.2 0x80000100 : AT(0x80000100) { *(.text.2) } \

This is interesting. Does linux/arch/arm have such a use case where VMA(.text.1) > VMA(.text.2) with optional LMA(.text.1) < LMA(.text.2)? If it does, we probably should let it drop that requirement.

For symbol assignments, lld has such a diagnostic:

void LinkerScript::setDot(Expr e, const Twine &loc, bool inSec) {
  uint64_t val = e().getValue();
  if (val < dot && inSec)
    error(loc + ": unable to move location counter backward for: " +
          ctx->outSec->name);

the only software I know (unintentionally) making use of this property is seabios but I have a patch to fix that (https://mail.coreboot.org/hyperkitty/list/seabios@seabios.org/thread/KBHQU4OLW2THG5Q5F5X7EWTQDJHKNVYC/ )

Not sure how the Linux kernel leverage non-increasing VMAs but the BFD logic does not make me feel comfortable that it can handle various corner cases well:
https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=ld/ldlang.c;hb=49d1d1f53df552f0592b1cc4cc9a74db70d5ffa7#l5782

I think that this also affects the standard SHF_LINK_ORDER, I think resolveSHFLinkOrder() probably needs to be moved to a similar place.

My understanding is that when linked-to sections span multiple output sections, the order is unspecified. That said, I agree that respecting the VMA order instead of the section header table order seems more reasonable.
If we are going to support non-increasing VMAs, making generic SHF_LINK_ORDER aligns with .ARM.exidx should be preferable.
I'll still decipher more about BFD's non-increasing VMA support.

This change moves the finalisation of .ARM.exidx till after the first call to AssignAddresses.

assignAddresses

lld/test/ELF/arm-exidx-script-order.s
2

// REQUIRES: ARM

11

delete excess space before %t

18

Missing CHECK-NEXT:

The patch looks good. I think applying it does not hurt.

// RUN: .text.1 0x80000200 : AT(0x80000200) { *(.text.1) } \
// RUN: .text.2 0x80000100 : AT(0x80000100) { *(.text.2) } \

This is interesting. Does linux/arch/arm have such a use case where VMA(.text.1) > VMA(.text.2) with optional LMA(.text.1) < LMA(.text.2)? If it does, we probably should let it drop that requirement.

Difficult to tell, the linker script is generated from macros and is not easy to understand, I've untangled it a bit and expanded.

__vectors_start = .; .vectors 0xffff0000 : AT(__vectors_start) { *(.vectors) } 
. = __vectors_start + SIZEOF(.vectors); __vectors_end = .; 
__stubs_start = .; .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { *(.stubs) } 
. = __stubs_start + SIZEOF(.stubs); __stubs_end = .
; PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
. = ALIGN(8); 
.init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text .init.text.*) *(.text.startup) *(.meminit.text*) _einittext = .; }
.exit.text : {
 *(.exit.text) *(.text.exit) *(.memexit.text)
}

With a corresponding map file

.vectors        0x00000000ffff0000       0x20 load address 0x0000000080b00000
 *(.vectors)
 .vectors       0x00000000ffff0000       0x20 arch/arm/kernel/entry-armv.o
                0x0000000080b00020                . = (__vectors_start + SIZEOF (.vectors))
                0x0000000080b00020                __vectors_end = .
                0x0000000080b00020                __stubs_start = .

.stubs          0x00000000ffff1000      0x2ac load address 0x0000000080b00020
 *(.stubs)
 .stubs         0x00000000ffff1000      0x2ac arch/arm/kernel/entry-armv.o
                0x00000000ffff1240                vector_fiq
                0x0000000080b002cc                . = (__stubs_start + SIZEOF (.stubs))
                0x0000000080b002cc                __stubs_end = .
                [!provide]                        PROVIDE (vector_fiq_offset = (vector_fiq - ADDR (.vectors)))
                0x0000000080b002d0                . = ALIGN (0x8)

.init.text      0x0000000080b002e0    0x32410
                0x0000000080b002e0                _sinittext = .
 *(.init.text .init.text.*)

For symbol assignments, lld has such a diagnostic:

void LinkerScript::setDot(Expr e, const Twine &loc, bool inSec) {
  uint64_t val = e().getValue();
  if (val < dot && inSec)
    error(loc + ": unable to move location counter backward for: " +
          ctx->outSec->name);

the only software I know (unintentionally) making use of this property is seabios but I have a patch to fix that (https://mail.coreboot.org/hyperkitty/list/seabios@seabios.org/thread/KBHQU4OLW2THG5Q5F5X7EWTQDJHKNVYC/ )

Not sure how the Linux kernel leverage non-increasing VMAs but the BFD logic does not make me feel comfortable that it can handle various corner cases well:
https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=ld/ldlang.c;hb=49d1d1f53df552f0592b1cc4cc9a74db70d5ffa7#l5782

FWIW in embedded systems it is quite common to have linker scripts where different OutputSections need to be copied to various disjoint memory locations and these are often not in order of ascending address. I think it is common for these to be in ascending LMA though. These are almost always done with an explicit assignment of the OutputSection base address though. I will need to search to see what examples I can find online, most of the complicated embedded projects are not open source so it is difficult to get examples.

I think that this also affects the standard SHF_LINK_ORDER, I think resolveSHFLinkOrder() probably needs to be moved to a similar place.

My understanding is that when linked-to sections span multiple output sections, the order is unspecified. That said, I agree that respecting the VMA order instead of the section header table order seems more reasonable.
If we are going to support non-increasing VMAs, making generic SHF_LINK_ORDER aligns with .ARM.exidx should be preferable.
I'll still decipher more about BFD's non-increasing VMA support.

This change moves the finalisation of .ARM.exidx till after the first call to AssignAddresses.

assignAddresses

I'll take a look to see what BFD supports. From looking at the linux kernel example it looks like it is assigning addresses to sections and symbols in a similar way to LLD. Instructions to reproduce the problem are in the PR if you are interested.

2 nits, otherwise LG.

lld/ELF/Writer.cpp
1622

I'd expand this comment a bit.
"relative address of OutputSections, because ... "

lld/test/ELF/arm-exidx-script-order.s
21

Missing an empty line before .text.

MaskRay added inline comments.Apr 21 2020, 2:10 PM
lld/test/ELF/arm-exidx-script-order.s
51

Capitalize

Thanks both for the comments. I'll make an update this evening.

psmith updated this revision to Diff 259367.Apr 22 2020, 12:52 PM

I've updated the tests to address review comments, and have made the addresses increase in LMA. I've got a follow up patch that I can send for SHF_LINK_ORDER as I can reproduce a problem with a similar test case.

MaskRay accepted this revision.Apr 22 2020, 1:05 PM

Thanks for the update. LG with the few test comments addressed, and @grimar's suggestion about expanding a code comment.

This revision is now accepted and ready to land.Apr 22 2020, 1:05 PM
psmith marked an inline comment as done.Apr 22 2020, 1:58 PM

Thanks, and apologies for forgetting George's comment request. I'll upload a new version and commit tomorrow.

lld/test/ELF/arm-exidx-script-order.s
2

I think it has to be all lower case (all other REQUIRES are lower case). If I use ARM then llvm-lit won't recognise it.

psmith updated this revision to Diff 259384.Apr 22 2020, 1:59 PM

Added additional detail in comment and fix up spacing in tests.

This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptApr 23 2020, 8:05 AM

I had to revert due to an msan build bot failure. I don't fully understand it yet http://lab.llvm.org:8011/builders/sanitizer-x86_64-linux-fast/builds/40693/steps/check-lld%20msan/logs/stdio as I can't yet see how script->assignAddresses would not set OutSec->addr. However msan is more likely to be right than me.

I had to revert due to an msan build bot failure. I don't fully understand it yet http://lab.llvm.org:8011/builders/sanitizer-x86_64-linux-fast/builds/40693/steps/check-lld%20msan/logs/stdio as I can't yet see how script->assignAddresses would not set OutSec->addr. However msan is more likely to be right than me.

OK, found it. It wasn't that OutSec->addr hadn't been set, it was that the .ARM.exidx SyntheticSection size was unitialized (it is set by finalize()) and this polluted the addr values calculated by assignAddresses, an update to initialize the size based on an estimate of .ARM.exidx section size pre-compression fixes the problem. Will recommit with fix.