llvm-project/llvm/tools/llvm-dwarfutil/llvm-dwarfutil.cpp
Chandler Carruth dd647e3e60
Rework the Option library to reduce dynamic relocations (#119198)
Apologies for the large change, I looked for ways to break this up and
all of the ones I saw added real complexity. This change focuses on the
option's prefixed names and the array of prefixes. These are present in
every option and the dominant source of dynamic relocations for PIE or
PIC users of LLVM and Clang tooling. In some cases, 100s or 1000s of
them for the Clang driver which has a huge number of options.

This PR addresses this by building a string table and a prefixes table
that can be referenced with indices rather than pointers that require
dynamic relocations. This removes almost 7k dynmaic relocations from the
`clang` binary, roughly 8% of the remaining dynmaic relocations outside
of vtables. For busy-boxing use cases where many different option tables
are linked into the same binary, the savings add up a bit more.

The string table is a straightforward mechanism, but the prefixes
required some subtlety. They are encoded in a Pascal-string fashion with
a size followed by a sequence of offsets. This works relatively well for
the small realistic prefixes arrays in use.

Lots of code has to change in order to land this though: both all the
option library code has to be updated to use the string table and
prefixes table, and all the users of the options library have to be
updated to correctly instantiate the objects.

Some follow-up patches in the works to provide an abstraction for this
style of code, and to start using the same technique for some of the
other strings here now that the infrastructure is in place.
2024-12-11 15:44:44 -08:00

549 lines
18 KiB
C++

//=== llvm-dwarfutil.cpp --------------------------------------------------===//
//
// 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 "DebugInfoLinker.h"
#include "Error.h"
#include "Options.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
#include "llvm/MC/MCTargetOptionsCommandFlags.h"
#include "llvm/ObjCopy/CommonConfig.h"
#include "llvm/ObjCopy/ConfigManager.h"
#include "llvm/ObjCopy/ObjCopy.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CRC.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetSelect.h"
using namespace llvm;
using namespace object;
namespace {
enum ID {
OPT_INVALID = 0, // This is not an option ID.
#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
#include "Options.inc"
#undef OPTION
};
#define OPTTABLE_STR_TABLE_CODE
#include "Options.inc"
#undef OPTTABLE_STR_TABLE_CODE
#define OPTTABLE_PREFIXES_TABLE_CODE
#include "Options.inc"
#undef OPTTABLE_PREFIXES_TABLE_CODE
using namespace llvm::opt;
static constexpr opt::OptTable::Info InfoTable[] = {
#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
#include "Options.inc"
#undef OPTION
};
class DwarfutilOptTable : public opt::GenericOptTable {
public:
DwarfutilOptTable()
: opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}
};
} // namespace
namespace llvm {
namespace dwarfutil {
std::string ToolName;
static mc::RegisterMCTargetOptionsFlags MOF;
static Error validateAndSetOptions(opt::InputArgList &Args, Options &Options) {
auto UnknownArgs = Args.filtered(OPT_UNKNOWN);
if (!UnknownArgs.empty())
return createStringError(
std::errc::invalid_argument,
formatv("unknown option: {0}", (*UnknownArgs.begin())->getSpelling())
.str()
.c_str());
std::vector<std::string> InputFiles = Args.getAllArgValues(OPT_INPUT);
if (InputFiles.size() != 2)
return createStringError(
std::errc::invalid_argument,
formatv("exactly two positional arguments expected, {0} provided",
InputFiles.size())
.str()
.c_str());
Options.InputFileName = InputFiles[0];
Options.OutputFileName = InputFiles[1];
Options.BuildSeparateDebugFile =
Args.hasFlag(OPT_separate_debug_file, OPT_no_separate_debug_file, false);
Options.DoODRDeduplication =
Args.hasFlag(OPT_odr_deduplication, OPT_no_odr_deduplication, true);
Options.DoGarbageCollection =
Args.hasFlag(OPT_garbage_collection, OPT_no_garbage_collection, true);
Options.Verbose = Args.hasArg(OPT_verbose);
Options.Verify = Args.hasArg(OPT_verify);
if (opt::Arg *NumThreads = Args.getLastArg(OPT_threads))
Options.NumThreads = atoi(NumThreads->getValue());
else
Options.NumThreads = 0; // Use all available hardware threads
if (opt::Arg *Tombstone = Args.getLastArg(OPT_tombstone)) {
StringRef S = Tombstone->getValue();
if (S == "bfd")
Options.Tombstone = TombstoneKind::BFD;
else if (S == "maxpc")
Options.Tombstone = TombstoneKind::MaxPC;
else if (S == "universal")
Options.Tombstone = TombstoneKind::Universal;
else if (S == "exec")
Options.Tombstone = TombstoneKind::Exec;
else
return createStringError(
std::errc::invalid_argument,
formatv("unknown tombstone value: '{0}'", S).str().c_str());
}
if (opt::Arg *LinkerKind = Args.getLastArg(OPT_linker)) {
StringRef S = LinkerKind->getValue();
if (S == "classic")
Options.UseDWARFLinkerParallel = false;
else if (S == "parallel")
Options.UseDWARFLinkerParallel = true;
else
return createStringError(
std::errc::invalid_argument,
formatv("unknown linker kind value: '{0}'", S).str().c_str());
}
if (opt::Arg *BuildAccelerator = Args.getLastArg(OPT_build_accelerator)) {
StringRef S = BuildAccelerator->getValue();
if (S == "none")
Options.AccelTableKind = DwarfUtilAccelKind::None;
else if (S == "DWARF")
Options.AccelTableKind = DwarfUtilAccelKind::DWARF;
else
return createStringError(
std::errc::invalid_argument,
formatv("unknown build-accelerator value: '{0}'", S).str().c_str());
}
if (Options.Verbose) {
if (Options.NumThreads != 1 && Args.hasArg(OPT_threads))
warning("--num-threads set to 1 because verbose mode is specified");
Options.NumThreads = 1;
}
if (Options.DoODRDeduplication && Args.hasArg(OPT_odr_deduplication) &&
!Options.DoGarbageCollection)
return createStringError(
std::errc::invalid_argument,
"cannot use --odr-deduplication without --garbage-collection");
if (Options.BuildSeparateDebugFile && Options.OutputFileName == "-")
return createStringError(
std::errc::invalid_argument,
"unable to write to stdout when --separate-debug-file specified");
return Error::success();
}
static Error setConfigToAddNewDebugSections(objcopy::ConfigManager &Config,
ObjectFile &ObjFile) {
// Add new debug sections.
for (SectionRef Sec : ObjFile.sections()) {
Expected<StringRef> SecName = Sec.getName();
if (!SecName)
return SecName.takeError();
if (isDebugSection(*SecName)) {
Expected<StringRef> SecData = Sec.getContents();
if (!SecData)
return SecData.takeError();
Config.Common.AddSection.emplace_back(objcopy::NewSectionInfo(
*SecName, MemoryBuffer::getMemBuffer(*SecData, *SecName, false)));
}
}
return Error::success();
}
static Error verifyOutput(const Options &Opts) {
if (Opts.OutputFileName == "-") {
warning("verification skipped because writing to stdout");
return Error::success();
}
std::string FileName = Opts.BuildSeparateDebugFile
? Opts.getSeparateDebugFileName()
: Opts.OutputFileName;
Expected<OwningBinary<Binary>> BinOrErr = createBinary(FileName);
if (!BinOrErr)
return createFileError(FileName, BinOrErr.takeError());
if (BinOrErr->getBinary()->isObject()) {
if (ObjectFile *Obj = static_cast<ObjectFile *>(BinOrErr->getBinary())) {
verbose("Verifying DWARF...", Opts.Verbose);
std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
DIDumpOptions DumpOpts;
if (!DICtx->verify(Opts.Verbose ? outs() : nulls(),
DumpOpts.noImplicitRecursion()))
return createFileError(FileName,
createError("output verification failed"));
return Error::success();
}
}
// The file "FileName" was created by this utility in the previous steps
// (i.e. it is already known that it should pass the isObject check).
// If the createBinary() function does not return an error, the isObject
// check should also be successful.
llvm_unreachable(
formatv("tool unexpectedly did not emit a supported object file: '{0}'",
FileName)
.str()
.c_str());
}
class raw_crc_ostream : public raw_ostream {
public:
explicit raw_crc_ostream(raw_ostream &O) : OS(O) { SetUnbuffered(); }
void reserveExtraSpace(uint64_t ExtraSize) override {
OS.reserveExtraSpace(ExtraSize);
}
uint32_t getCRC32() { return CRC32; }
protected:
raw_ostream &OS;
uint32_t CRC32 = 0;
/// See raw_ostream::write_impl.
void write_impl(const char *Ptr, size_t Size) override {
CRC32 = crc32(
CRC32, ArrayRef<uint8_t>(reinterpret_cast<const uint8_t *>(Ptr), Size));
OS.write(Ptr, Size);
}
/// Return the current position within the stream, not counting the bytes
/// currently in the buffer.
uint64_t current_pos() const override { return OS.tell(); }
};
static Expected<uint32_t> saveSeparateDebugInfo(const Options &Opts,
ObjectFile &InputFile) {
objcopy::ConfigManager Config;
std::string OutputFilename = Opts.getSeparateDebugFileName();
Config.Common.InputFilename = Opts.InputFileName;
Config.Common.OutputFilename = OutputFilename;
Config.Common.OnlyKeepDebug = true;
uint32_t WrittenFileCRC32 = 0;
if (Error Err = writeToOutput(
Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
raw_crc_ostream CRCBuffer(OutFile);
if (Error Err = objcopy::executeObjcopyOnBinary(Config, InputFile,
CRCBuffer))
return Err;
WrittenFileCRC32 = CRCBuffer.getCRC32();
return Error::success();
}))
return std::move(Err);
return WrittenFileCRC32;
}
static Error saveNonDebugInfo(const Options &Opts, ObjectFile &InputFile,
uint32_t GnuDebugLinkCRC32) {
objcopy::ConfigManager Config;
Config.Common.InputFilename = Opts.InputFileName;
Config.Common.OutputFilename = Opts.OutputFileName;
Config.Common.StripDebug = true;
std::string SeparateDebugFileName = Opts.getSeparateDebugFileName();
Config.Common.AddGnuDebugLink = sys::path::filename(SeparateDebugFileName);
Config.Common.GnuDebugLinkCRC32 = GnuDebugLinkCRC32;
if (Error Err = writeToOutput(
Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
if (Error Err =
objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile))
return Err;
return Error::success();
}))
return Err;
return Error::success();
}
static Error splitDebugIntoSeparateFile(const Options &Opts,
ObjectFile &InputFile) {
Expected<uint32_t> SeparateDebugFileCRC32OrErr =
saveSeparateDebugInfo(Opts, InputFile);
if (!SeparateDebugFileCRC32OrErr)
return SeparateDebugFileCRC32OrErr.takeError();
if (Error Err =
saveNonDebugInfo(Opts, InputFile, *SeparateDebugFileCRC32OrErr))
return Err;
return Error::success();
}
using DebugInfoBits = SmallString<10000>;
static Error addSectionsFromLinkedData(objcopy::ConfigManager &Config,
ObjectFile &InputFile,
DebugInfoBits &LinkedDebugInfoBits) {
if (isa<ELFObjectFile<ELF32LE>>(&InputFile)) {
Expected<ELFObjectFile<ELF32LE>> MemFile = ELFObjectFile<ELF32LE>::create(
MemoryBufferRef(LinkedDebugInfoBits, ""));
if (!MemFile)
return MemFile.takeError();
if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
return Err;
} else if (isa<ELFObjectFile<ELF64LE>>(&InputFile)) {
Expected<ELFObjectFile<ELF64LE>> MemFile = ELFObjectFile<ELF64LE>::create(
MemoryBufferRef(LinkedDebugInfoBits, ""));
if (!MemFile)
return MemFile.takeError();
if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
return Err;
} else if (isa<ELFObjectFile<ELF32BE>>(&InputFile)) {
Expected<ELFObjectFile<ELF32BE>> MemFile = ELFObjectFile<ELF32BE>::create(
MemoryBufferRef(LinkedDebugInfoBits, ""));
if (!MemFile)
return MemFile.takeError();
if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
return Err;
} else if (isa<ELFObjectFile<ELF64BE>>(&InputFile)) {
Expected<ELFObjectFile<ELF64BE>> MemFile = ELFObjectFile<ELF64BE>::create(
MemoryBufferRef(LinkedDebugInfoBits, ""));
if (!MemFile)
return MemFile.takeError();
if (Error Err = setConfigToAddNewDebugSections(Config, *MemFile))
return Err;
} else
return createStringError(std::errc::invalid_argument,
"unsupported file format");
return Error::success();
}
static Expected<uint32_t>
saveSeparateLinkedDebugInfo(const Options &Opts, ObjectFile &InputFile,
DebugInfoBits LinkedDebugInfoBits) {
objcopy::ConfigManager Config;
std::string OutputFilename = Opts.getSeparateDebugFileName();
Config.Common.InputFilename = Opts.InputFileName;
Config.Common.OutputFilename = OutputFilename;
Config.Common.StripDebug = true;
Config.Common.OnlyKeepDebug = true;
uint32_t WrittenFileCRC32 = 0;
if (Error Err =
addSectionsFromLinkedData(Config, InputFile, LinkedDebugInfoBits))
return std::move(Err);
if (Error Err = writeToOutput(
Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
raw_crc_ostream CRCBuffer(OutFile);
if (Error Err = objcopy::executeObjcopyOnBinary(Config, InputFile,
CRCBuffer))
return Err;
WrittenFileCRC32 = CRCBuffer.getCRC32();
return Error::success();
}))
return std::move(Err);
return WrittenFileCRC32;
}
static Error saveSingleLinkedDebugInfo(const Options &Opts,
ObjectFile &InputFile,
DebugInfoBits LinkedDebugInfoBits) {
objcopy::ConfigManager Config;
Config.Common.InputFilename = Opts.InputFileName;
Config.Common.OutputFilename = Opts.OutputFileName;
Config.Common.StripDebug = true;
if (Error Err =
addSectionsFromLinkedData(Config, InputFile, LinkedDebugInfoBits))
return Err;
if (Error Err = writeToOutput(
Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
return objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile);
}))
return Err;
return Error::success();
}
static Error saveLinkedDebugInfo(const Options &Opts, ObjectFile &InputFile,
DebugInfoBits LinkedDebugInfoBits) {
if (Opts.BuildSeparateDebugFile) {
Expected<uint32_t> SeparateDebugFileCRC32OrErr =
saveSeparateLinkedDebugInfo(Opts, InputFile,
std::move(LinkedDebugInfoBits));
if (!SeparateDebugFileCRC32OrErr)
return SeparateDebugFileCRC32OrErr.takeError();
if (Error Err =
saveNonDebugInfo(Opts, InputFile, *SeparateDebugFileCRC32OrErr))
return Err;
} else {
if (Error Err = saveSingleLinkedDebugInfo(Opts, InputFile,
std::move(LinkedDebugInfoBits)))
return Err;
}
return Error::success();
}
static Error saveCopyOfFile(const Options &Opts, ObjectFile &InputFile) {
objcopy::ConfigManager Config;
Config.Common.InputFilename = Opts.InputFileName;
Config.Common.OutputFilename = Opts.OutputFileName;
if (Error Err = writeToOutput(
Config.Common.OutputFilename, [&](raw_ostream &OutFile) -> Error {
return objcopy::executeObjcopyOnBinary(Config, InputFile, OutFile);
}))
return Err;
return Error::success();
}
static Error applyCLOptions(const struct Options &Opts, ObjectFile &InputFile) {
if (Opts.DoGarbageCollection ||
Opts.AccelTableKind != DwarfUtilAccelKind::None) {
verbose("Do debug info linking...", Opts.Verbose);
DebugInfoBits LinkedDebugInfo;
raw_svector_ostream OutStream(LinkedDebugInfo);
if (Error Err = linkDebugInfo(InputFile, Opts, OutStream))
return Err;
if (Error Err =
saveLinkedDebugInfo(Opts, InputFile, std::move(LinkedDebugInfo)))
return Err;
return Error::success();
} else if (Opts.BuildSeparateDebugFile) {
if (Error Err = splitDebugIntoSeparateFile(Opts, InputFile))
return Err;
} else {
if (Error Err = saveCopyOfFile(Opts, InputFile))
return Err;
}
return Error::success();
}
} // end of namespace dwarfutil
} // end of namespace llvm
int main(int Argc, char const *Argv[]) {
using namespace dwarfutil;
InitLLVM X(Argc, Argv);
ToolName = Argv[0];
// Parse arguments.
DwarfutilOptTable T;
unsigned MAI;
unsigned MAC;
ArrayRef<const char *> ArgsArr = ArrayRef(Argv + 1, Argc - 1);
opt::InputArgList Args = T.ParseArgs(ArgsArr, MAI, MAC);
if (Args.hasArg(OPT_help) || Args.size() == 0) {
T.printHelp(
outs(), (ToolName + " [options] <input file> <output file>").c_str(),
"llvm-dwarfutil is a tool to copy and manipulate debug info", false);
return EXIT_SUCCESS;
}
if (Args.hasArg(OPT_version)) {
cl::PrintVersionMessage();
return EXIT_SUCCESS;
}
Options Opts;
if (Error Err = validateAndSetOptions(Args, Opts))
error(std::move(Err), dwarfutil::ToolName);
InitializeAllTargets();
InitializeAllTargetMCs();
InitializeAllTargetInfos();
InitializeAllAsmPrinters();
ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
MemoryBuffer::getFileOrSTDIN(Opts.InputFileName);
if (BuffOrErr.getError())
error(createFileError(Opts.InputFileName, BuffOrErr.getError()));
Expected<std::unique_ptr<Binary>> BinOrErr =
object::createBinary(**BuffOrErr);
if (!BinOrErr)
error(createFileError(Opts.InputFileName, BinOrErr.takeError()));
Expected<FilePermissionsApplier> PermsApplierOrErr =
FilePermissionsApplier::create(Opts.InputFileName);
if (!PermsApplierOrErr)
error(createFileError(Opts.InputFileName, PermsApplierOrErr.takeError()));
if (!(*BinOrErr)->isObject())
error(createFileError(Opts.InputFileName,
createError("unsupported input file")));
if (Error Err =
applyCLOptions(Opts, *static_cast<ObjectFile *>((*BinOrErr).get())))
error(createFileError(Opts.InputFileName, std::move(Err)));
BinOrErr->reset();
BuffOrErr->reset();
if (Error Err = PermsApplierOrErr->apply(Opts.OutputFileName))
error(std::move(Err));
if (Opts.BuildSeparateDebugFile)
if (Error Err = PermsApplierOrErr->apply(Opts.getSeparateDebugFileName()))
error(std::move(Err));
if (Opts.Verify) {
if (Error Err = verifyOutput(Opts))
error(std::move(Err));
}
return EXIT_SUCCESS;
}