#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Triple.h"
#include "llvm/DebugInfo/DIContext.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/MachOUniversal.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cstring>
#include <inttypes.h>
#include <iostream>
#include <map>
#include <string>
#include <system_error>
#include <vector>
#include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
#include "llvm/DebugInfo/GSYM/FunctionInfo.h"
#include "llvm/DebugInfo/GSYM/GsymCreator.h"
#include "llvm/DebugInfo/GSYM/GsymReader.h"
#include "llvm/DebugInfo/GSYM/InlineInfo.h"
#include "llvm/DebugInfo/GSYM/LookupResult.h"
#include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h"
using namespace llvm;
using namespace gsym;
using namespace object;
namespace {
using namespace cl;
OptionCategory GeneralOptions("Options");
OptionCategory ConversionOptions("Conversion Options");
OptionCategory LookupOptions("Lookup Options");
static opt<bool> Help("h", desc("Alias for -help"), Hidden,
cat(GeneralOptions));
static opt<bool> Verbose("verbose",
desc("Enable verbose logging and encoding details."),
cat(GeneralOptions));
static list<std::string> InputFilenames(Positional, desc("<input GSYM files>"),
cat(GeneralOptions));
static opt<std::string>
ConvertFilename("convert", cl::init(""),
cl::desc("Convert the specified file to the GSYM format.\n"
"Supported files include ELF and mach-o files "
"that will have their debug info (DWARF) and "
"symbol table converted."),
cl::value_desc("path"), cat(ConversionOptions));
static list<std::string>
ArchFilters("arch",
desc("Process debug information for the specified CPU "
"architecture only.\nArchitectures may be specified by "
"name or by number.\nThis option can be specified "
"multiple times, once for each desired architecture."),
cl::value_desc("arch"), cat(ConversionOptions));
static opt<std::string>
OutputFilename("out-file", cl::init(""),
cl::desc("Specify the path where the converted GSYM file "
"will be saved.\nWhen not specified, a '.gsym' "
"extension will be appended to the file name "
"specified in the --convert option."),
cl::value_desc("path"), cat(ConversionOptions));
static alias OutputFilenameAlias("o", desc("Alias for -out-file."),
aliasopt(OutputFilename),
cat(ConversionOptions));
static opt<bool> Verify("verify",
desc("Verify the generated GSYM file against the "
"information in the file that was converted."),
cat(ConversionOptions));
static opt<unsigned>
NumThreads("num-threads",
desc("Specify the maximum number (n) of simultaneous threads "
"to use when converting files to GSYM.\nDefaults to the "
"number of cores on the current machine."),
cl::value_desc("n"), cat(ConversionOptions));
static opt<bool>
Quiet("quiet", desc("Do not output warnings about the debug information"),
cat(ConversionOptions));
static list<uint64_t> LookupAddresses("address",
desc("Lookup an address in a GSYM file"),
cl::value_desc("addr"),
cat(LookupOptions));
static opt<bool> LookupAddressesFromStdin(
"addresses-from-stdin",
desc("Lookup addresses in a GSYM file that are read from stdin\nEach input "
"line is expected to be of the following format: <addr> <gsym-path>"),
cat(LookupOptions));
}
static void error(Error Err) {
if (!Err)
return;
WithColor::error() << toString(std::move(Err)) << "\n";
exit(1);
}
static void error(StringRef Prefix, llvm::Error Err) {
if (!Err)
return;
errs() << Prefix << ": " << Err << "\n";
consumeError(std::move(Err));
exit(1);
}
static void error(StringRef Prefix, std::error_code EC) {
if (!EC)
return;
errs() << Prefix << ": " << EC.message() << "\n";
exit(1);
}
static uint32_t getCPUType(MachOObjectFile &MachO) {
if (MachO.is64Bit())
return MachO.getHeader64().cputype;
else
return MachO.getHeader().cputype;
}
static bool filterArch(MachOObjectFile &Obj) {
if (ArchFilters.empty())
return true;
Triple ObjTriple(Obj.getArchTriple());
StringRef ObjArch = ObjTriple.getArchName();
for (auto Arch : ArchFilters) {
if (Arch == ObjArch)
return true;
unsigned Value;
if (!StringRef(Arch).getAsInteger(0, Value))
if (Value == getCPUType(Obj))
return true;
}
return false;
}
template <class ELFT>
static llvm::Optional<uint64_t>
getImageBaseAddress(const object::ELFFile<ELFT> &ELFFile) {
auto PhdrRangeOrErr = ELFFile.program_headers();
if (!PhdrRangeOrErr) {
consumeError(PhdrRangeOrErr.takeError());
return llvm::None;
}
for (const typename ELFT::Phdr &Phdr : *PhdrRangeOrErr)
if (Phdr.p_type == ELF::PT_LOAD)
return (uint64_t)Phdr.p_vaddr;
return llvm::None;
}
static llvm::Optional<uint64_t>
getImageBaseAddress(const object::MachOObjectFile *MachO) {
for (const auto &Command : MachO->load_commands()) {
if (Command.C.cmd == MachO::LC_SEGMENT) {
MachO::segment_command SLC = MachO->getSegmentLoadCommand(Command);
StringRef SegName = SLC.segname;
if (SegName == "__TEXT")
return SLC.vmaddr;
} else if (Command.C.cmd == MachO::LC_SEGMENT_64) {
MachO::segment_command_64 SLC = MachO->getSegment64LoadCommand(Command);
StringRef SegName = SLC.segname;
if (SegName == "__TEXT")
return SLC.vmaddr;
}
}
return llvm::None;
}
static llvm::Optional<uint64_t> getImageBaseAddress(object::ObjectFile &Obj) {
if (const auto *MachO = dyn_cast<object::MachOObjectFile>(&Obj))
return getImageBaseAddress(MachO);
else if (const auto *ELFObj = dyn_cast<object::ELF32LEObjectFile>(&Obj))
return getImageBaseAddress(ELFObj->getELFFile());
else if (const auto *ELFObj = dyn_cast<object::ELF32BEObjectFile>(&Obj))
return getImageBaseAddress(ELFObj->getELFFile());
else if (const auto *ELFObj = dyn_cast<object::ELF64LEObjectFile>(&Obj))
return getImageBaseAddress(ELFObj->getELFFile());
else if (const auto *ELFObj = dyn_cast<object::ELF64BEObjectFile>(&Obj))
return getImageBaseAddress(ELFObj->getELFFile());
return llvm::None;
}
static llvm::Error handleObjectFile(ObjectFile &Obj,
const std::string &OutFile) {
auto ThreadCount =
NumThreads > 0 ? NumThreads : std::thread::hardware_concurrency();
auto &OS = outs();
GsymCreator Gsym(Quiet);
if (auto ImageBaseAddr = getImageBaseAddress(Obj))
Gsym.setBaseAddress(*ImageBaseAddr);
AddressRanges TextRanges;
for (const object::SectionRef &Sect : Obj.sections()) {
if (!Sect.isText())
continue;
const uint64_t Size = Sect.getSize();
if (Size == 0)
continue;
const uint64_t StartAddr = Sect.getAddress();
TextRanges.insert(AddressRange(StartAddr, StartAddr + Size));
}
std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(Obj);
if (!DICtx)
return createStringError(std::errc::invalid_argument,
"unable to create DWARF context");
logAllUnhandledErrors(DICtx->loadRegisterInfo(Obj), OS, "DwarfTransformer: ");
DwarfTransformer DT(*DICtx, OS, Gsym);
if (!TextRanges.empty())
Gsym.SetValidTextRanges(TextRanges);
if (auto Err = DT.convert(ThreadCount))
return Err;
if (auto Err = ObjectFileTransformer::convert(Obj, OS, Gsym))
return Err;
if (auto Err = Gsym.finalize(OS))
return Err;
support::endianness Endian =
Obj.makeTriple().isLittleEndian() ? support::little : support::big;
if (auto Err = Gsym.save(OutFile, Endian))
return Err;
if (Verify) {
if (auto Err = DT.verify(OutFile))
return Err;
}
return Error::success();
}
static llvm::Error handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
const std::string &OutFile) {
Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Buffer);
error(Filename, errorToErrorCode(BinOrErr.takeError()));
if (auto *Obj = dyn_cast<ObjectFile>(BinOrErr->get())) {
Triple ObjTriple(Obj->makeTriple());
auto ArchName = ObjTriple.getArchName();
outs() << "Output file (" << ArchName << "): " << OutFile << "\n";
if (auto Err = handleObjectFile(*Obj, OutFile))
return Err;
} else if (auto *Fat = dyn_cast<MachOUniversalBinary>(BinOrErr->get())) {
std::vector<std::unique_ptr<MachOObjectFile>> FilterObjs;
for (auto &ObjForArch : Fat->objects()) {
if (auto MachOOrErr = ObjForArch.getAsObjectFile()) {
auto &Obj = **MachOOrErr;
if (filterArch(Obj))
FilterObjs.emplace_back(MachOOrErr->release());
} else {
error(Filename, MachOOrErr.takeError());
}
}
if (FilterObjs.empty())
error(Filename, createStringError(std::errc::invalid_argument,
"no matching architectures found"));
for (auto &Obj : FilterObjs) {
Triple ObjTriple(Obj->getArchTriple());
auto ArchName = ObjTriple.getArchName();
std::string ArchOutFile(OutFile);
if (FilterObjs.size() > 1) {
ArchOutFile.append(1, '.');
ArchOutFile.append(ArchName.str());
}
outs() << "Output file (" << ArchName << "): " << ArchOutFile << "\n";
if (auto Err = handleObjectFile(*Obj, ArchOutFile))
return Err;
}
}
return Error::success();
}
static llvm::Error handleFileConversionToGSYM(StringRef Filename,
const std::string &OutFile) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
MemoryBuffer::getFileOrSTDIN(Filename);
error(Filename, BuffOrErr.getError());
std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
return handleBuffer(Filename, *Buffer, OutFile);
}
static llvm::Error convertFileToGSYM(raw_ostream &OS) {
std::vector<std::string> Objects;
std::string OutFile = OutputFilename;
if (OutFile.empty()) {
OutFile = ConvertFilename;
OutFile += ".gsym";
}
OS << "Input file: " << ConvertFilename << "\n";
if (auto DsymObjectsOrErr =
MachOObjectFile::findDsymObjectMembers(ConvertFilename)) {
if (DsymObjectsOrErr->empty())
Objects.push_back(ConvertFilename);
else
llvm::append_range(Objects, *DsymObjectsOrErr);
} else {
error(DsymObjectsOrErr.takeError());
}
for (auto Object : Objects) {
if (auto Err = handleFileConversionToGSYM(Object, OutFile))
return Err;
}
return Error::success();
}
static void doLookup(GsymReader &Gsym, uint64_t Addr, raw_ostream &OS) {
if (auto Result = Gsym.lookup(Addr)) {
if (Verbose) {
if (auto FI = Gsym.getFunctionInfo(Addr)) {
OS << "FunctionInfo for " << HEX64(Addr) << ":\n";
Gsym.dump(OS, *FI);
OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
}
}
OS << Result.get();
} else {
if (Verbose)
OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
OS << HEX64(Addr) << ": ";
logAllUnhandledErrors(Result.takeError(), OS, "error: ");
}
if (Verbose)
OS << "\n";
}
int main(int argc, char const *argv[]) {
sys::PrintStackTraceOnErrorSignal(argv[0]);
PrettyStackTraceProgram X(argc, argv);
llvm_shutdown_obj Y;
llvm::InitializeAllTargets();
const char *Overview =
"A tool for dumping, searching and creating GSYM files.\n\n"
"Specify one or more GSYM paths as arguments to dump all of the "
"information in each GSYM file.\n"
"Specify a single GSYM file along with one or more --lookup options to "
"lookup addresses within that GSYM file.\n"
"Use the --convert option to specify a file with option --out-file "
"option to convert to GSYM format.\n";
HideUnrelatedOptions({&GeneralOptions, &ConversionOptions, &LookupOptions});
cl::ParseCommandLineOptions(argc, argv, Overview);
if (Help) {
PrintHelpMessage(false, true);
return 0;
}
raw_ostream &OS = outs();
if (!ConvertFilename.empty()) {
if (!InputFilenames.empty()) {
OS << "error: no input files can be specified when using the --convert "
"option.\n";
return 1;
}
if (auto Err = convertFileToGSYM(OS))
error("DWARF conversion failed: ", std::move(Err));
return 0;
}
if (LookupAddressesFromStdin) {
if (!LookupAddresses.empty() || !InputFilenames.empty()) {
OS << "error: no input files or addresses can be specified when using "
"the --addresses-from-stdin "
"option.\n";
return 1;
}
std::string InputLine;
std::string CurrentGSYMPath;
llvm::Optional<Expected<GsymReader>> CurrentGsym;
while (std::getline(std::cin, InputLine)) {
std::string StrippedInputLine(InputLine);
llvm::erase_if(StrippedInputLine,
[](char c) { return c == '\r' || c == '\n'; });
StringRef AddrStr, GSYMPath;
std::tie(AddrStr, GSYMPath) =
llvm::StringRef{StrippedInputLine}.split(' ');
if (GSYMPath != CurrentGSYMPath) {
CurrentGsym = GsymReader::openFile(GSYMPath);
if (!*CurrentGsym)
error(GSYMPath, CurrentGsym->takeError());
}
uint64_t Addr;
if (AddrStr.getAsInteger(0, Addr)) {
OS << "error: invalid address " << AddrStr
<< ", expected: Address GsymFile.\n";
return 1;
}
doLookup(**CurrentGsym, Addr, OS);
OS << "\n";
OS.flush();
}
return EXIT_SUCCESS;
}
for (const auto &GSYMPath : InputFilenames) {
auto Gsym = GsymReader::openFile(GSYMPath);
if (!Gsym)
error(GSYMPath, Gsym.takeError());
if (LookupAddresses.empty()) {
Gsym->dump(outs());
continue;
}
OS << "Looking up addresses in \"" << GSYMPath << "\":\n";
for (auto Addr : LookupAddresses) {
doLookup(*Gsym, Addr, OS);
}
}
return EXIT_SUCCESS;
}