[llvm] 03bd38f - [llvm-otool] Add -m flag and archive(member) input syntax (#194234)

via llvm-commits llvm-commits at lists.llvm.org
Fri May 8 05:37:05 PDT 2026


Author: Ryan Mansfield
Date: 2026-05-08T08:36:59-04:00
New Revision: 03bd38f411de317422ced4a9527af11294196023

URL: https://github.com/llvm/llvm-project/commit/03bd38f411de317422ced4a9527af11294196023
DIFF: https://github.com/llvm/llvm-project/commit/03bd38f411de317422ced4a9527af11294196023.diff

LOG: [llvm-otool] Add -m flag and archive(member) input syntax (#194234)

Support classic otool's archive(member) input syntax where a filename
like 'foo.a(bar.o)' extracts and processes only the named member from
the archive. The -m flag disables this syntax parsing, treating the
entire string as a literal filename.

Fixes #126272

Added: 
    llvm/test/tools/llvm-objdump/MachO/otool-archive-member.test

Modified: 
    llvm/docs/CommandGuide/llvm-otool.rst
    llvm/tools/llvm-objdump/MachODump.cpp
    llvm/tools/llvm-objdump/MachODump.h
    llvm/tools/llvm-objdump/OtoolOpts.td
    llvm/tools/llvm-objdump/llvm-objdump.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/docs/CommandGuide/llvm-otool.rst b/llvm/docs/CommandGuide/llvm-otool.rst
index ab92673092c9c..17aa9089b3875 100644
--- a/llvm/docs/CommandGuide/llvm-otool.rst
+++ b/llvm/docs/CommandGuide/llvm-otool.rst
@@ -83,6 +83,10 @@ OPTIONS
 
  Print load commands.
 
+.. option:: -m
+
+ Don't use archive(member) syntax.
+
 .. option:: -mcpu=<value>
 
  Select cpu for disassembly.

diff  --git a/llvm/test/tools/llvm-objdump/MachO/otool-archive-member.test b/llvm/test/tools/llvm-objdump/MachO/otool-archive-member.test
new file mode 100644
index 0000000000000..3aaa2028b98ca
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/MachO/otool-archive-member.test
@@ -0,0 +1,48 @@
+# Test llvm-otool archive(member) input syntax and -m flag.
+
+RUN: mkdir -p %t.dir
+RUN: cp %p/Inputs/hello.obj.macho-x86_64 %t.dir/foo.o
+RUN: cp %p/Inputs/ObjC.obj.macho-x86_64 %t.dir/bar.o
+RUN: llvm-ar rcs %t.dir/lib.a %t.dir/foo.o %t.dir/bar.o
+
+# archive(member) syntax extracts only the named member.
+RUN: llvm-otool -l "%t.dir/lib.a(bar.o)" \
+RUN: | FileCheck %s --check-prefix=MEMBER --implicit-check-not="foo.o"
+
+MEMBER: bar.o
+
+# Without archive(member), both members are shown.
+RUN: llvm-otool -l %t.dir/lib.a \
+RUN: | FileCheck %s --check-prefix=BOTH
+
+BOTH: foo.o
+BOTH: bar.o
+
+# -m disables archive(member) parsing, treating the filename literally.
+RUN: not llvm-otool -mh "%t.dir/lib.a(bar.o)" 2>&1 \
+RUN: | FileCheck %s --check-prefix=NOPAREN -DMSG=%errc_ENOENT -DPATH=%t.dir
+
+NOPAREN: error: '[[PATH]]/lib.a(bar.o)': [[MSG]]
+
+# Non-existent member gives an error.
+RUN: not llvm-otool -h "%t.dir/lib.a(nonexistent)" 2>&1 \
+RUN: | FileCheck %s --check-prefix=BADMEMBER -DPATH=%t.dir
+
+BADMEMBER: error: '[[PATH]]/lib.a': archive does not contain a member named: nonexistent
+
+# archive(member) on a non-archive file gives an error.
+RUN: not llvm-otool -h "%p/Inputs/hello.obj.macho-x86_64(foo)" 2>&1 \
+RUN: | FileCheck %s --check-prefix=NOTARCHIVE
+
+NOTARCHIVE: error: '{{.*}}hello.obj.macho-x86_64': not an archive (cannot extract member: foo)
+
+# Handle multiple inputs with archive(member) syntax.
+RUN: llvm-otool -h "%t.dir/lib.a(foo.o)" "%t.dir/lib.a(bar.o)" \
+RUN: | FileCheck %s --check-prefix=MULTI
+
+MULTI:      Archive :
+MULTI-NEXT: Mach header
+MULTI:      496
+MULTI-NEXT: Archive :
+MULTI-NEXT: Mach header
+MULTI:      896

diff  --git a/llvm/tools/llvm-objdump/MachODump.cpp b/llvm/tools/llvm-objdump/MachODump.cpp
index 1676b17e14593..8b5a56b0db095 100644
--- a/llvm/tools/llvm-objdump/MachODump.cpp
+++ b/llvm/tools/llvm-objdump/MachODump.cpp
@@ -81,10 +81,12 @@ bool objdump::Verbose;
 bool objdump::ObjcMetaData;
 std::string objdump::DisSymName;
 bool objdump::IsOtool;
+bool objdump::UseMemberSyntax;
 bool objdump::SymbolicOperands;
 std::vector<std::string> objdump::ArchFlags;
 
 static bool ArchAll = false;
+static std::string ArchiveMemberFilter;
 static std::string ThumbTripleName;
 
 static StringRef ordinalName(const object::MachOObjectFile *, int);
@@ -2545,6 +2547,18 @@ static bool ValidateArchFlags() {
   return true;
 }
 
+static bool skipArchiveMember(const object::Archive::Child &C,
+                              StringRef Filename) {
+  if (ArchiveMemberFilter.empty())
+    return false;
+  Expected<StringRef> NameOrErr = C.getName();
+  if (!NameOrErr) {
+    reportError(NameOrErr.takeError(), Filename);
+    return true;
+  }
+  return *NameOrErr != ArchiveMemberFilter;
+}
+
 // ParseInputMachO() parses the named Mach-O file in Filename and handles the
 // -arch flags selecting just those slices as specified by them and also parses
 // archive files.  Then for each individual Mach-O file ProcessMachO() is
@@ -2553,6 +2567,19 @@ void objdump::parseInputMachO(StringRef Filename) {
   if (!ValidateArchFlags())
     return;
 
+  // In otool mode, support archive(member) syntax: if the filename ends
+  // with ')' and contains '(', split it into the archive path and member
+  // name. The -m option disables this parsing.
+  ArchiveMemberFilter.clear();
+  if (IsOtool && UseMemberSyntax && !Filename.empty() &&
+      Filename.back() == ')') {
+    auto Pos = Filename.rfind('(');
+    if (Pos != StringRef::npos && Pos > 0) {
+      ArchiveMemberFilter = Filename.substr(Pos + 1).drop_back().str();
+      Filename = Filename.substr(0, Pos);
+    }
+  }
+
   // Attempt to open the binary.
   Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(Filename);
   if (!BinaryOrErr) {
@@ -2571,8 +2598,12 @@ void objdump::parseInputMachO(StringRef Filename) {
 
     Error Err = Error::success();
     unsigned I = -1;
+    bool FoundMember = false;
     for (auto &C : A->children(Err)) {
       ++I;
+      if (skipArchiveMember(C, Filename))
+        continue;
+      FoundMember = true;
       Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary();
       if (!ChildOrErr) {
         if (Error E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError()))
@@ -2587,12 +2618,20 @@ void objdump::parseInputMachO(StringRef Filename) {
     }
     if (Err)
       reportError(std::move(Err), Filename);
+    if (!FoundMember && !ArchiveMemberFilter.empty())
+      reportError(Filename, "archive does not contain a member named: " +
+                                ArchiveMemberFilter);
     return;
   }
   if (MachOUniversalBinary *UB = dyn_cast<MachOUniversalBinary>(&Bin)) {
     parseInputMachO(UB);
     return;
   }
+  if (!ArchiveMemberFilter.empty()) {
+    reportError(Filename, "not an archive (cannot extract member: " +
+                              ArchiveMemberFilter + ")");
+    return;
+  }
   if (ObjectFile *O = dyn_cast<ObjectFile>(&Bin)) {
     if (!checkMachOAndArchFlags(O, Filename))
       return;
@@ -2652,8 +2691,12 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
                                   ArchiveMemberOffsets, ArchitectureName);
             Error Err = Error::success();
             unsigned I = -1;
+            bool FoundMember = false;
             for (auto &C : A->children(Err)) {
               ++I;
+              if (skipArchiveMember(C, Filename))
+                continue;
+              FoundMember = true;
               Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary();
               if (!ChildOrErr) {
                 if (Error E =
@@ -2668,6 +2711,10 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
             }
             if (Err)
               reportError(std::move(Err), Filename);
+            if (!FoundMember && !ArchiveMemberFilter.empty())
+              reportError(Filename,
+                          "archive does not contain a member named: " +
+                              ArchiveMemberFilter);
           } else {
             consumeError(AOrErr.takeError());
             reportError(Filename,
@@ -2714,8 +2761,12 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
                                 ArchiveMemberOffsets);
           Error Err = Error::success();
           unsigned I = -1;
+          bool FoundMember = false;
           for (auto &C : A->children(Err)) {
             ++I;
+            if (skipArchiveMember(C, Filename))
+              continue;
+            FoundMember = true;
             Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary();
             if (!ChildOrErr) {
               if (Error E =
@@ -2729,6 +2780,9 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
           }
           if (Err)
             reportError(std::move(Err), Filename);
+          if (!FoundMember && !ArchiveMemberFilter.empty())
+            reportError(Filename, "archive does not contain a member named: " +
+                                      ArchiveMemberFilter);
         } else {
           consumeError(AOrErr.takeError());
           reportError(Filename, "Mach-O universal file for architecture " +
@@ -2767,8 +2821,12 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
                             ArchitectureName);
       Error Err = Error::success();
       unsigned I = -1;
+      bool FoundMember = false;
       for (auto &C : A->children(Err)) {
         ++I;
+        if (skipArchiveMember(C, Filename))
+          continue;
+        FoundMember = true;
         Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary();
         if (!ChildOrErr) {
           if (Error E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError()))
@@ -2783,6 +2841,9 @@ void objdump::parseInputMachO(MachOUniversalBinary *UB) {
       }
       if (Err)
         reportError(std::move(Err), Filename);
+      if (!FoundMember && !ArchiveMemberFilter.empty())
+        reportError(Filename, "archive does not contain a member named: " +
+                                  ArchiveMemberFilter);
     } else {
       consumeError(AOrErr.takeError());
       reportError(Filename, "Mach-O universal file for architecture " +

diff  --git a/llvm/tools/llvm-objdump/MachODump.h b/llvm/tools/llvm-objdump/MachODump.h
index 81fd8775e1d20..b9a316a4deb16 100644
--- a/llvm/tools/llvm-objdump/MachODump.h
+++ b/llvm/tools/llvm-objdump/MachODump.h
@@ -59,6 +59,7 @@ extern bool Rebase;
 extern bool Rpaths;
 extern bool SymbolicOperands;
 extern bool UniversalHeaders;
+extern bool UseMemberSyntax;
 extern bool Verbose;
 extern bool WeakBind;
 extern std::vector<std::string> ArchFlags;

diff  --git a/llvm/tools/llvm-objdump/OtoolOpts.td b/llvm/tools/llvm-objdump/OtoolOpts.td
index 8cc70d3207245..e14c09aee42f4 100644
--- a/llvm/tools/llvm-objdump/OtoolOpts.td
+++ b/llvm/tools/llvm-objdump/OtoolOpts.td
@@ -43,9 +43,11 @@ def chained_fixups : Flag<["-"], "chained_fixups">,
 def dyld_info : Flag<["-"], "dyld_info">,
   HelpText<"print bind and rebase information">;
 
+def m : Flag<["-"], "m">,
+  HelpText<"don't use archive(member) syntax">;
+
 // Not (yet?) implemented:
 // -c print argument strings of a core file
-// -m don't use archive(member) syntax
 // -dyld_opcodes
 // -addr_slide=arg
 // -function_offsets

diff  --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 9cc658cbb341c..8c090ba1e29e7 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -3692,6 +3692,8 @@ static void parseOtoolOptions(const llvm::opt::InputArgList &InputArgs) {
   ChainedFixups = InputArgs.hasArg(OTOOL_chained_fixups);
   DyldInfo = InputArgs.hasArg(OTOOL_dyld_info);
 
+  UseMemberSyntax = !InputArgs.hasArg(OTOOL_m);
+
   InputFilenames = InputArgs.getAllArgValues(OTOOL_INPUT);
   if (InputFilenames.empty())
     reportCmdLineError("no input file");


        


More information about the llvm-commits mailing list