| 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/BinaryFormat/MachO.h" |
| 10 | #include "llvm/Object/ELFObjectFile.h" |
| 11 | #include "llvm/Object/MachO.h" |
| 12 | #include "llvm/Object/MachOUniversal.h" |
| 13 | #include "llvm/Object/ObjectFile.h" |
| 14 | #include "llvm/Support/DataExtractor.h" |
| 15 | #include "llvm/Support/raw_ostream.h" |
| 16 | |
| 17 | #include "llvm/DebugInfo/GSYM/GsymCreator.h" |
| 18 | #include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h" |
| 19 | #include "llvm/DebugInfo/GSYM/OutputAggregator.h" |
| 20 | |
| 21 | using namespace llvm; |
| 22 | using namespace gsym; |
| 23 | |
| 24 | constexpr uint32_t NT_GNU_BUILD_ID_TAG = 0x03; |
| 25 | |
| 26 | static std::vector<uint8_t> getUUID(const object::ObjectFile &Obj) { |
| 27 | // Extract the UUID from the object file |
| 28 | std::vector<uint8_t> UUID; |
| 29 | if (auto *MachO = dyn_cast<object::MachOObjectFile>(Val: &Obj)) { |
| 30 | const ArrayRef<uint8_t> MachUUID = MachO->getUuid(); |
| 31 | if (!MachUUID.empty()) |
| 32 | UUID.assign(first: MachUUID.data(), last: MachUUID.data() + MachUUID.size()); |
| 33 | } else if (isa<object::ELFObjectFileBase>(Val: &Obj)) { |
| 34 | const StringRef GNUBuildID(".note.gnu.build-id" ); |
| 35 | for (const object::SectionRef &Sect : Obj.sections()) { |
| 36 | Expected<StringRef> SectNameOrErr = Sect.getName(); |
| 37 | if (!SectNameOrErr) { |
| 38 | consumeError(Err: SectNameOrErr.takeError()); |
| 39 | continue; |
| 40 | } |
| 41 | StringRef SectName(*SectNameOrErr); |
| 42 | if (SectName != GNUBuildID) |
| 43 | continue; |
| 44 | StringRef BuildIDData; |
| 45 | Expected<StringRef> E = Sect.getContents(); |
| 46 | if (E) |
| 47 | BuildIDData = *E; |
| 48 | else { |
| 49 | consumeError(Err: E.takeError()); |
| 50 | continue; |
| 51 | } |
| 52 | DataExtractor Decoder(BuildIDData, Obj.makeTriple().isLittleEndian()); |
| 53 | uint64_t Offset = 0; |
| 54 | const uint32_t NameSize = Decoder.getU32(offset_ptr: &Offset); |
| 55 | const uint32_t PayloadSize = Decoder.getU32(offset_ptr: &Offset); |
| 56 | const uint32_t PayloadType = Decoder.getU32(offset_ptr: &Offset); |
| 57 | StringRef Name(Decoder.getFixedLengthString(OffsetPtr: &Offset, Length: NameSize)); |
| 58 | if (Name == "GNU" && PayloadType == NT_GNU_BUILD_ID_TAG) { |
| 59 | Offset = alignTo(Value: Offset, Align: 4); |
| 60 | StringRef UUIDBytes(Decoder.getBytes(OffsetPtr: &Offset, Length: PayloadSize)); |
| 61 | if (!UUIDBytes.empty()) { |
| 62 | auto Ptr = reinterpret_cast<const uint8_t *>(UUIDBytes.data()); |
| 63 | UUID.assign(first: Ptr, last: Ptr + UUIDBytes.size()); |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | return UUID; |
| 69 | } |
| 70 | |
| 71 | /// Create function information entries for Mach-O symbol stubs. |
| 72 | /// |
| 73 | /// Symbol stubs are small chunks of code, all with the same fixed size, that |
| 74 | /// resolves a function pointer on first call and then jumps to the resolved |
| 75 | /// function on subsequent calls for functions that live in another shared |
| 76 | /// library. The stubs are stored in sections whose type is S_SYMBOL_STUBS and |
| 77 | /// the size of a single stub is stored in the "reserved2" field of the section |
| 78 | /// header. These stubs have no entries in the symbol table of their own, so |
| 79 | /// they end up being attributed to whatever function precedes them unless they |
| 80 | /// are synthesized here. |
| 81 | /// |
| 82 | /// The name of the function a stub jumps to is found using the indirect symbol |
| 83 | /// table from the LC_DYSYMTAB load command. The "reserved1" field of the |
| 84 | /// section header contains the index of the indirect symbol table entry that |
| 85 | /// describes the first stub in the section, and each subsequent stub is |
| 86 | /// described by the entry that follows. Each indirect symbol table entry is an |
| 87 | /// index into the symbol table where the matching undefined (N_UNDF) symbol |
| 88 | /// supplies the name to use for the stub. Each name gets a "symbol stub for: " |
| 89 | /// prefix prepended to it so that symbolication makes it clear that the address |
| 90 | /// is the stub for a function and not the function itself. |
| 91 | /// |
| 92 | /// \returns The number of function infos that were added to \a Gsym. |
| 93 | static uint64_t addMachOSymbolStubs(const object::MachOObjectFile &MachO, |
| 94 | OutputAggregator &Out, GsymCreator &Gsym) { |
| 95 | const MachO::dysymtab_command Dysymtab = MachO.getDysymtabLoadCommand(); |
| 96 | if (Dysymtab.nindirectsyms == 0) |
| 97 | return 0; |
| 98 | const uint32_t NumSyms = MachO.getSymtabLoadCommand().nsyms; |
| 99 | const bool Is64Bit = MachO.is64Bit(); |
| 100 | |
| 101 | size_t NumBefore = Gsym.getNumFunctionInfos(); |
| 102 | for (const object::SectionRef &Sect : MachO.sections()) { |
| 103 | const object::DataRefImpl SectDRI = Sect.getRawDataRefImpl(); |
| 104 | uint32_t SectFlags, IndirectSymIdxStart, StubByteSize; |
| 105 | if (Is64Bit) { |
| 106 | const MachO::section_64 S = MachO.getSection64(DRI: SectDRI); |
| 107 | SectFlags = S.flags; |
| 108 | IndirectSymIdxStart = S.reserved1; |
| 109 | StubByteSize = S.reserved2; |
| 110 | } else { |
| 111 | const MachO::section S = MachO.getSection(DRI: SectDRI); |
| 112 | SectFlags = S.flags; |
| 113 | IndirectSymIdxStart = S.reserved1; |
| 114 | StubByteSize = S.reserved2; |
| 115 | } |
| 116 | if ((SectFlags & MachO::SECTION_TYPE) != MachO::S_SYMBOL_STUBS) |
| 117 | continue; |
| 118 | if (StubByteSize == 0) |
| 119 | continue; |
| 120 | |
| 121 | const uint64_t SectAddr = Sect.getAddress(); |
| 122 | const uint64_t NumStubs = Sect.getSize() / StubByteSize; |
| 123 | for (uint64_t StubIdx = 0; StubIdx < NumStubs; ++StubIdx) { |
| 124 | const uint64_t StubAddr = SectAddr + StubIdx * StubByteSize; |
| 125 | if (!Gsym.IsValidTextAddress(Addr: StubAddr)) |
| 126 | continue; |
| 127 | const uint64_t IndirectSymIdx = IndirectSymIdxStart + StubIdx; |
| 128 | if (IndirectSymIdx >= Dysymtab.nindirectsyms) |
| 129 | continue; |
| 130 | const uint32_t SymIdx = |
| 131 | MachO.getIndirectSymbolTableEntry(DLC: Dysymtab, Index: IndirectSymIdx); |
| 132 | // Entries that are absolute or local don't refer to a symbol table entry. |
| 133 | if (SymIdx & (MachO::INDIRECT_SYMBOL_ABS | MachO::INDIRECT_SYMBOL_LOCAL)) |
| 134 | continue; |
| 135 | if (SymIdx >= NumSyms) |
| 136 | continue; |
| 137 | const object::symbol_iterator SymIt = MachO.getSymbolByIndex(Index: SymIdx); |
| 138 | const object::DataRefImpl SymDRI = SymIt->getRawDataRefImpl(); |
| 139 | const uint8_t NType = Is64Bit ? MachO.getSymbol64TableEntry(DRI: SymDRI).n_type |
| 140 | : MachO.getSymbolTableEntry(DRI: SymDRI).n_type; |
| 141 | // Only undefined symbols name a stub, any other symbol type means the |
| 142 | // function itself is in this file and already has a symbol table entry. |
| 143 | if ((NType & MachO::N_TYPE) != MachO::N_UNDF) |
| 144 | continue; |
| 145 | Expected<StringRef> Name = SymIt->getName(); |
| 146 | if (!Name) { |
| 147 | if (Out.GetOS()) |
| 148 | logAllUnhandledErrors(E: Name.takeError(), OS&: *Out.GetOS(), |
| 149 | ErrorBanner: "ObjectFileTransformer: " ); |
| 150 | else |
| 151 | consumeError(Err: Name.takeError()); |
| 152 | continue; |
| 153 | } |
| 154 | // Remove the leading '_' character in any symbol names if there is one |
| 155 | // for mach-o files. |
| 156 | Name->consume_front(Prefix: "_" ); |
| 157 | if (Name->empty()) |
| 158 | continue; |
| 159 | // Append a "symbol stub for: " prefix so it is clear when symbolicating |
| 160 | // that the address is the stub for the function and not the function |
| 161 | // itself. The string must be copied into the string table since it is |
| 162 | // created here and, unlike the symbol names, has no backing storage in |
| 163 | // the object file. |
| 164 | constexpr bool Copy = true; |
| 165 | const std::string StubName = "symbol stub for: " + Name->str(); |
| 166 | Gsym.addFunctionInfo(FI: FunctionInfo(StubAddr, StubByteSize, |
| 167 | Gsym.insertString(S: StubName, Copy))); |
| 168 | } |
| 169 | } |
| 170 | return Gsym.getNumFunctionInfos() - NumBefore; |
| 171 | } |
| 172 | |
| 173 | llvm::Error ObjectFileTransformer::convert(const object::ObjectFile &Obj, |
| 174 | OutputAggregator &Out, |
| 175 | GsymCreator &Gsym) { |
| 176 | using namespace llvm::object; |
| 177 | |
| 178 | const auto *MachO = dyn_cast<MachOObjectFile>(Val: &Obj); |
| 179 | const bool IsMachO = MachO != nullptr; |
| 180 | const bool IsELF = isa<ELFObjectFileBase>(Val: &Obj); |
| 181 | |
| 182 | // Read build ID. |
| 183 | Gsym.setUUID(getUUID(Obj)); |
| 184 | |
| 185 | // Parse the symbol table. |
| 186 | size_t NumBefore = Gsym.getNumFunctionInfos(); |
| 187 | for (const object::SymbolRef &Sym : Obj.symbols()) { |
| 188 | Expected<SymbolRef::Type> SymType = Sym.getType(); |
| 189 | if (!SymType) { |
| 190 | consumeError(Err: SymType.takeError()); |
| 191 | continue; |
| 192 | } |
| 193 | Expected<uint64_t> AddrOrErr = Sym.getValue(); |
| 194 | if (!AddrOrErr) |
| 195 | // TODO: Test this error. |
| 196 | return AddrOrErr.takeError(); |
| 197 | |
| 198 | if (SymType.get() != SymbolRef::Type::ST_Function || |
| 199 | !Gsym.IsValidTextAddress(Addr: *AddrOrErr)) |
| 200 | continue; |
| 201 | // Function size for MachO files will be 0 |
| 202 | constexpr bool NoCopy = false; |
| 203 | const uint64_t size = IsELF ? ELFSymbolRef(Sym).getSize() : 0; |
| 204 | Expected<StringRef> Name = Sym.getName(); |
| 205 | if (!Name) { |
| 206 | if (Out.GetOS()) |
| 207 | logAllUnhandledErrors(E: Name.takeError(), OS&: *Out.GetOS(), |
| 208 | ErrorBanner: "ObjectFileTransformer: " ); |
| 209 | else |
| 210 | consumeError(Err: Name.takeError()); |
| 211 | continue; |
| 212 | } |
| 213 | // Remove the leading '_' character in any symbol names if there is one |
| 214 | // for mach-o files. |
| 215 | if (IsMachO) |
| 216 | Name->consume_front(Prefix: "_" ); |
| 217 | Gsym.addFunctionInfo( |
| 218 | FI: FunctionInfo(*AddrOrErr, size, Gsym.insertString(S: *Name, Copy: NoCopy))); |
| 219 | } |
| 220 | size_t FunctionsAddedCount = Gsym.getNumFunctionInfos() - NumBefore; |
| 221 | if (Out.GetOS()) |
| 222 | *Out.GetOS() << "Loaded " << FunctionsAddedCount |
| 223 | << " functions from symbol table.\n" ; |
| 224 | |
| 225 | // Mach-O symbol stubs have no symbol table entries of their own, so |
| 226 | // synthesize function infos for them using the indirect symbol table. |
| 227 | if (IsMachO) { |
| 228 | const uint64_t StubsAddedCount = addMachOSymbolStubs(MachO: *MachO, Out, Gsym); |
| 229 | if (Out.GetOS()) |
| 230 | *Out.GetOS() << "Loaded " << StubsAddedCount |
| 231 | << " functions from symbol stubs.\n" ; |
| 232 | } |
| 233 | return Error::success(); |
| 234 | } |
| 235 | |