[lld-macho] Ensure segments are laid out contiguously

codesign/libstuff checks that the `__LLVM` segment is directly
before `__LINKEDIT` by checking that `fileOff + fileSize == next segment
fileOff`. Previously, there would be gaps between the segments due to
the fact that their fileOffs are page-aligned but their fileSizes
aren't. In order to satisfy codesign, we page-align fileOff *before*
calculating fileSize. (I don't think codesign checks for the relative
ordering of other segments, so in theory we could do this just for
`__LLVM`, but ld64 seems to do it for all segments.)

Note that we *don't* round up the fileSize of the `__LINKEDIT` segment.
Since it's the last segment, it doesn't need to worry about contiguity;
in addition, codesign checks that the last (hidden) section in
`__LINKEDIT` covers the last byte of the segment, so if we rounded up
`__LINKEDIT`'s size we would have to do the same for its last section,
which is a bother.

While at it, I also addressed a FIXME in the linkedit-contiguity.s test
to cover more `__LINKEDIT` sections.

Reviewed By: #lld-macho, thakis, alexshap

Differential Revision: https://reviews.llvm.org/D100848
This commit is contained in:
Jez Ng 2021-04-20 16:58:07 -04:00
parent 1aa29dffce
commit bb62ef9943
7 changed files with 95 additions and 49 deletions

View File

@ -47,6 +47,7 @@ public:
uint64_t fileOff = 0;
uint64_t fileSize = 0;
uint64_t vmSize = 0;
StringRef name;
uint32_t maxProt = 0;
uint32_t initProt = 0;

View File

@ -197,17 +197,11 @@ public:
return;
c->vmaddr = seg->firstSection()->addr;
c->vmsize =
seg->lastSection()->addr + seg->lastSection()->getSize() - c->vmaddr;
c->vmsize = seg->vmSize;
c->filesize = seg->fileSize;
c->nsects = seg->numNonHiddenSections();
for (const OutputSection *osec : seg->getSections()) {
if (!isZeroFill(osec->flags)) {
assert(osec->fileOff >= seg->fileOff);
c->filesize = std::max<uint64_t>(
c->filesize, osec->fileOff + osec->getFileSize() - seg->fileOff);
}
if (osec->isHidden())
continue;
@ -682,6 +676,7 @@ static int segmentOrder(OutputSegment *seg) {
.Case(segment_names::text, -3)
.Case(segment_names::dataConst, -2)
.Case(segment_names::data, -1)
.Case(segment_names::llvm, std::numeric_limits<int>::max() - 1)
// Make sure __LINKEDIT is the last segment (i.e. all its hidden
// sections must be ordered after other sections).
.Case(segment_names::linkEdit, std::numeric_limits<int>::max())
@ -855,15 +850,26 @@ template <class LP> void Writer::createOutputSections() {
void Writer::finalizeAddresses() {
TimeTraceScope timeScope("Finalize addresses");
uint64_t pageSize = target->getPageSize();
// Ensure that segments (and the sections they contain) are allocated
// addresses in ascending order, which dyld requires.
//
// Note that at this point, __LINKEDIT sections are empty, but we need to
// determine addresses of other segments/sections before generating its
// contents.
for (OutputSegment *seg : outputSegments)
if (seg != linkEditSegment)
assignAddresses(seg);
for (OutputSegment *seg : outputSegments) {
if (seg == linkEditSegment)
continue;
assignAddresses(seg);
// codesign / libstuff checks for segment ordering by verifying that
// `fileOff + fileSize == next segment fileOff`. So we call alignTo() before
// (instead of after) computing fileSize to ensure that the segments are
// contiguous. We handle addr / vmSize similarly for the same reason.
fileOff = alignTo(fileOff, pageSize);
addr = alignTo(addr, pageSize);
seg->vmSize = addr - seg->firstSection()->addr;
seg->fileSize = fileOff - seg->fileOff;
}
// FIXME(gkm): create branch-extension thunks here, then adjust addresses
}
@ -883,12 +889,12 @@ void Writer::finalizeLinkEditSegment() {
// Now that __LINKEDIT is filled out, do a proper calculation of its
// addresses and offsets.
assignAddresses(linkEditSegment);
// No need to page-align fileOff / addr here since this is the last segment.
linkEditSegment->vmSize = addr - linkEditSegment->firstSection()->addr;
linkEditSegment->fileSize = fileOff - linkEditSegment->fileOff;
}
void Writer::assignAddresses(OutputSegment *seg) {
uint64_t pageSize = target->getPageSize();
addr = alignTo(addr, pageSize);
fileOff = alignTo(fileOff, pageSize);
seg->fileOff = fileOff;
for (OutputSection *osec : seg->getSections()) {
@ -903,7 +909,6 @@ void Writer::assignAddresses(OutputSegment *seg) {
addr += osec->getSize();
fileOff += osec->getFileSize();
}
seg->fileSize = fileOff - seg->fileOff;
}
void Writer::openFile() {

View File

@ -4,6 +4,7 @@
; RUN: opt -module-summary %t/foo.ll -o %t/foo.o
; RUN: %lld -lSystem -bitcode_bundle %t/test.o %t/foo.o -o %t/test
; RUN: llvm-objdump --macho --section=__LLVM,__bundle %t/test | FileCheck %s
; RUN: llvm-readobj --macho-segment %t/test | FileCheck %s --check-prefix=SEGMENT
; CHECK: Contents of (__LLVM,__bundle) section
; CHECK-NEXT: For (__LLVM,__bundle) section: xar header
@ -25,6 +26,32 @@
; CHECK-NEXT: </toc>
; CHECK-NEXT: </xar>
;; __LLVM must directly precede __LINKEDIT.
; SEGMENT: Name: __LLVM
; SEGMENT-NEXT: Size: 152
; SEGMENT-NEXT: vmaddr: 0x[[#%X,LLVM_ADDR:]]
; SEGMENT-NEXT: vmsize: 0x[[#%X,LLVM_VMSIZE:]]
; SEGMENT-NEXT: fileoff: [[#LLVM_OFF:]]
; SEGMENT-NEXT: filesize: [[#LLVM_FILESIZE:]]
; SEGMENT-NEXT: maxprot: rw-
; SEGMENT-NEXT: initprot: rw-
; SEGMENT-NEXT: nsects: 1
; SEGMENT-NEXT: flags: 0x0
; SEGMENT-NEXT: }
; SEGMENT-NEXT: Segment {
; SEGMENT-NEXT: Cmd: LC_SEGMENT_64
; SEGMENT-NEXT: Name: __LINKEDIT
; SEGMENT-NEXT: Size: 72
; SEGMENT-NEXT: vmaddr: 0x[[#LLVM_ADDR + LLVM_VMSIZE]]
; SEGMENT-NEXT: vmsize:
; SEGMENT-NEXT: fileoff: [[#LLVM_OFF + LLVM_FILESIZE]]
; SEGMENT-NEXT: filesize:
; SEGMENT-NEXT: maxprot: r--
; SEGMENT-NEXT: initprot: r--
; SEGMENT-NEXT: nsects: 0
; SEGMENT-NEXT: flags: 0x0
; SEGMENT-NEXT: }
;--- foo.ll
target triple = "x86_64-apple-darwin"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"

View File

@ -45,7 +45,7 @@
# CHECK-NEXT: Name: __bss
# CHECK-NEXT: Segment: __DATA
# CHECK-NEXT: Address:
# CHECK-NEXT: Size: 0x8
# CHECK-NEXT: Size: 0x10000
# CHECK-NEXT: Offset: 0
# CHECK-NEXT: Alignment: 0
# CHECK-NEXT: RelocationOffset: 0x0
@ -92,16 +92,16 @@
# CHECK: Name: __DATA
# CHECK-NEXT: Size:
# CHECK-NEXT: vmaddr:
# CHECK-NEXT: vmsize: 0x14
# CHECK-NEXT: vmsize: 0x11000
# CHECK-NEXT: fileoff:
# CHECK-NEXT: filesize: 8
# CHECK-NEXT: filesize: 4096
# CHECK: Name: FOO
# CHECK-NEXT: Size:
# CHECK-NEXT: vmaddr:
# CHECK-NEXT: vmsize: 0x10
# CHECK-NEXT: vmsize: 0x9000
# CHECK-NEXT: fileoff:
# CHECK-NEXT: filesize: 8
# CHECK-NEXT: filesize: 4096
.globl _main
@ -111,15 +111,15 @@ _main:
retq
.bss
.zero 4
.zero 0x8000
.tbss _foo, 4
.zero 4
.zero 0x8000
.data
.quad 0x1234
.zerofill FOO,bss,_zero_foo,0x8
.zerofill FOO,bss,_zero_foo,0x8000
.section FOO,foo
.quad 123

View File

@ -1,33 +1,36 @@
# REQUIRES: x86
# RUN: mkdir -p %t
# RUN: rm -rf %t; split-file %s %t
## codesign requires that each setion in __LINKEDIT ends where the next one
## starts. This test enforces that invariant.
## TODO: Test other __LINKEDIT sections here as support for them gets added.
## Examples of such sections include the data for LC_CODE_SIGNATURE and
## LC_DATA_IN_CODE.
## It also checks that the last section in __LINKEDIT covers the last byte of
## the segment.
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %p/Inputs/libhello.s \
# RUN: -o %t/libhello.o
# RUN: %lld -dylib \
# RUN: -install_name @executable_path/libhello.dylib %t/libhello.o \
# RUN: -o %t/libhello.dylib
## FIXME: Include LC_DATA_IN_CODE in this test when we add support for it.
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/test.o
# RUN: %lld -o %t/test \
# RUN: -L%t -lhello %t/test.o -lSystem
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/foo.s -o %t/foo.o
# RUN: %lld %t/foo.o -dylib -o %t/libfoo.dylib
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o
# RUN: %lld -lSystem -pie -adhoc_codesign -o %t/test %t/libfoo.dylib %t/test.o
# RUN: llvm-objdump --macho --all-headers %t/test | FileCheck %s
# CHECK: segname __LINKEDIT
# CHECK-NEXT: vmaddr
# CHECK-NEXT: vmsize
# CHECK-NEXT: fileoff [[#LINKEDIT_OFF:]]
# CHECK-NEXT: filesize [[#LINKEDIT_SIZE:]]
# CHECK: cmd LC_DYLD_INFO_ONLY
# CHECK-NEXT: cmdsize 48
# CHECK-NEXT: rebase_off 0
# CHECK-NEXT: rebase_size 0
# CHECK-NEXT: bind_off [[#BIND_OFF:]]
# CHECK-NEXT: rebase_off [[#REBASE_OFF:]]
# CHECK-NEXT: rebase_size [[#REBASE_SIZE:]]
# CHECK-NEXT: bind_off [[#BIND_OFF: REBASE_OFF + REBASE_SIZE]]
# CHECK-NEXT: bind_size [[#BIND_SIZE:]]
# CHECK-NEXT: weak_bind_off 0
# CHECK-NEXT: weak_bind_size 0
# CHECK-NEXT: lazy_bind_off [[#LAZY_OFF: BIND_OFF + BIND_SIZE]]
# CHECK-NEXT: weak_bind_off [[#WEAK_OFF: BIND_OFF + BIND_SIZE]]
# CHECK-NEXT: weak_bind_size [[#WEAK_SIZE:]]
# CHECK-NEXT: lazy_bind_off [[#LAZY_OFF: WEAK_OFF + WEAK_SIZE]]
# CHECK-NEXT: lazy_bind_size [[#LAZY_SIZE:]]
# CHECK-NEXT: export_off [[#EXPORT_OFF: LAZY_OFF + LAZY_SIZE]]
# CHECK-NEXT: export_size [[#EXPORT_SIZE:]]
@ -36,10 +39,20 @@
# CHECK-NEXT: cmdsize
# CHECK-NEXT: dataoff [[#FUNCSTARTS_OFF: EXPORT_OFF + EXPORT_SIZE]]
.text
# CHECK: cmd LC_CODE_SIGNATURE
# CHECK-NEXT: cmdsize 16
# CHECK-NEXT: dataoff [[#SIG_OFF:]]
# CHECK-NEXT: datasize [[#SIG_SIZE: LINKEDIT_OFF + LINKEDIT_SIZE - SIG_OFF]]
#--- foo.s
.globl _foo, _weak_foo
.weak_definition _weak_foo
_foo:
_weak_foo:
#--- test.s
.globl _main
_main:
sub $8, %rsp # 16-byte-align the stack; dyld checks for this
callq _print_hello
add $8, %rsp
callq _foo
callq _weak_foo
ret

View File

@ -26,8 +26,8 @@
# CHECK: Name: maxlen_16ch_name
# CHECK-NEXT: Segment: __TEXT
# CHECK-NEXT: Address:
# CHECK-NEXT: Size: [[#%x, LAST_SEC_SIZE:]]
# CHECK-NEXT: Offset: [[#%u, LAST_SEC_OFF:]]
# CHECK-NEXT: Size:
# CHECK-NEXT: Offset:
# CHECK-NEXT: Alignment: 3
# CHECK-NOT: }
# CHECK: Type: Regular (0x0)
@ -38,7 +38,7 @@
# CHECK-NEXT: vmaddr:
# CHECK-NEXT: vmsize:
# CHECK-NEXT: fileoff: 0
# CHECK-NEXT: filesize: [[#%u, LAST_SEC_SIZE + LAST_SEC_OFF]]
# CHECK-NEXT: filesize: 4096
.text
.align 1

View File

@ -13,7 +13,7 @@
# RUN: llvm-readobj --macho-segment %t/arm64_32 > %t/arm64-32.out
# RUN: echo "Total file size" >> %t/arm64-32.out
# RUN: wc -c %t/arm64_32 >> %t/arm64-32.out
# RUN: FileCheck %s -DSUFFIX= -DPAGEZERO_SIZE=0x1000 -DTEXT_ADDR=0x4000 < %t/arm64-32.out
# RUN: FileCheck %s -DSUFFIX= -DPAGEZERO_SIZE=0x4000 -DTEXT_ADDR=0x4000 < %t/arm64-32.out
## These two segments must always be present at the start of an executable.
# CHECK-NOT: Segment {