1//===- FunctionInfo.cpp ---------------------------------------------------===//
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/DebugInfo/GSYM/FunctionInfo.h"
10#include "llvm/DebugInfo/GSYM/FileWriter.h"
11#include "llvm/DebugInfo/GSYM/GsymReader.h"
12#include "llvm/DebugInfo/GSYM/LineTable.h"
13#include "llvm/DebugInfo/GSYM/InlineInfo.h"
14#include "llvm/Support/DataExtractor.h"
15#include <optional>
16
17using namespace llvm;
18using namespace gsym;
19
20/// FunctionInfo information type that is used to encode the optional data
21/// that is associated with a FunctionInfo object.
22enum InfoType : uint32_t {
23 EndOfList = 0u,
24 LineTableInfo = 1u,
25 InlineInfo = 2u
26};
27
28raw_ostream &llvm::gsym::operator<<(raw_ostream &OS, const FunctionInfo &FI) {
29 OS << FI.Range << ": " << "Name=" << HEX32(FI.Name) << '\n';
30 if (FI.OptLineTable)
31 OS << FI.OptLineTable << '\n';
32 if (FI.Inline)
33 OS << FI.Inline << '\n';
34 return OS;
35}
36
37llvm::Expected<FunctionInfo> FunctionInfo::decode(DataExtractor &Data,
38 uint64_t BaseAddr) {
39 FunctionInfo FI;
40 uint64_t Offset = 0;
41 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 4))
42 return createStringError(EC: std::errc::io_error,
43 Fmt: "0x%8.8" PRIx64 ": missing FunctionInfo Size", Vals: Offset);
44 FI.Range = {BaseAddr, BaseAddr + Data.getU32(offset_ptr: &Offset)};
45 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 4))
46 return createStringError(EC: std::errc::io_error,
47 Fmt: "0x%8.8" PRIx64 ": missing FunctionInfo Name", Vals: Offset);
48 FI.Name = Data.getU32(offset_ptr: &Offset);
49 if (FI.Name == 0)
50 return createStringError(EC: std::errc::io_error,
51 Fmt: "0x%8.8" PRIx64 ": invalid FunctionInfo Name value 0x%8.8x",
52 Vals: Offset - 4, Vals: FI.Name);
53 bool Done = false;
54 while (!Done) {
55 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 4))
56 return createStringError(EC: std::errc::io_error,
57 Fmt: "0x%8.8" PRIx64 ": missing FunctionInfo InfoType value", Vals: Offset);
58 const uint32_t IT = Data.getU32(offset_ptr: &Offset);
59 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 4))
60 return createStringError(EC: std::errc::io_error,
61 Fmt: "0x%8.8" PRIx64 ": missing FunctionInfo InfoType length", Vals: Offset);
62 const uint32_t InfoLength = Data.getU32(offset_ptr: &Offset);
63 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: InfoLength))
64 return createStringError(EC: std::errc::io_error,
65 Fmt: "0x%8.8" PRIx64 ": missing FunctionInfo data for InfoType %u",
66 Vals: Offset, Vals: IT);
67 DataExtractor InfoData(Data.getData().substr(Start: Offset, N: InfoLength),
68 Data.isLittleEndian(),
69 Data.getAddressSize());
70 switch (IT) {
71 case InfoType::EndOfList:
72 Done = true;
73 break;
74
75 case InfoType::LineTableInfo:
76 if (Expected<LineTable> LT = LineTable::decode(Data&: InfoData, BaseAddr))
77 FI.OptLineTable = std::move(LT.get());
78 else
79 return LT.takeError();
80 break;
81
82 case InfoType::InlineInfo:
83 if (Expected<InlineInfo> II = InlineInfo::decode(Data&: InfoData, BaseAddr))
84 FI.Inline = std::move(II.get());
85 else
86 return II.takeError();
87 break;
88
89 default:
90 return createStringError(EC: std::errc::io_error,
91 Fmt: "0x%8.8" PRIx64 ": unsupported InfoType %u",
92 Vals: Offset-8, Vals: IT);
93 }
94 Offset += InfoLength;
95 }
96 return std::move(FI);
97}
98
99uint64_t FunctionInfo::cacheEncoding() {
100 EncodingCache.clear();
101 if (!isValid())
102 return 0;
103 raw_svector_ostream OutStrm(EncodingCache);
104 FileWriter FW(OutStrm, llvm::endianness::native);
105 llvm::Expected<uint64_t> Result = encode(O&: FW);
106 if (!Result) {
107 EncodingCache.clear();
108 consumeError(Err: Result.takeError());
109 return 0;
110 }
111 return EncodingCache.size();
112}
113
114llvm::Expected<uint64_t> FunctionInfo::encode(FileWriter &Out) const {
115 if (!isValid())
116 return createStringError(EC: std::errc::invalid_argument,
117 Fmt: "attempted to encode invalid FunctionInfo object");
118 // Align FunctionInfo data to a 4 byte alignment.
119 Out.alignTo(Align: 4);
120 const uint64_t FuncInfoOffset = Out.tell();
121 // Check if we have already encoded this function info into EncodingCache.
122 // This will be non empty when creating segmented GSYM files as we need to
123 // precompute exactly how big FunctionInfo objects encode into so we can
124 // accurately make segments of a specific size.
125 if (!EncodingCache.empty() &&
126 llvm::endianness::native == Out.getByteOrder()) {
127 // We already encoded this object, just write out the bytes.
128 Out.writeData(Data: llvm::ArrayRef<uint8_t>((const uint8_t *)EncodingCache.data(),
129 EncodingCache.size()));
130 return FuncInfoOffset;
131 }
132 // Write the size in bytes of this function as a uint32_t. This can be zero
133 // if we just have a symbol from a symbol table and that symbol has no size.
134 Out.writeU32(Value: size());
135 // Write the name of this function as a uint32_t string table offset.
136 Out.writeU32(Value: Name);
137
138 if (OptLineTable) {
139 Out.writeU32(Value: InfoType::LineTableInfo);
140 // Write a uint32_t length as zero for now, we will fix this up after
141 // writing the LineTable out with the number of bytes that were written.
142 Out.writeU32(Value: 0);
143 const auto StartOffset = Out.tell();
144 llvm::Error err = OptLineTable->encode(O&: Out, BaseAddr: Range.start());
145 if (err)
146 return std::move(err);
147 const auto Length = Out.tell() - StartOffset;
148 if (Length > UINT32_MAX)
149 return createStringError(EC: std::errc::invalid_argument,
150 Fmt: "LineTable length is greater than UINT32_MAX");
151 // Fixup the size of the LineTable data with the correct size.
152 Out.fixup32(Value: static_cast<uint32_t>(Length), Offset: StartOffset - 4);
153 }
154
155 // Write out the inline function info if we have any and if it is valid.
156 if (Inline) {
157 Out.writeU32(Value: InfoType::InlineInfo);
158 // Write a uint32_t length as zero for now, we will fix this up after
159 // writing the LineTable out with the number of bytes that were written.
160 Out.writeU32(Value: 0);
161 const auto StartOffset = Out.tell();
162 llvm::Error err = Inline->encode(O&: Out, BaseAddr: Range.start());
163 if (err)
164 return std::move(err);
165 const auto Length = Out.tell() - StartOffset;
166 if (Length > UINT32_MAX)
167 return createStringError(EC: std::errc::invalid_argument,
168 Fmt: "InlineInfo length is greater than UINT32_MAX");
169 // Fixup the size of the InlineInfo data with the correct size.
170 Out.fixup32(Value: static_cast<uint32_t>(Length), Offset: StartOffset - 4);
171 }
172
173 // Terminate the data chunks with and end of list with zero size
174 Out.writeU32(Value: InfoType::EndOfList);
175 Out.writeU32(Value: 0);
176 return FuncInfoOffset;
177}
178
179
180llvm::Expected<LookupResult> FunctionInfo::lookup(DataExtractor &Data,
181 const GsymReader &GR,
182 uint64_t FuncAddr,
183 uint64_t Addr) {
184 LookupResult LR;
185 LR.LookupAddr = Addr;
186 uint64_t Offset = 0;
187 LR.FuncRange = {FuncAddr, FuncAddr + Data.getU32(offset_ptr: &Offset)};
188 uint32_t NameOffset = Data.getU32(offset_ptr: &Offset);
189 // The "lookup" functions doesn't report errors as accurately as the "decode"
190 // function as it is meant to be fast. For more accurage errors we could call
191 // "decode".
192 if (!Data.isValidOffset(offset: Offset))
193 return createStringError(EC: std::errc::io_error,
194 Fmt: "FunctionInfo data is truncated");
195 // This function will be called with the result of a binary search of the
196 // address table, we must still make sure the address does not fall into a
197 // gap between functions are after the last function.
198 if (LR.FuncRange.size() > 0 && !LR.FuncRange.contains(Addr))
199 return createStringError(EC: std::errc::io_error,
200 Fmt: "address 0x%" PRIx64 " is not in GSYM", Vals: Addr);
201
202 if (NameOffset == 0)
203 return createStringError(EC: std::errc::io_error,
204 Fmt: "0x%8.8" PRIx64 ": invalid FunctionInfo Name value 0x00000000",
205 Vals: Offset - 4);
206 LR.FuncName = GR.getString(Offset: NameOffset);
207 bool Done = false;
208 std::optional<LineEntry> LineEntry;
209 std::optional<DataExtractor> InlineInfoData;
210 while (!Done) {
211 if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 8))
212 return createStringError(EC: std::errc::io_error,
213 Fmt: "FunctionInfo data is truncated");
214 const uint32_t IT = Data.getU32(offset_ptr: &Offset);
215 const uint32_t InfoLength = Data.getU32(offset_ptr: &Offset);
216 const StringRef InfoBytes = Data.getData().substr(Start: Offset, N: InfoLength);
217 if (InfoLength != InfoBytes.size())
218 return createStringError(EC: std::errc::io_error,
219 Fmt: "FunctionInfo data is truncated");
220 DataExtractor InfoData(InfoBytes, Data.isLittleEndian(),
221 Data.getAddressSize());
222 switch (IT) {
223 case InfoType::EndOfList:
224 Done = true;
225 break;
226
227 case InfoType::LineTableInfo:
228 if (auto ExpectedLE = LineTable::lookup(Data&: InfoData, BaseAddr: FuncAddr, Addr))
229 LineEntry = ExpectedLE.get();
230 else
231 return ExpectedLE.takeError();
232 break;
233
234 case InfoType::InlineInfo:
235 // We will parse the inline info after our line table, but only if
236 // we have a line entry.
237 InlineInfoData = InfoData;
238 break;
239
240 default:
241 break;
242 }
243 Offset += InfoLength;
244 }
245
246 if (!LineEntry) {
247 // We don't have a valid line entry for our address, fill in our source
248 // location as best we can and return.
249 SourceLocation SrcLoc;
250 SrcLoc.Name = LR.FuncName;
251 SrcLoc.Offset = Addr - FuncAddr;
252 LR.Locations.push_back(x: SrcLoc);
253 return LR;
254 }
255
256 std::optional<FileEntry> LineEntryFile = GR.getFile(Index: LineEntry->File);
257 if (!LineEntryFile)
258 return createStringError(EC: std::errc::invalid_argument,
259 Fmt: "failed to extract file[%" PRIu32 "]",
260 Vals: LineEntry->File);
261
262 SourceLocation SrcLoc;
263 SrcLoc.Name = LR.FuncName;
264 SrcLoc.Offset = Addr - FuncAddr;
265 SrcLoc.Dir = GR.getString(Offset: LineEntryFile->Dir);
266 SrcLoc.Base = GR.getString(Offset: LineEntryFile->Base);
267 SrcLoc.Line = LineEntry->Line;
268 LR.Locations.push_back(x: SrcLoc);
269 // If we don't have inline information, we are done.
270 if (!InlineInfoData)
271 return LR;
272 // We have inline information. Try to augment the lookup result with this
273 // data.
274 llvm::Error Err = InlineInfo::lookup(GR, Data&: *InlineInfoData, BaseAddr: FuncAddr, Addr,
275 SrcLocs&: LR.Locations);
276 if (Err)
277 return std::move(Err);
278 return LR;
279}
280