| 1 | //===- GsymReader.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/GsymReader.h" |
| 10 | |
| 11 | #include <assert.h> |
| 12 | #include <inttypes.h> |
| 13 | #include <stdio.h> |
| 14 | #include <stdlib.h> |
| 15 | |
| 16 | #include "llvm/ADT/StringExtras.h" |
| 17 | #include "llvm/DebugInfo/GSYM/GsymReaderV1.h" |
| 18 | #include "llvm/DebugInfo/GSYM/GsymReaderV2.h" |
| 19 | #include "llvm/DebugInfo/GSYM/Header.h" |
| 20 | #include "llvm/DebugInfo/GSYM/HeaderV2.h" |
| 21 | #include "llvm/DebugInfo/GSYM/InlineInfo.h" |
| 22 | #include "llvm/DebugInfo/GSYM/LineTable.h" |
| 23 | #include "llvm/Support/JSON.h" |
| 24 | #include "llvm/Support/MemoryBuffer.h" |
| 25 | |
| 26 | using namespace llvm; |
| 27 | using namespace gsym; |
| 28 | |
| 29 | GsymReader::GsymReader(std::unique_ptr<MemoryBuffer> Buffer, |
| 30 | llvm::endianness Endian) |
| 31 | : MemBuffer(std::move(Buffer)), Endian(Endian), |
| 32 | AddrInfoOffsetsData(StringRef(), true), FileEntryData(StringRef(), true) { |
| 33 | } |
| 34 | |
| 35 | /// Check magic bytes, determine endianness, and return the GSYM version and |
| 36 | /// endianness. If magic bytes are invalid, return error. |
| 37 | static Expected<std::pair<uint16_t, llvm::endianness>> |
| 38 | checkMagicAndDetectVersionEndian(StringRef Bytes) { |
| 39 | if (Bytes.size() < 6) |
| 40 | return createStringError(EC: std::errc::invalid_argument, |
| 41 | Fmt: "data too small to be a GSYM file" ); |
| 42 | // Detect host endian |
| 43 | const auto HostEndian = llvm::endianness::native; |
| 44 | const bool IsHostLittleEndian = (HostEndian == llvm::endianness::little); |
| 45 | // Read magic bytes using host endian |
| 46 | GsymDataExtractor Data(Bytes, IsHostLittleEndian); |
| 47 | uint64_t Offset = 0; |
| 48 | uint32_t Magic = Data.getU32(offset_ptr: &Offset); |
| 49 | llvm::endianness FileEndian; |
| 50 | // If magic bytes looks alright, the host and the file have the same |
| 51 | // endianness, vice versa. |
| 52 | if (Magic == GSYM_MAGIC) { |
| 53 | FileEndian = HostEndian; |
| 54 | } else if (Magic == GSYM_CIGAM) { |
| 55 | FileEndian = |
| 56 | IsHostLittleEndian ? llvm::endianness::big : llvm::endianness::little; |
| 57 | // Re-create GsymDataExtractor with correct endianness to read version. |
| 58 | Data = GsymDataExtractor(Bytes, !IsHostLittleEndian); |
| 59 | } else { |
| 60 | return createStringError(EC: std::errc::invalid_argument, |
| 61 | Fmt: "not a GSYM file (bad magic)" ); |
| 62 | } |
| 63 | // Read version using the correct endian |
| 64 | uint16_t Version = Data.getU16(offset_ptr: &Offset); |
| 65 | return std::make_pair(x&: Version, y&: FileEndian); |
| 66 | } |
| 67 | |
| 68 | llvm::Expected<std::unique_ptr<GsymReader>> |
| 69 | GsymReader::openFile(StringRef Filename) { |
| 70 | // Open the input file and return an appropriate error if needed. |
| 71 | ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr = |
| 72 | MemoryBuffer::getFileOrSTDIN(Filename); |
| 73 | auto Err = BuffOrErr.getError(); |
| 74 | if (Err) |
| 75 | return llvm::errorCodeToError(EC: Err); |
| 76 | auto &Buf = BuffOrErr.get(); |
| 77 | Buf->randomAccessIfMmap(); |
| 78 | return create(MemBuffer&: Buf); |
| 79 | } |
| 80 | |
| 81 | llvm::Expected<std::unique_ptr<GsymReader>> |
| 82 | GsymReader::copyBuffer(StringRef Bytes) { |
| 83 | auto MemBuffer = MemoryBuffer::getMemBufferCopy(InputData: Bytes, BufferName: "GSYM bytes" ); |
| 84 | return create(MemBuffer); |
| 85 | } |
| 86 | |
| 87 | llvm::Expected<std::unique_ptr<GsymReader>> |
| 88 | GsymReader::create(std::unique_ptr<MemoryBuffer> &MemBuffer) { |
| 89 | if (!MemBuffer) |
| 90 | return createStringError(EC: std::errc::invalid_argument, |
| 91 | Fmt: "invalid memory buffer" ); |
| 92 | Expected<std::pair<uint16_t, llvm::endianness>> VersionEndianOrErr = |
| 93 | checkMagicAndDetectVersionEndian(Bytes: MemBuffer->getBuffer()); |
| 94 | if (!VersionEndianOrErr) |
| 95 | return VersionEndianOrErr.takeError(); |
| 96 | uint16_t Version; |
| 97 | llvm::endianness Endian; |
| 98 | std::tie(args&: Version, args&: Endian) = *VersionEndianOrErr; |
| 99 | std::unique_ptr<GsymReader> GR; |
| 100 | switch (Version) { |
| 101 | case Header::getVersion(): |
| 102 | GR.reset(p: new GsymReaderV1(std::move(MemBuffer), Endian)); |
| 103 | break; |
| 104 | case HeaderV2::getVersion(): |
| 105 | GR.reset(p: new GsymReaderV2(std::move(MemBuffer), Endian)); |
| 106 | break; |
| 107 | default: |
| 108 | return createStringError(EC: std::errc::invalid_argument, |
| 109 | Fmt: "unsupported GSYM version %u" , Vals: Version); |
| 110 | } |
| 111 | if (auto Err = GR->parse()) |
| 112 | return std::move(Err); |
| 113 | return std::move(GR); |
| 114 | } |
| 115 | |
| 116 | llvm::Error GsymReader::parse() { |
| 117 | // Step 1: Parse the version-specific header and populate GlobalDataSections. |
| 118 | if (auto Err = parseHeaderAndGlobalDataEntries()) |
| 119 | return Err; |
| 120 | |
| 121 | // Step 2: Validate that all required sections are present and consistent. |
| 122 | for (auto Type : |
| 123 | {GlobalInfoType::AddrOffsets, GlobalInfoType::AddrInfoOffsets, |
| 124 | GlobalInfoType::StringTable, GlobalInfoType::FileTable, |
| 125 | GlobalInfoType::FunctionInfo}) |
| 126 | if (!GlobalDataSections.count(x: Type)) |
| 127 | return createStringError( |
| 128 | EC: std::errc::invalid_argument, Fmt: "missing required section type %s (%u)" , |
| 129 | Vals: getNameForGlobalInfoType(Type).data(), Vals: static_cast<uint32_t>(Type)); |
| 130 | |
| 131 | if (GlobalDataSections[GlobalInfoType::AddrOffsets].FileSize != |
| 132 | static_cast<uint64_t>(getNumAddresses()) * getAddressOffsetSize()) |
| 133 | return createStringError(EC: std::errc::invalid_argument, |
| 134 | Fmt: "AddrOffsets section size mismatch" ); |
| 135 | |
| 136 | if (GlobalDataSections[GlobalInfoType::AddrInfoOffsets].FileSize != |
| 137 | static_cast<uint64_t>(getNumAddresses()) * getAddressInfoOffsetSize()) |
| 138 | return createStringError(EC: std::errc::invalid_argument, |
| 139 | Fmt: "AddrInfoOffsets section size mismatch" ); |
| 140 | |
| 141 | // Step 3: Parse each global data section. |
| 142 | llvm::Expected<StringRef> Bytes = |
| 143 | getRequiredGlobalDataBytes(Type: GlobalInfoType::AddrOffsets); |
| 144 | if (!Bytes) |
| 145 | return Bytes.takeError(); |
| 146 | if (auto Err = parseAddrOffsets(Bytes: *Bytes)) |
| 147 | return Err; |
| 148 | |
| 149 | Bytes = getRequiredGlobalDataBytes(Type: GlobalInfoType::AddrInfoOffsets); |
| 150 | if (!Bytes) |
| 151 | return Bytes.takeError(); |
| 152 | if (auto Err = setAddrInfoOffsetsData(*Bytes)) |
| 153 | return Err; |
| 154 | |
| 155 | Bytes = getRequiredGlobalDataBytes(Type: GlobalInfoType::StringTable); |
| 156 | if (!Bytes) |
| 157 | return Bytes.takeError(); |
| 158 | if (auto Err = setStringTableData(*Bytes)) |
| 159 | return Err; |
| 160 | |
| 161 | Bytes = getRequiredGlobalDataBytes(Type: GlobalInfoType::FileTable); |
| 162 | if (!Bytes) |
| 163 | return Bytes.takeError(); |
| 164 | if (auto Err = setFileTableData(*Bytes)) |
| 165 | return Err; |
| 166 | |
| 167 | return Error::success(); |
| 168 | } |
| 169 | |
| 170 | llvm::Error GsymReader::parseGlobalDataEntries(uint64_t Offset) { |
| 171 | if (getVersion() < HeaderV2::getVersion()) |
| 172 | return createStringError(EC: std::errc::invalid_argument, |
| 173 | Fmt: "GlobalData section not supported in GSYM V1" ); |
| 174 | |
| 175 | const StringRef Buf = MemBuffer->getBuffer(); |
| 176 | const uint64_t BufSize = Buf.size(); |
| 177 | GsymDataExtractor Data(Buf, isLittleEndian()); |
| 178 | while (Offset + sizeof(GlobalData) <= BufSize) { |
| 179 | auto GDOrErr = GlobalData::decode(GsymData&: Data, Offset); |
| 180 | if (!GDOrErr) |
| 181 | return GDOrErr.takeError(); |
| 182 | const GlobalData &GD = *GDOrErr; |
| 183 | |
| 184 | if (GD.Type == GlobalInfoType::EndOfList) |
| 185 | return Error::success(); |
| 186 | |
| 187 | if (GD.FileSize == 0) |
| 188 | return createStringError(EC: std::errc::invalid_argument, |
| 189 | Fmt: "GlobalData section type %u has zero size" , |
| 190 | Vals: static_cast<uint32_t>(GD.Type)); |
| 191 | |
| 192 | if (GD.FileOffset + GD.FileSize > BufSize) |
| 193 | return createStringError( |
| 194 | EC: std::errc::invalid_argument, |
| 195 | Fmt: "GlobalData section type %u extends beyond " |
| 196 | "buffer (offset=%" PRIu64 ", size=%" PRIu64 ", bufsize=%" PRIu64 ")" , |
| 197 | Vals: static_cast<uint32_t>(GD.Type), Vals: GD.FileOffset, Vals: GD.FileSize, Vals: BufSize); |
| 198 | |
| 199 | GlobalDataSections[GD.Type] = GD; |
| 200 | } |
| 201 | return createStringError(EC: std::errc::invalid_argument, |
| 202 | Fmt: "GlobalData array not terminated by EndOfList" ); |
| 203 | } |
| 204 | |
| 205 | llvm::Error GsymReader::parseAddrOffsets(StringRef Bytes) { |
| 206 | const uint8_t AddrOffSize = getAddressOffsetSize(); |
| 207 | const uint32_t NumAddrs = getNumAddresses(); |
| 208 | const size_t TotalBytes = NumAddrs * AddrOffSize; |
| 209 | if (Bytes.size() < TotalBytes) |
| 210 | return createStringError(EC: std::errc::invalid_argument, |
| 211 | Fmt: "failed to read address table" ); |
| 212 | |
| 213 | // Parse the non-swap case |
| 214 | if (Endian == llvm::endianness::native) { |
| 215 | AddrOffsets = ArrayRef<uint8_t>( |
| 216 | reinterpret_cast<const uint8_t *>(Bytes.data()), TotalBytes); |
| 217 | return Error::success(); |
| 218 | } |
| 219 | |
| 220 | // Parse the swap case |
| 221 | GsymDataExtractor Data(Bytes, isLittleEndian()); |
| 222 | uint64_t Offset = 0; |
| 223 | SwappedAddrOffsets.resize(new_size: TotalBytes); |
| 224 | switch (AddrOffSize) { |
| 225 | case 1: |
| 226 | if (!Data.getU8(offset_ptr: &Offset, dst: SwappedAddrOffsets.data(), count: NumAddrs)) |
| 227 | return createStringError(EC: std::errc::invalid_argument, |
| 228 | Fmt: "failed to read address table" ); |
| 229 | break; |
| 230 | case 2: |
| 231 | if (!Data.getU16(offset_ptr: &Offset, |
| 232 | dst: reinterpret_cast<uint16_t *>(SwappedAddrOffsets.data()), |
| 233 | count: NumAddrs)) |
| 234 | return createStringError(EC: std::errc::invalid_argument, |
| 235 | Fmt: "failed to read address table" ); |
| 236 | break; |
| 237 | case 4: |
| 238 | if (!Data.getU32(offset_ptr: &Offset, |
| 239 | dst: reinterpret_cast<uint32_t *>(SwappedAddrOffsets.data()), |
| 240 | count: NumAddrs)) |
| 241 | return createStringError(EC: std::errc::invalid_argument, |
| 242 | Fmt: "failed to read address table" ); |
| 243 | break; |
| 244 | case 8: |
| 245 | if (!Data.getU64(offset_ptr: &Offset, |
| 246 | dst: reinterpret_cast<uint64_t *>(SwappedAddrOffsets.data()), |
| 247 | count: NumAddrs)) |
| 248 | return createStringError(EC: std::errc::invalid_argument, |
| 249 | Fmt: "failed to read address table" ); |
| 250 | break; |
| 251 | } |
| 252 | AddrOffsets = ArrayRef<uint8_t>(SwappedAddrOffsets); |
| 253 | return Error::success(); |
| 254 | } |
| 255 | |
| 256 | llvm::Error GsymReader::setAddrInfoOffsetsData(StringRef Bytes) { |
| 257 | AddrInfoOffsetsData = GsymDataExtractor(Bytes, isLittleEndian()); |
| 258 | return Error::success(); |
| 259 | } |
| 260 | |
| 261 | llvm::Error GsymReader::setStringTableData(StringRef Bytes) { |
| 262 | StrTab.Data = Bytes; |
| 263 | return Error::success(); |
| 264 | } |
| 265 | |
| 266 | llvm::Error GsymReader::setFileTableData(StringRef Bytes) { |
| 267 | const uint8_t StrpSize = getStringOffsetSize(); |
| 268 | GsymDataExtractor Data(Bytes, isLittleEndian(), StrpSize); |
| 269 | uint64_t Offset = 0; |
| 270 | uint32_t NumFiles = Data.getU32(offset_ptr: &Offset); |
| 271 | uint64_t EntriesSize = |
| 272 | static_cast<uint64_t>(NumFiles) * FileEntry::getEncodedSize(StringOffsetSize: StrpSize); |
| 273 | if (Bytes.size() < Offset + EntriesSize) |
| 274 | return createStringError(EC: std::errc::invalid_argument, |
| 275 | Fmt: "FileTable section too small for %u files" , |
| 276 | Vals: NumFiles); |
| 277 | FileEntryData = GsymDataExtractor(Data, Offset, EntriesSize); |
| 278 | return Error::success(); |
| 279 | } |
| 280 | |
| 281 | std::optional<GlobalData> GsymReader::getGlobalData(GlobalInfoType Type) const { |
| 282 | auto It = GlobalDataSections.find(x: Type); |
| 283 | if (It == GlobalDataSections.end()) |
| 284 | return std::nullopt; |
| 285 | return It->second; |
| 286 | } |
| 287 | |
| 288 | llvm::Expected<StringRef> |
| 289 | GsymReader::getRequiredGlobalDataBytes(GlobalInfoType Type) const { |
| 290 | if (auto Data = getOptionalGlobalDataBytes(Type)) |
| 291 | return *Data; |
| 292 | const char *TypeName = getNameForGlobalInfoType(Type).data(); |
| 293 | std::optional<GlobalData> GD = getGlobalData(Type); |
| 294 | // We have a GlobalData entry but didn't get any bytes — the file may be |
| 295 | // truncated. |
| 296 | if (GD) |
| 297 | return createStringError( |
| 298 | EC: std::errc::invalid_argument, |
| 299 | Fmt: "missing bytes for %s, GSYM file might be truncated" , Vals: TypeName); |
| 300 | return createStringError(EC: std::errc::invalid_argument, |
| 301 | Fmt: "missing required section type %s" , Vals: TypeName); |
| 302 | } |
| 303 | |
| 304 | std::optional<StringRef> |
| 305 | GsymReader::getOptionalGlobalDataBytes(GlobalInfoType Type) const { |
| 306 | std::optional<GlobalData> GD = getGlobalData(Type); |
| 307 | if (!GD) |
| 308 | return std::nullopt; |
| 309 | StringRef Buf = MemBuffer->getBuffer(); |
| 310 | if (GD->FileSize == 0 || GD->FileOffset + GD->FileSize > Buf.size()) |
| 311 | return std::nullopt; |
| 312 | return Buf.substr(Start: GD->FileOffset, N: GD->FileSize); |
| 313 | } |
| 314 | |
| 315 | std::optional<uint64_t> GsymReader::getAddress(size_t Index) const { |
| 316 | switch (getAddressOffsetSize()) { |
| 317 | case 1: return addressForIndex<uint8_t>(Index); |
| 318 | case 2: return addressForIndex<uint16_t>(Index); |
| 319 | case 4: return addressForIndex<uint32_t>(Index); |
| 320 | case 8: return addressForIndex<uint64_t>(Index); |
| 321 | default: |
| 322 | llvm_unreachable("unsupported address offset size" ); |
| 323 | } |
| 324 | return std::nullopt; |
| 325 | } |
| 326 | |
| 327 | std::optional<uint64_t> GsymReader::getAddressInfoOffset(size_t Index) const { |
| 328 | if (Index >= getNumAddresses()) |
| 329 | return std::nullopt; |
| 330 | const uint8_t AddrInfoOffsetSize = getAddressInfoOffsetSize(); |
| 331 | uint64_t Offset = Index * AddrInfoOffsetSize; |
| 332 | uint64_t AddrInfoOffset = |
| 333 | AddrInfoOffsetsData.getUnsigned(offset_ptr: &Offset, byte_size: AddrInfoOffsetSize); |
| 334 | // V1 stores absolute file offsets in AddrInfoOffsets, so no base offset is |
| 335 | // needed. V2+ stores offsets relative to the FunctionInfo section start. |
| 336 | if (getVersion() != Header::getVersion()) |
| 337 | AddrInfoOffset += |
| 338 | GlobalDataSections.at(k: GlobalInfoType::FunctionInfo).FileOffset; |
| 339 | return AddrInfoOffset; |
| 340 | } |
| 341 | |
| 342 | Expected<uint64_t> GsymReader::getAddressIndex(const uint64_t Addr) const { |
| 343 | const uint64_t BaseAddr = getBaseAddress(); |
| 344 | if (Addr >= BaseAddr) { |
| 345 | const uint64_t AddrOffset = Addr - BaseAddr; |
| 346 | std::optional<uint64_t> AddrOffsetIndex; |
| 347 | switch (getAddressOffsetSize()) { |
| 348 | case 1: |
| 349 | AddrOffsetIndex = getAddressOffsetIndex<uint8_t>(AddrOffset); |
| 350 | break; |
| 351 | case 2: |
| 352 | AddrOffsetIndex = getAddressOffsetIndex<uint16_t>(AddrOffset); |
| 353 | break; |
| 354 | case 4: |
| 355 | AddrOffsetIndex = getAddressOffsetIndex<uint32_t>(AddrOffset); |
| 356 | break; |
| 357 | case 8: |
| 358 | AddrOffsetIndex = getAddressOffsetIndex<uint64_t>(AddrOffset); |
| 359 | break; |
| 360 | default: |
| 361 | return createStringError(EC: std::errc::invalid_argument, |
| 362 | Fmt: "unsupported address offset size %u" , |
| 363 | Vals: getAddressOffsetSize()); |
| 364 | } |
| 365 | if (AddrOffsetIndex) |
| 366 | return *AddrOffsetIndex; |
| 367 | } |
| 368 | return createStringError(EC: std::errc::invalid_argument, |
| 369 | Fmt: "address 0x%" PRIx64 " is not in GSYM" , Vals: Addr); |
| 370 | } |
| 371 | |
| 372 | llvm::Expected<GsymDataExtractor> |
| 373 | GsymReader::getFunctionInfoDataForAddress(uint64_t Addr, |
| 374 | uint64_t &FuncStartAddr) const { |
| 375 | Expected<uint64_t> ExpectedAddrIdx = getAddressIndex(Addr); |
| 376 | if (!ExpectedAddrIdx) |
| 377 | return ExpectedAddrIdx.takeError(); |
| 378 | const uint64_t FirstAddrIdx = *ExpectedAddrIdx; |
| 379 | // The AddrIdx is the first index of the function info entries that match |
| 380 | // \a Addr. We need to iterate over all function info objects that start with |
| 381 | // the same address until we find a range that contains \a Addr. |
| 382 | std::optional<uint64_t> FirstFuncStartAddr; |
| 383 | const size_t NumAddresses = getNumAddresses(); |
| 384 | for (uint64_t AddrIdx = FirstAddrIdx; AddrIdx < NumAddresses; ++AddrIdx) { |
| 385 | auto ExpextedData = getFunctionInfoDataAtIndex(AddrIdx, FuncStartAddr); |
| 386 | // If there was an error, return the error. |
| 387 | if (!ExpextedData) |
| 388 | return ExpextedData; |
| 389 | |
| 390 | // Remember the first function start address if it hasn't already been set. |
| 391 | // If it is already valid, check to see if it matches the first function |
| 392 | // start address and only continue if it matches. |
| 393 | if (FirstFuncStartAddr.has_value()) { |
| 394 | if (*FirstFuncStartAddr != FuncStartAddr) |
| 395 | break; // Done with consecutive function entries with same address. |
| 396 | } else { |
| 397 | FirstFuncStartAddr = FuncStartAddr; |
| 398 | } |
| 399 | // Make sure the current function address ranges contains \a Addr. |
| 400 | // Some symbols on Darwin don't have valid sizes, so if we run into a |
| 401 | // symbol with zero size, then we have found a match for our address. |
| 402 | |
| 403 | // The first thing the encoding of a FunctionInfo object is the function |
| 404 | // size. |
| 405 | uint64_t Offset = 0; |
| 406 | uint32_t FuncSize = ExpextedData->getU32(offset_ptr: &Offset); |
| 407 | if (FuncSize == 0 || |
| 408 | AddressRange(FuncStartAddr, FuncStartAddr + FuncSize).contains(Addr)) |
| 409 | return ExpextedData; |
| 410 | } |
| 411 | return createStringError(EC: std::errc::invalid_argument, |
| 412 | Fmt: "address 0x%" PRIx64 " is not in GSYM" , Vals: Addr); |
| 413 | } |
| 414 | |
| 415 | llvm::Expected<GsymDataExtractor> |
| 416 | GsymReader::getFunctionInfoDataAtIndex(uint64_t AddrIdx, |
| 417 | uint64_t &FuncStartAddr) const { |
| 418 | const std::optional<uint64_t> AddrInfoOffset = getAddressInfoOffset(Index: AddrIdx); |
| 419 | if (AddrInfoOffset == std::nullopt) |
| 420 | return createStringError(EC: std::errc::invalid_argument, |
| 421 | Fmt: "invalid address index %" PRIu64, Vals: AddrIdx); |
| 422 | assert((Endian == endianness::big || Endian == endianness::little) && |
| 423 | "Endian must be either big or little" ); |
| 424 | StringRef Bytes = MemBuffer->getBuffer().substr(Start: *AddrInfoOffset); |
| 425 | if (Bytes.empty()) |
| 426 | return createStringError(EC: std::errc::invalid_argument, |
| 427 | Fmt: "invalid address info offset 0x%" PRIx64, |
| 428 | Vals: *AddrInfoOffset); |
| 429 | std::optional<uint64_t> OptFuncStartAddr = getAddress(Index: AddrIdx); |
| 430 | if (!OptFuncStartAddr) |
| 431 | return createStringError(EC: std::errc::invalid_argument, |
| 432 | Fmt: "failed to extract address[%" PRIu64 "]" , Vals: AddrIdx); |
| 433 | FuncStartAddr = *OptFuncStartAddr; |
| 434 | GsymDataExtractor Data(Bytes, isLittleEndian(), getStringOffsetSize()); |
| 435 | return Data; |
| 436 | } |
| 437 | |
| 438 | llvm::Expected<FunctionInfo> GsymReader::getFunctionInfo(uint64_t Addr) const { |
| 439 | uint64_t FuncStartAddr = 0; |
| 440 | if (auto ExpectedData = getFunctionInfoDataForAddress(Addr, FuncStartAddr)) |
| 441 | return FunctionInfo::decode(Data&: *ExpectedData, BaseAddr: FuncStartAddr); |
| 442 | else |
| 443 | return ExpectedData.takeError(); |
| 444 | } |
| 445 | |
| 446 | llvm::Expected<FunctionInfo> |
| 447 | GsymReader::getFunctionInfoAtIndex(uint64_t Idx) const { |
| 448 | uint64_t FuncStartAddr = 0; |
| 449 | if (auto ExpectedData = getFunctionInfoDataAtIndex(AddrIdx: Idx, FuncStartAddr)) |
| 450 | return FunctionInfo::decode(Data&: *ExpectedData, BaseAddr: FuncStartAddr); |
| 451 | else |
| 452 | return ExpectedData.takeError(); |
| 453 | } |
| 454 | |
| 455 | llvm::Expected<LookupResult> GsymReader::( |
| 456 | uint64_t Addr, |
| 457 | std::optional<GsymDataExtractor> *MergedFunctionsData) const { |
| 458 | uint64_t FuncStartAddr = 0; |
| 459 | if (auto ExpectedData = getFunctionInfoDataForAddress(Addr, FuncStartAddr)) |
| 460 | return FunctionInfo::lookup(Data&: *ExpectedData, GR: *this, FuncAddr: FuncStartAddr, Addr, |
| 461 | MergedFuncsData: MergedFunctionsData); |
| 462 | else |
| 463 | return ExpectedData.takeError(); |
| 464 | } |
| 465 | |
| 466 | llvm::Expected<std::vector<LookupResult>> |
| 467 | GsymReader::lookupAll(uint64_t Addr) const { |
| 468 | std::vector<LookupResult> Results; |
| 469 | std::optional<GsymDataExtractor> MergedFunctionsData; |
| 470 | |
| 471 | // First perform a lookup to get the primary function info result. |
| 472 | auto MainResult = lookup(Addr, MergedFunctionsData: &MergedFunctionsData); |
| 473 | if (!MainResult) |
| 474 | return MainResult.takeError(); |
| 475 | |
| 476 | // Add the main result as the first entry. |
| 477 | Results.push_back(x: std::move(*MainResult)); |
| 478 | |
| 479 | // Now process any merged functions data that was found during the lookup. |
| 480 | if (MergedFunctionsData) { |
| 481 | // Get data extractors for each merged function. |
| 482 | auto = |
| 483 | MergedFunctionsInfo::getFuncsDataExtractors(Data&: *MergedFunctionsData); |
| 484 | if (!ExpectedMergedFuncExtractors) |
| 485 | return ExpectedMergedFuncExtractors.takeError(); |
| 486 | |
| 487 | // Process each merged function data. |
| 488 | for (GsymDataExtractor &MergedData : *ExpectedMergedFuncExtractors) { |
| 489 | if (auto FI = FunctionInfo::lookup(Data&: MergedData, GR: *this, |
| 490 | FuncAddr: MainResult->FuncRange.start(), Addr)) { |
| 491 | Results.push_back(x: std::move(*FI)); |
| 492 | } else { |
| 493 | return FI.takeError(); |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | return Results; |
| 499 | } |
| 500 | |
| 501 | /// Format raw UUID bytes as a hex string, using the canonical 8-4-4-4-12 |
| 502 | /// dashed layout for the common 16-byte UUID and plain hex otherwise. |
| 503 | static std::string formatGsymUUID(StringRef Bytes) { |
| 504 | std::string Hex = toHex(Input: Bytes, /*LowerCase=*/false); |
| 505 | if (Bytes.size() == 16) { |
| 506 | Hex.insert(pos: 20, s: "-" ); |
| 507 | Hex.insert(pos: 16, s: "-" ); |
| 508 | Hex.insert(pos: 12, s: "-" ); |
| 509 | Hex.insert(pos: 8, s: "-" ); |
| 510 | } |
| 511 | return Hex; |
| 512 | } |
| 513 | |
| 514 | void GsymReader::dumpStatistics(raw_ostream &OS, StatisticsFormat Format, |
| 515 | StringRef GSYMPath) { |
| 516 | // The total file size is the size of the in-memory buffer this reader was |
| 517 | // created from, so no filesystem access is required and in-memory GSYM data |
| 518 | // can be analyzed too. |
| 519 | const uint64_t FileSize = MemBuffer->getBufferSize(); |
| 520 | |
| 521 | // Section sizes come from the GlobalData directory, which is populated for |
| 522 | // both GSYM v1 and v2 readers, so the same logic works for both versions. |
| 523 | auto SectionSize = [&](GlobalInfoType Type) -> uint64_t { |
| 524 | if (std::optional<GlobalData> GD = getGlobalData(Type)) |
| 525 | return GD->FileSize; |
| 526 | return 0; |
| 527 | }; |
| 528 | const uint64_t AddrTableSize = SectionSize(GlobalInfoType::AddrOffsets); |
| 529 | const uint64_t AddrInfoOffsetsSize = |
| 530 | SectionSize(GlobalInfoType::AddrInfoOffsets); |
| 531 | const uint64_t FileTableSize = SectionSize(GlobalInfoType::FileTable); |
| 532 | const uint64_t StrtabSize = SectionSize(GlobalInfoType::StringTable); |
| 533 | const uint64_t FuncInfoSize = SectionSize(GlobalInfoType::FunctionInfo); |
| 534 | // The V2 GlobalData directory is an on-disk array of 20-byte entries (Type |
| 535 | // u32 |
| 536 | // + FileOffset u64 + FileSize u64) terminated by an EndOfList entry. V1 |
| 537 | // synthesizes its GlobalData entries and has no on-disk directory. |
| 538 | const uint64_t GlobalDataDirSize = |
| 539 | getVersion() >= 2 ? (GlobalDataSections.size() + 1) * 20 : 0; |
| 540 | // In V2 the UUID is its own data section; report its payload separately. In |
| 541 | // V1 the UUID lives inline in the fixed header, so it is already counted |
| 542 | // there. |
| 543 | const uint64_t UUIDSize = |
| 544 | getVersion() >= 2 ? SectionSize(GlobalInfoType::UUID) : 0; |
| 545 | // The fixed file header precedes the GlobalData directory (V2) and the data |
| 546 | // sections. Its V2 size is a constant; in V1 (no on-disk directory) the |
| 547 | // header ends where the earliest data section begins. |
| 548 | uint64_t = HeaderV2::getEncodedSize(); |
| 549 | if (getVersion() < 2) { |
| 550 | uint64_t MinSectionOffset = FileSize; |
| 551 | for (const auto &KV : GlobalDataSections) |
| 552 | MinSectionOffset = std::min(a: MinSectionOffset, b: KV.second.FileOffset); |
| 553 | HeaderSize = MinSectionOffset; |
| 554 | } |
| 555 | // Anything left over (alignment padding between sections) is reported as |
| 556 | // padding so that the byte-sizes sum exactly to the file size. |
| 557 | const uint64_t KnownSize = HeaderSize + GlobalDataDirSize + UUIDSize + |
| 558 | AddrTableSize + AddrInfoOffsetsSize + |
| 559 | FileTableSize + StrtabSize + FuncInfoSize; |
| 560 | const uint64_t PaddingSize = FileSize > KnownSize ? FileSize - KnownSize : 0; |
| 561 | const uint64_t NumAddresses = getNumAddresses(); |
| 562 | |
| 563 | // Walk every FunctionInfo to accumulate the per-field byte sizes. |
| 564 | FunctionInfoStats FI; |
| 565 | FunctionInfoStats Merged; |
| 566 | for (uint64_t I = 0; I < NumAddresses; ++I) { |
| 567 | uint64_t FuncStartAddr = 0; |
| 568 | if (auto ExpData = getFunctionInfoDataAtIndex(AddrIdx: I, FuncStartAddr)) { |
| 569 | GsymDataExtractor Data = std::move(*ExpData); |
| 570 | FunctionInfo::parseStatistics(Data, Stats&: FI, MergedFuncInfoStats: &Merged); |
| 571 | } else { |
| 572 | consumeError(Err: ExpData.takeError()); |
| 573 | } |
| 574 | } |
| 575 | // Alignment padding between top-level FunctionInfos (each is 4-byte aligned) |
| 576 | // is not attributed to any per-function field; report it as the remainder so |
| 577 | // that the sum of the type sizes equals function_info_data. |
| 578 | const uint64_t FIAttributed = FI.SizeAndName + FI.LineTableInfo + |
| 579 | FI.InlineInfo + FI.CallSiteInfo + |
| 580 | FI.MergedFuncInfo + FI.EndOfList; |
| 581 | const uint64_t Padding = |
| 582 | FuncInfoSize > FIAttributed ? FuncInfoSize - FIAttributed : 0; |
| 583 | |
| 584 | const std::string UUIDStr = formatGsymUUID(Bytes: getUUID()); |
| 585 | |
| 586 | if (Format == StatisticsFormat::JSON || |
| 587 | Format == StatisticsFormat::PrettyJSON) { |
| 588 | json::Object MergedTypes{ |
| 589 | {.K: "infotype_infolength_count_and_fnsize" , |
| 590 | .V: static_cast<int64_t>(Merged.InfoTypeInfoLengthCountAndFnSize)}, |
| 591 | {.K: "size_and_name" , .V: static_cast<int64_t>(Merged.SizeAndName)}, |
| 592 | {.K: "line_table_info" , .V: static_cast<int64_t>(Merged.LineTableInfo)}, |
| 593 | {.K: "inline_info" , .V: static_cast<int64_t>(Merged.InlineInfo)}, |
| 594 | {.K: "call_site_info" , .V: static_cast<int64_t>(Merged.CallSiteInfo)}, |
| 595 | {.K: "merged_func_info" , .V: static_cast<int64_t>(Merged.MergedFuncInfo)}, |
| 596 | {.K: "end_of_list" , .V: static_cast<int64_t>(Merged.EndOfList)}}; |
| 597 | |
| 598 | json::Object FuncTypes{ |
| 599 | {.K: "size_and_name" , .V: static_cast<int64_t>(FI.SizeAndName)}, |
| 600 | {.K: "line_table_info" , .V: static_cast<int64_t>(FI.LineTableInfo)}, |
| 601 | {.K: "inline_info" , .V: static_cast<int64_t>(FI.InlineInfo)}, |
| 602 | {.K: "call_site_info" , .V: static_cast<int64_t>(FI.CallSiteInfo)}, |
| 603 | {.K: "merged_func_info" , .V: static_cast<int64_t>(FI.MergedFuncInfo)}, |
| 604 | {.K: "end_of_list" , .V: static_cast<int64_t>(FI.EndOfList)}, |
| 605 | {.K: "padding" , .V: static_cast<int64_t>(Padding)}, |
| 606 | {.K: "merged_func_info_type_sizes" , .V: std::move(MergedTypes)}}; |
| 607 | |
| 608 | json::Object ByteSizes{ |
| 609 | {.K: "file_size" , .V: static_cast<int64_t>(FileSize)}, |
| 610 | {.K: "header" , .V: static_cast<int64_t>(HeaderSize)}, |
| 611 | {.K: "global_data_directory" , .V: static_cast<int64_t>(GlobalDataDirSize)}, |
| 612 | {.K: "uuid_section" , .V: static_cast<int64_t>(UUIDSize)}, |
| 613 | {.K: "padding" , .V: static_cast<int64_t>(PaddingSize)}, |
| 614 | {.K: "address_table" , .V: static_cast<int64_t>(AddrTableSize)}, |
| 615 | {.K: "addr_info_offsets" , .V: static_cast<int64_t>(AddrInfoOffsetsSize)}, |
| 616 | {.K: "file_table" , .V: static_cast<int64_t>(FileTableSize)}, |
| 617 | {.K: "string_table" , .V: static_cast<int64_t>(StrtabSize)}, |
| 618 | {.K: "function_info_data" , .V: static_cast<int64_t>(FuncInfoSize)}, |
| 619 | {.K: "function_info_type_sizes" , .V: std::move(FuncTypes)}}; |
| 620 | |
| 621 | json::Object Root{{.K: "path" , .V: GSYMPath.str()}, |
| 622 | {.K: "uuid" , .V: UUIDStr}, |
| 623 | {.K: "num_addresses" , .V: static_cast<int64_t>(NumAddresses)}, |
| 624 | {.K: "byte-sizes" , .V: std::move(ByteSizes)}}; |
| 625 | |
| 626 | json::Value V(std::move(Root)); |
| 627 | if (Format == StatisticsFormat::PrettyJSON) |
| 628 | OS << formatv(Fmt: "{0:2}" , Vals&: V) << "\n" ; |
| 629 | else |
| 630 | OS << V << "\n" ; |
| 631 | return; |
| 632 | } |
| 633 | |
| 634 | // Text format output. |
| 635 | auto Fmt = [](uint64_t Value) { |
| 636 | std::string Num = std::to_string(val: Value); |
| 637 | int InsertPosition = Num.length() - 3; |
| 638 | while (InsertPosition > 0) { |
| 639 | Num.insert(pos: InsertPosition, s: "," ); |
| 640 | InsertPosition -= 3; |
| 641 | } |
| 642 | return std::string(std::max(a: (size_t)0, b: 14 - Num.length()), ' ') + Num; |
| 643 | }; |
| 644 | auto Pct = [&](uint64_t Value) -> std::string { |
| 645 | char Buf[16]; |
| 646 | snprintf(s: Buf, maxlen: sizeof(Buf), format: "(%5.2f%%)" , 100.0 * Value / FileSize); |
| 647 | return Buf; |
| 648 | }; |
| 649 | |
| 650 | OS << "GSYM statistics for \"" << GSYMPath << "\":\n" ; |
| 651 | OS << " UUID: " << UUIDStr << "\n" ; |
| 652 | OS << " Number of addresses: " << Fmt(NumAddresses) << "\n" ; |
| 653 | OS << " File size: " << Fmt(FileSize) << " bytes\n" ; |
| 654 | OS << " Header: " << Fmt(HeaderSize) << " bytes " |
| 655 | << Pct(HeaderSize) << "\n" ; |
| 656 | OS << " Global data dir: " << Fmt(GlobalDataDirSize) << " bytes " |
| 657 | << Pct(GlobalDataDirSize) << "\n" ; |
| 658 | OS << " UUID section: " << Fmt(UUIDSize) << " bytes " << Pct(UUIDSize) |
| 659 | << "\n" ; |
| 660 | OS << " Address table: " << Fmt(AddrTableSize) << " bytes " |
| 661 | << Pct(AddrTableSize) << "\n" ; |
| 662 | OS << " Addr info offsets: " << Fmt(AddrInfoOffsetsSize) << " bytes " |
| 663 | << Pct(AddrInfoOffsetsSize) << "\n" ; |
| 664 | OS << " File table: " << Fmt(FileTableSize) << " bytes " |
| 665 | << Pct(FileTableSize) << "\n" ; |
| 666 | OS << " String table: " << Fmt(StrtabSize) << " bytes " |
| 667 | << Pct(StrtabSize) << "\n" ; |
| 668 | OS << " Function info data: " << Fmt(FuncInfoSize) << " bytes " |
| 669 | << Pct(FuncInfoSize) << "\n" ; |
| 670 | OS << " Size and name: " << Fmt(FI.SizeAndName) << " bytes " |
| 671 | << Pct(FI.SizeAndName) << "\n" ; |
| 672 | OS << " Line table info: " << Fmt(FI.LineTableInfo) << " bytes " |
| 673 | << Pct(FI.LineTableInfo) << "\n" ; |
| 674 | OS << " Inline info: " << Fmt(FI.InlineInfo) << " bytes " |
| 675 | << Pct(FI.InlineInfo) << "\n" ; |
| 676 | OS << " Call site info: " << Fmt(FI.CallSiteInfo) << " bytes " |
| 677 | << Pct(FI.CallSiteInfo) << "\n" ; |
| 678 | OS << " End of list: " << Fmt(FI.EndOfList) << " bytes " |
| 679 | << Pct(FI.EndOfList) << "\n" ; |
| 680 | OS << " Padding: " << Fmt(Padding) << " bytes " << Pct(Padding) |
| 681 | << "\n" ; |
| 682 | OS << " Merged func info: " << Fmt(FI.MergedFuncInfo) << " bytes " |
| 683 | << Pct(FI.MergedFuncInfo) << "\n" ; |
| 684 | OS << " InfoType/InfoLength/Count/FnSize: " |
| 685 | << Fmt(Merged.InfoTypeInfoLengthCountAndFnSize) << " bytes " |
| 686 | << Pct(Merged.InfoTypeInfoLengthCountAndFnSize) << "\n" ; |
| 687 | OS << " Size and name: " << Fmt(Merged.SizeAndName) << " bytes " |
| 688 | << Pct(Merged.SizeAndName) << "\n" ; |
| 689 | OS << " Line table info: " << Fmt(Merged.LineTableInfo) << " bytes " |
| 690 | << Pct(Merged.LineTableInfo) << "\n" ; |
| 691 | OS << " Inline info: " << Fmt(Merged.InlineInfo) << " bytes " |
| 692 | << Pct(Merged.InlineInfo) << "\n" ; |
| 693 | OS << " Call site info: " << Fmt(Merged.CallSiteInfo) << " bytes " |
| 694 | << Pct(Merged.CallSiteInfo) << "\n" ; |
| 695 | OS << " Merged func info:" << Fmt(Merged.MergedFuncInfo) << " bytes " |
| 696 | << Pct(Merged.MergedFuncInfo) << "\n" ; |
| 697 | OS << " End of list: " << Fmt(Merged.EndOfList) << " bytes " |
| 698 | << Pct(Merged.EndOfList) << "\n" ; |
| 699 | OS << " Padding: " << Fmt(PaddingSize) << " bytes " |
| 700 | << Pct(PaddingSize) << "\n" ; |
| 701 | } |
| 702 | |
| 703 | void GsymReader::dump(raw_ostream &OS, const FunctionInfo &FI, |
| 704 | uint32_t Indent) { |
| 705 | OS.indent(NumSpaces: Indent); |
| 706 | OS << FI.Range << " \"" << getString(Offset: FI.Name) << "\"\n" ; |
| 707 | if (FI.OptLineTable) |
| 708 | dump(OS, LT: *FI.OptLineTable, Indent); |
| 709 | if (FI.Inline) |
| 710 | dump(OS, II: *FI.Inline, Indent); |
| 711 | |
| 712 | if (FI.CallSites) |
| 713 | dump(OS, CSIC: *FI.CallSites, Indent); |
| 714 | |
| 715 | if (FI.MergedFunctions) { |
| 716 | assert(Indent == 0 && "MergedFunctionsInfo should only exist at top level" ); |
| 717 | dump(OS, MFI: *FI.MergedFunctions); |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | void GsymReader::dump(raw_ostream &OS, const MergedFunctionsInfo &MFI) { |
| 722 | for (uint32_t inx = 0; inx < MFI.MergedFunctions.size(); inx++) { |
| 723 | OS << "++ Merged FunctionInfos[" << inx << "]:\n" ; |
| 724 | dump(OS, FI: MFI.MergedFunctions[inx], Indent: 4); |
| 725 | } |
| 726 | } |
| 727 | |
| 728 | void GsymReader::dump(raw_ostream &OS, const CallSiteInfo &CSI) { |
| 729 | OS << HEX16(CSI.ReturnOffset); |
| 730 | |
| 731 | std::string Flags; |
| 732 | auto addFlag = [&](const char *Flag) { |
| 733 | if (!Flags.empty()) |
| 734 | Flags += " | " ; |
| 735 | Flags += Flag; |
| 736 | }; |
| 737 | |
| 738 | if (CSI.Flags == CallSiteInfo::Flags::None) |
| 739 | Flags = "None" ; |
| 740 | else { |
| 741 | if (CSI.Flags & CallSiteInfo::Flags::InternalCall) |
| 742 | addFlag("InternalCall" ); |
| 743 | |
| 744 | if (CSI.Flags & CallSiteInfo::Flags::ExternalCall) |
| 745 | addFlag("ExternalCall" ); |
| 746 | } |
| 747 | OS << " Flags[" << Flags << "]" ; |
| 748 | |
| 749 | if (!CSI.MatchRegex.empty()) { |
| 750 | OS << " MatchRegex[" ; |
| 751 | for (uint32_t i = 0; i < CSI.MatchRegex.size(); ++i) { |
| 752 | if (i > 0) |
| 753 | OS << ";" ; |
| 754 | OS << getString(Offset: CSI.MatchRegex[i]); |
| 755 | } |
| 756 | OS << "]" ; |
| 757 | } |
| 758 | } |
| 759 | |
| 760 | void GsymReader::dump(raw_ostream &OS, const CallSiteInfoCollection &CSIC, |
| 761 | uint32_t Indent) { |
| 762 | OS.indent(NumSpaces: Indent); |
| 763 | OS << "CallSites (by relative return offset):\n" ; |
| 764 | for (const auto &CS : CSIC.CallSites) { |
| 765 | OS.indent(NumSpaces: Indent); |
| 766 | OS << " " ; |
| 767 | dump(OS, CSI: CS); |
| 768 | OS << "\n" ; |
| 769 | } |
| 770 | } |
| 771 | |
| 772 | void GsymReader::dump(raw_ostream &OS, const LineTable <, uint32_t Indent) { |
| 773 | OS.indent(NumSpaces: Indent); |
| 774 | OS << "LineTable:\n" ; |
| 775 | for (auto &LE : LT) { |
| 776 | OS.indent(NumSpaces: Indent); |
| 777 | OS << " " << HEX64(LE.Addr) << ' '; |
| 778 | if (LE.File) |
| 779 | dump(OS, FE: getFile(Index: LE.File)); |
| 780 | OS << ':' << LE.Line << '\n'; |
| 781 | } |
| 782 | } |
| 783 | |
| 784 | void GsymReader::dump(raw_ostream &OS, const InlineInfo &II, uint32_t Indent) { |
| 785 | if (Indent == 0) |
| 786 | OS << "InlineInfo:\n" ; |
| 787 | else |
| 788 | OS.indent(NumSpaces: Indent); |
| 789 | OS << II.Ranges << ' ' << getString(Offset: II.Name); |
| 790 | if (II.CallFile != 0) { |
| 791 | if (auto File = getFile(Index: II.CallFile)) { |
| 792 | OS << " called from " ; |
| 793 | dump(OS, FE: File); |
| 794 | OS << ':' << II.CallLine; |
| 795 | } |
| 796 | } |
| 797 | OS << '\n'; |
| 798 | for (const auto &ChildII : II.Children) |
| 799 | dump(OS, II: ChildII, Indent: Indent + 2); |
| 800 | } |
| 801 | |
| 802 | void GsymReader::dump(raw_ostream &OS, std::optional<FileEntry> FE) { |
| 803 | if (FE) { |
| 804 | // IF we have the file from index 0, then don't print anything |
| 805 | if (FE->Dir == 0 && FE->Base == 0) |
| 806 | return; |
| 807 | StringRef Dir = getString(Offset: FE->Dir); |
| 808 | StringRef Base = getString(Offset: FE->Base); |
| 809 | if (!Dir.empty()) { |
| 810 | OS << Dir; |
| 811 | if (Dir.contains(C: '\\') && !Dir.contains(C: '/')) |
| 812 | OS << '\\'; |
| 813 | else |
| 814 | OS << '/'; |
| 815 | } |
| 816 | if (!Base.empty()) { |
| 817 | OS << Base; |
| 818 | } |
| 819 | if (!Dir.empty() || !Base.empty()) |
| 820 | return; |
| 821 | } |
| 822 | OS << "<invalid-file>" ; |
| 823 | } |
| 824 | |