[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