[llvm] 7b0d00f - [llvm-objcopy] Add --verbose option to llvm-strip/llvm-objcopy (#196611)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 01:17:31 PDT 2026
Author: Aayush Shrivastava
Date: 2026-06-03T08:17:25Z
New Revision: 7b0d00f374097fc364ff7a40c846bb98f4950945
URL: https://github.com/llvm/llvm-project/commit/7b0d00f374097fc364ff7a40c846bb98f4950945
DIFF: https://github.com/llvm/llvm-project/commit/7b0d00f374097fc364ff7a40c846bb98f4950945.diff
LOG: [llvm-objcopy] Add --verbose option to llvm-strip/llvm-objcopy (#196611)
Added `--verbose / -v` option to `llvm-strip` and `llvm-objcopy` as part
of #123041, matching GNU strip's output format. When the flag is passed,
the tool prints one line per file processed.
copy from `'input.o' [elf64-x86-64] to 'output.o' [elf64-x86-64]`
Added:
llvm/test/tools/llvm-objcopy/verbose.test
Modified:
llvm/docs/CommandGuide/llvm-objcopy.rst
llvm/docs/CommandGuide/llvm-strip.rst
llvm/include/llvm/ObjCopy/CommonConfig.h
llvm/include/llvm/ObjCopy/ObjCopy.h
llvm/lib/ObjCopy/Archive.cpp
llvm/lib/ObjCopy/ObjCopy.cpp
llvm/tools/llvm-objcopy/CommonOpts.td
llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
llvm/tools/llvm-objcopy/llvm-objcopy.cpp
Removed:
################################################################################
diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst
index 9219a5a5852a8..967253d984a35 100644
--- a/llvm/docs/CommandGuide/llvm-objcopy.rst
+++ b/llvm/docs/CommandGuide/llvm-objcopy.rst
@@ -294,6 +294,10 @@ multiple file formats.
``<file>``. If the section ``<name>`` is part of a segment, the new contents
cannot be larger than the existing section.
+.. option:: --verbose, -v
+
+ Print the paths of files that are copied.
+
.. option:: --version, -V
Display the version of the :program:`llvm-objcopy` executable.
diff --git a/llvm/docs/CommandGuide/llvm-strip.rst b/llvm/docs/CommandGuide/llvm-strip.rst
index a870e210dfea0..d6f4ab1d37461 100644
--- a/llvm/docs/CommandGuide/llvm-strip.rst
+++ b/llvm/docs/CommandGuide/llvm-strip.rst
@@ -110,6 +110,10 @@ multiple file formats.
Remove from the output all local or undefined symbols that are not required by
relocations. Also remove all debug sections.
+.. option:: --verbose, -v
+
+ Print the paths of files that are copied.
+
.. option:: --version, -V
Display the version of the :program:`llvm-strip` executable.
diff --git a/llvm/include/llvm/ObjCopy/CommonConfig.h b/llvm/include/llvm/ObjCopy/CommonConfig.h
index bad4bfdeaa46e..1f984f68c6287 100644
--- a/llvm/include/llvm/ObjCopy/CommonConfig.h
+++ b/llvm/include/llvm/ObjCopy/CommonConfig.h
@@ -276,6 +276,7 @@ struct CommonConfig {
bool StripNonAlloc = false;
bool StripSections = false;
bool StripUnneeded = false;
+ bool Verbose = false;
bool Weaken = false;
bool DecompressDebugSections = false;
diff --git a/llvm/include/llvm/ObjCopy/ObjCopy.h b/llvm/include/llvm/ObjCopy/ObjCopy.h
index 9554930e20970..d90c2e832400a 100644
--- a/llvm/include/llvm/ObjCopy/ObjCopy.h
+++ b/llvm/include/llvm/ObjCopy/ObjCopy.h
@@ -23,6 +23,14 @@ class Binary;
namespace objcopy {
class MultiFormatConfig;
+/// Returns the format name of \p B if it is an ObjectFile, or "" otherwise.
+LLVM_ABI StringRef getObjectFormatName(const object::Binary &B);
+
+/// Prints information about the input and output files involved in a copy
+/// operation to stdout.
+LLVM_ABI void printCopyMessage(StringRef InPath, StringRef InFormatName,
+ StringRef OutPath, StringRef OutFormatName);
+
/// Applies the transformations described by \p Config to
/// each member in archive \p Ar.
/// Writes a result in a file specified by \p Config.OutputFilename.
diff --git a/llvm/lib/ObjCopy/Archive.cpp b/llvm/lib/ObjCopy/Archive.cpp
index a4e90ce387269..191174102c66f 100644
--- a/llvm/lib/ObjCopy/Archive.cpp
+++ b/llvm/lib/ObjCopy/Archive.cpp
@@ -28,11 +28,22 @@ objcopy::createNewArchiveMembers(const MultiFormatConfig &Config,
if (!ChildNameOrErr)
return createFileError(Ar.getFileName(), ChildNameOrErr.takeError());
+ auto MemberName = [&](StringRef Prefix) {
+ return (Twine(Prefix) + "(" + *ChildNameOrErr + ")").str();
+ };
+
Expected<std::unique_ptr<Binary>> ChildOrErr = Child.getAsBinary();
if (!ChildOrErr)
- return createFileError(Ar.getFileName() + "(" + *ChildNameOrErr + ")",
+ return createFileError(MemberName(Ar.getFileName()),
ChildOrErr.takeError());
+ const CommonConfig &CC = Config.getCommonConfig();
+ if (CC.Verbose) {
+ StringRef FormatName = getObjectFormatName(*ChildOrErr->get());
+ printCopyMessage(MemberName(Ar.getFileName()), FormatName,
+ MemberName(CC.OutputFilename), FormatName);
+ }
+
SmallVector<char, 0> Buffer;
raw_svector_ostream MemStream(Buffer);
diff --git a/llvm/lib/ObjCopy/ObjCopy.cpp b/llvm/lib/ObjCopy/ObjCopy.cpp
index 152600f4f243f..288b2432e8e8b 100644
--- a/llvm/lib/ObjCopy/ObjCopy.cpp
+++ b/llvm/lib/ObjCopy/ObjCopy.cpp
@@ -32,6 +32,20 @@
using namespace llvm;
using namespace llvm::object;
+StringRef objcopy::getObjectFormatName(const object::Binary &B) {
+ if (const auto *OF = dyn_cast<ObjectFile>(&B))
+ return OF->getFileFormatName();
+ return {};
+}
+
+void objcopy::printCopyMessage(StringRef InPath, StringRef InFormatName,
+ StringRef OutPath, StringRef OutFormatName) {
+ if (OutFormatName.empty())
+ OutFormatName = InFormatName;
+ outs() << "copy from '" << InPath << "' [" << InFormatName << "] to '"
+ << OutPath << "' [" << OutFormatName << "]\n";
+}
+
/// The function executeObjcopyOnBinary does the dispatch based on the format
/// of the input binary (ELF, MachO or COFF).
Error objcopy::executeObjcopyOnBinary(const MultiFormatConfig &Config,
diff --git a/llvm/test/tools/llvm-objcopy/verbose.test b/llvm/test/tools/llvm-objcopy/verbose.test
new file mode 100644
index 0000000000000..09544d40636e2
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/verbose.test
@@ -0,0 +1,91 @@
+## Test --verbose / -v for llvm-objcopy and llvm-strip.
+
+# RUN: rm -rf %t && mkdir %t
+# RUN: yaml2obj %s --docnum=1 -o %t/input.o
+# RUN: yaml2obj %s --docnum=2 -o %t/coff.o
+
+## --verbose and -v both print to stdout.
+# RUN: llvm-objcopy --verbose %t/input.o %t/output.o 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=OBJCOPY --implicit-check-not={{.}} \
+# RUN: -DIN=%t/input.o -DOUT=%t/output.o
+# RUN: llvm-objcopy -v %t/input.o %t/output.o 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=OBJCOPY --implicit-check-not={{.}} \
+# RUN: -DIN=%t/input.o -DOUT=%t/output.o
+# OBJCOPY: copy from '[[IN]]' [elf64-x86-64] to '[[OUT]]' [elf64-x86-64]
+
+## In-place: a single positional argument means input == output.
+# RUN: cp %t/input.o %t/inplace.o
+# RUN: llvm-objcopy --verbose %t/inplace.o 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=OBJCOPY --implicit-check-not={{.}} \
+# RUN: -DIN=%t/inplace.o -DOUT=%t/inplace.o
+
+## The output format name reflects the requested output type, not the input.
+# RUN: llvm-objcopy --verbose -O binary %t/input.o %t/output.bin 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=BINARY --implicit-check-not={{.}} \
+# RUN: -DIN=%t/input.o -DOUT=%t/output.bin
+# BINARY: copy from '[[IN]]' [elf64-x86-64] to '[[OUT]]' [binary]
+
+## Explicit input/output formats (-I ihex -O srec): both format names come
+## from the command-line flags rather than the file content.
+# RUN: printf ':0100000001FE\n:00000001FF\n' > %t/input.hex
+# RUN: llvm-objcopy --verbose -I ihex -O srec %t/input.hex %t/output.srec 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=IHEX --implicit-check-not={{.}} \
+# RUN: -DIN=%t/input.hex -DOUT=%t/output.srec
+# IHEX: copy from '[[IN]]' [ihex] to '[[OUT]]' [srec]
+
+## Non-ELF input (COFF i386): the format name reflects the actual object type and architecture
+# RUN: llvm-objcopy --verbose %t/coff.o %t/coff.out 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=COFF --implicit-check-not={{.}} \
+# RUN: -DIN=%t/coff.o -DOUT=%t/coff.out
+# COFF: copy from '[[IN]]' [COFF-i386] to '[[OUT]]' [COFF-i386]
+
+## llvm-strip --verbose and -v each print one line per input file (in-place).
+# RUN: cp %t/input.o %t/strip1.o
+# RUN: cp %t/input.o %t/strip2.o
+# RUN: llvm-strip --verbose %t/strip1.o %t/strip2.o 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=STRIP --implicit-check-not={{.}} \
+# RUN: -DF1=%t/strip1.o -DF2=%t/strip2.o
+# RUN: cp %t/input.o %t/strip3.o
+# RUN: cp %t/input.o %t/strip4.o
+# RUN: llvm-strip -v %t/strip3.o %t/strip4.o 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=STRIP --implicit-check-not={{.}} \
+# RUN: -DF1=%t/strip3.o -DF2=%t/strip4.o
+# STRIP: copy from '[[F1]]' [elf64-x86-64] to '[[F1]]' [elf64-x86-64]
+# STRIP-NEXT: copy from '[[F2]]' [elf64-x86-64] to '[[F2]]' [elf64-x86-64]
+
+## Archive: one line per member using <archive>(<member>) syntax.
+# RUN: cp %t/input.o %t/member1.o
+# RUN: cp %t/input.o %t/member2.o
+# RUN: rm -f %t/archive.a
+# RUN: llvm-ar crs %t/archive.a %t/member1.o %t/member2.o
+# RUN: llvm-objcopy --verbose %t/archive.a %t/archive.out 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=ARCHIVE --implicit-check-not={{.}} \
+# RUN: -DAR=%t/archive.a -DOUT=%t/archive.out \
+# RUN: -DMEM1=member1.o -DMEM2=member2.o
+# ARCHIVE: copy from '[[AR]]([[MEM1]])' [elf64-x86-64] to '[[OUT]]([[MEM1]])' [elf64-x86-64]
+# ARCHIVE-NEXT: copy from '[[AR]]([[MEM2]])' [elf64-x86-64] to '[[OUT]]([[MEM2]])' [elf64-x86-64]
+
+## llvm-strip on an archive modifies in-place (OUT == AR).
+# RUN: rm -f %t/strip.a
+# RUN: llvm-ar crs %t/strip.a %t/member1.o %t/member2.o
+# RUN: llvm-strip --verbose %t/strip.a 2>/dev/null \
+# RUN: | FileCheck %s --check-prefix=ARCHIVE --implicit-check-not={{.}} \
+# RUN: -DAR=%t/strip.a -DOUT=%t/strip.a \
+# RUN: -DMEM1=member1.o -DMEM2=member2.o
+
+## No output without the flag.
+# RUN: llvm-objcopy %t/input.o %t/output.o 2>/dev/null | count 0
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_REL
+ Machine: EM_X86_64
+
+--- !COFF
+header:
+ Machine: IMAGE_FILE_MACHINE_I386
+ Characteristics: []
+sections: []
+symbols: []
diff --git a/llvm/tools/llvm-objcopy/CommonOpts.td b/llvm/tools/llvm-objcopy/CommonOpts.td
index 76bc00285f962..cc4504f7df6d6 100644
--- a/llvm/tools/llvm-objcopy/CommonOpts.td
+++ b/llvm/tools/llvm-objcopy/CommonOpts.td
@@ -122,6 +122,11 @@ def regex
: Flag<["--"], "regex">,
HelpText<"Permit regular expressions in name comparison">;
+def verbose : Flag<["--"], "verbose">,
+ HelpText<"Print the paths of files that are copied">;
+def v : Flag<["-"], "v">, Alias<verbose>,
+ HelpText<"Alias for --verbose">;
+
def version : Flag<["--"], "version">,
HelpText<"Print the version and exit.">;
def V : Flag<["-"], "V">,
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
index c5a83208c92a1..8f555beb45c35 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
+++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
@@ -1148,6 +1148,7 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
Config.ExtractMainPartition =
InputArgs.hasArg(OBJCOPY_extract_main_partition);
ELFConfig.LocalizeHidden = InputArgs.hasArg(OBJCOPY_localize_hidden);
+ Config.Verbose = InputArgs.hasArg(OBJCOPY_verbose);
Config.Weaken = InputArgs.hasArg(OBJCOPY_weaken);
if (auto *Arg =
InputArgs.getLastArg(OBJCOPY_discard_all, OBJCOPY_discard_locals)) {
@@ -1682,6 +1683,7 @@ objcopy::parseStripOptions(ArrayRef<const char *> RawArgsArr,
STRIP_disable_deterministic_archives, /*default=*/true);
Config.PreserveDates = InputArgs.hasArg(STRIP_preserve_dates);
+ Config.Verbose = InputArgs.hasArg(STRIP_verbose);
Config.InputFormat = FileFormat::Unspecified;
Config.OutputFormat = FileFormat::Unspecified;
diff --git a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
index c199149f1e014..482249f67d38c 100644
--- a/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
+++ b/llvm/tools/llvm-objcopy/llvm-objcopy.cpp
@@ -132,6 +132,22 @@ static Error executeObjcopyOnRawBinary(ConfigManager &ConfigMgr,
llvm_unreachable("unsupported output format");
}
+/// Returns the format name string for explicit file formats (binary, ihex,
+/// srec). Returns "" for all other formats so callers can fall back to the
+/// input object's own format string (e.g. "elf64-x86-64").
+static StringRef toFileFormatName(FileFormat Fmt) {
+ switch (Fmt) {
+ case FileFormat::Binary:
+ return "binary";
+ case FileFormat::IHex:
+ return "ihex";
+ case FileFormat::SREC:
+ return "srec";
+ default:
+ return "";
+ }
+}
+
/// The function executeObjcopy does the higher level dispatch based on the type
/// of input (raw binary, archive or single object file) and takes care of the
/// format-agnostic modifications, i.e. preserving dates.
@@ -156,6 +172,11 @@ static Error executeObjcopy(ConfigManager &ConfigMgr) {
return createFileError(Config.InputFilename, BufOrErr.getError());
MemoryBufferHolder = std::move(*BufOrErr);
+ if (Config.Verbose)
+ printCopyMessage(
+ Config.InputFilename, toFileFormatName(Config.InputFormat),
+ Config.OutputFilename, toFileFormatName(Config.OutputFormat));
+
if (Config.InputFormat == FileFormat::Binary)
ObjcopyFunc = [&](raw_ostream &OutFile) -> Error {
// Handle FileFormat::Binary.
@@ -175,10 +196,14 @@ static Error executeObjcopy(ConfigManager &ConfigMgr) {
BinaryHolder = std::move(*BinaryOrErr);
if (Archive *Ar = dyn_cast<Archive>(BinaryHolder.getBinary())) {
- // Handle Archive.
if (Error E = executeObjcopyOnArchive(ConfigMgr, *Ar))
return E;
} else {
+ if (Config.Verbose)
+ printCopyMessage(Config.InputFilename,
+ getObjectFormatName(*BinaryHolder.getBinary()),
+ Config.OutputFilename,
+ toFileFormatName(Config.OutputFormat));
// Handle llvm::object::Binary.
ObjcopyFunc = [&](raw_ostream &OutFile) -> Error {
return executeObjcopyOnBinary(ConfigMgr, *BinaryHolder.getBinary(),
More information about the llvm-commits
mailing list