diff --git a/llvm/test/tools/llvm-objcopy/MachO/binary-output.test b/llvm/test/tools/llvm-objcopy/MachO/binary-output.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/binary-output.test @@ -0,0 +1,84 @@ +## Shows that for binary output, llvm-objcopy extracts section contents in +## a MachO object properly, that is: +## +## - It removes any gap before the first section: __bar section content is +## at the beginning of the output. +## - It preserves any gaps between sections: 12 bytes padding (zeroes) before +## __foo section content. +## + +## Case 1: Write existing sections into a file. +# RUN: yaml2obj %s > %t +# RUN: llvm-objcopy -Obinary %t %t.out +# RUN: od -t x1 %t.out | FileCheck %s + +# CHECK: 0000000 aa bb cc dd 00 00 00 00 00 00 00 00 00 00 00 00 +# CHECK-NEXT: 0000020 ee ff +# CHECK-NEXT: 0000022{{$}} + +## Case 2: Write an empty output to a non-existent file. The output file should +## be created. + +# RUN: llvm-objcopy -R __DATA,__bar -R __DATA,__foo -Obinary %t %t.out2 +# RUN: wc -c %t.out2 | FileCheck %s --check-prefix=EMPTY + +## Case 3: Write an empty output to an existing file. The output file should +## be truncated. + +# RUN: echo abcd > %t.out3 +# RUN: llvm-objcopy -R __DATA,__bar -R __DATA,__foo -Obinary %t %t.out3 +# RUN: wc -c %t.out3 | FileCheck %s --check-prefix=EMPTY + +## In both case 2 and 3, the output file should be empty. +# EMPTY: 0 + + +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x80000003 + filetype: 0x00000002 + ncmds: 1 + sizeofcmds: 232 + flags: 0x00002000 + reserved: 0x00000000 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 232 + segname: '__DATA' + vmaddr: 0x0000000000001000 + vmsize: 32 + fileoff: 472 + filesize: 6 + maxprot: 7 + initprot: 7 + nsects: 2 + flags: 0 + Sections: + - sectname: __bar + segname: __DATA + addr: 0x0000000000001000 + content: 'AABBCCDD' + size: 4 + offset: 0x0000010c + align: 0 + reloff: 0x00000000 + nreloc: 0 + flags: 0x00000000 + reserved1: 0x00000000 + reserved2: 0x00000000 + reserved3: 0x00000000 + - sectname: __foo + segname: __DATA + addr: 0x0000000000001010 + size: 2 + content: 'EEFF' + offset: 0x00000110 + align: 0 + reloff: 0x00000000 + nreloc: 0 + flags: 0x00000000 + reserved1: 0x00000000 + reserved2: 0x00000000 + reserved3: 0x00000000 diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp @@ -198,15 +198,30 @@ return Error::success(); } -Error writeOutput(Object &O, Buffer &Out, bool Is64Bit, bool IsLittleEndian) { +static std::unique_ptr createWriter(const CopyConfig &Config, Object &O, + Buffer &Out, bool Is64Bit, + bool IsLittleEndian) { // TODO: Support 16KB pages which are employed in iOS arm64 binaries: // https://github.com/llvm/llvm-project/commit/1bebb2832ee312d3b0316dacff457a7a29435edb const uint64_t PageSize = 4096; - MachOWriter Writer(O, Is64Bit, IsLittleEndian, PageSize, Out); - if (auto E = Writer.finalize()) + switch (Config.OutputFormat) { + case FileFormat::Binary: + return std::make_unique( + BinaryWriter(O, Is64Bit, IsLittleEndian, PageSize, Out)); + default: + return std::make_unique( + MachOWriter(O, Is64Bit, IsLittleEndian, PageSize, Out)); + } +} + +Error writeOutput(const CopyConfig &Config, Object &O, Buffer &Out, + bool Is64Bit, bool IsLittleEndian) { + std::unique_ptr W = + createWriter(Config, O, Out, Is64Bit, IsLittleEndian); + if (auto E = W->finalize()) return E; - return Writer.write(); + return W->write(); } Error executeObjcopyOnRawBinary(const CopyConfig &Config, MemoryBuffer &In, @@ -218,7 +233,7 @@ if (Error E = handleArgs(Config, *O)) return createFileError(Config.InputFilename, std::move(E)); - return writeOutput(*O, Out, MI.Is64Bit, MI.IsLittleEndian); + return writeOutput(Config, *O, Out, MI.Is64Bit, MI.IsLittleEndian); } Error executeObjcopyOnBinary(const CopyConfig &Config, @@ -234,7 +249,7 @@ if (Error E = handleArgs(Config, *O)) return createFileError(Config.InputFilename, std::move(E)); - return writeOutput(*O, Out, In.is64Bit(), In.isLittleEndian()); + return writeOutput(Config, *O, Out, In.is64Bit(), In.isLittleEndian()); } } // end namespace macho diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h --- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.h +++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.h @@ -19,7 +19,14 @@ namespace objcopy { namespace macho { -class MachOWriter { +class Writer { +public: + virtual ~Writer() {} + virtual Error finalize() = 0; + virtual Error write() = 0; +}; + +class MachOWriter : public Writer { Object &O; bool Is64Bit; bool IsLittleEndian; @@ -54,10 +61,25 @@ Buffer &B) : O(O), Is64Bit(Is64Bit), IsLittleEndian(IsLittleEndian), PageSize(PageSize), B(B), LayoutBuilder(O, Is64Bit, PageSize) {} - + ~MachOWriter() {} size_t totalSize() const; - Error finalize(); - Error write(); + Error finalize() override; + Error write() override; +}; + +class BinaryWriter : public Writer { + Object &O; + Buffer &B; + MachOLayoutBuilder LayoutBuilder; + std::vector
OrderedSections; + +public: + BinaryWriter(Object &O, bool Is64Bit, bool IsLittleEndian, uint64_t PageSize, + Buffer &B) + : O(O), B(B), LayoutBuilder(O, Is64Bit, PageSize) {} + ~BinaryWriter() {} + Error finalize() override; + Error write() override; }; } // end namespace macho diff --git a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOWriter.cpp @@ -495,6 +495,50 @@ return B.commit(); } +Error BinaryWriter::finalize() { + if (Error E = LayoutBuilder.layout()) + return E; + + // Sort sections by their addresses. + for (LoadCommand &LC : O.LoadCommands) + for (Section &Sec : LC.Sections) + if (!Sec.isVirtualSection()) + OrderedSections.push_back(&Sec); + + llvm::stable_sort(OrderedSections, [](const Section *A, const Section *B) { + return A->Addr < B->Addr; + }); + + // Remove gap at the start. From here, `Addr` is used as the file offset in + // the output file. + if (!OrderedSections.empty()) { + uint64_t AddrBase = OrderedSections[0]->Addr; + for (Section *Sec : OrderedSections) + Sec->Addr -= AddrBase; + } + + return Error::success(); +} + +Error BinaryWriter::write() { + size_t TotalSize = + OrderedSections.empty() + ? 0 + : (OrderedSections.back()->Addr + OrderedSections.back()->Size); + + if (Error E = B.allocate(TotalSize)) + return E; + + uint64_t PrevEnd = 0; + for (const Section *Sec : OrderedSections) { + memset(B.getBufferStart() + PrevEnd, 0, Sec->Addr - PrevEnd); + memcpy(B.getBufferStart() + Sec->Addr, Sec->getContent().data(), Sec->Size); + PrevEnd = Sec->Addr + Sec->Size; + } + + return B.commit(); +} + } // end namespace macho } // end namespace objcopy } // end namespace llvm