xref: /freebsd/contrib/llvm-project/llvm/lib/DebugInfo/GSYM/InlineInfo.cpp (revision a0ca4af9455b844c5e094fc1b09b1390ffa979fc)
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(InlineStack.begin(), &II);
47     for (const auto &Child : II.Children) {
48       if (::getInlineStackHelper(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(*this, Addr, 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 skip(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) != 0;
83   Data.getU32(&Offset); // Skip Inline.Name.
84   Data.getULEB128(&Offset); // Skip Inline.CallFile.
85   Data.getULEB128(&Offset); // Skip Inline.CallLine.
86   if (HasChildren) {
87     while (skip(Data, Offset, 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 lookup(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(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, 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) != 0;
126   Inline.Name = Data.getU32(&Offset);
127   Inline.CallFile = (uint32_t)Data.getULEB128(&Offset);
128   Inline.CallLine = (uint32_t)Data.getULEB128(&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, ChildBaseAddr, Addr, SrcLocs, Err);
136   }
137 
138   std::optional<FileEntry> CallFile = GR.getFile(Inline.CallFile);
139   if (!CallFile) {
140     Err = createStringError(std::errc::invalid_argument,
141                             "failed to extract file[%" PRIu32 "]",
142                             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(CallFile->Dir);
151     SrcLoc.Base = GR.getString(CallFile->Base);
152     SrcLoc.Line = Inline.CallLine;
153     SrcLocs.back().Name = GR.getString(Inline.Name);
154     SrcLocs.back().Offset = Addr - Inline.Ranges[0].start();
155     SrcLocs.push_back(SrcLoc);
156   }
157   return true;
158 }
159 
160 llvm::Error InlineInfo::lookup(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> decode(DataExtractor &Data, uint64_t &Offset,
181                                          uint64_t BaseAddr) {
182   InlineInfo Inline;
183   if (!Data.isValidOffset(Offset))
184     return createStringError(std::errc::io_error,
185         "0x%8.8" PRIx64 ": missing InlineInfo address ranges data", Offset);
186   decodeRanges(Inline.Ranges, Data, BaseAddr, Offset);
187   if (Inline.Ranges.empty())
188     return Inline;
189   if (!Data.isValidOffsetForDataOfSize(Offset, 1))
190     return createStringError(std::errc::io_error,
191         "0x%8.8" PRIx64 ": missing InlineInfo uint8_t indicating children",
192         Offset);
193   bool HasChildren = Data.getU8(&Offset) != 0;
194   if (!Data.isValidOffsetForDataOfSize(Offset, 4))
195     return createStringError(std::errc::io_error,
196         "0x%8.8" PRIx64 ": missing InlineInfo uint32_t for name", Offset);
197   Inline.Name = Data.getU32(&Offset);
198   if (!Data.isValidOffset(Offset))
199     return createStringError(std::errc::io_error,
200         "0x%8.8" PRIx64 ": missing ULEB128 for InlineInfo call file", Offset);
201   Inline.CallFile = (uint32_t)Data.getULEB128(&Offset);
202   if (!Data.isValidOffset(Offset))
203     return createStringError(std::errc::io_error,
204         "0x%8.8" PRIx64 ": missing ULEB128 for InlineInfo call line", Offset);
205   Inline.CallLine = (uint32_t)Data.getULEB128(&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, 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(std::move(*Child));
218     }
219   }
220   return Inline;
221 }
222 
223 llvm::Expected<InlineInfo> InlineInfo::decode(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(std::errc::invalid_argument,
235                              "attempted to encode invalid InlineInfo object");
236   encodeRanges(Ranges, O, BaseAddr);
237   bool HasChildren = !Children.empty();
238   O.writeU8(HasChildren);
239   O.writeU32(Name);
240   O.writeULEB(CallFile);
241   O.writeULEB(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(ChildRange))
252           return createStringError(std::errc::invalid_argument,
253                                    "child range not contained in parent");
254       }
255       llvm::Error Err = Child.encode(O, 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(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(Child);
272   return NumChildren;
273 }
274 
275 bool InlineInfo::operator<(const InlineInfo &RHS) const {
276   return GetTotalNumChildren(*this) < GetTotalNumChildren(RHS);
277 }
278