diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst --- a/llvm/docs/CommandGuide/llvm-objcopy.rst +++ b/llvm/docs/CommandGuide/llvm-objcopy.rst @@ -43,6 +43,9 @@ starts with ".note". Otherwise, it will have type `SHT_PROGBITS`. Can be specified multiple times to add multiple sections. + For MachO objects, the ``
`` must be formatted as + ``,
``. + .. option:: --disable-deterministic-archives, -U Use real values for UIDs, GIDs and timestamps when updating archive member diff --git a/llvm/test/tools/llvm-objcopy/MachO/add-section.test b/llvm/test/tools/llvm-objcopy/MachO/add-section.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/add-section.test @@ -0,0 +1,116 @@ +## Show that llvm-objcopy adds a new section into the object if +## --add-section is given. + +# RUN: yaml2obj %s > %t +# RUN: echo -n abcdefg > %t.data + +## Error case 1: invalid section name. +# RUN: not llvm-objcopy --add-section __text=%t.data %t %t.invalid-section-name 2>&1 \ +# RUN: | FileCheck %s -DINPUT=%t --check-prefix=INVALID-SECTION-NAME +# INVALID-SECTION-NAME: error: '[[INPUT]]': invalid section name '__text' (should be formatted as ',
') + +## Error case 2: Nonexistent input file is specified by --add-section. +# RUN: not llvm-objcopy --add-section __TEXT,__text=%t.missing %t %t.nonexistent-file 2>&1 \ +# RUN: | FileCheck %s -DINPUT=%t -DSECTION_DATA_FILE=%t.missing --check-prefix=NONEXSITENT-FILE +# NONEXSITENT-FILE: error: '[[INPUT]]': '[[SECTION_DATA_FILE]]': {{[Nn]}}o such file or directory + +## Error case 3: Too long segment name. +# RUN: not llvm-objcopy --add-section __TOOOOOOOOO_LONG,__text=%t.data %t %t.too-long-seg-name 2>&1 \ +# RUN: | FileCheck %s -DINPUT=%t --check-prefix=TOO-LONG-SEG-NAME +# TOO-LONG-SEG-NAME: error: '[[INPUT]]': too long segment name: '__TOOOOOOOOO_LONG' + +## Case 1: Add a new section into an existing segment. +# RUN: llvm-objcopy --add-section __TEXT,__text=%t.data %t %t.out1 +# RUN: llvm-readobj --sections --section-data %t.out1 \ +# RUN: | FileCheck %s --check-prefixes=COMMON,CASE1 + +## Case 2: Add a new section into an nonexistent segment. +# RUN: llvm-objcopy --add-section __FOO,__bar=%t.data %t %t.out2 +# RUN: llvm-readobj --sections --section-data --macho-segment %t.out2 \ +# RUN: | FileCheck %s --check-prefixes=COMMON,CASE2 + +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x00000003 + filetype: 0x00000001 + ncmds: 1 + sizeofcmds: 152 + flags: 0x00002000 + reserved: 0x00000000 +LoadCommands: + - cmd: LC_SEGMENT_64 + cmdsize: 152 + segname: '' + vmaddr: 0 + vmsize: 4 + fileoff: 184 + filesize: 4 + maxprot: 7 + initprot: 7 + nsects: 1 + flags: 0 + Sections: + - sectname: __text + segname: __TEXT + addr: 0x0000000000000000 + content: 'AABBCCDD' + size: 4 + offset: 184 + align: 0 + reloff: 0x00000000 + nreloc: 0 + flags: 0x80000400 + reserved1: 0x00000000 + reserved2: 0x00000000 + reserved3: 0x00000000 + +# COMMON: Index: 1 +# CASE1-NEXT: __text (5F 5F 74 65 78 74 00 00 00 00 00 00 00 00 00 00) +# CASE1-NEXT: Segment: __TEXT (5F 5F 54 45 58 54 00 00 00 00 00 00 00 00 00 00) +# CASE2-NEXT: Name: __bar (5F 5F 62 61 72 00 00 00 00 00 00 00 00 00 00 00) +# CASE2-NEXT: Segment: __FOO (5F 5F 46 4F 4F 00 00 00 00 00 00 00 00 00 00 00) +# COMMON-NEXT: Address: 0x0 +# COMMON-NEXT: Size: 0x7 +# COMMON-NEXT: Offset: 340 +# COMMON-NEXT: Alignment: 0 +# COMMON-NEXT: RelocationOffset: 0x0 +# COMMON-NEXT: RelocationCount: 0 +# COMMON-NEXT: Type: Regular (0x0) +# COMMON-NEXT: Attributes [ (0x0) +# COMMON-NEXT: ] +# COMMON-NEXT: Reserved1: 0x0 +# COMMON-NEXT: Reserved2: 0x0 +# COMMON-NEXT: Reserved3: 0x0 +# COMMON-NEXT: SectionData ( +# COMMON-NEXT: 0000: 61626364 656667 |abcdefg| +# COMMON-NEXT: ) + +## Make sure that new segment (__FOO) is created. +# CASE2: Segment { +# CASE2-NEXT: Cmd: LC_SEGMENT_64 +# CASE2-NEXT: Name: +# CASE2-NEXT: Size: 152 +# CASE2-NEXT: vmaddr: 0x0 +# CASE2-NEXT: vmsize: 0x4 +# CASE2-NEXT: fileoff: 336 +# CASE2-NEXT: filesize: 4 +# CASE2-NEXT: maxprot: rwx +# CASE2-NEXT: initprot: rwx +# CASE2-NEXT: nsects: 1 +# CASE2-NEXT: flags: 0x0 +# CASE2-NEXT: } +# CASE2-NEXT: Segment { +# CASE2-NEXT: Cmd: LC_SEGMENT_64 +# CASE2-NEXT: Name: __FOO +# CASE2-NEXT: Size: 152 +# CASE2-NEXT: vmaddr: 0x0 +# CASE2-NEXT: vmsize: 0x7 +# CASE2-NEXT: fileoff: 340 +# CASE2-NEXT: filesize: 7 +# CASE2-NEXT: maxprot: --- +# CASE2-NEXT: initprot: --- +# CASE2-NEXT: nsects: 1 +# CASE2-NEXT: flags: 0x0 +# CASE2-NEXT: } diff --git a/llvm/tools/llvm-objcopy/CMakeLists.txt b/llvm/tools/llvm-objcopy/CMakeLists.txt --- a/llvm/tools/llvm-objcopy/CMakeLists.txt +++ b/llvm/tools/llvm-objcopy/CMakeLists.txt @@ -28,6 +28,7 @@ MachO/MachOWriter.cpp MachO/MachOLayoutBuilder.cpp MachO/Object.cpp + MachO/Utils.cpp DEPENDS ObjcopyOptsTableGen StripOptsTableGen 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 @@ -65,6 +65,38 @@ Obj.SymTable.removeSymbols(RemovePred); } +static Error addSection(StringRef SecName, StringRef Filename, Object &Obj) { + // FIXME: Read the file in CopyConfig. + ErrorOr> BufOrErr = + MemoryBuffer::getFile(Filename); + if (!BufOrErr) + return createFileError(Filename, errorCodeToError(BufOrErr.getError())); + std::unique_ptr Buf = std::move(*BufOrErr); + ArrayRef Content( + reinterpret_cast(Buf->getBufferStart()), + Buf->getBufferSize()); + + std::pair Pair = SecName.split(','); + StringRef TargetSegName = Pair.first; + Section Sec(TargetSegName, Pair.second); + Sec.setOwnedContentData(Content); + + for (LoadCommand &LC : Obj.LoadCommands) { + Optional SegName = LC.getSegmentName(); + if (SegName && SegName == TargetSegName) { + LC.Sections.push_back(Sec); + return Error::success(); + } + } + + // There's no segment named TargetSegName. Create a new load command and + // Insert a new section into it. + // TODO: 32-bit + LoadCommand &NewSegment = Obj.addSegment(TargetSegName); + NewSegment.Sections.push_back(Sec); + return Error::success(); +} + static Error validateOptions(const CopyConfig &Config) { // TODO: Support section renaming in GNU objcopy for compatibility (see // http://lists.llvm.org/pipermail/llvm-dev/2019-May/132570.html). @@ -88,12 +120,11 @@ if (Config.AllowBrokenLinks || !Config.BuildIdLinkDir.empty() || Config.BuildIdLinkInput || Config.BuildIdLinkOutput || !Config.SplitDWO.empty() || !Config.SymbolsPrefix.empty() || - !Config.AllocSectionsPrefix.empty() || !Config.AddSection.empty() || - !Config.DumpSection.empty() || !Config.KeepSection.empty() || - !Config.SymbolsToGlobalize.empty() || !Config.SymbolsToKeep.empty() || - !Config.SymbolsToLocalize.empty() || !Config.SymbolsToWeaken.empty() || - !Config.SymbolsToKeepGlobal.empty() || !Config.SectionsToRename.empty() || - !Config.SymbolsToRename.empty() || + !Config.AllocSectionsPrefix.empty() || !Config.DumpSection.empty() || + !Config.KeepSection.empty() || !Config.SymbolsToGlobalize.empty() || + !Config.SymbolsToKeep.empty() || !Config.SymbolsToLocalize.empty() || + !Config.SymbolsToWeaken.empty() || !Config.SymbolsToKeepGlobal.empty() || + !Config.SectionsToRename.empty() || !Config.SymbolsToRename.empty() || !Config.UnneededSymbolsToRemove.empty() || !Config.SetSectionFlags.empty() || Config.ExtractDWO || Config.KeepFileSymbols || Config.LocalizeHidden || Config.PreserveDates || @@ -117,6 +148,16 @@ removeSymbols(Config, Obj); + for (const auto &Flag : Config.AddSection) { + std::pair SecPair = Flag.split("="); + StringRef SecName = SecPair.first; + StringRef File = SecPair.second; + if (Error E = isValidMachOCannonicalName(SecName)) + return E; + if (Error E = addSection(SecName, File, Obj)) + return E; + } + if (Config.StripAll) for (LoadCommand &LC : Obj.LoadCommands) for (Section &Sec : LC.Sections) diff --git a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp @@ -29,13 +29,9 @@ template Section constructSectionCommon(SectionType Sec) { - Section S; - S.Sectname = - StringRef(Sec.sectname, strnlen(Sec.sectname, sizeof(Sec.sectname))) - .str(); - S.Segname = - StringRef(Sec.segname, strnlen(Sec.segname, sizeof(Sec.sectname))).str(); - S.CannonicalName = (Twine(S.Segname) + "," + S.Sectname).str(); + StringRef Sectname(Sec.sectname, strnlen(Sec.sectname, sizeof(Sec.sectname))); + StringRef Segname(Sec.segname, strnlen(Sec.segname, sizeof(Sec.sectname))); + Section S(Segname, Sectname); S.Addr = Sec.addr; S.Size = Sec.size; S.Offset = Sec.offset; 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 @@ -233,9 +233,9 @@ continue; assert(Sec.Offset && "Section offset can not be zero"); - assert((Sec.Size == Sec.Content.size()) && "Incorrect section size"); - memcpy(B.getBufferStart() + Sec.Offset, Sec.Content.data(), - Sec.Content.size()); + assert((Sec.Size == Sec.getContent().size()) && "Incorrect section size"); + memcpy(B.getBufferStart() + Sec.Offset, Sec.getContent().data(), + Sec.getContent().size()); for (size_t Index = 0; Index < Sec.Relocations.size(); ++Index) { auto RelocInfo = Sec.Relocations[Index]; if (!RelocInfo.Scattered) { diff --git a/llvm/tools/llvm-objcopy/MachO/Object.h b/llvm/tools/llvm-objcopy/MachO/Object.h --- a/llvm/tools/llvm-objcopy/MachO/Object.h +++ b/llvm/tools/llvm-objcopy/MachO/Object.h @@ -35,25 +35,51 @@ }; struct RelocationInfo; -struct Section { +class Section { +private: + std::vector OwnedContentData; + +public: std::string Sectname; std::string Segname; // CannonicalName is a string formatted as “,". std::string CannonicalName; - uint64_t Addr; - uint64_t Size; - uint32_t Offset; - uint32_t Align; - uint32_t RelOff; - uint32_t NReloc; - uint32_t Flags; - uint32_t Reserved1; - uint32_t Reserved2; - uint32_t Reserved3; - + uint64_t Addr = 0; + uint64_t Size = 0; + uint32_t Offset = 0; + uint32_t Align = 0; + uint32_t RelOff = 0; + uint32_t NReloc = 0; + uint32_t Flags = 0; + uint32_t Reserved1 = 0; + uint32_t Reserved2 = 0; + uint32_t Reserved3 = 0; StringRef Content; + std::vector Relocations; + Section(StringRef SegName, StringRef SectName) + : Sectname(SectName), Segname(SegName), + CannonicalName((Twine(SegName) + Twine(',') + SectName).str()) {} + + Section(StringRef SegName, StringRef SectName, StringRef Content) + : Sectname(SectName), Segname(SegName), + CannonicalName((Twine(SegName) + Twine(',') + SectName).str()), + Content(Content) {} + + void setOwnedContentData(ArrayRef Data) { + OwnedContentData.assign(Data.begin(), Data.end()); + Content = StringRef(reinterpret_cast(OwnedContentData.data()), + OwnedContentData.size()); + } + + StringRef getContent() const { + if (OwnedContentData.size() > 0) + return StringRef(reinterpret_cast(OwnedContentData.data()), + OwnedContentData.size()); + return Content; + } + MachO::SectionType getType() const { return static_cast(Flags & MachO::SECTION_TYPE); } @@ -81,6 +107,9 @@ // Section describes only sections' metadata and where to find the // corresponding content inside the binary. std::vector
Sections; + + // Returns the segment name if the load command is a segment command. + Optional getSegmentName() const; }; // A symbol information. Fields which starts with "n_" are same as them in the @@ -269,6 +298,16 @@ Optional FunctionStartsCommandIndex; void removeSections(function_ref ToRemove); + + bool is64Bit() { + return Header.Magic == MachO::MH_MAGIC_64 || + Header.Magic == MachO::MH_CIGAM_64; + } + + /// Creates a new segment load command in the object and returns a reference + /// to the newly created load command. The caller should verify that SegName + /// is not too long (SegName.size() should be less than or equal to 16). + LoadCommand &addSegment(StringRef SegName); }; } // end namespace macho diff --git a/llvm/tools/llvm-objcopy/MachO/Object.cpp b/llvm/tools/llvm-objcopy/MachO/Object.cpp --- a/llvm/tools/llvm-objcopy/MachO/Object.cpp +++ b/llvm/tools/llvm-objcopy/MachO/Object.cpp @@ -1,5 +1,8 @@ #include "Object.h" #include "../llvm-objcopy.h" +#include "Utils.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/ErrorOr.h" namespace llvm { namespace objcopy { @@ -29,6 +32,34 @@ std::end(LC.Sections)); } +LoadCommand &Object::addSegment(StringRef SegName) { + assert(is64Bit() && "adding segment_command is not yet supported"); + + LoadCommand LC; + MachO::segment_command_64 &Seg = LC.MachOLoadCommand.segment_command_64_data; + + assert(SegName.size() <= sizeof(Seg.segname) && "too long segment name"); + + memset(&Seg, 0, sizeof(MachO::segment_command_64)); + Seg.cmd = MachO::LC_SEGMENT_64; + strncpy(Seg.segname, SegName.data(), SegName.size()); + LoadCommands.push_back(LC); + + return LoadCommands.back(); +} + +Optional LoadCommand::getSegmentName() const { + const MachO::macho_load_command &MLC = MachOLoadCommand; + switch (MLC.load_command_data.cmd) { + case MachO::LC_SEGMENT: + return extractSegmentName(MLC.segment_command_data.segname); + case MachO::LC_SEGMENT_64: + return extractSegmentName(MLC.segment_command_64_data.segname); + default: + return None; + } +} + } // end namespace macho } // end namespace objcopy } // end namespace llvm diff --git a/llvm/tools/llvm-objcopy/MachO/Utils.h b/llvm/tools/llvm-objcopy/MachO/Utils.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/MachO/Utils.h @@ -0,0 +1,25 @@ +//===- Utils.h --------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_OBJCOPY_MACHO_UTILS_H +#define LLVM_OBJCOPY_MACHO_UTILS_H + +#include "llvm/ADT/StringRef.h" + +namespace llvm { +namespace objcopy { +namespace macho { + +/// Extracts a segment name from a string which is possibly non-null-terminated. +StringRef extractSegmentName(const char *segName); + +} // end namespace macho +} // end namespace objcopy +} // end namespace llvm + +#endif diff --git a/llvm/tools/llvm-objcopy/MachO/Utils.cpp b/llvm/tools/llvm-objcopy/MachO/Utils.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-objcopy/MachO/Utils.cpp @@ -0,0 +1,29 @@ +//===- MachOObjcopy.cpp -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Utils.h" +#include "llvm/BinaryFormat/MachO.h" + +namespace llvm { +namespace objcopy { +namespace macho { + +// Make sure that the binary format has not changed. +static_assert(sizeof(MachO::segment_command::segname) == + sizeof(MachO::segment_command_64::segname), + "The MachO format has been changed."); + +/// Extracts a segment name from a string which is possibly non-null-terminated. +StringRef extractSegmentName(const char *SegName) { + return StringRef(SegName, + strnlen(SegName, sizeof(MachO::segment_command::segname))); +} + +} // end namespace macho +} // end namespace objcopy +} // end namespace llvm