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 | |
17 | using namespace llvm; |
18 | using namespace gsym; |
19 | |
20 | /// FunctionInfo information type that is used to encode the optional data |
21 | /// that is associated with a FunctionInfo object. |
22 | enum InfoType : uint32_t { |
23 | EndOfList = 0u, |
24 | LineTableInfo = 1u, |
25 | InlineInfo = 2u |
26 | }; |
27 | |
28 | raw_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 | |
37 | llvm::Expected<FunctionInfo> FunctionInfo::(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 | |
99 | uint64_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 | |
114 | llvm::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 | |
180 | llvm::Expected<LookupResult> FunctionInfo::(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 | |