1 | //===- InlineInfo.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/DebugInfo/GSYM/FileEntry.h" |
10 | #include "llvm/DebugInfo/GSYM/FileWriter.h" |
11 | #include "llvm/DebugInfo/GSYM/GsymReader.h" |
12 | #include "llvm/DebugInfo/GSYM/InlineInfo.h" |
13 | #include "llvm/Support/DataExtractor.h" |
14 | #include <algorithm> |
15 | #include <inttypes.h> |
16 | |
17 | using namespace llvm; |
18 | using namespace gsym; |
19 | |
20 | |
21 | raw_ostream &llvm::gsym::operator<<(raw_ostream &OS, const InlineInfo &II) { |
22 | if (!II.isValid()) |
23 | return OS; |
24 | bool First = true; |
25 | for (auto Range : II.Ranges) { |
26 | if (First) |
27 | First = false; |
28 | else |
29 | OS << ' '; |
30 | OS << Range; |
31 | } |
32 | OS << " Name = " << HEX32(II.Name) << ", CallFile = " << II.CallFile |
33 | << ", CallLine = " << II.CallFile << '\n'; |
34 | for (const auto &Child : II.Children) |
35 | OS << Child; |
36 | return OS; |
37 | } |
38 | |
39 | static bool getInlineStackHelper(const InlineInfo &II, uint64_t Addr, |
40 | std::vector<const InlineInfo *> &InlineStack) { |
41 | if (II.Ranges.contains(Addr)) { |
42 | // If this is the top level that represents the concrete function, |
43 | // there will be no name and we shoud clear the inline stack. Otherwise |
44 | // we have found an inline call stack that we need to insert. |
45 | if (II.Name != 0) |
46 | InlineStack.insert(position: InlineStack.begin(), x: &II); |
47 | for (const auto &Child : II.Children) { |
48 | if (::getInlineStackHelper(II: Child, Addr, InlineStack)) |
49 | break; |
50 | } |
51 | return !InlineStack.empty(); |
52 | } |
53 | return false; |
54 | } |
55 | |
56 | std::optional<InlineInfo::InlineArray> |
57 | InlineInfo::getInlineStack(uint64_t Addr) const { |
58 | InlineArray Result; |
59 | if (getInlineStackHelper(II: *this, Addr, InlineStack&: Result)) |
60 | return Result; |
61 | return std::nullopt; |
62 | } |
63 | |
64 | /// Skip an InlineInfo object in the specified data at the specified offset. |
65 | /// |
66 | /// Used during the InlineInfo::lookup() call to quickly skip child InlineInfo |
67 | /// objects where the addres ranges isn't contained in the InlineInfo object |
68 | /// or its children. This avoids allocations by not appending child InlineInfo |
69 | /// objects to the InlineInfo::Children array. |
70 | /// |
71 | /// \param Data The binary stream to read the data from. |
72 | /// |
73 | /// \param Offset The byte offset within \a Data. |
74 | /// |
75 | /// \param SkippedRanges If true, address ranges have already been skipped. |
76 | |
77 | static bool (DataExtractor &Data, uint64_t &Offset, bool SkippedRanges) { |
78 | if (!SkippedRanges) { |
79 | if (skipRanges(Data, Offset) == 0) |
80 | return false; |
81 | } |
82 | bool HasChildren = Data.getU8(offset_ptr: &Offset) != 0; |
83 | Data.getU32(offset_ptr: &Offset); // Skip Inline.Name. |
84 | Data.getULEB128(offset_ptr: &Offset); // Skip Inline.CallFile. |
85 | Data.getULEB128(offset_ptr: &Offset); // Skip Inline.CallLine. |
86 | if (HasChildren) { |
87 | while (skip(Data, Offset, SkippedRanges: false /* SkippedRanges */)) |
88 | /* Do nothing */; |
89 | } |
90 | // We skipped a valid InlineInfo. |
91 | return true; |
92 | } |
93 | |
94 | /// A Lookup helper functions. |
95 | /// |
96 | /// Used during the InlineInfo::lookup() call to quickly only parse an |
97 | /// InlineInfo object if the address falls within this object. This avoids |
98 | /// allocations by not appending child InlineInfo objects to the |
99 | /// InlineInfo::Children array and also skips any InlineInfo objects that do |
100 | /// not contain the address we are looking up. |
101 | /// |
102 | /// \param Data The binary stream to read the data from. |
103 | /// |
104 | /// \param Offset The byte offset within \a Data. |
105 | /// |
106 | /// \param BaseAddr The address that the relative address range offsets are |
107 | /// relative to. |
108 | |
109 | static bool (const GsymReader &GR, DataExtractor &Data, uint64_t &Offset, |
110 | uint64_t BaseAddr, uint64_t Addr, SourceLocations &SrcLocs, |
111 | llvm::Error &Err) { |
112 | InlineInfo Inline; |
113 | decodeRanges(Ranges&: Inline.Ranges, Data, BaseAddr, Offset); |
114 | if (Inline.Ranges.empty()) |
115 | return true; |
116 | // Check if the address is contained within the inline information, and if |
117 | // not, quickly skip this InlineInfo object and all its children. |
118 | if (!Inline.Ranges.contains(Addr)) { |
119 | skip(Data, Offset, SkippedRanges: true /* SkippedRanges */); |
120 | return false; |
121 | } |
122 | |
123 | // The address range is contained within this InlineInfo, add the source |
124 | // location for this InlineInfo and any children that contain the address. |
125 | bool HasChildren = Data.getU8(offset_ptr: &Offset) != 0; |
126 | Inline.Name = Data.getU32(offset_ptr: &Offset); |
127 | Inline.CallFile = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
128 | Inline.CallLine = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
129 | if (HasChildren) { |
130 | // Child address ranges are encoded relative to the first address in the |
131 | // parent InlineInfo object. |
132 | const auto ChildBaseAddr = Inline.Ranges[0].start(); |
133 | bool Done = false; |
134 | while (!Done) |
135 | Done = lookup(GR, Data, Offset, BaseAddr: ChildBaseAddr, Addr, SrcLocs, Err); |
136 | } |
137 | |
138 | std::optional<FileEntry> CallFile = GR.getFile(Index: Inline.CallFile); |
139 | if (!CallFile) { |
140 | Err = createStringError(EC: std::errc::invalid_argument, |
141 | Fmt: "failed to extract file[%" PRIu32 "]" , |
142 | Vals: Inline.CallFile); |
143 | return false; |
144 | } |
145 | |
146 | if (CallFile->Dir || CallFile->Base) { |
147 | SourceLocation SrcLoc; |
148 | SrcLoc.Name = SrcLocs.back().Name; |
149 | SrcLoc.Offset = SrcLocs.back().Offset; |
150 | SrcLoc.Dir = GR.getString(Offset: CallFile->Dir); |
151 | SrcLoc.Base = GR.getString(Offset: CallFile->Base); |
152 | SrcLoc.Line = Inline.CallLine; |
153 | SrcLocs.back().Name = GR.getString(Offset: Inline.Name); |
154 | SrcLocs.back().Offset = Addr - Inline.Ranges[0].start(); |
155 | SrcLocs.push_back(x: SrcLoc); |
156 | } |
157 | return true; |
158 | } |
159 | |
160 | llvm::Error InlineInfo::(const GsymReader &GR, DataExtractor &Data, |
161 | uint64_t BaseAddr, uint64_t Addr, |
162 | SourceLocations &SrcLocs) { |
163 | // Call our recursive helper function starting at offset zero. |
164 | uint64_t Offset = 0; |
165 | llvm::Error Err = Error::success(); |
166 | ::lookup(GR, Data, Offset, BaseAddr, Addr, SrcLocs, Err); |
167 | return Err; |
168 | } |
169 | |
170 | /// Decode an InlineInfo in Data at the specified offset. |
171 | /// |
172 | /// A local helper function to decode InlineInfo objects. This function is |
173 | /// called recursively when parsing child InlineInfo objects. |
174 | /// |
175 | /// \param Data The data extractor to decode from. |
176 | /// \param Offset The offset within \a Data to decode from. |
177 | /// \param BaseAddr The base address to use when decoding address ranges. |
178 | /// \returns An InlineInfo or an error describing the issue that was |
179 | /// encountered during decoding. |
180 | static llvm::Expected<InlineInfo> (DataExtractor &Data, uint64_t &Offset, |
181 | uint64_t BaseAddr) { |
182 | InlineInfo Inline; |
183 | if (!Data.isValidOffset(offset: Offset)) |
184 | return createStringError(EC: std::errc::io_error, |
185 | Fmt: "0x%8.8" PRIx64 ": missing InlineInfo address ranges data" , Vals: Offset); |
186 | decodeRanges(Ranges&: Inline.Ranges, Data, BaseAddr, Offset); |
187 | if (Inline.Ranges.empty()) |
188 | return Inline; |
189 | if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 1)) |
190 | return createStringError(EC: std::errc::io_error, |
191 | Fmt: "0x%8.8" PRIx64 ": missing InlineInfo uint8_t indicating children" , |
192 | Vals: Offset); |
193 | bool HasChildren = Data.getU8(offset_ptr: &Offset) != 0; |
194 | if (!Data.isValidOffsetForDataOfSize(offset: Offset, length: 4)) |
195 | return createStringError(EC: std::errc::io_error, |
196 | Fmt: "0x%8.8" PRIx64 ": missing InlineInfo uint32_t for name" , Vals: Offset); |
197 | Inline.Name = Data.getU32(offset_ptr: &Offset); |
198 | if (!Data.isValidOffset(offset: Offset)) |
199 | return createStringError(EC: std::errc::io_error, |
200 | Fmt: "0x%8.8" PRIx64 ": missing ULEB128 for InlineInfo call file" , Vals: Offset); |
201 | Inline.CallFile = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
202 | if (!Data.isValidOffset(offset: Offset)) |
203 | return createStringError(EC: std::errc::io_error, |
204 | Fmt: "0x%8.8" PRIx64 ": missing ULEB128 for InlineInfo call line" , Vals: Offset); |
205 | Inline.CallLine = (uint32_t)Data.getULEB128(offset_ptr: &Offset); |
206 | if (HasChildren) { |
207 | // Child address ranges are encoded relative to the first address in the |
208 | // parent InlineInfo object. |
209 | const auto ChildBaseAddr = Inline.Ranges[0].start(); |
210 | while (true) { |
211 | llvm::Expected<InlineInfo> Child = decode(Data, Offset, BaseAddr: ChildBaseAddr); |
212 | if (!Child) |
213 | return Child.takeError(); |
214 | // InlineInfo with empty Ranges termintes a child sibling chain. |
215 | if (Child.get().Ranges.empty()) |
216 | break; |
217 | Inline.Children.emplace_back(args: std::move(*Child)); |
218 | } |
219 | } |
220 | return Inline; |
221 | } |
222 | |
223 | llvm::Expected<InlineInfo> InlineInfo::(DataExtractor &Data, |
224 | uint64_t BaseAddr) { |
225 | uint64_t Offset = 0; |
226 | return ::decode(Data, Offset, BaseAddr); |
227 | } |
228 | |
229 | llvm::Error InlineInfo::encode(FileWriter &O, uint64_t BaseAddr) const { |
230 | // Users must verify the InlineInfo is valid prior to calling this funtion. |
231 | // We don't want to emit any InlineInfo objects if they are not valid since |
232 | // it will waste space in the GSYM file. |
233 | if (!isValid()) |
234 | return createStringError(EC: std::errc::invalid_argument, |
235 | Fmt: "attempted to encode invalid InlineInfo object" ); |
236 | encodeRanges(Ranges, O, BaseAddr); |
237 | bool HasChildren = !Children.empty(); |
238 | O.writeU8(Value: HasChildren); |
239 | O.writeU32(Value: Name); |
240 | O.writeULEB(Value: CallFile); |
241 | O.writeULEB(Value: CallLine); |
242 | if (HasChildren) { |
243 | // Child address ranges are encoded as relative to the first |
244 | // address in the Ranges for this object. This keeps the offsets |
245 | // small and allows for efficient encoding using ULEB offsets. |
246 | const uint64_t ChildBaseAddr = Ranges[0].start(); |
247 | for (const auto &Child : Children) { |
248 | // Make sure all child address ranges are contained in the parent address |
249 | // ranges. |
250 | for (const auto &ChildRange: Child.Ranges) { |
251 | if (!Ranges.contains(Range: ChildRange)) |
252 | return createStringError(EC: std::errc::invalid_argument, |
253 | Fmt: "child range not contained in parent" ); |
254 | } |
255 | llvm::Error Err = Child.encode(O, BaseAddr: ChildBaseAddr); |
256 | if (Err) |
257 | return Err; |
258 | } |
259 | |
260 | // Terminate child sibling chain by emitting a zero. This zero will cause |
261 | // the decodeAll() function above to return false and stop the decoding |
262 | // of child InlineInfo objects that are siblings. |
263 | O.writeULEB(Value: 0); |
264 | } |
265 | return Error::success(); |
266 | } |
267 | |
268 | static uint64_t GetTotalNumChildren(const InlineInfo &II) { |
269 | uint64_t NumChildren = II.Children.size(); |
270 | for (const auto &Child : II.Children) |
271 | NumChildren += GetTotalNumChildren(II: Child); |
272 | return NumChildren; |
273 | } |
274 | |
275 | bool InlineInfo::operator<(const InlineInfo &RHS) const { |
276 | return GetTotalNumChildren(II: *this) < GetTotalNumChildren(II: RHS); |
277 | } |
278 | |