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