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
21using namespace llvm;
22using namespace gsym;
23
24constexpr uint32_t NT_GNU_BUILD_ID_TAG = 0x03;
25
26static 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.
93static 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
173llvm::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