Skip to content

Conversation

MaskRay
Copy link
Member

@MaskRay MaskRay commented May 16, 2025

When using -no-pie without a SECTIONS command, the linker uses the
target's default image base. If -Ttext= or --section-start specifies
an output section address below this base, the result is likely
unintended.

  • With --no-rosegment, the PT_LOAD segment covering the ELF header cannot include .text if .text's address is too low, causing an error: output file too large.
  • With default --rosegment:
    • If a read-only section (e.g., .rodata) exists, a similar error: output file too large occurs.
    • Without read-only sections, the PT_LOAD segment covering the ELF header and program headers includes no sections, which is unusual and likely undesired. This also causes non-ascending PT_LOAD p_vaddr values related to the PT_LOAD that overlaps with PT_PHDR ([lld] Unsorted loadable program headers with -Ttext= argument #138584).

To prevent these issues, report an error if a section address is below
the image base and suggest --image-base. This check also applies when
--image-base is explicitly set but is skipped when a SECTIONS
command is used.

Created using spr 1.3.5-bogner
@llvmbot
Copy link
Member

llvmbot commented May 16, 2025

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-elf

Author: Fangrui Song (MaskRay)

Changes

When -Ttext= or --section-start specifies an address lower than the
image base, this creates a read-only PT_LOAD segment covering the ELF
and program headers but not any sections. This is likely unintentional
and may result in non-ascending PT_LOAD segment p_vaddr values, which
some loaders reject (#138584). Warn and suggest --image-base in this scenario.


Full diff: https://github.com/llvm/llvm-project/pull/140187.diff

4 Files Affected:

  • (modified) lld/ELF/Writer.cpp (+23-9)
  • (modified) lld/test/ELF/linkerscript/out-of-order.s (+12)
  • (modified) lld/test/ELF/linkerscript/section-align2.test (+1-1)
  • (modified) lld/test/ELF/sectionstart.s (+13-4)
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 6a0552e808c7b..ea11841731d27 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -1615,17 +1615,31 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
       sec->addr = 0;
 
   // If addrExpr is set, the address may not be a multiple of the alignment.
-  // Warn because this is error-prone.
-  for (SectionCommand *cmd : ctx.script->sectionCommands)
-    if (auto *osd = dyn_cast<OutputDesc>(cmd)) {
-      OutputSection *osec = &osd->osec;
-      if (osec->addr % osec->addralign != 0)
-        Warn(ctx) << "address (0x" << Twine::utohexstr(osec->addr)
-                  << ") of section " << osec->name
-                  << " is not a multiple of alignment (" << osec->addralign
-                  << ")";
+  // Warn because this is error-prone. In addition, warn if the address
+  // is smaller than the image base when SECTIONS is absent (e.g. when -Ttext
+  // is specified and smaller than the default target image base for no-pie).
+  uint64_t imageBase = ctx.script->hasSectionsCommand || ctx.arg.relocatable
+                           ? 0
+                           : ctx.target->getImageBase();
+  for (SectionCommand *cmd : ctx.script->sectionCommands) {
+    auto *osd = dyn_cast<OutputDesc>(cmd);
+    if (!osd)
+      continue;
+    OutputSection *osec = &osd->osec;
+    if (osec->addr < imageBase && (osec->flags & SHF_ALLOC)) {
+      Warn(ctx) << "section '" << osec->name << "' address (0x"
+                << Twine::utohexstr(osec->addr)
+                << ") is smaller than image base (0x"
+                << Twine::utohexstr(imageBase) << "); specify --image-base";
     }
 
+    if (osec->addr % osec->addralign != 0)
+      Warn(ctx) << "address (0x" << Twine::utohexstr(osec->addr)
+                << ") of section " << osec->name
+                << " is not a multiple of alignment (" << osec->addralign
+                << ")";
+  }
+
   // Sizes are no longer allowed to grow, so all allowable spills have been
   // taken. Remove any leftover potential spills.
   ctx.script->erasePotentialSpillSections();
diff --git a/lld/test/ELF/linkerscript/out-of-order.s b/lld/test/ELF/linkerscript/out-of-order.s
index 9b834cf712038..2f990e0205f0e 100644
--- a/lld/test/ELF/linkerscript/out-of-order.s
+++ b/lld/test/ELF/linkerscript/out-of-order.s
@@ -31,6 +31,18 @@
 # CHECK-NEXT:   5 .hash         00000010 000000000000201c
 # CHECK-NEXT:   6 .text         00000008 000000000000202c
 
+# RUN: ld.lld -e 0 -o %t --script %t.script %t.o --fatal-warnings
+# RUN: llvm-readelf -Sl %t | FileCheck %s --check-prefix=CHECK1
+
+# CHECK1:       Name              Type            Address          Off    Size   ES Flg Lk Inf Al
+# CHECK1-NEXT:                    NULL            0000000000000000 000000 000000 00      0   0  0
+# CHECK1-NEXT:  .text             PROGBITS        0000000000000000 001000 000008 00  AX  0   0  4
+# CHECK1-NEXT:  .data             PROGBITS        0000000000004000 002000 000008 00  WA  0   0  1
+# CHECK1:     Program Headers:
+# CHECK1-NEXT:  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
+# CHECK1-NEXT:  LOAD           0x001000 0x0000000000000000 0x0000000000000000 0x000008 0x000008 R E 0x1000
+# CHECK1-NEXT:  LOAD           0x002000 0x0000000000004000 0x0000000000004000 0x000008 0x000008 RW  0x1000
+
 .quad 0
 .data
 .quad 0
diff --git a/lld/test/ELF/linkerscript/section-align2.test b/lld/test/ELF/linkerscript/section-align2.test
index 3eb42cadc059e..3269633b92930 100644
--- a/lld/test/ELF/linkerscript/section-align2.test
+++ b/lld/test/ELF/linkerscript/section-align2.test
@@ -10,7 +10,7 @@
 # RUN: llvm-readelf -S %t | FileCheck %s
 
 ## Check we don't warn in the absence of SECTIONS.
-# RUN: ld.lld --fatal-warnings -Ttext=0x10000 %t.o -o /dev/null
+# RUN: ld.lld --fatal-warnings -Ttext=0x10000 --image-base=0x10000 %t.o -o /dev/null
 
 # WARN: warning: address (0x10004) of section .data.rel.ro is not a multiple of alignment (16)
 # WARN: warning: address (0x20001) of section .data2 is not a multiple of alignment (8)
diff --git a/lld/test/ELF/sectionstart.s b/lld/test/ELF/sectionstart.s
index d694c9375fd80..a1202bc185a70 100644
--- a/lld/test/ELF/sectionstart.s
+++ b/lld/test/ELF/sectionstart.s
@@ -1,9 +1,13 @@
 # REQUIRES: x86
 # RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
 # RUN: ld.lld %t.o --section-start .text=0x100000 \
-# RUN:   --section-start=.data=0x110000 --section-start .bss=0x200000 -o %t
+# RUN:   --section-start=.data=0x110000 --section-start .bss=0x200000 -o %t 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=LINK --implicit-check-not=warning:
 # RUN: llvm-objdump --section-headers %t | FileCheck %s
 
+# LINK:      warning: section '.text' address (0x100000) is smaller than image base (0x200000); specify --image-base
+# LINK-NEXT: warning: section '.data' address (0x110000) is smaller than image base (0x200000); specify --image-base
+
 # CHECK:      Sections:
 # CHECK-NEXT:  Idx Name          Size     VMA              Type
 # CHECK-NEXT:    0               00000000 0000000000000000
@@ -11,9 +15,14 @@
 # CHECK-NEXT:    2 .data         00000004 0000000000110000 DATA
 # CHECK-NEXT:    3 .bss          00000004 0000000000200000 BSS
 
-## The same, but dropped "0x" prefix.
-# RUN: ld.lld %t.o --section-start .text=100000 \
-# RUN:   --section-start .data=110000 --section-start .bss=0x200000 -o %t1
+## The warnings go away when the image base is 0.
+# RUN: ld.lld %t.o -pie --section-start .text=0x100000 \
+# RUN:   --section-start=.data=0x110000 --section-start .bss=0x200000 -o %t --noinhibit-exec
+# RUN: llvm-objdump --section-headers %t | FileCheck %s
+
+## The same, but dropped "0x" prefix. Specify a smaller --image-base to suppress warnings.
+# RUN: ld.lld %t.o --image-base=0x90000 --section-start .text=100000 \
+# RUN:   --section-start .data=110000 --section-start .bss=0x200000 -o %t1 --noinhibit-exec
 # RUN: llvm-objdump --section-headers %t1 | FileCheck %s
 
 ## Use -Ttext, -Tdata, -Tbss as replacement for --section-start:

Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish GNU ld had a better specification of what these options are meant to do.

I've set approved as a warning is definitely an improvement. I've got a few suggestions on some alternatives but I don't have a particularly strong case for any of them as this is likely a corner case.

It looks like we're defining --image-base to be the address of the first loadable segment (and not specifically the first loadable text segment as in ld.bfd, or the first loadable ro-data segment using lld order). With that definition any section placed lower than --image-base is now generating a warning, and we're generating a loadable program header (potentially out of order for it anyway).

There's a few alternatives we could consider:

  • It is an error if a SHF_ALLOC is out of bounds of the lowest loadable segment.
  • We take the instruction literally and the out of bounds section does not get a program header, this includes potentially making the ELF header and segement header non allocateable.
  • We could lower (with a warning) the image base to the lowest allocateable section.
    A possible addition when this occurs is to set the headers as not allocatable so we don't create the program header for the ELF header and Program Header.

I've got a mild preference to making this an error.

As an aside the ELF spec can be satisfied by sorting the loadable program header entries by p_vaddr. The segments in the file don't have to be in order if we're going by the letter of the specification. That could be worth doing anyway to help out people with strict loaders but without a linker script (or suitable command-line option) that has ascending segment p_vaddr order.

@MaskRay
Copy link
Member Author

MaskRay commented May 16, 2025

Thanks for the analysis!
The GNU ld's text segment concept, often (ab)used to specify the image base, doesn't align well with our layout, which places .rodata before .text and uses the --rosegment option.
Our layout offers advantages like better -z separate-segment and PT_GNU_RELRO support with smaller file size, but it requires adjustments when users specify -Ttext or -Ttext-segment options.
To maintain simpler layout rules, we intentionally reject certain GNU ld behaviors, such as -Ttext-segment (https://reviews.llvm.org/D70468).

When there is a PT_LOAD segment that covers ELF header/program headers but no sections

  • In the default --ro-segment case, the output is correct, though it may appear unusual (ignoring the non-ascending p_vaddr for now).
  • In the --no-rosegment case, an error: output file too large occurs.

I agree that this diagnostic should be elevated to an error for clarity.
I have mild preference that we don't change program header allocation behavior

37 tests that use -Ttext= will need adjustment.

Failed Tests (37):
  lld :: ELF/aarch64-reloc-implicit-addend.test
  lld :: ELF/aarch64-thunk-align.s
  lld :: ELF/arm-cmse-diagnostics.s
  lld :: ELF/arm-cmse-implib.s
  lld :: ELF/arm-cmse-keep-sections.s
  lld :: ELF/arm-cmse-noveneers.s
  lld :: ELF/arm-cmse-secure.s
  lld :: ELF/arm-cmse-veneers.s
  lld :: ELF/avr-reloc-error.s
  lld :: ELF/basic-avr.s
  lld :: ELF/compress-debug-sections-reloc.s
  lld :: ELF/eh-frame-value-format7.s
  lld :: ELF/eh-frame-value-format8.s
  lld :: ELF/gdb-index-dwarf5-type-unit.s
  lld :: ELF/gdb-index-icf.s
  lld :: ELF/i386-pc16.test
  lld :: ELF/i386-pc8.s
  lld :: ELF/i386-reloc-16-large-addend.s
  lld :: ELF/i386-reloc-8-large-addend.s
  lld :: ELF/i386-reloc-large-addend.s
  lld :: ELF/i386-reloc-range.s
  lld :: ELF/i386-reloc8-reloc16-addend.s
  lld :: ELF/just-symbols-cref.s
  lld :: ELF/just-symbols.s
  lld :: ELF/linkerscript/avr5.test
  lld :: ELF/linkerscript/memory-no-sections.test
  lld :: ELF/loongarch-pc-aligned.s
  lld :: ELF/msp430.s
  lld :: ELF/nmagic.s
  lld :: ELF/oformat-binary-ttext.s
  lld :: ELF/sectionstart-noallochdr.s
  lld :: ELF/systemz-reloc-disp12.s
  lld :: ELF/systemz-reloc-disp20.s
  lld :: ELF/systemz-reloc-pc16.s
  lld :: ELF/systemz-reloc-pc32.s
  lld :: ELF/x86-64-reloc-32.s
  lld :: ELF/x86-64-reloc-pc32.s

(Assemble sectionstart.s to a.o)

% /tmp/Debug/bin/ld.lld a.o -Ttext=0x3000 -o a && readelf -W -Sl a
ld.lld: warning: section '.text' address (0x3000) is smaller than image base (0x200000); specify --image-base
ld.lld: warning: section '.data' address (0x4001) is smaller than image base (0x200000); specify --image-base
ld.lld: warning: section '.bss' address (0x4005) is smaller than image base (0x200000); specify --image-base
There are 8 section headers, starting at offset 0x1088:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000003000 001000 000001 00  AX  0   0  4
  [ 2] .data             PROGBITS        0000000000004001 001001 000004 00  WA  0   0  1
  [ 3] .bss              NOBITS          0000000000004005 001005 000004 00  WA  0   0  1
  [ 4] .comment          PROGBITS        0000000000000000 001005 000013 01  MS  0   0  1
  [ 5] .symtab           SYMTAB          0000000000000000 001018 000030 18      7   1  8
  [ 6] .shstrtab         STRTAB          0000000000000000 001048 000035 00      0   0  1
  [ 7] .strtab           STRTAB          0000000000000000 00107d 000008 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

Elf file type is EXEC (Executable file)
Entry point 0x3000
There are 5 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000200040 0x0000000000200040 0x000118 0x000118 R   0x8
  LOAD           0x000000 0x0000000000200000 0x0000000000200000 0x000158 0x000158 R   0x1000
  LOAD           0x001000 0x0000000000003000 0x0000000000003000 0x000001 0x000001 R E 0x1000
  LOAD           0x001001 0x0000000000004001 0x0000000000004001 0x000004 0x000008 RW  0x1000
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0

 Section to Segment mapping:
  Segment Sections...
   00
   01
   02     .text
   03     .data .bss
   04

% /tmp/Debug/bin/ld.lld --no-rosegment -Ttext 0x3000 a.o -o a3 && readelf -W -Sl a3
ld.lld: warning: section '.text' address (0x3000) is smaller than image base (0x200000); specify --image-base
ld.lld: warning: section '.data' address (0x4001) is smaller than image base (0x200000); specify --image-base
ld.lld: warning: section '.bss' address (0x4005) is smaller than image base (0x200000); specify --image-base
ld.lld: error: output file too large: 18446744073707467400 bytes
section sizes:
.text 1
.data 4
.bss 4
.comment 19
.symtab 48
.shstrtab 53
.strtab 8

MaskRay added a commit that referenced this pull request May 17, 2025
…ss sensitive

Many tests specify -Ttext, -Tdata, or --section-start without SECTIONS
commands. When the specified address is lower than the image base,

* In the default --ro-segment case, there will be a read-only PT_LOAD segment covering ELF header/program headers, which may cover no section (as many tests omit .rodata). This appears unusual and has non-ascending p_vaddr.
* In the --no-rosegment case, an `error: output file too large` occurs.

We will add a diagnostic (#140187) and possibly change layout. Add
--image-base to make these tests less sensitive.
Created using spr 1.3.5-bogner
@MaskRay MaskRay changed the title [ELF] Warn if specified section address is smaller than image base [ELF] Error if a section address is smaller than image base May 17, 2025
Copy link
Collaborator

@smithp35 smithp35 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to error LGTM.

Looks to be some Bolt tests using --section-start that need updating (details in CI) as these are triggering the error.

We probably should add this change to the release notes as a potentially breaking change (without using --image-base).

MaskRay added a commit that referenced this pull request May 21, 2025
When using -no-pie without a SECTIONS command, the linker uses the
target's default image base. If -Ttext= or --section-start specifies an
output section address below this base, the result is likely unintended.
LLD will give a diagnostic (#140187) and may change the behavior in the future.
It's good to set an explicit image base to avoid relying on its current
behavior. BOLT doesn't seem to care whether a PT_PHDR segment is
present.

Pull Request: #140570
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner
@MaskRay MaskRay merged commit 0edc8b5 into main May 21, 2025
7 of 10 checks passed
@MaskRay MaskRay deleted the users/MaskRay/spr/elf-warn-if-specified-section-address-is-smaller-than-image-base branch May 21, 2025 16:19
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 21, 2025
When using `-no-pie` without a `SECTIONS` command, the linker uses the
target's default image base. If `-Ttext=` or `--section-start` specifies
an output section address below this base, the result is likely
unintended.

- With `--no-rosegment`, the PT_LOAD segment covering the ELF header cannot include `.text` if `.text`'s address is too low, causing an `error: output file too large`.
- With default `--rosegment`:
  - If a read-only section (e.g., `.rodata`) exists, a similar `error: output file too large` occurs.
  - Without read-only sections, the PT_LOAD segment covering the ELF header and program headers includes no sections, which is unusual and likely undesired. This also causes non-ascending PT_LOAD `p_vaddr` values related to the PT_LOAD that overlaps with PT_PHDR (#138584).

To prevent these issues, report an error if a section address is below
the image base and suggest `--image-base`. This check also applies when
`--image-base` is explicitly set but is skipped when a `SECTIONS`
command is used.

Pull Request: llvm/llvm-project#140187
MaskRay pushed a commit that referenced this pull request May 22, 2025
similar to #140570

getting this error:

exit status 1
ld.lld: error: section '.text' address (0x8074) is smaller than image
base (0x10000); specify --image-base
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants