Index: test/tools/llvm-objcopy/binary-align-first.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/binary-align-first.test @@ -0,0 +1,34 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R=.note -O binary %t %t2 +# RUN: od -Ax -t x1 %t2 | FileCheck %s + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .note + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 32 + - Name: .rodata + Flags: [ SHF_ALLOC ] + Type: SHT_PROGBITS + Address: 0x1020 + Size: 4064 + Content: "DEADBEEF" +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .note + - Section: .rodata + +# CHECK: 000000 de ad be ef Index: test/tools/llvm-objcopy/binary-remove-all-but-one.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/binary-remove-all-but-one.test @@ -0,0 +1,46 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text -R .text3 -O binary %t %t2 +# RUN: od -Ax -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Section: .text3 + +# CHECK: 000000 de ad be ef + +# SIZE: 4096 Index: test/tools/llvm-objcopy/binary-remove-end.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/binary-remove-end.test @@ -0,0 +1,47 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text3 -O binary %t %t2 +# RUN: od -Ax -v -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Section: .text3 + +# CHECK: 000000 c3 c3 c3 c3 +# CHECK: 001000 de ad be ef + +# SIZE: 8192 Index: test/tools/llvm-objcopy/binary-remove-middle.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/binary-remove-middle.test @@ -0,0 +1,48 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text2 -O binary %t %t2 +# RUN: od -Ax -v -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Section: .text3 + +# CHECK: 000000 c3 c3 c3 c3 +# CHECK: 001000 00 00 00 00 +# CHECK: 002000 32 32 32 32 + +# SIZE: 12288 Index: test/tools/llvm-objcopy/parent-loop-check.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/parent-loop-check.test @@ -0,0 +1,50 @@ +# This test has a subtle purpose. Because of the specifics of the layout +# algorithm if we're not careful cycles can occur when we resolve the order +# that we should layout segments in. In this test we're making sure that the +# PT_LOAD segment won't set the PT_NOTE segment as the first to be laid out +# while the PT_NOTE segment sets the PT_LOAD load to be the first to be laid +# out. This problem becomes visible if the layout of the file changes due to +# the removal of a section. We use -O binary here because removing a section +# in this way won't cause the location of the PT_LOAD segment to change if we +# don't. + +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R=.note -O binary %t %t2 +# RUN: od -Ax -t x1 %t2 | FileCheck %s + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .note + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 32 + - Name: .rodata + Flags: [ SHF_ALLOC ] + Type: SHT_PROGBITS + Address: 0x1020 + Size: 4064 + Content: "DEADBEEF" +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .note + - Section: .rodata + - Type: PT_NOTE + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .note + +# CHECK: 000000 de ad be ef Index: test/tools/llvm-objcopy/two-seg-remove-end.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/two-seg-remove-end.test @@ -0,0 +1,61 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text4 -O binary %t %t2 +# RUN: od -Ax -v -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 + - Name: .text4 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x4000 + AddressAlign: 0x1000 + Content: "FFFFFFFF" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text3 + - Section: .text4 + +# CHECK: 000000 c3 c3 c3 c3 +# CHECK: 001000 de ad be ef +# CHECK: 002000 32 32 32 32 + +# SIZE: 12288 Index: test/tools/llvm-objcopy/two-seg-remove-first.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/two-seg-remove-first.test @@ -0,0 +1,61 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text -O binary %t %t2 +# RUN: od -Ax -v -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 + - Name: .text4 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x4000 + AddressAlign: 0x1000 + Content: "FFFFFFFF" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text3 + - Section: .text4 + +# CHECK: 000000 de ad be ef +# CHECK: 001000 32 32 32 32 +# CHECK: 002000 ff ff ff ff + +# SIZE: 12288 Index: test/tools/llvm-objcopy/two-seg-remove-third-sec.test =================================================================== --- /dev/null +++ test/tools/llvm-objcopy/two-seg-remove-third-sec.test @@ -0,0 +1,62 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy -R .text3 -O binary %t %t2 +# RUN: od -Ax -v -t x1 %t2 | FileCheck %s +# RUN: wc -c %t2 | FileCheck %s -check-prefix=SIZE + +!ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x1000 + Content: "c3c3c3c3" + Size: 0x1000 + - Name: .text2 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2000 + AddressAlign: 0x1000 + Content: "DEADBEEF" + Size: 0x1000 + - Name: .text3 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x3000 + AddressAlign: 0x1000 + Content: "32323232" + Size: 0x1000 + - Name: .text4 + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x4000 + AddressAlign: 0x1000 + Content: "FFFFFFFF" + Size: 0x1000 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text + - Section: .text2 + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Sections: + - Section: .text3 + - Section: .text4 + +# CHECK: 000000 c3 c3 c3 c3 +# CHECK: 001000 de ad be ef +# CHECK: 002000 00 00 00 00 +# CHECK: 003000 ff ff ff ff + +# SIZE: 16384 Index: tools/llvm-objcopy/Object.cpp =================================================================== --- tools/llvm-objcopy/Object.cpp +++ tools/llvm-objcopy/Object.cpp @@ -339,6 +339,16 @@ Parent.OriginalOffset + Parent.FileSize > Child.OriginalOffset; } +static bool compareSegments(const Segment *A, const Segment *B) { + // Any segment without a parent segment should come before a segment + // that has a parent segment. + if (A->OriginalOffset < B->OriginalOffset) + return true; + if (A->OriginalOffset > B->OriginalOffset) + return false; + return A->Index < B->Index; +} + template void Object::readProgramHeaders(const ELFFile &ElfFile) { uint32_t Index = 0; @@ -376,18 +386,11 @@ if (&Child != &Parent && segmentOverlapsSegment(*Child, *Parent)) { // We want a canonical "most parental" segment but this requires // inspecting the ParentSegment. - if (Child->ParentSegment != nullptr) { - if (Child->ParentSegment->OriginalOffset > Parent->OriginalOffset) { - Child->ParentSegment = Parent.get(); - } else if (Child->ParentSegment->Index > Parent->Index) { - // They must have equal OriginalOffsets so we need to disambiguate. - // To decide which is the parent we'll choose the one with the - // higher index. + if (compareSegments(Parent.get(), Child.get())) + if (Child->ParentSegment == nullptr || + compareSegments(Parent.get(), Child->ParentSegment)) { Child->ParentSegment = Parent.get(); } - } else { - Child->ParentSegment = Parent.get(); - } } } } @@ -698,40 +701,24 @@ return Offset + Diff; } -template void ELFObject::assignOffsets() { - // We need a temporary list of segments that has a special order to it - // so that we know that anytime ->ParentSegment is set that segment has - // already had it's offset properly set. - std::vector OrderedSegments; - for (auto &Segment : this->Segments) - OrderedSegments.push_back(Segment.get()); - auto CompareSegments = [](const Segment *A, const Segment *B) { - // Any segment without a parent segment should come before a segment - // that has a parent segment. - if (A->OriginalOffset < B->OriginalOffset) - return true; - if (A->OriginalOffset > B->OriginalOffset) - return false; - return A->Index < B->Index; - }; - std::stable_sort(std::begin(OrderedSegments), std::end(OrderedSegments), - CompareSegments); - // The size of ELF + program headers will not change so it is ok to assume - // that the first offset of the first segment is a good place to start - // outputting sections. This covers both the standard case and the PT_PHDR - // case. - uint64_t Offset; - if (!OrderedSegments.empty()) { - Offset = OrderedSegments[0]->Offset; - } else { - Offset = sizeof(Elf_Ehdr); - } +// Orders segments such that if x = y->ParentSegment then y comes before x. +static void OrderSegments(std::vector &Segments) { + std::stable_sort(std::begin(Segments), std::end(Segments), compareSegments); +} + +// This function finds a consistent layout for a list of segments starting from +// an Offset. It assumes that Segments have been sorted by OrderSegments and +// returns an Offset one past the end of the last segment. +static uint64_t LayoutSegments(std::vector &Segments, + uint64_t Offset) { + assert(std::is_sorted(std::begin(Segments), std::end(Segments), + compareSegments)); // The only way a segment should move is if a section was between two // segments and that section was removed. If that section isn't in a segment // then it's acceptable, but not ideal, to simply move it to after the // segments. So we can simply layout segments one after the other accounting // for alignment. - for (auto &Segment : OrderedSegments) { + for (auto &Segment : Segments) { // We assume that segments have been ordered by OriginalOffset and Index // such that a parent segment will always come before a child segment in // OrderedSegments. This means that the Offset of the ParentSegment should @@ -746,6 +733,17 @@ } Offset = std::max(Offset, Segment->Offset + Segment->FileSize); } + return Offset; +} + +// This function finds a consistent layout for a list of sections. It assumes +// that the ->ParentSegment of each section has already been laid out. The +// supplied starting Offset is used for the starting offset of any section that +// does not have a ParentSegment. It returns either the offset given if all +// sections had a ParentSegment or an offset one past the last section if there +// was a section that didn't have a ParentSegment. +template +static uint64_t LayoutSections(std::vector &Sections, uint64_t Offset) { // Now the offset of every segment has been set we can assign the offsets // of each section. For sections that are covered by a segment we should use // the segment's original offset and the section's original offset to compute @@ -753,7 +751,7 @@ // of the segment we can assign a new offset to the section. For sections not // covered by segments we can just bump Offset to the next valid location. uint32_t Index = 1; - for (auto &Section : this->Sections) { + for (auto &Section : Sections) { Section->Index = Index++; if (Section->ParentSegment != nullptr) { auto Segment = Section->ParentSegment; @@ -766,10 +764,33 @@ Offset += Section->Size; } } + return Offset; +} - if (this->WriteSectionHeaders) { - Offset = alignTo(Offset, sizeof(typename ELFT::Addr)); +template void ELFObject::assignOffsets() { + // We need a temporary list of segments that has a special order to it + // so that we know that anytime ->ParentSegment is set that segment has + // already had its offset properly set. + std::vector OrderedSegments; + for (auto &Segment : this->Segments) + OrderedSegments.push_back(Segment.get()); + OrderSegments(OrderedSegments); + // The size of ELF + program headers will not change so it is ok to assume + // that the first offset of the first segment is a good place to start + // outputting sections. This covers both the standard case and the PT_PHDR + // case. + uint64_t Offset; + if (!OrderedSegments.empty()) { + Offset = OrderedSegments[0]->Offset; + } else { + Offset = sizeof(Elf_Ehdr); } + Offset = LayoutSegments(OrderedSegments, Offset); + Offset = LayoutSections(this->Sections, Offset); + // If we need to write the section header table out then we need to align the + // Offset so that SHOffset is valid. + if (this->WriteSectionHeaders) + Offset = alignTo(Offset, sizeof(typename ELFT::Addr)); this->SHOffset = Offset; } @@ -823,33 +844,78 @@ template void BinaryObject::write(FileOutputBuffer &Out) const { - for (auto &Segment : this->Segments) { - // GNU objcopy does not output segments that do not cover a section. Such - // segments can sometimes be produced by LLD due to how LLD handles PT_PHDR. - if (Segment->Type == PT_LOAD && Segment->firstSection() != nullptr) { - Segment->writeSegment(Out); - } + for (auto &Section : this->Sections) { + if ((Section->Flags & SHF_ALLOC) == 0) + continue; + Section->writeSection(Out); } } template void BinaryObject::finalize() { - // Put all segments in offset order. - auto CompareSegments = [](const SegPtr &A, const SegPtr &B) { - return A->Offset < B->Offset; - }; - std::sort(std::begin(this->Segments), std::end(this->Segments), - CompareSegments); - - uint64_t Offset = 0; - for (auto &Segment : this->Segments) { - if (Segment->Type == llvm::ELF::PT_LOAD && - Segment->firstSection() != nullptr) { - Offset = alignToAddr(Offset, Segment->VAddr, Segment->Align); - Segment->Offset = Offset; - Offset += Segment->FileSize; + // TODO: Create a filter range to construct OrderedSegments from so that this + // code can be deduped with assignOffsets above. This should also solve the + // todo below for LayoutSections. + // We need a temporary list of segments that has a special order to it + // so that we know that anytime ->ParentSegment is set that segment has + // already had it's offset properly set. We only want to consider the segments + // that will affect layout of allocated sections so we only add those. + std::vector OrderedSegments; + for (auto &Section : this->Sections) { + if ((Section->Flags & SHF_ALLOC) != 0 && + Section->ParentSegment != nullptr) { + OrderedSegments.push_back(Section->ParentSegment); } } - TotalSize = Offset; + OrderSegments(OrderedSegments); + // Because we add a ParentSegment for each section we might have duplicate + // segments in OrderedSegments. If there were duplicates then LayoutSegments + // would do very strange things. + auto End = + std::unique(std::begin(OrderedSegments), std::end(OrderedSegments)); + OrderedSegments.erase(End, std::end(OrderedSegments)); + + // Modify the first segment so that there is no gap at the start. This allows + // our layout algorithm to proceed as expected while not out writing out the + // gap at the start. + if (!OrderedSegments.empty()) { + auto Seg = OrderedSegments[0]; + auto Sec = Seg->firstSection(); + auto Diff = Sec->OriginalOffset - Seg->OriginalOffset; + Seg->OriginalOffset += Diff; + // The size needs to be shrunk as well + Seg->FileSize -= Diff; + Seg->MemSize -= Diff; + // The VAddr needs to be adjusted so that the alignment is correct as well + Seg->VAddr += Diff; + Seg->PAddr = Seg->VAddr; + // We don't want this to be shifted by alignment so we need to set the + // alignment to zero. + Seg->Align = 0; + } + + uint64_t Offset = LayoutSegments(OrderedSegments, 0); + + // TODO: generalize LayoutSections to take a range. Pass a special range + // constructed from an iterator that skips values for which a predicate does + // not hold. Then pass such a range to LayoutSections instead of constructing + // AllocatedSections here. + std::vector AllocatedSections; + for (auto &Section : this->Sections) { + if ((Section->Flags & SHF_ALLOC) == 0) + continue; + AllocatedSections.push_back(Section.get()); + } + LayoutSections(AllocatedSections, Offset); + + // Now that every section has been laid out we just need to compute the total + // file size. This might not be the same as the offset returned by + // LayoutSections, because we want to truncate the last segment to the end of + // its last section, to match GNU objcopy's behaviour. + TotalSize = 0; + for (const auto &Section : AllocatedSections) { + if (Section->Type != SHT_NOBITS) + TotalSize = std::max(TotalSize, Section->Offset + Section->Size); + } } namespace llvm {