blob: b2386e8b836c0381da954d8ec6cd3a176ddba65b [file] [edit]
//===- ObjectFileTransformer.cpp --------------------------------*- C++ -*-===//
//
// 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 "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/MachO.h"
#include "llvm/Object/MachOUniversal.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/Compression.h"
#include "llvm/Support/DataExtractor.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/DebugInfo/GSYM/GsymCreator.h"
#include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h"
#include "llvm/DebugInfo/GSYM/OutputAggregator.h"
using namespace llvm;
using namespace gsym;
constexpr uint32_t NT_GNU_BUILD_ID_TAG = 0x03;
static std::vector<uint8_t> getUUID(const object::ObjectFile &Obj) {
// Extract the UUID from the object file
std::vector<uint8_t> UUID;
if (auto *MachO = dyn_cast<object::MachOObjectFile>(&Obj)) {
const ArrayRef<uint8_t> MachUUID = MachO->getUuid();
if (!MachUUID.empty())
UUID.assign(MachUUID.data(), MachUUID.data() + MachUUID.size());
} else if (isa<object::ELFObjectFileBase>(&Obj)) {
const StringRef GNUBuildID(".note.gnu.build-id");
for (const object::SectionRef &Sect : Obj.sections()) {
Expected<StringRef> SectNameOrErr = Sect.getName();
if (!SectNameOrErr) {
consumeError(SectNameOrErr.takeError());
continue;
}
StringRef SectName(*SectNameOrErr);
if (SectName != GNUBuildID)
continue;
StringRef BuildIDData;
Expected<StringRef> E = Sect.getContents();
if (E)
BuildIDData = *E;
else {
consumeError(E.takeError());
continue;
}
DataExtractor Decoder(BuildIDData, Obj.makeTriple().isLittleEndian());
uint64_t Offset = 0;
const uint32_t NameSize = Decoder.getU32(&Offset);
const uint32_t PayloadSize = Decoder.getU32(&Offset);
const uint32_t PayloadType = Decoder.getU32(&Offset);
StringRef Name(Decoder.getFixedLengthString(&Offset, NameSize));
if (Name == "GNU" && PayloadType == NT_GNU_BUILD_ID_TAG) {
Offset = alignTo(Offset, 4);
StringRef UUIDBytes(Decoder.getBytes(&Offset, PayloadSize));
if (!UUIDBytes.empty()) {
auto Ptr = reinterpret_cast<const uint8_t *>(UUIDBytes.data());
UUID.assign(Ptr, Ptr + UUIDBytes.size());
}
}
}
}
return UUID;
}
/// Create function information entries for Mach-O symbol stubs.
///
/// Symbol stubs are small chunks of code, all with the same fixed size, that
/// resolves a function pointer on first call and then jumps to the resolved
/// function on subsequent calls for functions that live in another shared
/// library. The stubs are stored in sections whose type is S_SYMBOL_STUBS and
/// the size of a single stub is stored in the "reserved2" field of the section
/// header. These stubs have no entries in the symbol table of their own, so
/// they end up being attributed to whatever function precedes them unless they
/// are synthesized here.
///
/// The name of the function a stub jumps to is found using the indirect symbol
/// table from the LC_DYSYMTAB load command. The "reserved1" field of the
/// section header contains the index of the indirect symbol table entry that
/// describes the first stub in the section, and each subsequent stub is
/// described by the entry that follows. Each indirect symbol table entry is an
/// index into the symbol table where the matching undefined (N_UNDF) symbol
/// supplies the name to use for the stub. Each name gets a "symbol stub for: "
/// prefix prepended to it so that symbolication makes it clear that the address
/// is the stub for a function and not the function itself.
///
/// \returns The number of function infos that were added to \a Gsym.
static uint64_t addMachOSymbolStubs(const object::MachOObjectFile &MachO,
OutputAggregator &Out, GsymCreator &Gsym) {
const MachO::dysymtab_command Dysymtab = MachO.getDysymtabLoadCommand();
if (Dysymtab.nindirectsyms == 0)
return 0;
const uint32_t NumSyms = MachO.getSymtabLoadCommand().nsyms;
const bool Is64Bit = MachO.is64Bit();
size_t NumBefore = Gsym.getNumFunctionInfos();
for (const object::SectionRef &Sect : MachO.sections()) {
const object::DataRefImpl SectDRI = Sect.getRawDataRefImpl();
uint32_t SectFlags, IndirectSymIdxStart, StubByteSize;
if (Is64Bit) {
const MachO::section_64 S = MachO.getSection64(SectDRI);
SectFlags = S.flags;
IndirectSymIdxStart = S.reserved1;
StubByteSize = S.reserved2;
} else {
const MachO::section S = MachO.getSection(SectDRI);
SectFlags = S.flags;
IndirectSymIdxStart = S.reserved1;
StubByteSize = S.reserved2;
}
if ((SectFlags & MachO::SECTION_TYPE) != MachO::S_SYMBOL_STUBS)
continue;
if (StubByteSize == 0)
continue;
const uint64_t SectAddr = Sect.getAddress();
const uint64_t NumStubs = Sect.getSize() / StubByteSize;
for (uint64_t StubIdx = 0; StubIdx < NumStubs; ++StubIdx) {
const uint64_t StubAddr = SectAddr + StubIdx * StubByteSize;
if (!Gsym.IsValidTextAddress(StubAddr))
continue;
const uint64_t IndirectSymIdx = IndirectSymIdxStart + StubIdx;
if (IndirectSymIdx >= Dysymtab.nindirectsyms)
continue;
const uint32_t SymIdx =
MachO.getIndirectSymbolTableEntry(Dysymtab, IndirectSymIdx);
// Entries that are absolute or local don't refer to a symbol table entry.
if (SymIdx & (MachO::INDIRECT_SYMBOL_ABS | MachO::INDIRECT_SYMBOL_LOCAL))
continue;
if (SymIdx >= NumSyms)
continue;
const object::symbol_iterator SymIt = MachO.getSymbolByIndex(SymIdx);
const object::DataRefImpl SymDRI = SymIt->getRawDataRefImpl();
const uint8_t NType = Is64Bit ? MachO.getSymbol64TableEntry(SymDRI).n_type
: MachO.getSymbolTableEntry(SymDRI).n_type;
// Only undefined symbols name a stub, any other symbol type means the
// function itself is in this file and already has a symbol table entry.
if ((NType & MachO::N_TYPE) != MachO::N_UNDF)
continue;
Expected<StringRef> Name = SymIt->getName();
if (!Name) {
if (Out.GetOS())
logAllUnhandledErrors(Name.takeError(), *Out.GetOS(),
"ObjectFileTransformer: ");
else
consumeError(Name.takeError());
continue;
}
// Remove the leading '_' character in any symbol names if there is one
// for mach-o files.
Name->consume_front("_");
if (Name->empty())
continue;
// Append a "symbol stub for: " prefix so it is clear when symbolicating
// that the address is the stub for the function and not the function
// itself. The string must be copied into the string table since it is
// created here and, unlike the symbol names, has no backing storage in
// the object file.
constexpr bool Copy = true;
const std::string StubName = "symbol stub for: " + Name->str();
Gsym.addFunctionInfo(FunctionInfo(StubAddr, StubByteSize,
Gsym.insertString(StubName, Copy)));
}
}
return Gsym.getNumFunctionInfos() - NumBefore;
}
static Expected<size_t>
loadSymbols(const object::ObjectFile &Obj,
object::ObjectFile::symbol_iterator_range Symbols,
OutputAggregator &Out, GsymCreator &Gsym, bool CopyStrings) {
using namespace llvm::object;
const bool IsMachO = isa<MachOObjectFile>(&Obj);
const bool IsELF = isa<ELFObjectFileBase>(&Obj);
const size_t NumBefore = Gsym.getNumFunctionInfos();
for (const object::SymbolRef &Sym : Symbols) {
Expected<SymbolRef::Type> SymType = Sym.getType();
if (!SymType) {
consumeError(SymType.takeError());
continue;
}
Expected<uint64_t> AddrOrErr = Sym.getValue();
if (!AddrOrErr)
// TODO: Test this error.
return AddrOrErr.takeError();
if (SymType.get() != SymbolRef::Type::ST_Function ||
!Gsym.IsValidTextAddress(*AddrOrErr))
continue;
// Function size for MachO files will be 0
const uint64_t size = IsELF ? ELFSymbolRef(Sym).getSize() : 0;
Expected<StringRef> Name = Sym.getName();
if (!Name) {
if (Out.GetOS())
logAllUnhandledErrors(Name.takeError(), *Out.GetOS(),
"ObjectFileTransformer: ");
else
consumeError(Name.takeError());
continue;
}
// Remove the leading '_' character in any symbol names if there is one
// for mach-o files.
if (IsMachO)
Name->consume_front("_");
Gsym.addFunctionInfo(
FunctionInfo(*AddrOrErr, size, Gsym.insertString(*Name, CopyStrings)));
}
return Gsym.getNumFunctionInfos() - NumBefore;
}
// Add function symbols from the .gnu_debugdata section (MiniDebugInfo), which
// contains an xz-compressed ELF file with a .symtab. Errors reading the section
// are reported as warnings.
static llvm::Error loadGnuDebugDataSymbols(const object::ELFObjectFileBase &Obj,
OutputAggregator &Out,
GsymCreator &Gsym) {
using namespace llvm::object;
std::optional<SectionRef> DebugDataSect;
for (const SectionRef &Sect : Obj.sections()) {
Expected<StringRef> SectNameOrErr = Sect.getName();
if (!SectNameOrErr) {
consumeError(SectNameOrErr.takeError());
continue;
}
if (*SectNameOrErr == ".gnu_debugdata") {
DebugDataSect = Sect;
break;
}
}
if (!DebugDataSect)
return Error::success();
// MiniDebugInfo leaves out symbols that are in .dynsym, so add those too.
Expected<size_t> DynFunctionsAdded = loadSymbols(
Obj, Obj.getDynamicSymbolIterators(), Out, Gsym, /*CopyStrings=*/false);
if (!DynFunctionsAdded)
return DynFunctionsAdded.takeError();
if (Out.GetOS())
*Out.GetOS() << "Loaded " << *DynFunctionsAdded
<< " functions from dynamic symbol table.\n";
auto Warn = [&](const Twine &Msg) {
Out.Report(
"Failed to load the .gnu_debugdata section", [&](raw_ostream &OS) {
OS << "warning: unable to read the .gnu_debugdata section: " << Msg
<< "\n";
});
return Error::success();
};
if (!compression::xz::isAvailable())
return Warn("missing LZMA support (LLVM_ENABLE_LZMA)");
Expected<StringRef> Contents = DebugDataSect->getContents();
if (!Contents)
return Warn(toString(Contents.takeError()));
SmallVector<uint8_t, 0> Decompressed;
if (Error E = compression::xz::decompress(arrayRefFromStringRef(*Contents),
Decompressed))
return Warn("failed to decompress: " + toString(std::move(E)));
Expected<std::unique_ptr<ObjectFile>> DebugObj =
ObjectFile::createELFObjectFile(
MemoryBufferRef(toStringRef(Decompressed), Obj.getFileName()));
if (!DebugObj)
return Warn("failed to parse the embedded ELF object: " +
toString(DebugObj.takeError()));
// Decompressed is freed when we return, so the names need to be copied.
Expected<size_t> DebugFunctionsAdded =
loadSymbols(**DebugObj, (*DebugObj)->symbols(), Out, Gsym,
/*CopyStrings=*/true);
if (!DebugFunctionsAdded)
return Warn(toString(DebugFunctionsAdded.takeError()));
if (Out.GetOS())
*Out.GetOS() << "Loaded " << *DebugFunctionsAdded
<< " functions from .gnu_debugdata symbol table.\n";
return Error::success();
}
llvm::Error ObjectFileTransformer::convert(const object::ObjectFile &Obj,
OutputAggregator &Out,
GsymCreator &Gsym) {
using namespace llvm::object;
// Read build ID.
Gsym.setUUID(getUUID(Obj));
// Parse the symbol table.
Expected<size_t> FunctionsAddedCount =
loadSymbols(Obj, Obj.symbols(), Out, Gsym, /*CopyStrings=*/false);
if (!FunctionsAddedCount)
return FunctionsAddedCount.takeError();
if (Out.GetOS())
*Out.GetOS() << "Loaded " << *FunctionsAddedCount
<< " functions from symbol table.\n";
// Mach-O symbol stubs have no symbol table entries of their own, so
// synthesize function infos for them using the indirect symbol table.
if (const auto *MachO = dyn_cast<MachOObjectFile>(&Obj)) {
const uint64_t StubsAddedCount = addMachOSymbolStubs(*MachO, Out, Gsym);
if (Out.GetOS())
*Out.GetOS() << "Loaded " << StubsAddedCount
<< " functions from symbol stubs.\n";
}
if (const auto *ELFObj = dyn_cast<ELFObjectFileBase>(&Obj))
return loadGnuDebugDataSymbols(*ELFObj, Out, Gsym);
return Error::success();
}