[llvm-objcopy][MachO] Implement --only-section

Reviewers: alexshap, rupprecht, jdoerfert, jhenderson

Reviewed By: alexshap, rupprecht, jhenderson

Subscribers: mgorny, jakehehrlich, abrachet, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D65541
diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst
index 56c3a77..063300a 100644
--- a/llvm/docs/CommandGuide/llvm-objcopy.rst
+++ b/llvm/docs/CommandGuide/llvm-objcopy.rst
@@ -72,6 +72,9 @@
  Remove all sections from the output, except for sections named ``<section>``.
  Can be specified multiple times to keep multiple sections.
 
+ For MachO objects, ``<section>`` must be formatted as
+ ``<segment name>,<section name>``.
+
 .. option:: --regex
 
  If specified, symbol and section names specified by other switches are treated
diff --git a/llvm/test/tools/llvm-objcopy/MachO/only-section.test b/llvm/test/tools/llvm-objcopy/MachO/only-section.test
new file mode 100644
index 0000000..4e26031
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/MachO/only-section.test
@@ -0,0 +1,153 @@
+## Show that if --only-section is given, llvm-objcopy removes all sections
+## except ones specified in the option.
+
+# RUN: yaml2obj %s > %t
+
+## Specify all sections. The output file should be the same as the input.
+# RUN: llvm-objcopy -j __TEXT,__text -j __DATA,__data -j __TEXT,__const %t %t2
+# RUN: cmp %t %t2
+
+## Specify one section. The output file should contain only that section.
+# RUN: llvm-objcopy --only-section __TEXT,__text %t %t3
+# RUN: llvm-readobj --sections --section-data --macho-segment %t3 \
+# RUN:   | FileCheck %s --check-prefix=ONLY-TEXT-SECTION
+
+# ONLY-TEXT-SECTION:      Sections [
+# ONLY-TEXT-SECTION-NEXT:   Section {
+# ONLY-TEXT-SECTION-NEXT:     Index: 0
+# ONLY-TEXT-SECTION-NEXT:     Name: __text (5F 5F 74 65 78 74 00 00 00 00 00 00 00 00 00 00)
+# ONLY-TEXT-SECTION-NEXT:     Segment: __TEXT (5F 5F 54 45 58 54 00 00 00 00 00 00 00 00 00 00)
+# ONLY-TEXT-SECTION-NEXT:     Address: 0x0
+# ONLY-TEXT-SECTION-NEXT:     Size: 0x4
+# ONLY-TEXT-SECTION-NEXT:     Offset: 184
+# ONLY-TEXT-SECTION-NEXT:     Alignment: 0
+# ONLY-TEXT-SECTION-NEXT:     RelocationOffset: 0x0
+# ONLY-TEXT-SECTION-NEXT:     RelocationCount: 0
+# ONLY-TEXT-SECTION-NEXT:     Type: Regular (0x0)
+# ONLY-TEXT-SECTION-NEXT:     Attributes [ (0x800004)
+# ONLY-TEXT-SECTION-NEXT:       PureInstructions (0x800000)
+# ONLY-TEXT-SECTION-NEXT:       SomeInstructions (0x4)
+# ONLY-TEXT-SECTION-NEXT:     ]
+# ONLY-TEXT-SECTION-NEXT:     Reserved1: 0x0
+# ONLY-TEXT-SECTION-NEXT:     Reserved2: 0x0
+# ONLY-TEXT-SECTION-NEXT:     Reserved3: 0x0
+# ONLY-TEXT-SECTION-NEXT:     SectionData (
+# ONLY-TEXT-SECTION-NEXT:       0000: AABBCCDD                             |....|
+# ONLY-TEXT-SECTION-NEXT:     )
+# ONLY-TEXT-SECTION-NEXT:   }
+# ONLY-TEXT-SECTION-NEXT: ]
+# ONLY-TEXT-SECTION-NEXT: Segment {
+# ONLY-TEXT-SECTION-NEXT:   Cmd: LC_SEGMENT_64
+# ONLY-TEXT-SECTION-NEXT:   Name:
+# ONLY-TEXT-SECTION-NEXT:   Size: 152
+# ONLY-TEXT-SECTION-NEXT:   vmaddr: 0x0
+# ONLY-TEXT-SECTION-NEXT:   vmsize: 0x4
+# ONLY-TEXT-SECTION-NEXT:   fileoff: 184
+# ONLY-TEXT-SECTION-NEXT:   filesize: 4
+# ONLY-TEXT-SECTION-NEXT:   maxprot: rwx
+# ONLY-TEXT-SECTION-NEXT:   initprot: rwx
+# ONLY-TEXT-SECTION-NEXT:   nsects: 1
+# ONLY-TEXT-SECTION-NEXT:   flags: 0x0
+# ONLY-TEXT-SECTION-NEXT: }
+
+## Remove all sections if the specified section name is not present in the input.
+# RUN: llvm-objcopy --only-section __TEXT,__nonexistent %t %t4 2>&1
+# RUN: llvm-readobj --sections --section-data --macho-segment %t4 \
+# RUN:   | FileCheck %s --check-prefix=NONEXISTENT-SECTION
+
+# NONEXISTENT-SECTION:      Sections [
+# NONEXISTENT-SECTION-NEXT: ]
+# NONEXISTENT-SECTION-NEXT: Segment {
+# NONEXISTENT-SECTION-NEXT:   Cmd: LC_SEGMENT_64
+# NONEXISTENT-SECTION-NEXT:   Name:
+# NONEXISTENT-SECTION-NEXT:   Size: 72
+# NONEXISTENT-SECTION-NEXT:   vmaddr: 0x0
+# NONEXISTENT-SECTION-NEXT:   vmsize: 0x0
+# NONEXISTENT-SECTION-NEXT:   fileoff: 104
+# NONEXISTENT-SECTION-NEXT:   filesize: 0
+# NONEXISTENT-SECTION-NEXT:   maxprot: rwx
+# NONEXISTENT-SECTION-NEXT:   initprot: rwx
+# NONEXISTENT-SECTION-NEXT:   nsects: 0
+# NONEXISTENT-SECTION-NEXT:   flags: 0x0
+# NONEXISTENT-SECTION-NEXT: }
+
+## Use wildcard to specify all sections under the __TEXT segment.
+# RUN: llvm-objcopy --only-section "__TEXT,*" %t %t5 2>&1
+# RUN: llvm-readobj --file-header --sections %t5 \
+# RUN:   | FileCheck %s --check-prefix=WILDCARD
+
+## Make sure that it doesn't care about the segment/section name separator ",".
+# RUN: llvm-objcopy --only-section "__TEXT*" %t %t6 2>&1
+# RUN: cmp %t5 %t6
+
+# WILDCARD:      NumOfLoadCommands: 1
+# WILDCARD:      Sections [
+# WILDCARD:        Index: 0
+# WILDCARD-NEXT:   Name: __text (5F 5F 74 65 78 74 00 00 00 00 00 00 00 00 00 00)
+# WILDCARD:        Index: 1
+# WILDCARD-NEXT:   Name: __const (5F 5F 63 6F 6E 73 74 00 00 00 00 00 00 00 00 00)
+# WILDCARD-NOT:    Index: 2
+# WILDCARD:      ]
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x00000003
+  filetype:        0x00000001
+  ncmds:           1
+  sizeofcmds:      312
+  flags:           0x00002000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         312
+    segname:         ''
+    vmaddr:          0
+    vmsize:          12
+    fileoff:         344
+    filesize:        12
+    maxprot:         7
+    initprot:        7
+    nsects:          3
+    flags:           0
+    Sections:
+      - sectname:        __text
+        segname:         __TEXT
+        addr:            0x0000000000000000
+        content:         'AABBCCDD'
+        size:            4
+        offset:          344
+        align:           0
+        reloff:          0x00000000
+        nreloc:          0
+        flags:           0x80000400
+        reserved1:       0x00000000
+        reserved2:       0x00000000
+        reserved3:       0x00000000
+      - sectname:        __data
+        segname:         __DATA
+        addr:            0x0000000000000004
+        content:         'DDAADDAA'
+        size:            4
+        offset:          348
+        align:           0
+        reloff:          0x00000000
+        nreloc:          0
+        flags:           0x00000000
+        reserved1:       0x00000000
+        reserved2:       0x00000000
+        reserved3:       0x00000000
+      - sectname:        __const
+        segname:         __TEXT
+        addr:            0x0000000000000008
+        content:         'EEFFEEFF'
+        size:            4
+        offset:          352
+        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
index 6d586e7..73983a1 100644
--- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp
+++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp
@@ -18,6 +18,19 @@
 namespace macho {
 
 using namespace object;
+using SectionPred = std::function<bool(const Section &Sec)>;
+
+static void removeSections(const CopyConfig &Config, Object &Obj) {
+  SectionPred RemovePred = [](const Section &) { return false; };
+
+  if (!Config.OnlySection.empty()) {
+    RemovePred = [&Config, RemovePred](const Section &Sec) {
+      return !Config.OnlySection.matches(Sec.CanonicalName);
+    };
+  }
+
+  return Obj.removeSections(RemovePred);
+}
 
 static Error handleArgs(const CopyConfig &Config, Object &Obj) {
   if (Config.AllowBrokenLinks || !Config.BuildIdLinkDir.empty() ||
@@ -25,11 +38,10 @@
       !Config.SplitDWO.empty() || !Config.SymbolsPrefix.empty() ||
       !Config.AllocSectionsPrefix.empty() || !Config.AddSection.empty() ||
       !Config.DumpSection.empty() || !Config.KeepSection.empty() ||
-      Config.NewSymbolVisibility || !Config.OnlySection.empty() ||
-      !Config.SymbolsToGlobalize.empty() || !Config.SymbolsToKeep.empty() ||
-      !Config.SymbolsToLocalize.empty() || !Config.SymbolsToWeaken.empty() ||
-      !Config.SymbolsToKeepGlobal.empty() || !Config.SectionsToRename.empty() ||
-      !Config.SymbolsToRename.empty() ||
+      Config.NewSymbolVisibility || !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.SetSectionAlignment.empty() || !Config.SetSectionFlags.empty() ||
       !Config.ToRemove.empty() || Config.ExtractDWO || Config.KeepFileSymbols ||
@@ -43,6 +55,7 @@
                              "option not supported by llvm-objcopy for MachO");
   }
 
+  removeSections(Config, Obj);
   return Error::success();
 }
 
diff --git a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp
index b48a0d8..ed2b3eb 100644
--- a/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp
+++ b/llvm/tools/llvm-objcopy/MachO/MachOReader.cpp
@@ -35,6 +35,7 @@
           .str();
   S.Segname =
       StringRef(Sec.segname, strnlen(Sec.segname, sizeof(Sec.sectname))).str();
+  S.CanonicalName = (Twine(S.Segname) + "," + S.Sectname).str();
   S.Addr = Sec.addr;
   S.Size = Sec.size;
   S.Offset = Sec.offset;
diff --git a/llvm/tools/llvm-objcopy/MachO/Object.cpp b/llvm/tools/llvm-objcopy/MachO/Object.cpp
index 264f39c..ba3e2ef 100644
--- a/llvm/tools/llvm-objcopy/MachO/Object.cpp
+++ b/llvm/tools/llvm-objcopy/MachO/Object.cpp
@@ -10,6 +10,13 @@
   return Symbols[Index].get();
 }
 
+void Object::removeSections(function_ref<bool(const Section &)> ToRemove) {
+  for (LoadCommand &LC : LoadCommands)
+    LC.Sections.erase(std::remove_if(std::begin(LC.Sections),
+                                     std::end(LC.Sections), ToRemove),
+                      std::end(LC.Sections));
+}
+
 } // end namespace macho
 } // end namespace objcopy
 } // end namespace llvm
diff --git a/llvm/tools/llvm-objcopy/MachO/Object.h b/llvm/tools/llvm-objcopy/MachO/Object.h
index 1cebf82..36b0f7e 100644
--- a/llvm/tools/llvm-objcopy/MachO/Object.h
+++ b/llvm/tools/llvm-objcopy/MachO/Object.h
@@ -38,6 +38,8 @@
 struct Section {
   std::string Sectname;
   std::string Segname;
+  // CanonicalName is a string formatted as “<Segname>,<Sectname>".
+  std::string CanonicalName;
   uint64_t Addr;
   uint64_t Size;
   uint32_t Offset;
@@ -250,6 +252,8 @@
   Optional<size_t> DataInCodeCommandIndex;
   /// The index LC_FUNCTION_STARTS load comamnd if present.
   Optional<size_t> FunctionStartsCommandIndex;
+
+  void removeSections(function_ref<bool(const Section &)> ToRemove);
 };
 
 } // end namespace macho