| 1 | //===- ObjectFileTransformer.cpp --------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "llvm/ADT/StringExtras.h" |
| 10 | #include "llvm/BinaryFormat/MachO.h" |
| 11 | #include "llvm/Object/ELFObjectFile.h" |
| 12 | #include "llvm/Object/MachO.h" |
| 13 | #include "llvm/Object/MachOUniversal.h" |
| 14 | #include "llvm/Object/ObjectFile.h" |
| 15 | #include "llvm/Support/Compression.h" |
| 16 | #include "llvm/Support/DataExtractor.h" |
| 17 | #include "llvm/Support/raw_ostream.h" |
| 18 | |
| 19 | #include "llvm/DebugInfo/GSYM/GsymCreator.h" |
| 20 | #include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h" |
| 21 | #include "llvm/DebugInfo/GSYM/OutputAggregator.h" |
| 22 | |
| 23 | using namespace llvm; |
| 24 | using namespace gsym; |
| 25 | |
| 26 | constexpr uint32_t NT_GNU_BUILD_ID_TAG = 0x03; |
| 27 | |
| 28 | static std::vector<uint8_t> getUUID(const object::ObjectFile &Obj) { |
| 29 | // Extract the UUID from the object file |
| 30 | std::vector<uint8_t> UUID; |
| 31 | if (auto *MachO = dyn_cast<object::MachOObjectFile>(Val: &Obj)) { |
| 32 | const ArrayRef<uint8_t> MachUUID = MachO->getUuid(); |
| 33 | if (!MachUUID.empty()) |
| 34 | UUID.assign(first: MachUUID.data(), last: MachUUID.data() + MachUUID.size()); |
| 35 | } else if (isa<object::ELFObjectFileBase>(Val: &Obj)) { |
| 36 | const StringRef GNUBuildID(".note.gnu.build-id" ); |
| 37 | for (const object::SectionRef &Sect : Obj.sections()) { |
| 38 | Expected<StringRef> SectNameOrErr = Sect.getName(); |
| 39 | if (!SectNameOrErr) { |
| 40 | consumeError(Err: SectNameOrErr.takeError()); |
| 41 | continue; |
| 42 | } |
| 43 | StringRef SectName(*SectNameOrErr); |
| 44 | if (SectName != GNUBuildID) |
| 45 | continue; |
| 46 | StringRef BuildIDData; |
| 47 | Expected<StringRef> E = Sect.getContents(); |
| 48 | if (E) |
| 49 | BuildIDData = *E; |
| 50 | else { |
| 51 | consumeError(Err: E.takeError()); |
| 52 | continue; |
| 53 | } |
| 54 | DataExtractor Decoder(BuildIDData, Obj.makeTriple().isLittleEndian()); |
| 55 | uint64_t Offset = 0; |
| 56 | const uint32_t NameSize = Decoder.getU32(offset_ptr: &Offset); |
| 57 | const uint32_t PayloadSize = Decoder.getU32(offset_ptr: &Offset); |
| 58 | const uint32_t PayloadType = Decoder.getU32(offset_ptr: &Offset); |
| 59 | StringRef Name(Decoder.getFixedLengthString(OffsetPtr: &Offset, Length: NameSize)); |
| 60 | if (Name == "GNU" && PayloadType == NT_GNU_BUILD_ID_TAG) { |
| 61 | Offset = alignTo(Value: Offset, Align: 4); |
| 62 | StringRef UUIDBytes(Decoder.getBytes(OffsetPtr: &Offset, Length: PayloadSize)); |
| 63 | if (!UUIDBytes.empty()) { |
| 64 | auto Ptr = reinterpret_cast<const uint8_t *>(UUIDBytes.data()); |
| 65 | UUID.assign(first: Ptr, last: Ptr + UUIDBytes.size()); |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | return UUID; |
| 71 | } |
| 72 | |
| 73 | /// Create function information entries for Mach-O symbol stubs. |
| 74 | /// |
| 75 | /// Symbol stubs are small chunks of code, all with the same fixed size, that |
| 76 | /// resolves a function pointer on first call and then jumps to the resolved |
| 77 | /// function on subsequent calls for functions that live in another shared |
| 78 | /// library. The stubs are stored in sections whose type is S_SYMBOL_STUBS and |
| 79 | /// the size of a single stub is stored in the "reserved2" field of the section |
| 80 | /// header. These stubs have no entries in the symbol table of their own, so |
| 81 | /// they end up being attributed to whatever function precedes them unless they |
| 82 | /// are synthesized here. |
| 83 | /// |
| 84 | /// The name of the function a stub jumps to is found using the indirect symbol |
| 85 | /// table from the LC_DYSYMTAB load command. The "reserved1" field of the |
| 86 | /// section header contains the index of the indirect symbol table entry that |
| 87 | /// describes the first stub in the section, and each subsequent stub is |
| 88 | /// described by the entry that follows. Each indirect symbol table entry is an |
| 89 | /// index into the symbol table where the matching undefined (N_UNDF) symbol |
| 90 | /// supplies the name to use for the stub. Each name gets a "symbol stub for: " |
| 91 | /// prefix prepended to it so that symbolication makes it clear that the address |
| 92 | /// is the stub for a function and not the function itself. |
| 93 | /// |
| 94 | /// \returns The number of function infos that were added to \a Gsym. |
| 95 | static uint64_t addMachOSymbolStubs(const object::MachOObjectFile &MachO, |
| 96 | OutputAggregator &Out, GsymCreator &Gsym) { |
| 97 | const MachO::dysymtab_command Dysymtab = MachO.getDysymtabLoadCommand(); |
| 98 | if (Dysymtab.nindirectsyms == 0) |
| 99 | return 0; |
| 100 | const uint32_t NumSyms = MachO.getSymtabLoadCommand().nsyms; |
| 101 | const bool Is64Bit = MachO.is64Bit(); |
| 102 | |
| 103 | size_t NumBefore = Gsym.getNumFunctionInfos(); |
| 104 | for (const object::SectionRef &Sect : MachO.sections()) { |
| 105 | const object::DataRefImpl SectDRI = Sect.getRawDataRefImpl(); |
| 106 | uint32_t SectFlags, IndirectSymIdxStart, StubByteSize; |
| 107 | if (Is64Bit) { |
| 108 | const MachO::section_64 S = MachO.getSection64(DRI: SectDRI); |
| 109 | SectFlags = S.flags; |
| 110 | IndirectSymIdxStart = S.reserved1; |
| 111 | StubByteSize = S.reserved2; |
| 112 | } else { |
| 113 | const MachO::section S = MachO.getSection(DRI: SectDRI); |
| 114 | SectFlags = S.flags; |
| 115 | IndirectSymIdxStart = S.reserved1; |
| 116 | StubByteSize = S.reserved2; |
| 117 | } |
| 118 | if ((SectFlags & MachO::SECTION_TYPE) != MachO::S_SYMBOL_STUBS) |
| 119 | continue; |
| 120 | if (StubByteSize == 0) |
| 121 | continue; |
| 122 | |
| 123 | const uint64_t SectAddr = Sect.getAddress(); |
| 124 | const uint64_t NumStubs = Sect.getSize() / StubByteSize; |
| 125 | for (uint64_t StubIdx = 0; StubIdx < NumStubs; ++StubIdx) { |
| 126 | const uint64_t StubAddr = SectAddr + StubIdx * StubByteSize; |
| 127 | if (!Gsym.IsValidTextAddress(Addr: StubAddr)) |
| 128 | continue; |
| 129 | const uint64_t IndirectSymIdx = IndirectSymIdxStart + StubIdx; |
| 130 | if (IndirectSymIdx >= Dysymtab.nindirectsyms) |
| 131 | continue; |
| 132 | const uint32_t SymIdx = |
| 133 | MachO.getIndirectSymbolTableEntry(DLC: Dysymtab, Index: IndirectSymIdx); |
| 134 | // Entries that are absolute or local don't refer to a symbol table entry. |
| 135 | if (SymIdx & (MachO::INDIRECT_SYMBOL_ABS | MachO::INDIRECT_SYMBOL_LOCAL)) |
| 136 | continue; |
| 137 | if (SymIdx >= NumSyms) |
| 138 | continue; |
| 139 | const object::symbol_iterator SymIt = MachO.getSymbolByIndex(Index: SymIdx); |
| 140 | const object::DataRefImpl SymDRI = SymIt->getRawDataRefImpl(); |
| 141 | const uint8_t NType = Is64Bit ? MachO.getSymbol64TableEntry(DRI: SymDRI).n_type |
| 142 | : MachO.getSymbolTableEntry(DRI: SymDRI).n_type; |
| 143 | // Only undefined symbols name a stub, any other symbol type means the |
| 144 | // function itself is in this file and already has a symbol table entry. |
| 145 | if ((NType & MachO::N_TYPE) != MachO::N_UNDF) |
| 146 | continue; |
| 147 | Expected<StringRef> Name = SymIt->getName(); |
| 148 | if (!Name) { |
| 149 | if (Out.GetOS()) |
| 150 | logAllUnhandledErrors(E: Name.takeError(), OS&: *Out.GetOS(), |
| 151 | ErrorBanner: "ObjectFileTransformer: " ); |
| 152 | else |
| 153 | consumeError(Err: Name.takeError()); |
| 154 | continue; |
| 155 | } |
| 156 | // Remove the leading '_' character in any symbol names if there is one |
| 157 | // for mach-o files. |
| 158 | Name->consume_front(Prefix: "_" ); |
| 159 | if (Name->empty()) |
| 160 | continue; |
| 161 | // Append a "symbol stub for: " prefix so it is clear when symbolicating |
| 162 | // that the address is the stub for the function and not the function |
| 163 | // itself. The string must be copied into the string table since it is |
| 164 | // created here and, unlike the symbol names, has no backing storage in |
| 165 | // the object file. |
| 166 | constexpr bool Copy = true; |
| 167 | const std::string StubName = "symbol stub for: " + Name->str(); |
| 168 | Gsym.addFunctionInfo(FI: FunctionInfo(StubAddr, StubByteSize, |
| 169 | Gsym.insertString(S: StubName, Copy))); |
| 170 | } |
| 171 | } |
| 172 | return Gsym.getNumFunctionInfos() - NumBefore; |
| 173 | } |
| 174 | |
| 175 | static Expected<size_t> |
| 176 | loadSymbols(const object::ObjectFile &Obj, |
| 177 | object::ObjectFile::symbol_iterator_range Symbols, |
| 178 | OutputAggregator &Out, GsymCreator &Gsym, bool CopyStrings) { |
| 179 | using namespace llvm::object; |
| 180 | |
| 181 | const bool IsMachO = isa<MachOObjectFile>(Val: &Obj); |
| 182 | const bool IsELF = isa<ELFObjectFileBase>(Val: &Obj); |
| 183 | const size_t NumBefore = Gsym.getNumFunctionInfos(); |
| 184 | |
| 185 | for (const object::SymbolRef &Sym : Symbols) { |
| 186 | Expected<SymbolRef::Type> SymType = Sym.getType(); |
| 187 | if (!SymType) { |
| 188 | consumeError(Err: SymType.takeError()); |
| 189 | continue; |
| 190 | } |
| 191 | Expected<uint64_t> AddrOrErr = Sym.getValue(); |
| 192 | if (!AddrOrErr) |
| 193 | // TODO: Test this error. |
| 194 | return AddrOrErr.takeError(); |
| 195 | |
| 196 | if (SymType.get() != SymbolRef::Type::ST_Function || |
| 197 | !Gsym.IsValidTextAddress(Addr: *AddrOrErr)) |
| 198 | continue; |
| 199 | // Function size for MachO files will be 0 |
| 200 | const uint64_t size = IsELF ? ELFSymbolRef(Sym).getSize() : 0; |
| 201 | Expected<StringRef> Name = Sym.getName(); |
| 202 | if (!Name) { |
| 203 | if (Out.GetOS()) |
| 204 | logAllUnhandledErrors(E: Name.takeError(), OS&: *Out.GetOS(), |
| 205 | ErrorBanner: "ObjectFileTransformer: " ); |
| 206 | else |
| 207 | consumeError(Err: Name.takeError()); |
| 208 | continue; |
| 209 | } |
| 210 | // Remove the leading '_' character in any symbol names if there is one |
| 211 | // for mach-o files. |
| 212 | if (IsMachO) |
| 213 | Name->consume_front(Prefix: "_" ); |
| 214 | Gsym.addFunctionInfo( |
| 215 | FI: FunctionInfo(*AddrOrErr, size, Gsym.insertString(S: *Name, Copy: CopyStrings))); |
| 216 | } |
| 217 | return Gsym.getNumFunctionInfos() - NumBefore; |
| 218 | } |
| 219 | |
| 220 | // Add function symbols from the .gnu_debugdata section (MiniDebugInfo), which |
| 221 | // contains an xz-compressed ELF file with a .symtab. Errors reading the section |
| 222 | // are reported as warnings. |
| 223 | static llvm::Error loadGnuDebugDataSymbols(const object::ELFObjectFileBase &Obj, |
| 224 | OutputAggregator &Out, |
| 225 | GsymCreator &Gsym) { |
| 226 | using namespace llvm::object; |
| 227 | |
| 228 | std::optional<SectionRef> DebugDataSect; |
| 229 | for (const SectionRef &Sect : Obj.sections()) { |
| 230 | Expected<StringRef> SectNameOrErr = Sect.getName(); |
| 231 | if (!SectNameOrErr) { |
| 232 | consumeError(Err: SectNameOrErr.takeError()); |
| 233 | continue; |
| 234 | } |
| 235 | if (*SectNameOrErr == ".gnu_debugdata" ) { |
| 236 | DebugDataSect = Sect; |
| 237 | break; |
| 238 | } |
| 239 | } |
| 240 | if (!DebugDataSect) |
| 241 | return Error::success(); |
| 242 | |
| 243 | // MiniDebugInfo leaves out symbols that are in .dynsym, so add those too. |
| 244 | Expected<size_t> DynFunctionsAdded = loadSymbols( |
| 245 | Obj, Symbols: Obj.getDynamicSymbolIterators(), Out, Gsym, /*CopyStrings=*/false); |
| 246 | if (!DynFunctionsAdded) |
| 247 | return DynFunctionsAdded.takeError(); |
| 248 | if (Out.GetOS()) |
| 249 | *Out.GetOS() << "Loaded " << *DynFunctionsAdded |
| 250 | << " functions from dynamic symbol table.\n" ; |
| 251 | |
| 252 | auto Warn = [&](const Twine &Msg) { |
| 253 | Out.Report( |
| 254 | s: "Failed to load the .gnu_debugdata section" , detailCallback: [&](raw_ostream &OS) { |
| 255 | OS << "warning: unable to read the .gnu_debugdata section: " << Msg |
| 256 | << "\n" ; |
| 257 | }); |
| 258 | return Error::success(); |
| 259 | }; |
| 260 | |
| 261 | if (!compression::xz::isAvailable()) |
| 262 | return Warn("missing LZMA support (LLVM_ENABLE_LZMA)" ); |
| 263 | Expected<StringRef> Contents = DebugDataSect->getContents(); |
| 264 | if (!Contents) |
| 265 | return Warn(toString(E: Contents.takeError())); |
| 266 | SmallVector<uint8_t, 0> Decompressed; |
| 267 | if (Error E = compression::xz::decompress(Input: arrayRefFromStringRef(Input: *Contents), |
| 268 | Output&: Decompressed)) |
| 269 | return Warn("failed to decompress: " + toString(E: std::move(E))); |
| 270 | Expected<std::unique_ptr<ObjectFile>> DebugObj = |
| 271 | ObjectFile::createELFObjectFile( |
| 272 | Object: MemoryBufferRef(toStringRef(Input: Decompressed), Obj.getFileName())); |
| 273 | if (!DebugObj) |
| 274 | return Warn("failed to parse the embedded ELF object: " + |
| 275 | toString(E: DebugObj.takeError())); |
| 276 | |
| 277 | // Decompressed is freed when we return, so the names need to be copied. |
| 278 | Expected<size_t> DebugFunctionsAdded = |
| 279 | loadSymbols(Obj: **DebugObj, Symbols: (*DebugObj)->symbols(), Out, Gsym, |
| 280 | /*CopyStrings=*/true); |
| 281 | if (!DebugFunctionsAdded) |
| 282 | return Warn(toString(E: DebugFunctionsAdded.takeError())); |
| 283 | if (Out.GetOS()) |
| 284 | *Out.GetOS() << "Loaded " << *DebugFunctionsAdded |
| 285 | << " functions from .gnu_debugdata symbol table.\n" ; |
| 286 | return Error::success(); |
| 287 | } |
| 288 | |
| 289 | llvm::Error ObjectFileTransformer::convert(const object::ObjectFile &Obj, |
| 290 | OutputAggregator &Out, |
| 291 | GsymCreator &Gsym) { |
| 292 | using namespace llvm::object; |
| 293 | |
| 294 | // Read build ID. |
| 295 | Gsym.setUUID(getUUID(Obj)); |
| 296 | |
| 297 | // Parse the symbol table. |
| 298 | Expected<size_t> FunctionsAddedCount = |
| 299 | loadSymbols(Obj, Symbols: Obj.symbols(), Out, Gsym, /*CopyStrings=*/false); |
| 300 | if (!FunctionsAddedCount) |
| 301 | return FunctionsAddedCount.takeError(); |
| 302 | if (Out.GetOS()) |
| 303 | *Out.GetOS() << "Loaded " << *FunctionsAddedCount |
| 304 | << " functions from symbol table.\n" ; |
| 305 | |
| 306 | // Mach-O symbol stubs have no symbol table entries of their own, so |
| 307 | // synthesize function infos for them using the indirect symbol table. |
| 308 | if (const auto *MachO = dyn_cast<MachOObjectFile>(Val: &Obj)) { |
| 309 | const uint64_t StubsAddedCount = addMachOSymbolStubs(MachO: *MachO, Out, Gsym); |
| 310 | if (Out.GetOS()) |
| 311 | *Out.GetOS() << "Loaded " << StubsAddedCount |
| 312 | << " functions from symbol stubs.\n" ; |
| 313 | } |
| 314 | |
| 315 | if (const auto *ELFObj = dyn_cast<ELFObjectFileBase>(Val: &Obj)) |
| 316 | return loadGnuDebugDataSymbols(Obj: *ELFObj, Out, Gsym); |
| 317 | return Error::success(); |
| 318 | } |
| 319 | |