CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
1136 lines
41 KiB
C++
1136 lines
41 KiB
C++
//===- llvm/unittest/DebugInfo/GSYMV2Test.cpp -----------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/DebugInfo/GSYM/FileWriter.h"
|
|
#include "llvm/DebugInfo/GSYM/FunctionInfo.h"
|
|
#include "llvm/DebugInfo/GSYM/GlobalData.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymCreator.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymCreatorV1.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymCreatorV2.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymDataExtractor.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymReader.h"
|
|
#include "llvm/DebugInfo/GSYM/HeaderV2.h"
|
|
#include "llvm/DebugInfo/GSYM/InlineInfo.h"
|
|
#include "llvm/DebugInfo/GSYM/OutputAggregator.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include <string>
|
|
|
|
using namespace llvm;
|
|
using namespace gsym;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Creator V2 tests
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Helper functions
|
|
|
|
static void checkError(std::string ExpectedMsg, Error Err) {
|
|
ASSERT_TRUE(bool(Err));
|
|
handleAllErrors(std::move(Err), [&](const ErrorInfoBase &Actual) {
|
|
EXPECT_EQ(Actual.message(), ExpectedMsg);
|
|
});
|
|
}
|
|
|
|
/// Helper to encode a GsymCreatorV2 and return the raw bytes.
|
|
static Expected<SmallString<512>> encodeV2(const GsymCreatorV2 &GC,
|
|
llvm::endianness ByteOrder) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
FW.setStringOffsetSize(GC.getStringOffsetSize());
|
|
if (auto Err = GC.encode(FW))
|
|
return std::move(Err);
|
|
return Str;
|
|
}
|
|
|
|
/// Helper to decode the HeaderV2 from raw bytes.
|
|
static Expected<HeaderV2> decodeHeaderV2(StringRef Bytes,
|
|
llvm::endianness ByteOrder) {
|
|
GsymDataExtractor Data(Bytes, ByteOrder == llvm::endianness::little);
|
|
return HeaderV2::decode(Data);
|
|
}
|
|
|
|
/// Helper to decode a GlobalData entry at a given offset.
|
|
static GlobalData decodeGlobalDataEntry(StringRef Bytes, uint64_t &Offset,
|
|
llvm::endianness ByteOrder) {
|
|
GsymDataExtractor Data(Bytes, ByteOrder == llvm::endianness::little);
|
|
GlobalData GD;
|
|
GD.Type = static_cast<GlobalInfoType>(Data.getU32(&Offset));
|
|
GD.FileOffset = Data.getU64(&Offset);
|
|
GD.FileSize = Data.getU64(&Offset);
|
|
return GD;
|
|
}
|
|
|
|
/// Encode error tests
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2DoubleFinalize) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name = GC.insertString("foo");
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x100, Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(bool(Err));
|
|
Err = GC.finalize(Null);
|
|
ASSERT_TRUE(bool(Err));
|
|
checkError("already finalized", std::move(Err));
|
|
}
|
|
|
|
/// Header and GlobalData structure tests
|
|
|
|
/// Encode a V2 GSYM and verify the header fields and GlobalData layout.
|
|
static void TestV2HeaderAndGlobalData(llvm::endianness ByteOrder,
|
|
uint64_t BaseAddr,
|
|
uint8_t ExpectedAddrOffSize,
|
|
uint32_t ExpectedNumAddresses,
|
|
bool HasUUID) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr + 0x00, 0x10, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr + 0x20, 0x10, Func2Name));
|
|
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
if (HasUUID)
|
|
GC.setUUID(UUID);
|
|
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(bool(Err));
|
|
|
|
auto Result = encodeV2(GC, ByteOrder);
|
|
ASSERT_THAT_EXPECTED(Result, Succeeded());
|
|
StringRef Data = *Result;
|
|
|
|
// Verify header.
|
|
auto HdrOrErr = decodeHeaderV2(Data, ByteOrder);
|
|
ASSERT_THAT_EXPECTED(HdrOrErr, Succeeded());
|
|
const HeaderV2 &Hdr = *HdrOrErr;
|
|
EXPECT_EQ(Hdr.Magic, GSYM_MAGIC);
|
|
EXPECT_EQ(Hdr.Version, HeaderV2::getVersion());
|
|
EXPECT_EQ(Hdr.BaseAddress, BaseAddr);
|
|
EXPECT_EQ(Hdr.NumAddresses, ExpectedNumAddresses);
|
|
EXPECT_EQ(Hdr.AddrOffSize, ExpectedAddrOffSize);
|
|
EXPECT_EQ(Hdr.StrTableEncoding, StringTableEncoding::Default);
|
|
|
|
// Decode GlobalData entries starting at offset 24 (after fixed header).
|
|
uint64_t Offset = HeaderV2::getEncodedSize();
|
|
bool FoundAddrOffsets = false;
|
|
bool FoundAddrInfoOffsets = false;
|
|
bool FoundStringTable = false;
|
|
bool FoundFileTable = false;
|
|
bool FoundFunctionInfo = false;
|
|
bool FoundUUID = false;
|
|
bool FoundEndOfList = false;
|
|
|
|
while (Offset < Data.size()) {
|
|
GlobalData GD = decodeGlobalDataEntry(Data, Offset, ByteOrder);
|
|
|
|
switch (GD.Type) {
|
|
case GlobalInfoType::EndOfList:
|
|
EXPECT_EQ(GD.FileOffset, 0u);
|
|
EXPECT_EQ(GD.FileSize, 0u);
|
|
FoundEndOfList = true;
|
|
break;
|
|
case GlobalInfoType::AddrOffsets:
|
|
EXPECT_EQ(GD.FileSize,
|
|
ExpectedNumAddresses * (uint64_t)ExpectedAddrOffSize);
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundAddrOffsets = true;
|
|
break;
|
|
case GlobalInfoType::AddrInfoOffsets:
|
|
EXPECT_EQ(GD.FileSize,
|
|
ExpectedNumAddresses *
|
|
(uint64_t)HeaderV2::getAddressInfoOffsetSize());
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundAddrInfoOffsets = true;
|
|
break;
|
|
case GlobalInfoType::StringTable:
|
|
EXPECT_GT(GD.FileSize, 0u);
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundStringTable = true;
|
|
break;
|
|
case GlobalInfoType::FileTable:
|
|
EXPECT_GT(GD.FileSize, 0u);
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundFileTable = true;
|
|
break;
|
|
case GlobalInfoType::FunctionInfo:
|
|
EXPECT_GT(GD.FileSize, 0u);
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundFunctionInfo = true;
|
|
break;
|
|
case GlobalInfoType::UUID:
|
|
EXPECT_EQ(GD.FileSize, sizeof(UUID));
|
|
EXPECT_GT(GD.FileOffset, 0u);
|
|
FoundUUID = true;
|
|
break;
|
|
}
|
|
if (FoundEndOfList)
|
|
break;
|
|
}
|
|
|
|
EXPECT_TRUE(FoundAddrOffsets);
|
|
EXPECT_TRUE(FoundAddrInfoOffsets);
|
|
EXPECT_TRUE(FoundStringTable);
|
|
EXPECT_TRUE(FoundFileTable);
|
|
EXPECT_TRUE(FoundFunctionInfo);
|
|
EXPECT_TRUE(FoundEndOfList);
|
|
EXPECT_EQ(FoundUUID, HasUUID);
|
|
|
|
// Verify that all section data fits within the encoded buffer.
|
|
Offset = HeaderV2::getEncodedSize();
|
|
while (Offset < Data.size()) {
|
|
GlobalData GD = decodeGlobalDataEntry(Data, Offset, ByteOrder);
|
|
if (GD.Type == GlobalInfoType::EndOfList)
|
|
break;
|
|
EXPECT_LE(GD.FileOffset + GD.FileSize, Data.size())
|
|
<< "Section type " << static_cast<uint32_t>(GD.Type)
|
|
<< " extends beyond buffer";
|
|
}
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2HeaderAndGlobalDataLittle) {
|
|
TestV2HeaderAndGlobalData(llvm::endianness::little, 0x1000,
|
|
/*ExpectedAddrOffSize=*/1,
|
|
/*ExpectedNumAddresses=*/2,
|
|
/*HasUUID=*/true);
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2HeaderAndGlobalDataBig) {
|
|
TestV2HeaderAndGlobalData(llvm::endianness::big, 0x1000,
|
|
/*ExpectedAddrOffSize=*/1,
|
|
/*ExpectedNumAddresses=*/2,
|
|
/*HasUUID=*/true);
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2HeaderAndGlobalDataNoUUID) {
|
|
TestV2HeaderAndGlobalData(llvm::endianness::little, 0x1000,
|
|
/*ExpectedAddrOffSize=*/1,
|
|
/*ExpectedNumAddresses=*/2,
|
|
/*HasUUID=*/false);
|
|
}
|
|
|
|
/// AddrInfoOffsets verification
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2AddrInfoOffsetsPointToFunctionInfo) {
|
|
// Verify that each AddrInfoOffset entry (relative to FunctionInfo section)
|
|
// points to a valid location within the FunctionInfo section.
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Func1Name = GC.insertString("func_a");
|
|
const uint32_t Func2Name = GC.insertString("func_b");
|
|
const uint32_t Func3Name = GC.insertString("func_c");
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x100, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(0x1100, 0x100, Func2Name));
|
|
GC.addFunctionInfo(FunctionInfo(0x1200, 0x100, Func3Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(bool(Err));
|
|
|
|
auto Result = encodeV2(GC, llvm::endianness::little);
|
|
ASSERT_THAT_EXPECTED(Result, Succeeded());
|
|
StringRef Bytes = *Result;
|
|
|
|
constexpr uint8_t AddrInfoOffSize = HeaderV2::getAddressInfoOffsetSize();
|
|
|
|
// Find the AddrInfoOffsets and FunctionInfo sections from GlobalData.
|
|
uint64_t Offset = HeaderV2::getEncodedSize();
|
|
uint64_t AIOffsetsOffset = 0;
|
|
uint64_t FISize = 0;
|
|
while (Offset < Bytes.size()) {
|
|
GlobalData GD =
|
|
decodeGlobalDataEntry(Bytes, Offset, llvm::endianness::little);
|
|
if (GD.Type == GlobalInfoType::AddrInfoOffsets) {
|
|
AIOffsetsOffset = GD.FileOffset;
|
|
} else if (GD.Type == GlobalInfoType::FunctionInfo) {
|
|
FISize = GD.FileSize;
|
|
} else if (GD.Type == GlobalInfoType::EndOfList) {
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_GT(AIOffsetsOffset, 0u);
|
|
ASSERT_GT(FISize, 0u);
|
|
|
|
// Each AddrInfoOffset is relative to the FunctionInfo section and should
|
|
// be within [0, FISize).
|
|
GsymDataExtractor Data(Bytes, /*IsLittleEndian=*/true);
|
|
uint64_t AIOffset = AIOffsetsOffset;
|
|
for (uint32_t I = 0; I < 3; ++I) {
|
|
uint64_t RelOff = Data.getUnsigned(&AIOffset, AddrInfoOffSize);
|
|
EXPECT_LT(RelOff, FISize)
|
|
<< "AddrInfoOffset[" << I << "] beyond FunctionInfo section";
|
|
}
|
|
|
|
// Relative offsets should be strictly increasing (sorted functions).
|
|
AIOffset = AIOffsetsOffset;
|
|
uint64_t PrevOff = Data.getUnsigned(&AIOffset, AddrInfoOffSize);
|
|
EXPECT_EQ(PrevOff, 0u) << "First AddrInfoOffset should be 0";
|
|
for (uint32_t I = 1; I < 3; ++I) {
|
|
uint64_t CurOff = Data.getUnsigned(&AIOffset, AddrInfoOffSize);
|
|
EXPECT_GT(CurOff, PrevOff)
|
|
<< "AddrInfoOffset[" << I << "] not strictly increasing";
|
|
PrevOff = CurOff;
|
|
}
|
|
}
|
|
|
|
/// UUID section verification
|
|
|
|
TEST(GSYMV2Test, TestCreatorV2UUIDSection) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name = GC.insertString("main");
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x100, Name));
|
|
uint8_t UUID[] = {0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44};
|
|
GC.setUUID(UUID);
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(bool(Err));
|
|
|
|
auto Result = encodeV2(GC, llvm::endianness::little);
|
|
ASSERT_THAT_EXPECTED(Result, Succeeded());
|
|
StringRef Data = *Result;
|
|
|
|
// Find UUID section.
|
|
uint64_t Offset = HeaderV2::getEncodedSize();
|
|
uint64_t UUIDOffset = 0, UUIDSize = 0;
|
|
while (Offset < Data.size()) {
|
|
GlobalData GD =
|
|
decodeGlobalDataEntry(Data, Offset, llvm::endianness::little);
|
|
if (GD.Type == GlobalInfoType::UUID) {
|
|
UUIDOffset = GD.FileOffset;
|
|
UUIDSize = GD.FileSize;
|
|
} else if (GD.Type == GlobalInfoType::EndOfList) {
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_EQ(UUIDSize, sizeof(UUID));
|
|
ASSERT_GT(UUIDOffset, 0u);
|
|
|
|
// Verify the UUID bytes match.
|
|
StringRef UUIDData = Data.substr(UUIDOffset, UUIDSize);
|
|
EXPECT_EQ(UUIDData,
|
|
StringRef(reinterpret_cast<const char *>(UUID), sizeof(UUID)));
|
|
}
|
|
|
|
/// Verify that all sections in a V2 GSYM are correctly aligned.
|
|
/// Uses a 13-byte UUID and 8-byte AddrOffSize to create non-trivial
|
|
/// alignment scenarios where padding is required between sections.
|
|
TEST(GSYMV2Test, TestCreatorV2SectionAlignment) {
|
|
// 13-byte UUID: after header (24) + GlobalData (7 entries * 20 = 140),
|
|
// UUID ends at offset 177. AddrOffsets needs 8-byte alignment → 184.
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
|
|
GsymCreatorV2 GC;
|
|
GC.setUUID(UUID);
|
|
// Addresses far apart to force 8-byte AddrOffSize.
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x100, GC.insertString("foo")));
|
|
GC.addFunctionInfo(FunctionInfo(0x100001000, 0x100, GC.insertString("bar")));
|
|
GC.addFunctionInfo(FunctionInfo(0x200002000, 0x100, GC.insertString("baz")));
|
|
OutputAggregator Null(nullptr);
|
|
ASSERT_FALSE(GC.finalize(Null));
|
|
|
|
auto Result = encodeV2(GC, llvm::endianness::little);
|
|
ASSERT_THAT_EXPECTED(Result, Succeeded());
|
|
StringRef Data = *Result;
|
|
|
|
// Decode header to get alignment sizes.
|
|
auto HdrOrErr = decodeHeaderV2(Data, llvm::endianness::little);
|
|
ASSERT_THAT_EXPECTED(HdrOrErr, Succeeded());
|
|
const HeaderV2 &Hdr = *HdrOrErr;
|
|
// Delta > UINT32_MAX rounds up to 8 (power-of-two only).
|
|
EXPECT_EQ(Hdr.AddrOffSize, 8u);
|
|
|
|
// Decode GlobalData entries and verify alignment for each section.
|
|
uint64_t Offset = HeaderV2::getEncodedSize();
|
|
while (Offset < Data.size()) {
|
|
GlobalData GD =
|
|
decodeGlobalDataEntry(Data, Offset, llvm::endianness::little);
|
|
if (GD.Type == GlobalInfoType::EndOfList)
|
|
break;
|
|
switch (GD.Type) {
|
|
case GlobalInfoType::UUID:
|
|
// UUID has no alignment requirement.
|
|
break;
|
|
case GlobalInfoType::AddrOffsets:
|
|
EXPECT_EQ(GD.FileOffset % Hdr.AddrOffSize, 0u)
|
|
<< "AddrOffsets not aligned to " << (unsigned)Hdr.AddrOffSize;
|
|
break;
|
|
case GlobalInfoType::AddrInfoOffsets:
|
|
EXPECT_EQ(GD.FileOffset % HeaderV2::getAddressInfoOffsetSize(), 0u)
|
|
<< "AddrInfoOffsets not aligned to "
|
|
<< (unsigned)HeaderV2::getAddressInfoOffsetSize();
|
|
break;
|
|
case GlobalInfoType::FileTable:
|
|
EXPECT_EQ(GD.FileOffset % 4, 0u) << "FileTable not 4-byte aligned";
|
|
break;
|
|
case GlobalInfoType::StringTable:
|
|
// StringTable has no alignment requirement.
|
|
break;
|
|
case GlobalInfoType::FunctionInfo:
|
|
EXPECT_EQ(GD.FileOffset % 4, 0u) << "FunctionInfo not 4-byte aligned";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Also verify the round-trip works.
|
|
auto GROrErr = GsymReader::copyBuffer(Data);
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
EXPECT_EQ((*GROrErr)->getNumAddresses(), 3u);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Reader V2 tests (without creator — hand-crafted binary)
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Helper to build a minimal valid V2 GSYM binary in native endianness.
|
|
/// Creates one function "main" at BaseAddr with size FuncSize.
|
|
static SmallString<512> buildMinimalV2Binary(uint64_t BaseAddr,
|
|
uint32_t FuncSize) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OS(Str);
|
|
FileWriter FW(OS, llvm::endianness::native);
|
|
|
|
// We'll build: header (20) + GlobalData entries (6 entries * 20 = 120) +
|
|
// sections. Total GlobalData entries: AddrOffsets, AddrInfoOffsets,
|
|
// StringTable, FileTable, FunctionInfo, EndOfList = 6.
|
|
constexpr uint64_t HeaderSize = HeaderV2::getEncodedSize();
|
|
constexpr uint64_t NumGlobalEntries = 6;
|
|
constexpr uint64_t GlobalDataSize = NumGlobalEntries * 20;
|
|
constexpr uint8_t AddrOffSize = 1;
|
|
constexpr uint8_t AddrInfoOffSize = HeaderV2::getAddressInfoOffsetSize();
|
|
constexpr uint8_t StrpSize = HeaderV2::getStringOffsetSize();
|
|
constexpr uint32_t NumAddresses = 1;
|
|
|
|
// Layout sections sequentially after header + GlobalData.
|
|
uint64_t CurOffset = HeaderSize + GlobalDataSize;
|
|
|
|
// AddrOffsets: 1 address * 1 byte.
|
|
const uint64_t AddrOffsetsOff = CurOffset;
|
|
const uint64_t AddrOffsetsSize = NumAddresses * AddrOffSize;
|
|
CurOffset += AddrOffsetsSize;
|
|
|
|
// Pad to alignment for AddrInfoOffsets.
|
|
CurOffset = llvm::alignTo(CurOffset, AddrInfoOffSize);
|
|
const uint64_t AddrInfoOffsetsOff = CurOffset;
|
|
const uint64_t AddrInfoOffsetsSize = NumAddresses * AddrInfoOffSize;
|
|
CurOffset += AddrInfoOffsetsSize;
|
|
|
|
// FileTable: 4 bytes (NumFiles=1) + 1 FileEntry (2 * StrpSize bytes).
|
|
CurOffset = llvm::alignTo(CurOffset, 4);
|
|
const uint64_t FileTableOff = CurOffset;
|
|
const uint64_t FileTableSize = 4 + 2 * StrpSize; // 1 file entry.
|
|
CurOffset += FileTableSize;
|
|
|
|
// StringTable: "\0main\0" = 6 bytes.
|
|
const uint64_t StringTableOff = CurOffset;
|
|
const char StrTabData[] = "\0main";
|
|
const uint64_t StringTableSize = sizeof(StrTabData); // includes trailing \0
|
|
CurOffset += StringTableSize;
|
|
|
|
// FunctionInfo: encode a minimal FunctionInfo.
|
|
CurOffset = llvm::alignTo(CurOffset, 4);
|
|
const uint64_t FuncInfoOff = CurOffset;
|
|
// FunctionInfo encoding: uint32_t Size, uint32_t Name (strp offset).
|
|
// "main" is at offset 1 in the string table.
|
|
// Minimal FI: size (4 bytes) + name (4 bytes) = 8 bytes, no line table or
|
|
// inline info (InfoType::EndOfList = 0 follows).
|
|
// Actually FunctionInfo::encode writes: size, name, then info types.
|
|
// Let's pre-encode a FunctionInfo to get exact bytes.
|
|
SmallString<64> FIBuf;
|
|
{
|
|
raw_svector_ostream FIOS(FIBuf);
|
|
FileWriter FIFW(FIOS, llvm::endianness::native);
|
|
FIFW.setStringOffsetSize(HeaderV2::getStringOffsetSize());
|
|
FunctionInfo FI(BaseAddr, FuncSize,
|
|
/*Name=*/1); // "main" at strtab offset 1
|
|
auto OffOrErr = FI.encode(FIFW);
|
|
assert(OffOrErr && "FunctionInfo encode failed");
|
|
(void)OffOrErr;
|
|
}
|
|
const uint64_t FuncInfoSize = FIBuf.size();
|
|
|
|
// Write header.
|
|
FW.writeU32(GSYM_MAGIC); // Magic
|
|
FW.writeU16(HeaderV2::getVersion()); // Version
|
|
FW.writeU8(AddrOffSize); // AddrOffSize
|
|
FW.writeU8(0); // StrTableEncoding
|
|
FW.writeU64(BaseAddr); // BaseAddress
|
|
FW.writeU32(NumAddresses); // NumAddresses
|
|
|
|
// GlobalData entries.
|
|
auto writeGD = [&](GlobalInfoType Type, uint64_t Off, uint64_t Size) {
|
|
FW.writeU32(static_cast<uint32_t>(Type));
|
|
FW.writeU64(Off);
|
|
FW.writeU64(Size);
|
|
};
|
|
writeGD(GlobalInfoType::AddrOffsets, AddrOffsetsOff, AddrOffsetsSize);
|
|
writeGD(GlobalInfoType::AddrInfoOffsets, AddrInfoOffsetsOff,
|
|
AddrInfoOffsetsSize);
|
|
writeGD(GlobalInfoType::StringTable, StringTableOff, StringTableSize);
|
|
writeGD(GlobalInfoType::FileTable, FileTableOff, FileTableSize);
|
|
writeGD(GlobalInfoType::FunctionInfo, FuncInfoOff, FuncInfoSize);
|
|
writeGD(GlobalInfoType::EndOfList, 0, 0);
|
|
|
|
// AddrOffsets section.
|
|
assert(FW.tell() == AddrOffsetsOff);
|
|
FW.writeU8(0); // Offset from BaseAddr = 0 for first function.
|
|
|
|
// Pad to AddrInfoOffsets. Values are relative to FunctionInfo section.
|
|
FW.alignTo(AddrInfoOffSize);
|
|
assert(FW.tell() == AddrInfoOffsetsOff);
|
|
FW.writeU64(0); // RelOff = 0 (first and only FunctionInfo).
|
|
|
|
// FileTable.
|
|
FW.alignTo(4);
|
|
assert(FW.tell() == FileTableOff);
|
|
FW.writeU32(1); // NumFiles = 1
|
|
FW.writeU64(0); // File[0].Dir = 0
|
|
FW.writeU64(0); // File[0].Base = 0
|
|
|
|
// StringTable.
|
|
assert(FW.tell() == StringTableOff);
|
|
FW.writeData(ArrayRef<uint8_t>(reinterpret_cast<const uint8_t *>(StrTabData),
|
|
StringTableSize));
|
|
|
|
// FunctionInfo.
|
|
FW.alignTo(4);
|
|
assert(FW.tell() == FuncInfoOff);
|
|
FW.writeData(ArrayRef<uint8_t>(
|
|
reinterpret_cast<const uint8_t *>(FIBuf.data()), FIBuf.size()));
|
|
|
|
return Str;
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2ParseHandCrafted) {
|
|
// Build a minimal V2 binary by hand and verify the reader can parse it.
|
|
auto Bytes = buildMinimalV2Binary(0x1000, 0x100);
|
|
auto GROrErr = GsymReader::copyBuffer(StringRef(Bytes.data(), Bytes.size()));
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
EXPECT_EQ(GR->getBaseAddress(), 0x1000u);
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
EXPECT_EQ(GR->getAddressOffsetSize(), 1u);
|
|
|
|
// Verify address lookup.
|
|
auto Addr = GR->getAddress(0);
|
|
ASSERT_TRUE(Addr.has_value());
|
|
EXPECT_EQ(*Addr, 0x1000u);
|
|
|
|
// Verify getString.
|
|
EXPECT_EQ(GR->getString(1), "main");
|
|
|
|
// Verify getFile (index 0 is the empty file entry).
|
|
auto FE = GR->getFile(0);
|
|
ASSERT_TRUE(FE.has_value());
|
|
EXPECT_EQ(FE->Dir, 0u);
|
|
EXPECT_EQ(FE->Base, 0u);
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2GetFunctionInfoHandCrafted) {
|
|
auto Bytes = buildMinimalV2Binary(0x1000, 0x100);
|
|
auto GROrErr = GsymReader::copyBuffer(StringRef(Bytes.data(), Bytes.size()));
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
// getFunctionInfo should decode the function at 0x1000.
|
|
auto FI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(FI, Succeeded());
|
|
EXPECT_EQ(FI->Range, AddressRange(0x1000, 0x1100));
|
|
EXPECT_EQ(FI->Name, 1u); // "main" at strtab offset 1
|
|
EXPECT_EQ(GR->getString(FI->Name), "main");
|
|
|
|
// Address within the function range should also work.
|
|
auto FI2 = GR->getFunctionInfo(0x1050);
|
|
ASSERT_THAT_EXPECTED(FI2, Succeeded());
|
|
EXPECT_EQ(FI2->Range, AddressRange(0x1000, 0x1100));
|
|
|
|
// Address outside range should fail.
|
|
auto FI3 = GR->getFunctionInfo(0x2000);
|
|
EXPECT_THAT_EXPECTED(FI3, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2LookupHandCrafted) {
|
|
auto Bytes = buildMinimalV2Binary(0x1000, 0x100);
|
|
auto GROrErr = GsymReader::copyBuffer(StringRef(Bytes.data(), Bytes.size()));
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
// lookup should return a LookupResult.
|
|
auto LR = GR->lookup(0x1000);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_EQ(LR->FuncName, "main");
|
|
EXPECT_EQ(LR->FuncRange, AddressRange(0x1000, 0x1100));
|
|
|
|
// lookup within range.
|
|
auto LR2 = GR->lookup(0x1080);
|
|
ASSERT_THAT_EXPECTED(LR2, Succeeded());
|
|
EXPECT_EQ(LR2->FuncName, "main");
|
|
|
|
// lookup outside range.
|
|
auto LR3 = GR->lookup(0x2000);
|
|
EXPECT_THAT_EXPECTED(LR3, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2InvalidMagic) {
|
|
// Create a buffer with invalid magic.
|
|
char Buf[24] = {};
|
|
Buf[0] = 'X'; // Bad magic.
|
|
auto GR = GsymReader::copyBuffer(StringRef(Buf, sizeof(Buf)));
|
|
EXPECT_THAT_EXPECTED(GR, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2TooSmall) {
|
|
// Buffer smaller than header.
|
|
char Buf[10] = {};
|
|
auto GR = GsymReader::copyBuffer(StringRef(Buf, sizeof(Buf)));
|
|
EXPECT_THAT_EXPECTED(GR, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestReaderV2TruncatedFileTable) {
|
|
// Build a valid V2 binary, then truncate the file table by reducing its
|
|
// GlobalData FileSize. The reader should report the file table is too small.
|
|
auto Bytes = buildMinimalV2Binary(0x1000, 0x100);
|
|
// The binary has 1 file entry. Find the FileTable GlobalData entry and
|
|
// shrink its FileSize to be too small.
|
|
// GlobalData entries start at offset 24 (after HeaderV2).
|
|
// Each entry is 20 bytes: Type(4) + FileOffset(8) + FileSize(8).
|
|
// We need to find the FileTable entry and modify its FileSize.
|
|
GsymDataExtractor Data(StringRef(Bytes.data(), Bytes.size()),
|
|
llvm::endianness::native == llvm::endianness::little);
|
|
uint64_t Offset = HeaderV2::getEncodedSize();
|
|
while (Offset < Bytes.size()) {
|
|
uint64_t EntryOffset = Offset;
|
|
uint32_t Type = Data.getU32(&Offset);
|
|
uint64_t FileOffset = Data.getU64(&Offset);
|
|
uint64_t FileSize = Data.getU64(&Offset);
|
|
(void)FileOffset;
|
|
(void)FileSize;
|
|
if (Type == static_cast<uint32_t>(GlobalInfoType::FileTable)) {
|
|
// Set FileSize to 4 (just the NumFiles field, no room for entries).
|
|
// FileSize is at EntryOffset + 4 (Type) + 8 (FileOffset) = +12.
|
|
uint64_t FileSizeOffset = EntryOffset + 12;
|
|
support::endian::write64(Bytes.data() + FileSizeOffset, 4,
|
|
llvm::endianness::native);
|
|
break;
|
|
}
|
|
if (Type == static_cast<uint32_t>(GlobalInfoType::EndOfList))
|
|
break;
|
|
}
|
|
auto GR = GsymReader::copyBuffer(StringRef(Bytes.data(), Bytes.size()));
|
|
ASSERT_FALSE(bool(GR));
|
|
std::string ErrMsg;
|
|
handleAllErrors(GR.takeError(),
|
|
[&](const ErrorInfoBase &E) { ErrMsg = E.message(); });
|
|
EXPECT_NE(ErrMsg.find("FileTable section too small"), std::string::npos)
|
|
<< "Unexpected error: " << ErrMsg;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Creator/reader round-trip tests: Creator V2 -> Reader V2
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Helper to create, finalize, encode with GsymCreatorV2, then decode with
|
|
/// GsymReaderV2 and return the reader.
|
|
static Expected<std::unique_ptr<GsymReader>>
|
|
createAndReadV2(GsymCreatorV2 &GC,
|
|
llvm::endianness ByteOrder = llvm::endianness::native) {
|
|
OutputAggregator Null(nullptr);
|
|
if (auto Err = GC.finalize(Null))
|
|
return std::move(Err);
|
|
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
FW.setStringOffsetSize(GC.getStringOffsetSize());
|
|
if (auto Err = GC.encode(FW))
|
|
return std::move(Err);
|
|
|
|
return GsymReader::copyBuffer(OutStrm.str());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripGetFunctionInfoAtIndex) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name1 = GC.insertString("func_x");
|
|
const uint32_t Name2 = GC.insertString("func_y");
|
|
GC.addFunctionInfo(FunctionInfo(0x3000, 0x100, Name1));
|
|
GC.addFunctionInfo(FunctionInfo(0x3100, 0x100, Name2));
|
|
|
|
auto GROrErr = createAndReadV2(GC);
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
// Access by index.
|
|
auto FI0 = GR->getFunctionInfoAtIndex(0);
|
|
ASSERT_THAT_EXPECTED(FI0, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI0->Name), "func_x");
|
|
|
|
auto FI1 = GR->getFunctionInfoAtIndex(1);
|
|
ASSERT_THAT_EXPECTED(FI1, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI1->Name), "func_y");
|
|
|
|
// Out of bounds index.
|
|
auto FI2 = GR->getFunctionInfoAtIndex(2);
|
|
EXPECT_THAT_EXPECTED(FI2, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripAddressTable) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t N1 = GC.insertString("a");
|
|
const uint32_t N2 = GC.insertString("b");
|
|
const uint32_t N3 = GC.insertString("c");
|
|
GC.addFunctionInfo(FunctionInfo(0x8000, 0x10, N1));
|
|
GC.addFunctionInfo(FunctionInfo(0x8020, 0x10, N2));
|
|
GC.addFunctionInfo(FunctionInfo(0x8040, 0x10, N3));
|
|
|
|
auto GROrErr = createAndReadV2(GC);
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
// Verify addresses via getAddress.
|
|
EXPECT_EQ(GR->getAddress(0), std::optional<uint64_t>(0x8000u));
|
|
EXPECT_EQ(GR->getAddress(1), std::optional<uint64_t>(0x8020u));
|
|
EXPECT_EQ(GR->getAddress(2), std::optional<uint64_t>(0x8040u));
|
|
EXPECT_EQ(GR->getAddress(3), std::nullopt); // Out of bounds.
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripLargeAddressOffsets) {
|
|
// Test with address offsets that require more than 2 bytes (rounds up to 4).
|
|
GsymCreatorV2 GC;
|
|
const uint32_t N1 = GC.insertString("near");
|
|
const uint32_t N2 = GC.insertString("far");
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x10, N1));
|
|
GC.addFunctionInfo(FunctionInfo(0x1000 + 0x20000, 0x10, N2));
|
|
|
|
auto GROrErr = createAndReadV2(GC);
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
// V2 only supports power-of-two AddrOffSize (1/2/4/8), so 3 rounds up to 4.
|
|
EXPECT_EQ(GR->getAddressOffsetSize(), 4u);
|
|
EXPECT_EQ(GR->getNumAddresses(), 2u);
|
|
|
|
auto FI1 = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(FI1, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI1->Name), "near");
|
|
|
|
auto FI2 = GR->getFunctionInfo(0x1000 + 0x20000);
|
|
ASSERT_THAT_EXPECTED(FI2, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI2->Name), "far");
|
|
}
|
|
|
|
/// Swapped-endianness round-trip tests
|
|
|
|
/// Get the non-native byte order.
|
|
static llvm::endianness swappedEndianness() {
|
|
if constexpr (llvm::endianness::native == llvm::endianness::little)
|
|
return llvm::endianness::big;
|
|
else
|
|
return llvm::endianness::little;
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripSwappedSingleFunction) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name = GC.insertString("hello");
|
|
GC.addFunctionInfo(FunctionInfo(0x2000, 0x200, Name));
|
|
|
|
auto GROrErr = createAndReadV2(GC, swappedEndianness());
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
EXPECT_EQ(GR->getBaseAddress(), 0x2000u);
|
|
|
|
auto FI = GR->getFunctionInfo(0x2000);
|
|
ASSERT_THAT_EXPECTED(FI, Succeeded());
|
|
EXPECT_EQ(FI->Range, AddressRange(0x2000, 0x2200));
|
|
EXPECT_EQ(GR->getString(FI->Name), "hello");
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripSwappedMultipleFunctions) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name1 = GC.insertString("alpha");
|
|
const uint32_t Name2 = GC.insertString("beta");
|
|
const uint32_t Name3 = GC.insertString("gamma");
|
|
GC.addFunctionInfo(FunctionInfo(0x1000, 0x100, Name1));
|
|
GC.addFunctionInfo(FunctionInfo(0x1100, 0x100, Name2));
|
|
GC.addFunctionInfo(FunctionInfo(0x1200, 0x100, Name3));
|
|
|
|
auto GROrErr = createAndReadV2(GC, swappedEndianness());
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
EXPECT_EQ(GR->getNumAddresses(), 3u);
|
|
|
|
auto FI1 = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(FI1, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI1->Name), "alpha");
|
|
|
|
auto FI2 = GR->getFunctionInfo(0x1100);
|
|
ASSERT_THAT_EXPECTED(FI2, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI2->Name), "beta");
|
|
|
|
auto FI3 = GR->getFunctionInfo(0x1200);
|
|
ASSERT_THAT_EXPECTED(FI3, Succeeded());
|
|
EXPECT_EQ(GR->getString(FI3->Name), "gamma");
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripSwappedLookup) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t Name1 = GC.insertString("start");
|
|
const uint32_t Name2 = GC.insertString("end");
|
|
GC.addFunctionInfo(FunctionInfo(0x5000, 0x500, Name1));
|
|
GC.addFunctionInfo(FunctionInfo(0x5500, 0x500, Name2));
|
|
|
|
auto GROrErr = createAndReadV2(GC, swappedEndianness());
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
auto LR1 = GR->lookup(0x5000);
|
|
ASSERT_THAT_EXPECTED(LR1, Succeeded());
|
|
EXPECT_EQ(LR1->FuncName, "start");
|
|
EXPECT_EQ(LR1->FuncRange, AddressRange(0x5000, 0x5500));
|
|
|
|
auto LR2 = GR->lookup(0x5500);
|
|
ASSERT_THAT_EXPECTED(LR2, Succeeded());
|
|
EXPECT_EQ(LR2->FuncName, "end");
|
|
|
|
auto LR3 = GR->lookup(0x5100);
|
|
ASSERT_THAT_EXPECTED(LR3, Succeeded());
|
|
EXPECT_EQ(LR3->FuncName, "start");
|
|
|
|
auto LR4 = GR->lookup(0x6000);
|
|
EXPECT_THAT_EXPECTED(LR4, Failed());
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestRoundTripSwappedAddressTable) {
|
|
GsymCreatorV2 GC;
|
|
const uint32_t N1 = GC.insertString("a");
|
|
const uint32_t N2 = GC.insertString("b");
|
|
const uint32_t N3 = GC.insertString("c");
|
|
GC.addFunctionInfo(FunctionInfo(0x8000, 0x10, N1));
|
|
GC.addFunctionInfo(FunctionInfo(0x8020, 0x10, N2));
|
|
GC.addFunctionInfo(FunctionInfo(0x8040, 0x10, N3));
|
|
|
|
auto GROrErr = createAndReadV2(GC, swappedEndianness());
|
|
ASSERT_THAT_EXPECTED(GROrErr, Succeeded());
|
|
auto &GR = *GROrErr;
|
|
|
|
EXPECT_EQ(GR->getAddress(0), std::optional<uint64_t>(0x8000u));
|
|
EXPECT_EQ(GR->getAddress(1), std::optional<uint64_t>(0x8020u));
|
|
EXPECT_EQ(GR->getAddress(2), std::optional<uint64_t>(0x8040u));
|
|
EXPECT_EQ(GR->getAddress(3), std::nullopt);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Version round-trip tests: V1 -> V2 -> V1 and V2 -> V1 -> V2
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Recursively re-insert inline info strings and files from a reader into a
|
|
/// creator.
|
|
static void fixupInlineInfoForTransfer(const GsymReader &Reader,
|
|
GsymCreator &Creator, InlineInfo &II) {
|
|
II.Name = Creator.insertString(Reader.getString(II.Name));
|
|
if (II.CallFile != 0) {
|
|
if (auto FE = Reader.getFile(II.CallFile)) {
|
|
StringRef Dir = Reader.getString(FE->Dir);
|
|
StringRef Base = Reader.getString(FE->Base);
|
|
SmallString<128> Path;
|
|
if (!Dir.empty()) {
|
|
Path = Dir;
|
|
llvm::sys::path::append(Path, Base);
|
|
} else {
|
|
Path = Base;
|
|
}
|
|
II.CallFile = Creator.insertFile(Path);
|
|
}
|
|
}
|
|
for (auto &Child : II.Children)
|
|
fixupInlineInfoForTransfer(Reader, Creator, Child);
|
|
}
|
|
|
|
/// Transfer all function infos from a reader into a creator, re-inserting
|
|
/// all strings and files so that offsets are valid in the new creator.
|
|
static void transferFunctions(const GsymReader &Reader, GsymCreator &Creator) {
|
|
for (uint32_t I = 0; I < Reader.getNumAddresses(); ++I) {
|
|
auto FI = Reader.getFunctionInfoAtIndex(I);
|
|
ASSERT_THAT_EXPECTED(FI, Succeeded());
|
|
|
|
// Re-insert function name.
|
|
FI->Name = Creator.insertString(Reader.getString(FI->Name));
|
|
|
|
// Re-insert line table file entries.
|
|
if (FI->OptLineTable) {
|
|
for (size_t J = 0; J < FI->OptLineTable->size(); ++J) {
|
|
LineEntry &LE = FI->OptLineTable->get(J);
|
|
if (LE.File != 0) {
|
|
if (auto FE = Reader.getFile(LE.File)) {
|
|
StringRef Dir = Reader.getString(FE->Dir);
|
|
StringRef Base = Reader.getString(FE->Base);
|
|
SmallString<128> Path;
|
|
if (!Dir.empty()) {
|
|
Path = Dir;
|
|
llvm::sys::path::append(Path, Base);
|
|
} else {
|
|
Path = Base;
|
|
}
|
|
LE.File = Creator.insertFile(Path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-insert inline info strings and files.
|
|
if (FI->Inline)
|
|
fixupInlineInfoForTransfer(Reader, Creator, *FI->Inline);
|
|
|
|
Creator.addFunctionInfo(std::move(*FI));
|
|
}
|
|
}
|
|
|
|
/// Encode a GsymCreator to bytes.
|
|
static SmallString<1024> encodeCreator(const GsymCreator &GC) {
|
|
SmallString<1024> Str;
|
|
raw_svector_ostream OS(Str);
|
|
FileWriter FW(OS, llvm::endianness::native);
|
|
FW.setStringOffsetSize(GC.getStringOffsetSize());
|
|
llvm::Error Err = GC.encode(FW);
|
|
EXPECT_FALSE(bool(Err));
|
|
return Str;
|
|
}
|
|
|
|
/// Collect lookup results for a set of addresses from a reader.
|
|
static std::vector<LookupResult> collectLookups(const GsymReader &Reader,
|
|
ArrayRef<uint64_t> Addrs) {
|
|
std::vector<LookupResult> Results;
|
|
for (auto Addr : Addrs) {
|
|
auto LR = Reader.lookup(Addr);
|
|
EXPECT_TRUE(bool(LR));
|
|
if (LR)
|
|
Results.push_back(std::move(*LR));
|
|
}
|
|
return Results;
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestVersionRoundTripV1ToV2ToV1) {
|
|
// Create a V1 GSYM with line tables and inline info.
|
|
GsymCreatorV1 GC1;
|
|
FunctionInfo FI(0x1000, 0x100, GC1.insertString("main"));
|
|
FI.OptLineTable = LineTable();
|
|
const uint32_t MainFile = GC1.insertFile("/tmp/main.c");
|
|
const uint32_t FooFile = GC1.insertFile("/tmp/foo.h");
|
|
FI.OptLineTable->push(LineEntry(0x1000, MainFile, 5));
|
|
FI.OptLineTable->push(LineEntry(0x1010, FooFile, 10));
|
|
FI.OptLineTable->push(LineEntry(0x1020, MainFile, 8));
|
|
FI.Inline = InlineInfo();
|
|
FI.Inline->Name = GC1.insertString("inlined_func");
|
|
FI.Inline->CallFile = MainFile;
|
|
FI.Inline->CallLine = 6;
|
|
FI.Inline->Ranges.insert(AddressRange(0x1010, 0x1020));
|
|
InlineInfo NestedInline;
|
|
NestedInline.Name = GC1.insertString("deep_inline");
|
|
NestedInline.CallFile = FooFile;
|
|
NestedInline.CallLine = 33;
|
|
NestedInline.Ranges.insert(AddressRange(0x1012, 0x1018));
|
|
FI.Inline->Children.emplace_back(NestedInline);
|
|
GC1.addFunctionInfo(std::move(FI));
|
|
|
|
FunctionInfo FI2(0x1100, 0x50, GC1.insertString("helper"));
|
|
FI2.OptLineTable = LineTable();
|
|
FI2.OptLineTable->push(LineEntry(0x1100, MainFile, 20));
|
|
FI2.OptLineTable->push(LineEntry(0x1120, MainFile, 25));
|
|
GC1.addFunctionInfo(std::move(FI2));
|
|
|
|
OutputAggregator Null(nullptr);
|
|
ASSERT_FALSE(bool(GC1.finalize(Null)));
|
|
SmallString<1024> OrigV1Bytes = encodeCreator(GC1);
|
|
ASSERT_GT(OrigV1Bytes.size(), 0u);
|
|
|
|
// Read original V1.
|
|
auto OrigReaderOrErr = GsymReader::copyBuffer(OrigV1Bytes);
|
|
ASSERT_THAT_EXPECTED(OrigReaderOrErr, Succeeded());
|
|
auto &OrigReader = *OrigReaderOrErr;
|
|
|
|
// Collect lookup results from original V1.
|
|
std::vector<uint64_t> TestAddrs = {0x1000, 0x1008, 0x1010, 0x1012,
|
|
0x1015, 0x1020, 0x1100, 0x1120};
|
|
auto OrigResults = collectLookups(*OrigReader, TestAddrs);
|
|
ASSERT_EQ(OrigResults.size(), TestAddrs.size());
|
|
|
|
// Convert V1 → V2.
|
|
GsymCreatorV2 GC2;
|
|
transferFunctions(*OrigReader, GC2);
|
|
ASSERT_FALSE(bool(GC2.finalize(Null)));
|
|
SmallString<1024> V2Bytes = encodeCreator(GC2);
|
|
ASSERT_GT(V2Bytes.size(), 0u);
|
|
|
|
auto V2ReaderOrErr = GsymReader::copyBuffer(V2Bytes);
|
|
ASSERT_THAT_EXPECTED(V2ReaderOrErr, Succeeded());
|
|
auto &V2Reader = *V2ReaderOrErr;
|
|
|
|
// Verify V2 lookups match original V1.
|
|
auto V2Results = collectLookups(*V2Reader, TestAddrs);
|
|
ASSERT_EQ(V2Results.size(), TestAddrs.size());
|
|
for (size_t I = 0; I < TestAddrs.size(); ++I)
|
|
EXPECT_EQ(V2Results[I], OrigResults[I])
|
|
<< "Mismatch at address " << TestAddrs[I] << " after V1->V2";
|
|
|
|
// Convert V2 → V1.
|
|
GsymCreatorV1 GC3;
|
|
transferFunctions(*V2Reader, GC3);
|
|
ASSERT_FALSE(bool(GC3.finalize(Null)));
|
|
SmallString<1024> FinalV1Bytes = encodeCreator(GC3);
|
|
ASSERT_GT(FinalV1Bytes.size(), 0u);
|
|
|
|
auto FinalReaderOrErr = GsymReader::copyBuffer(FinalV1Bytes);
|
|
ASSERT_THAT_EXPECTED(FinalReaderOrErr, Succeeded());
|
|
auto &FinalReader = *FinalReaderOrErr;
|
|
|
|
// Verify final V1 lookups match original V1.
|
|
auto FinalResults = collectLookups(*FinalReader, TestAddrs);
|
|
ASSERT_EQ(FinalResults.size(), TestAddrs.size());
|
|
for (size_t I = 0; I < TestAddrs.size(); ++I)
|
|
EXPECT_EQ(FinalResults[I], OrigResults[I])
|
|
<< "Mismatch at address " << TestAddrs[I] << " after V1->V2->V1";
|
|
}
|
|
|
|
TEST(GSYMV2Test, TestVersionRoundTripV2ToV1ToV2) {
|
|
// Create a V2 GSYM with line tables and inline info.
|
|
GsymCreatorV2 GC1;
|
|
FunctionInfo FI(0x2000, 0x200, GC1.insertString("entry"));
|
|
FI.OptLineTable = LineTable();
|
|
const uint32_t SrcFile = GC1.insertFile("/src/app.cc");
|
|
const uint32_t HdrFile = GC1.insertFile("/src/util.h");
|
|
FI.OptLineTable->push(LineEntry(0x2000, SrcFile, 10));
|
|
FI.OptLineTable->push(LineEntry(0x2040, HdrFile, 50));
|
|
FI.OptLineTable->push(LineEntry(0x2080, HdrFile, 55));
|
|
FI.OptLineTable->push(LineEntry(0x20C0, SrcFile, 15));
|
|
FI.Inline = InlineInfo();
|
|
FI.Inline->Name = GC1.insertString("util_helper");
|
|
FI.Inline->CallFile = SrcFile;
|
|
FI.Inline->CallLine = 11;
|
|
FI.Inline->Ranges.insert(AddressRange(0x2040, 0x20C0));
|
|
InlineInfo Child;
|
|
Child.Name = GC1.insertString("util_detail");
|
|
Child.CallFile = HdrFile;
|
|
Child.CallLine = 52;
|
|
Child.Ranges.insert(AddressRange(0x2080, 0x20A0));
|
|
FI.Inline->Children.emplace_back(Child);
|
|
GC1.addFunctionInfo(std::move(FI));
|
|
|
|
FunctionInfo FI2(0x2200, 0x100, GC1.insertString("cleanup"));
|
|
FI2.OptLineTable = LineTable();
|
|
FI2.OptLineTable->push(LineEntry(0x2200, SrcFile, 30));
|
|
FI2.OptLineTable->push(LineEntry(0x2250, SrcFile, 35));
|
|
GC1.addFunctionInfo(std::move(FI2));
|
|
|
|
OutputAggregator Null(nullptr);
|
|
ASSERT_FALSE(bool(GC1.finalize(Null)));
|
|
SmallString<1024> OrigV2Bytes = encodeCreator(GC1);
|
|
ASSERT_GT(OrigV2Bytes.size(), 0u);
|
|
|
|
// Read original V2.
|
|
auto OrigReaderOrErr = GsymReader::copyBuffer(OrigV2Bytes);
|
|
ASSERT_THAT_EXPECTED(OrigReaderOrErr, Succeeded());
|
|
auto &OrigReader = *OrigReaderOrErr;
|
|
|
|
// Collect lookup results from original V2.
|
|
std::vector<uint64_t> TestAddrs = {0x2000, 0x2020, 0x2040, 0x2080,
|
|
0x2090, 0x20C0, 0x2200, 0x2250};
|
|
auto OrigResults = collectLookups(*OrigReader, TestAddrs);
|
|
ASSERT_EQ(OrigResults.size(), TestAddrs.size());
|
|
|
|
// Convert V2 → V1.
|
|
GsymCreatorV1 GC2;
|
|
transferFunctions(*OrigReader, GC2);
|
|
ASSERT_FALSE(bool(GC2.finalize(Null)));
|
|
SmallString<1024> V1Bytes = encodeCreator(GC2);
|
|
ASSERT_GT(V1Bytes.size(), 0u);
|
|
|
|
auto V1ReaderOrErr = GsymReader::copyBuffer(V1Bytes);
|
|
ASSERT_THAT_EXPECTED(V1ReaderOrErr, Succeeded());
|
|
auto &V1Reader = *V1ReaderOrErr;
|
|
|
|
// Verify V1 lookups match original V2.
|
|
auto V1Results = collectLookups(*V1Reader, TestAddrs);
|
|
ASSERT_EQ(V1Results.size(), TestAddrs.size());
|
|
for (size_t I = 0; I < TestAddrs.size(); ++I)
|
|
EXPECT_EQ(V1Results[I], OrigResults[I])
|
|
<< "Mismatch at address " << TestAddrs[I] << " after V2->V1";
|
|
|
|
// Convert V1 → V2.
|
|
GsymCreatorV2 GC3;
|
|
transferFunctions(*V1Reader, GC3);
|
|
ASSERT_FALSE(bool(GC3.finalize(Null)));
|
|
SmallString<1024> FinalV2Bytes = encodeCreator(GC3);
|
|
ASSERT_GT(FinalV2Bytes.size(), 0u);
|
|
|
|
auto FinalReaderOrErr = GsymReader::copyBuffer(FinalV2Bytes);
|
|
ASSERT_THAT_EXPECTED(FinalReaderOrErr, Succeeded());
|
|
auto &FinalReader = *FinalReaderOrErr;
|
|
|
|
// Verify final V2 lookups match original V2.
|
|
auto FinalResults = collectLookups(*FinalReader, TestAddrs);
|
|
ASSERT_EQ(FinalResults.size(), TestAddrs.size());
|
|
for (size_t I = 0; I < TestAddrs.size(); ++I)
|
|
EXPECT_EQ(FinalResults[I], OrigResults[I])
|
|
<< "Mismatch at address " << TestAddrs[I] << " after V2->V1->V2";
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Segmenting tests
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
TEST(GSYMV2Test, TestV2SegmentingSize) {
|
|
// Test that V2 segmenting produces segments whose actual encoded size
|
|
// does not exceed the requested segment size. This catches bugs where
|
|
// calculateHeaderAndTableSize() overestimates (e.g. using wrong
|
|
// GlobalData entry size), causing segments to contain fewer functions
|
|
// than they could.
|
|
GsymCreatorV2 GC;
|
|
const uint64_t BaseAddr = 0x1000;
|
|
// Add 10 simple functions (no line table, minimal size).
|
|
for (uint32_t I = 0; I < 10; ++I) {
|
|
std::string Name = "f" + std::to_string(I);
|
|
uint32_t NameOff = GC.insertString(Name);
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr + I * 0x100, 0x100, NameOff));
|
|
}
|
|
OutputAggregator Null(nullptr);
|
|
ASSERT_FALSE(GC.finalize(Null));
|
|
|
|
// Create the first segment with a generous-but-bounded size.
|
|
// We want a size that fits all 10 functions if the estimate is correct,
|
|
// but might not fit all 10 if the estimate is too large.
|
|
// First, encode the full GSYM to know the actual total size.
|
|
SmallString<512> FullStr;
|
|
raw_svector_ostream FullOS(FullStr);
|
|
FileWriter FullFW(FullOS, llvm::endianness::native);
|
|
FullFW.setStringOffsetSize(GC.getStringOffsetSize());
|
|
ASSERT_FALSE(GC.encode(FullFW));
|
|
const uint64_t FullSize = FullStr.size();
|
|
|
|
// Use the full size as segment size — all functions should fit in one
|
|
// segment. If calculateHeaderAndTableSize() overestimates, some functions
|
|
// won't fit.
|
|
size_t FuncIdx = 0;
|
|
auto SegOrErr = GC.createSegment(FullSize, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(SegOrErr, Succeeded());
|
|
ASSERT_NE(SegOrErr->get(), nullptr);
|
|
// All or almost all functions should fit. The estimate may slightly
|
|
// overestimate due to string/file table differences between the full
|
|
// creator and the segment, but it should not be off by more than 1.
|
|
EXPECT_GE(FuncIdx, 9u)
|
|
<< "Too few functions fit in segment — calculateHeaderAndTableSize() "
|
|
"may be overestimating";
|
|
}
|