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
23using namespace llvm;
24using namespace gsym;
25
26constexpr uint32_t NT_GNU_BUILD_ID_TAG = 0x03;
27
28static 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.
95static 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
175static Expected<size_t>
176loadSymbols(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.
223static 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
289llvm::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