mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-19 01:16:55 +00:00

This PR adds support in the gSYM format for call site information and adds support for loading call sites from a YAML file. The support for YAML input is mostly for testing purposes - so we have a way to test the functionality. Note that this data is not currently used in the gSYM tooling - the logic to use call sites will be added in a later PR. The reason why we need call site information in gSYM files is so that we can support better call stack function disambiguation in the case where multiple functions have been merged due to optimization (linker ICF). When resolving a merged function on the callstack, we can use the call site information of the calling function to narrow down the actual function that is being called, from the set of all merged functions. See [this RFC](https://discourse.llvm.org/t/rfc-extending-gsym-format-with-call-site-information-for-merged-function-disambiguation/80682) for more details on this change.
4902 lines
188 KiB
C++
4902 lines
188 KiB
C++
//===- llvm/unittest/DebugInfo/GSYMTest.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/DenseMap.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
|
|
#include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
|
|
#include "llvm/DebugInfo/GSYM/ExtractRanges.h"
|
|
#include "llvm/DebugInfo/GSYM/FileEntry.h"
|
|
#include "llvm/DebugInfo/GSYM/FileWriter.h"
|
|
#include "llvm/DebugInfo/GSYM/FunctionInfo.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymCreator.h"
|
|
#include "llvm/DebugInfo/GSYM/GsymReader.h"
|
|
#include "llvm/DebugInfo/GSYM/Header.h"
|
|
#include "llvm/DebugInfo/GSYM/InlineInfo.h"
|
|
#include "llvm/DebugInfo/GSYM/OutputAggregator.h"
|
|
#include "llvm/DebugInfo/GSYM/StringTable.h"
|
|
#include "llvm/ObjectYAML/DWARFEmitter.h"
|
|
#include "llvm/Support/DataExtractor.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "gmock/gmock.h"
|
|
#include <string>
|
|
|
|
using namespace llvm;
|
|
using namespace gsym;
|
|
|
|
void checkError(ArrayRef<std::string> ExpectedMsgs, Error Err) {
|
|
ASSERT_TRUE(bool(Err));
|
|
size_t WhichMsg = 0;
|
|
Error Remaining =
|
|
handleErrors(std::move(Err), [&](const ErrorInfoBase &Actual) {
|
|
ASSERT_LT(WhichMsg, ExpectedMsgs.size());
|
|
// Use .str(), because googletest doesn't visualise a StringRef
|
|
// properly.
|
|
EXPECT_EQ(Actual.message(), ExpectedMsgs[WhichMsg++]);
|
|
});
|
|
EXPECT_EQ(WhichMsg, ExpectedMsgs.size());
|
|
EXPECT_FALSE(Remaining);
|
|
}
|
|
|
|
void checkError(std::string ExpectedMsg, Error Err) {
|
|
checkError(ArrayRef<std::string>{ExpectedMsg}, std::move(Err));
|
|
}
|
|
TEST(GSYMTest, TestFileEntry) {
|
|
// Make sure default constructed GSYM FileEntry has zeroes in the
|
|
// directory and basename string table indexes.
|
|
FileEntry empty1;
|
|
FileEntry empty2;
|
|
EXPECT_EQ(empty1.Dir, 0u);
|
|
EXPECT_EQ(empty1.Base, 0u);
|
|
// Verify equality operator works
|
|
FileEntry a1(10, 30);
|
|
FileEntry a2(10, 30);
|
|
FileEntry b(10, 40);
|
|
EXPECT_EQ(empty1, empty2);
|
|
EXPECT_EQ(a1, a2);
|
|
EXPECT_NE(a1, b);
|
|
EXPECT_NE(a1, empty1);
|
|
// Test we can use llvm::gsym::FileEntry in llvm::DenseMap.
|
|
DenseMap<FileEntry, uint32_t> EntryToIndex;
|
|
constexpr uint32_t Index1 = 1;
|
|
constexpr uint32_t Index2 = 1;
|
|
auto R = EntryToIndex.insert(std::make_pair(a1, Index1));
|
|
EXPECT_TRUE(R.second);
|
|
EXPECT_EQ(R.first->second, Index1);
|
|
R = EntryToIndex.insert(std::make_pair(a1, Index1));
|
|
EXPECT_FALSE(R.second);
|
|
EXPECT_EQ(R.first->second, Index1);
|
|
R = EntryToIndex.insert(std::make_pair(b, Index2));
|
|
EXPECT_TRUE(R.second);
|
|
EXPECT_EQ(R.first->second, Index2);
|
|
R = EntryToIndex.insert(std::make_pair(a1, Index2));
|
|
EXPECT_FALSE(R.second);
|
|
EXPECT_EQ(R.first->second, Index2);
|
|
}
|
|
|
|
TEST(GSYMTest, TestFunctionInfo) {
|
|
// Test GSYM FunctionInfo structs and functionality.
|
|
FunctionInfo invalid;
|
|
EXPECT_FALSE(invalid.isValid());
|
|
EXPECT_FALSE(invalid.hasRichInfo());
|
|
const uint64_t StartAddr = 0x1000;
|
|
const uint64_t EndAddr = 0x1100;
|
|
const uint64_t Size = EndAddr - StartAddr;
|
|
const uint32_t NameOffset = 30;
|
|
FunctionInfo FI(StartAddr, Size, NameOffset);
|
|
EXPECT_TRUE(FI.isValid());
|
|
EXPECT_FALSE(FI.hasRichInfo());
|
|
EXPECT_EQ(FI.startAddress(), StartAddr);
|
|
EXPECT_EQ(FI.endAddress(), EndAddr);
|
|
EXPECT_EQ(FI.size(), Size);
|
|
const uint32_t FileIdx = 1;
|
|
const uint32_t Line = 12;
|
|
FI.OptLineTable = LineTable();
|
|
FI.OptLineTable->push(LineEntry(StartAddr,FileIdx,Line));
|
|
EXPECT_TRUE(FI.hasRichInfo());
|
|
FI.clear();
|
|
EXPECT_FALSE(FI.isValid());
|
|
EXPECT_FALSE(FI.hasRichInfo());
|
|
|
|
FunctionInfo A1(0x1000, 0x100, NameOffset);
|
|
FunctionInfo A2(0x1000, 0x100, NameOffset);
|
|
FunctionInfo B;
|
|
// Check == operator
|
|
EXPECT_EQ(A1, A2);
|
|
// Make sure things are not equal if they only differ by start address.
|
|
B = A2;
|
|
B.Range = {0x1001, B.endAddress()};
|
|
EXPECT_NE(B, A2);
|
|
// Make sure things are not equal if they only differ by size.
|
|
B = A2;
|
|
B.Range = {B.startAddress(), B.startAddress() + 0x101};
|
|
EXPECT_NE(B, A2);
|
|
// Make sure things are not equal if they only differ by name.
|
|
B = A2;
|
|
B.Name = 60;
|
|
EXPECT_NE(B, A2);
|
|
// Check < operator.
|
|
// Check less than where address differs.
|
|
B = A2;
|
|
B.Range = {A2.startAddress() + 0x1000, A2.endAddress() + 0x1000};
|
|
EXPECT_LT(A1, B);
|
|
|
|
// We use the < operator to take a variety of different FunctionInfo
|
|
// structs from a variety of sources: symtab, debug info, runtime info
|
|
// and we sort them and want the sorting to allow us to quickly get the
|
|
// best version of a function info.
|
|
FunctionInfo FISymtab(StartAddr, Size, NameOffset);
|
|
FunctionInfo FIWithLines(StartAddr, Size, NameOffset);
|
|
FIWithLines.OptLineTable = LineTable();
|
|
FIWithLines.OptLineTable->push(LineEntry(StartAddr,FileIdx,Line));
|
|
// Test that a FunctionInfo with just a name and size is less than one
|
|
// that has name, size and any number of line table entries
|
|
EXPECT_LT(FISymtab, FIWithLines);
|
|
|
|
// Test that if we have a function info without inline info and one with
|
|
// that the one without inline info is less than the one with.
|
|
FunctionInfo FIWithInlines = FISymtab;
|
|
FIWithInlines.Inline = InlineInfo();
|
|
FIWithInlines.Inline->Ranges.insert(
|
|
AddressRange(StartAddr, StartAddr + 0x10));
|
|
EXPECT_LT(FISymtab, FIWithInlines);
|
|
|
|
// Test that if we have a function info with inline entries and one more
|
|
// inline entries that the one with fewer inline functins is less than the
|
|
// one with more.
|
|
FunctionInfo FIWithMoreInlines = FIWithInlines;
|
|
FIWithMoreInlines.Inline->Children.push_back(InlineInfo());
|
|
EXPECT_LT(FIWithInlines, FIWithMoreInlines);
|
|
|
|
FunctionInfo FIWithLinesAndInline = FIWithLines;
|
|
FIWithLinesAndInline.Inline = InlineInfo();
|
|
FIWithLinesAndInline.Inline->Ranges.insert(
|
|
AddressRange(StartAddr, StartAddr + 0x10));
|
|
// Test that a FunctionInfo with name, size, and line entries is less than
|
|
// the same one with valid inline info
|
|
EXPECT_LT(FIWithLines, FIWithLinesAndInline);
|
|
|
|
// Test if we have an entry with lines and one with more lines for the same
|
|
// range, the ones with more lines is greater than the one with less.
|
|
FunctionInfo FIWithMoreLines = FIWithLines;
|
|
FIWithMoreLines.OptLineTable->push(LineEntry(StartAddr,FileIdx,Line+5));
|
|
EXPECT_LT(FIWithLines, FIWithMoreLines);
|
|
|
|
// Test that if we have the same number of lines we compare the line entries
|
|
// in the FunctionInfo.OptLineTable.Lines vector.
|
|
FunctionInfo FIWithLinesWithHigherAddress = FIWithLines;
|
|
FIWithLinesWithHigherAddress.OptLineTable->get(0).Addr += 0x10;
|
|
EXPECT_LT(FIWithLines, FIWithLinesWithHigherAddress);
|
|
}
|
|
|
|
static void TestFunctionInfoDecodeError(llvm::endianness ByteOrder,
|
|
StringRef Bytes,
|
|
const uint64_t BaseAddr,
|
|
std::string ExpectedErrorMsg) {
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<FunctionInfo> Decoded = FunctionInfo::decode(Data, BaseAddr);
|
|
// Make sure decoding fails.
|
|
ASSERT_FALSE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
checkError(ExpectedErrorMsg, Decoded.takeError());
|
|
}
|
|
|
|
TEST(GSYMTest, TestFunctionInfoDecodeErrors) {
|
|
// Test decoding FunctionInfo objects that ensure we report an appropriate
|
|
// error message.
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = 0x100;
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000000: missing FunctionInfo Size");
|
|
FW.writeU32(0x100); // Function size.
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000004: missing FunctionInfo Name");
|
|
// Write out an invalid Name string table offset of zero.
|
|
FW.writeU32(0);
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000004: invalid FunctionInfo Name value 0x00000000");
|
|
// Modify the Name to be 0x00000001, which is a valid value.
|
|
FW.fixup32(0x00000001, 4);
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000008: missing FunctionInfo InfoType value");
|
|
auto FixupOffset = FW.tell();
|
|
FW.writeU32(1); // InfoType::LineTableInfo.
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x0000000c: missing FunctionInfo InfoType length");
|
|
FW.fixup32(7, FixupOffset); // Write an invalid InfoType enumeration value
|
|
FW.writeU32(0); // LineTableInfo InfoType data length.
|
|
TestFunctionInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000008: unsupported InfoType 7");
|
|
}
|
|
|
|
static void TestFunctionInfoEncodeError(llvm::endianness ByteOrder,
|
|
const FunctionInfo &FI,
|
|
std::string ExpectedErrorMsg) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
Expected<uint64_t> ExpectedOffset = FI.encode(FW);
|
|
ASSERT_FALSE(ExpectedOffset);
|
|
checkError(ExpectedErrorMsg, ExpectedOffset.takeError());
|
|
}
|
|
|
|
TEST(GSYMTest, TestFunctionInfoEncodeErrors) {
|
|
const uint64_t FuncAddr = 0x1000;
|
|
const uint64_t FuncSize = 0x100;
|
|
const uint32_t InvalidName = 0;
|
|
const uint32_t ValidName = 1;
|
|
FunctionInfo InvalidNameFI(FuncAddr, FuncSize, InvalidName);
|
|
TestFunctionInfoEncodeError(
|
|
llvm::endianness::little, InvalidNameFI,
|
|
"attempted to encode invalid FunctionInfo object");
|
|
|
|
FunctionInfo InvalidLineTableFI(FuncAddr, FuncSize, ValidName);
|
|
// Empty line tables are not valid. Verify if the encoding of anything
|
|
// in our line table fails, that we see get the error propagated.
|
|
InvalidLineTableFI.OptLineTable = LineTable();
|
|
TestFunctionInfoEncodeError(llvm::endianness::little, InvalidLineTableFI,
|
|
"attempted to encode invalid LineTable object");
|
|
|
|
FunctionInfo InvalidInlineInfoFI(FuncAddr, FuncSize, ValidName);
|
|
// Empty line tables are not valid. Verify if the encoding of anything
|
|
// in our line table fails, that we see get the error propagated.
|
|
InvalidInlineInfoFI.Inline = InlineInfo();
|
|
TestFunctionInfoEncodeError(llvm::endianness::little, InvalidInlineInfoFI,
|
|
"attempted to encode invalid InlineInfo object");
|
|
}
|
|
|
|
static void TestFunctionInfoEncodeDecode(llvm::endianness ByteOrder,
|
|
const FunctionInfo &FI) {
|
|
// Test encoding and decoding FunctionInfo objects.
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Expected<uint64_t> ExpectedOffset = FI.encode(FW);
|
|
ASSERT_TRUE(bool(ExpectedOffset));
|
|
// Verify we got the encoded offset back from the encode function.
|
|
ASSERT_EQ(ExpectedOffset.get(), 0ULL);
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<FunctionInfo> Decoded =
|
|
FunctionInfo::decode(Data, FI.Range.start());
|
|
// Make sure decoding succeeded.
|
|
ASSERT_TRUE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
EXPECT_EQ(FI, Decoded.get());
|
|
}
|
|
|
|
static void AddLines(uint64_t FuncAddr, uint32_t FileIdx, FunctionInfo &FI) {
|
|
FI.OptLineTable = LineTable();
|
|
LineEntry Line0(FuncAddr + 0x000, FileIdx, 10);
|
|
LineEntry Line1(FuncAddr + 0x010, FileIdx, 11);
|
|
LineEntry Line2(FuncAddr + 0x100, FileIdx, 1000);
|
|
FI.OptLineTable->push(Line0);
|
|
FI.OptLineTable->push(Line1);
|
|
FI.OptLineTable->push(Line2);
|
|
}
|
|
|
|
|
|
static void AddInline(uint64_t FuncAddr, uint64_t FuncSize, FunctionInfo &FI) {
|
|
FI.Inline = InlineInfo();
|
|
FI.Inline->Ranges.insert(AddressRange(FuncAddr, FuncAddr + FuncSize));
|
|
InlineInfo Inline1;
|
|
Inline1.Ranges.insert(AddressRange(FuncAddr + 0x10, FuncAddr + 0x30));
|
|
Inline1.Name = 1;
|
|
Inline1.CallFile = 1;
|
|
Inline1.CallLine = 11;
|
|
FI.Inline->Children.push_back(Inline1);
|
|
}
|
|
|
|
TEST(GSYMTest, TestFunctionInfoEncoding) {
|
|
constexpr uint64_t FuncAddr = 0x1000;
|
|
constexpr uint64_t FuncSize = 0x100;
|
|
constexpr uint32_t FuncName = 1;
|
|
constexpr uint32_t FileIdx = 1;
|
|
// Make sure that we can encode and decode a FunctionInfo with no line table
|
|
// or inline info.
|
|
FunctionInfo FI(FuncAddr, FuncSize, FuncName);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::little, FI);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::big, FI);
|
|
|
|
// Make sure that we can encode and decode a FunctionInfo with a line table
|
|
// and no inline info.
|
|
FunctionInfo FILines(FuncAddr, FuncSize, FuncName);
|
|
AddLines(FuncAddr, FileIdx, FILines);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::little, FILines);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::big, FILines);
|
|
|
|
// Make sure that we can encode and decode a FunctionInfo with no line table
|
|
// and with inline info.
|
|
FunctionInfo FIInline(FuncAddr, FuncSize, FuncName);
|
|
AddInline(FuncAddr, FuncSize, FIInline);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::little, FIInline);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::big, FIInline);
|
|
|
|
// Make sure that we can encode and decode a FunctionInfo with no line table
|
|
// and with inline info.
|
|
FunctionInfo FIBoth(FuncAddr, FuncSize, FuncName);
|
|
AddLines(FuncAddr, FileIdx, FIBoth);
|
|
AddInline(FuncAddr, FuncSize, FIBoth);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::little, FIBoth);
|
|
TestFunctionInfoEncodeDecode(llvm::endianness::big, FIBoth);
|
|
}
|
|
|
|
static void TestInlineInfoEncodeDecode(llvm::endianness ByteOrder,
|
|
const InlineInfo &Inline) {
|
|
// Test encoding and decoding InlineInfo objects
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = Inline.Ranges[0].start();
|
|
llvm::Error Err = Inline.encode(FW, BaseAddr);
|
|
ASSERT_FALSE(Err);
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<InlineInfo> Decoded = InlineInfo::decode(Data, BaseAddr);
|
|
// Make sure decoding succeeded.
|
|
ASSERT_TRUE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
EXPECT_EQ(Inline, Decoded.get());
|
|
}
|
|
|
|
static void TestInlineInfoDecodeError(llvm::endianness ByteOrder,
|
|
StringRef Bytes, const uint64_t BaseAddr,
|
|
std::string ExpectedErrorMsg) {
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<InlineInfo> Decoded = InlineInfo::decode(Data, BaseAddr);
|
|
// Make sure decoding fails.
|
|
ASSERT_FALSE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
checkError(ExpectedErrorMsg, Decoded.takeError());
|
|
}
|
|
|
|
static void TestInlineInfoEncodeError(llvm::endianness ByteOrder,
|
|
const InlineInfo &Inline,
|
|
std::string ExpectedErrorMsg) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr =
|
|
Inline.Ranges.empty() ? 0 : Inline.Ranges[0].start();
|
|
llvm::Error Err = Inline.encode(FW, BaseAddr);
|
|
checkError(ExpectedErrorMsg, std::move(Err));
|
|
}
|
|
|
|
TEST(GSYMTest, TestInlineInfo) {
|
|
// Test InlineInfo structs.
|
|
InlineInfo II;
|
|
EXPECT_FALSE(II.isValid());
|
|
II.Ranges.insert(AddressRange(0x1000, 0x2000));
|
|
// Make sure InlineInfo in valid with just an address range since
|
|
// top level InlineInfo objects have ranges with no name, call file
|
|
// or call line
|
|
EXPECT_TRUE(II.isValid());
|
|
// Make sure InlineInfo isn't after being cleared.
|
|
II.clear();
|
|
EXPECT_FALSE(II.isValid());
|
|
|
|
// Create an InlineInfo that contains the following data. The
|
|
// indentation of the address range indicates the parent child
|
|
// relationships of the InlineInfo objects:
|
|
//
|
|
// Variable Range and values
|
|
// =========== ====================================================
|
|
// Root [0x100-0x200) (no name, file, or line)
|
|
// Inline1 [0x150-0x160) Name = 1, File = 1, Line = 11
|
|
// Inline1Sub1 [0x152-0x155) Name = 2, File = 2, Line = 22
|
|
// Inline1Sub2 [0x157-0x158) Name = 3, File = 3, Line = 33
|
|
InlineInfo Root;
|
|
Root.Ranges.insert(AddressRange(0x100, 0x200));
|
|
InlineInfo Inline1;
|
|
Inline1.Ranges.insert(AddressRange(0x150, 0x160));
|
|
Inline1.Name = 1;
|
|
Inline1.CallFile = 1;
|
|
Inline1.CallLine = 11;
|
|
InlineInfo Inline1Sub1;
|
|
Inline1Sub1.Ranges.insert(AddressRange(0x152, 0x155));
|
|
Inline1Sub1.Name = 2;
|
|
Inline1Sub1.CallFile = 2;
|
|
Inline1Sub1.CallLine = 22;
|
|
InlineInfo Inline1Sub2;
|
|
Inline1Sub2.Ranges.insert(AddressRange(0x157, 0x158));
|
|
Inline1Sub2.Name = 3;
|
|
Inline1Sub2.CallFile = 3;
|
|
Inline1Sub2.CallLine = 33;
|
|
Inline1.Children.push_back(Inline1Sub1);
|
|
Inline1.Children.push_back(Inline1Sub2);
|
|
Root.Children.push_back(Inline1);
|
|
|
|
// Make sure an address that is out of range won't match
|
|
EXPECT_FALSE(Root.getInlineStack(0x50));
|
|
|
|
// Verify that we get no inline stacks for addresses out of [0x100-0x200)
|
|
EXPECT_FALSE(Root.getInlineStack(Root.Ranges[0].start() - 1));
|
|
EXPECT_FALSE(Root.getInlineStack(Root.Ranges[0].end()));
|
|
|
|
// Verify we get no inline stack entries for addresses that are in
|
|
// [0x100-0x200) but not in [0x150-0x160)
|
|
EXPECT_FALSE(Root.getInlineStack(Inline1.Ranges[0].start() - 1));
|
|
EXPECT_FALSE(Root.getInlineStack(Inline1.Ranges[0].end()));
|
|
|
|
// Verify we get one inline stack entry for addresses that are in
|
|
// [[0x150-0x160)) but not in [0x152-0x155) or [0x157-0x158)
|
|
auto InlineInfos = Root.getInlineStack(Inline1.Ranges[0].start());
|
|
ASSERT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 1u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1);
|
|
InlineInfos = Root.getInlineStack(Inline1.Ranges[0].end() - 1);
|
|
EXPECT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 1u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1);
|
|
|
|
// Verify we get two inline stack entries for addresses that are in
|
|
// [0x152-0x155)
|
|
InlineInfos = Root.getInlineStack(Inline1Sub1.Ranges[0].start());
|
|
EXPECT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 2u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1Sub1);
|
|
ASSERT_EQ(*InlineInfos->at(1), Inline1);
|
|
InlineInfos = Root.getInlineStack(Inline1Sub1.Ranges[0].end() - 1);
|
|
EXPECT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 2u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1Sub1);
|
|
ASSERT_EQ(*InlineInfos->at(1), Inline1);
|
|
|
|
// Verify we get two inline stack entries for addresses that are in
|
|
// [0x157-0x158)
|
|
InlineInfos = Root.getInlineStack(Inline1Sub2.Ranges[0].start());
|
|
EXPECT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 2u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1Sub2);
|
|
ASSERT_EQ(*InlineInfos->at(1), Inline1);
|
|
InlineInfos = Root.getInlineStack(Inline1Sub2.Ranges[0].end() - 1);
|
|
EXPECT_TRUE(InlineInfos);
|
|
ASSERT_EQ(InlineInfos->size(), 2u);
|
|
ASSERT_EQ(*InlineInfos->at(0), Inline1Sub2);
|
|
ASSERT_EQ(*InlineInfos->at(1), Inline1);
|
|
|
|
// Test encoding and decoding InlineInfo objects
|
|
TestInlineInfoEncodeDecode(llvm::endianness::little, Root);
|
|
TestInlineInfoEncodeDecode(llvm::endianness::big, Root);
|
|
}
|
|
|
|
TEST(GSYMTest, TestInlineInfoEncodeErrors) {
|
|
// Test InlineInfo encoding errors.
|
|
|
|
// Test that we get an error when trying to encode an InlineInfo object
|
|
// that has no ranges.
|
|
InlineInfo Empty;
|
|
std::string EmptyErr("attempted to encode invalid InlineInfo object");
|
|
TestInlineInfoEncodeError(llvm::endianness::little, Empty, EmptyErr);
|
|
TestInlineInfoEncodeError(llvm::endianness::big, Empty, EmptyErr);
|
|
|
|
// Verify that we get an error trying to encode an InlineInfo object that has
|
|
// a child InlineInfo that has no ranges.
|
|
InlineInfo ContainsEmpty;
|
|
ContainsEmpty.Ranges.insert({0x100, 0x200});
|
|
ContainsEmpty.Children.push_back(Empty);
|
|
TestInlineInfoEncodeError(llvm::endianness::little, ContainsEmpty, EmptyErr);
|
|
TestInlineInfoEncodeError(llvm::endianness::big, ContainsEmpty, EmptyErr);
|
|
|
|
// Verify that we get an error trying to encode an InlineInfo object that has
|
|
// a child whose address range is not contained in the parent address range.
|
|
InlineInfo ChildNotContained;
|
|
std::string ChildNotContainedErr("child range not contained in parent");
|
|
ChildNotContained.Ranges.insert({0x100, 0x200});
|
|
InlineInfo ChildNotContainedChild;
|
|
ChildNotContainedChild.Ranges.insert({0x200, 0x300});
|
|
ChildNotContained.Children.push_back(ChildNotContainedChild);
|
|
TestInlineInfoEncodeError(llvm::endianness::little, ChildNotContained,
|
|
ChildNotContainedErr);
|
|
TestInlineInfoEncodeError(llvm::endianness::big, ChildNotContained,
|
|
ChildNotContainedErr);
|
|
}
|
|
|
|
TEST(GSYMTest, TestInlineInfoDecodeErrors) {
|
|
// Test decoding InlineInfo objects that ensure we report an appropriate
|
|
// error message.
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = 0x100;
|
|
TestInlineInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000000: missing InlineInfo address ranges data");
|
|
AddressRanges Ranges;
|
|
Ranges.insert({BaseAddr, BaseAddr+0x100});
|
|
encodeRanges(Ranges, FW, BaseAddr);
|
|
TestInlineInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000004: missing InlineInfo uint8_t indicating children");
|
|
FW.writeU8(0);
|
|
TestInlineInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000005: missing InlineInfo uint32_t for name");
|
|
FW.writeU32(0);
|
|
TestInlineInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000009: missing ULEB128 for InlineInfo call file");
|
|
FW.writeU8(0);
|
|
TestInlineInfoDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x0000000a: missing ULEB128 for InlineInfo call line");
|
|
}
|
|
|
|
TEST(GSYMTest, TestLineEntry) {
|
|
// test llvm::gsym::LineEntry structs.
|
|
const uint64_t ValidAddr = 0x1000;
|
|
const uint64_t InvalidFileIdx = 0;
|
|
const uint32_t ValidFileIdx = 1;
|
|
const uint32_t ValidLine = 5;
|
|
|
|
LineEntry Invalid;
|
|
EXPECT_FALSE(Invalid.isValid());
|
|
// Make sure that an entry is invalid if it has a bad file index.
|
|
LineEntry BadFile(ValidAddr, InvalidFileIdx, ValidLine);
|
|
EXPECT_FALSE(BadFile.isValid());
|
|
// Test operators
|
|
LineEntry E1(ValidAddr, ValidFileIdx, ValidLine);
|
|
LineEntry E2(ValidAddr, ValidFileIdx, ValidLine);
|
|
LineEntry DifferentAddr(ValidAddr + 1, ValidFileIdx, ValidLine);
|
|
LineEntry DifferentFile(ValidAddr, ValidFileIdx + 1, ValidLine);
|
|
LineEntry DifferentLine(ValidAddr, ValidFileIdx, ValidLine + 1);
|
|
EXPECT_TRUE(E1.isValid());
|
|
EXPECT_EQ(E1, E2);
|
|
EXPECT_NE(E1, DifferentAddr);
|
|
EXPECT_NE(E1, DifferentFile);
|
|
EXPECT_NE(E1, DifferentLine);
|
|
EXPECT_LT(E1, DifferentAddr);
|
|
}
|
|
|
|
TEST(GSYMTest, TestStringTable) {
|
|
StringTable StrTab(StringRef("\0Hello\0World\0", 13));
|
|
// Test extracting strings from a string table.
|
|
EXPECT_EQ(StrTab.getString(0), "");
|
|
EXPECT_EQ(StrTab.getString(1), "Hello");
|
|
EXPECT_EQ(StrTab.getString(7), "World");
|
|
EXPECT_EQ(StrTab.getString(8), "orld");
|
|
// Test pointing to last NULL terminator gets empty string.
|
|
EXPECT_EQ(StrTab.getString(12), "");
|
|
// Test pointing to past end gets empty string.
|
|
EXPECT_EQ(StrTab.getString(13), "");
|
|
}
|
|
|
|
static void TestFileWriterHelper(llvm::endianness ByteOrder) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const int64_t MinSLEB = INT64_MIN;
|
|
const int64_t MaxSLEB = INT64_MAX;
|
|
const uint64_t MinULEB = 0;
|
|
const uint64_t MaxULEB = UINT64_MAX;
|
|
const uint8_t U8 = 0x10;
|
|
const uint16_t U16 = 0x1122;
|
|
const uint32_t U32 = 0x12345678;
|
|
const uint64_t U64 = 0x33445566778899aa;
|
|
const char *Hello = "hello";
|
|
FW.writeU8(U8);
|
|
FW.writeU16(U16);
|
|
FW.writeU32(U32);
|
|
FW.writeU64(U64);
|
|
FW.alignTo(16);
|
|
const off_t FixupOffset = FW.tell();
|
|
FW.writeU32(0);
|
|
FW.writeSLEB(MinSLEB);
|
|
FW.writeSLEB(MaxSLEB);
|
|
FW.writeULEB(MinULEB);
|
|
FW.writeULEB(MaxULEB);
|
|
FW.writeNullTerminated(Hello);
|
|
// Test Seek, Tell using Fixup32.
|
|
FW.fixup32(U32, FixupOffset);
|
|
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
uint64_t Offset = 0;
|
|
EXPECT_EQ(Data.getU8(&Offset), U8);
|
|
EXPECT_EQ(Data.getU16(&Offset), U16);
|
|
EXPECT_EQ(Data.getU32(&Offset), U32);
|
|
EXPECT_EQ(Data.getU64(&Offset), U64);
|
|
Offset = alignTo(Offset, 16);
|
|
EXPECT_EQ(Data.getU32(&Offset), U32);
|
|
EXPECT_EQ(Data.getSLEB128(&Offset), MinSLEB);
|
|
EXPECT_EQ(Data.getSLEB128(&Offset), MaxSLEB);
|
|
EXPECT_EQ(Data.getULEB128(&Offset), MinULEB);
|
|
EXPECT_EQ(Data.getULEB128(&Offset), MaxULEB);
|
|
EXPECT_EQ(Data.getCStrRef(&Offset), StringRef(Hello));
|
|
}
|
|
|
|
TEST(GSYMTest, TestFileWriter) {
|
|
TestFileWriterHelper(llvm::endianness::little);
|
|
TestFileWriterHelper(llvm::endianness::big);
|
|
}
|
|
|
|
TEST(GSYMTest, TestAddressRangeEncodeDecode) {
|
|
// Test encoding and decoding AddressRange objects. AddressRange objects
|
|
// are always stored as offsets from the a base address. The base address
|
|
// is the FunctionInfo's base address for function level ranges, and is
|
|
// the base address of the parent range for subranges.
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = 0x1000;
|
|
const AddressRange Range1(0x1000, 0x1010);
|
|
const AddressRange Range2(0x1020, 0x1030);
|
|
encodeRange(Range1, FW, BaseAddr);
|
|
encodeRange(Range2, FW, BaseAddr);
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
|
|
AddressRange DecodedRange1, DecodedRange2;
|
|
uint64_t Offset = 0;
|
|
DecodedRange1 = decodeRange(Data, BaseAddr, Offset);
|
|
DecodedRange2 = decodeRange(Data, BaseAddr, Offset);
|
|
EXPECT_EQ(Range1, DecodedRange1);
|
|
EXPECT_EQ(Range2, DecodedRange2);
|
|
}
|
|
|
|
static void TestAddressRangeEncodeDecodeHelper(const AddressRanges &Ranges,
|
|
const uint64_t BaseAddr) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
encodeRanges(Ranges, FW, BaseAddr);
|
|
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
|
|
AddressRanges DecodedRanges;
|
|
uint64_t Offset = 0;
|
|
decodeRanges(DecodedRanges, Data, BaseAddr, Offset);
|
|
EXPECT_EQ(Ranges, DecodedRanges);
|
|
}
|
|
|
|
TEST(GSYMTest, TestAddressRangesEncodeDecode) {
|
|
// Test encoding and decoding AddressRanges. AddressRanges objects contain
|
|
// ranges that are stored as offsets from the a base address. The base address
|
|
// is the FunctionInfo's base address for function level ranges, and is the
|
|
// base address of the parent range for subranges.
|
|
const uint64_t BaseAddr = 0x1000;
|
|
|
|
// Test encoding and decoding with no ranges.
|
|
AddressRanges Ranges;
|
|
TestAddressRangeEncodeDecodeHelper(Ranges, BaseAddr);
|
|
|
|
// Test encoding and decoding with 1 range.
|
|
Ranges.insert(AddressRange(0x1000, 0x1010));
|
|
TestAddressRangeEncodeDecodeHelper(Ranges, BaseAddr);
|
|
|
|
// Test encoding and decoding with multiple ranges.
|
|
Ranges.insert(AddressRange(0x1020, 0x1030));
|
|
Ranges.insert(AddressRange(0x1050, 0x1070));
|
|
TestAddressRangeEncodeDecodeHelper(Ranges, BaseAddr);
|
|
}
|
|
|
|
static void TestLineTableHelper(llvm::endianness ByteOrder,
|
|
const LineTable <) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = LT[0].Addr;
|
|
llvm::Error Err = LT.encode(FW, BaseAddr);
|
|
ASSERT_FALSE(Err);
|
|
std::string Bytes(OutStrm.str());
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<LineTable> Decoded = LineTable::decode(Data, BaseAddr);
|
|
// Make sure decoding succeeded.
|
|
ASSERT_TRUE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
EXPECT_EQ(LT, Decoded.get());
|
|
}
|
|
|
|
TEST(GSYMTest, TestLineTable) {
|
|
const uint64_t StartAddr = 0x1000;
|
|
const uint32_t FileIdx = 1;
|
|
LineTable LT;
|
|
LineEntry Line0(StartAddr+0x000, FileIdx, 10);
|
|
LineEntry Line1(StartAddr+0x010, FileIdx, 11);
|
|
LineEntry Line2(StartAddr+0x100, FileIdx, 1000);
|
|
ASSERT_TRUE(LT.empty());
|
|
ASSERT_EQ(LT.size(), (size_t)0);
|
|
LT.push(Line0);
|
|
ASSERT_EQ(LT.size(), (size_t)1);
|
|
LT.push(Line1);
|
|
LT.push(Line2);
|
|
LT.push(LineEntry(StartAddr+0x120, FileIdx, 900));
|
|
LT.push(LineEntry(StartAddr+0x120, FileIdx, 2000));
|
|
LT.push(LineEntry(StartAddr+0x121, FileIdx, 2001));
|
|
LT.push(LineEntry(StartAddr+0x122, FileIdx, 2002));
|
|
LT.push(LineEntry(StartAddr+0x123, FileIdx, 2003));
|
|
ASSERT_FALSE(LT.empty());
|
|
ASSERT_EQ(LT.size(), (size_t)8);
|
|
// Test operator[].
|
|
ASSERT_EQ(LT[0], Line0);
|
|
ASSERT_EQ(LT[1], Line1);
|
|
ASSERT_EQ(LT[2], Line2);
|
|
|
|
// Test encoding and decoding line tables.
|
|
TestLineTableHelper(llvm::endianness::little, LT);
|
|
TestLineTableHelper(llvm::endianness::big, LT);
|
|
|
|
// Verify the clear method works as expected.
|
|
LT.clear();
|
|
ASSERT_TRUE(LT.empty());
|
|
ASSERT_EQ(LT.size(), (size_t)0);
|
|
|
|
LineTable LT1;
|
|
LineTable LT2;
|
|
|
|
// Test that two empty line tables are equal and neither are less than
|
|
// each other.
|
|
ASSERT_EQ(LT1, LT2);
|
|
ASSERT_FALSE(LT1 < LT1);
|
|
ASSERT_FALSE(LT1 < LT2);
|
|
ASSERT_FALSE(LT2 < LT1);
|
|
ASSERT_FALSE(LT2 < LT2);
|
|
|
|
// Test that a line table with less number of line entries is less than a
|
|
// line table with more line entries and that they are not equal.
|
|
LT2.push(Line0);
|
|
ASSERT_LT(LT1, LT2);
|
|
ASSERT_NE(LT1, LT2);
|
|
|
|
// Test that two line tables with the same entries are equal.
|
|
LT1.push(Line0);
|
|
ASSERT_EQ(LT1, LT2);
|
|
ASSERT_FALSE(LT1 < LT2);
|
|
ASSERT_FALSE(LT2 < LT2);
|
|
}
|
|
|
|
static void TestLineTableDecodeError(llvm::endianness ByteOrder,
|
|
StringRef Bytes, const uint64_t BaseAddr,
|
|
std::string ExpectedErrorMsg) {
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<LineTable> Decoded = LineTable::decode(Data, BaseAddr);
|
|
// Make sure decoding fails.
|
|
ASSERT_FALSE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
checkError(ExpectedErrorMsg, Decoded.takeError());
|
|
}
|
|
|
|
TEST(GSYMTest, TestLineTableDecodeErrors) {
|
|
// Test decoding InlineInfo objects that ensure we report an appropriate
|
|
// error message.
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
const uint64_t BaseAddr = 0x100;
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000000: missing LineTable MinDelta");
|
|
FW.writeU8(1); // MinDelta (ULEB)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000001: missing LineTable MaxDelta");
|
|
FW.writeU8(10); // MaxDelta (ULEB)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000002: missing LineTable FirstLine");
|
|
FW.writeU8(20); // FirstLine (ULEB)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000003: EOF found before EndSequence");
|
|
// Test a SetFile with the argument missing from the stream
|
|
FW.writeU8(1); // SetFile opcode (uint8_t)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000004: EOF found before SetFile value");
|
|
FW.writeU8(5); // SetFile value as index (ULEB)
|
|
// Test a AdvancePC with the argument missing from the stream
|
|
FW.writeU8(2); // AdvancePC opcode (uint8_t)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000006: EOF found before AdvancePC value");
|
|
FW.writeU8(20); // AdvancePC value as offset (ULEB)
|
|
// Test a AdvancePC with the argument missing from the stream
|
|
FW.writeU8(3); // AdvanceLine opcode (uint8_t)
|
|
TestLineTableDecodeError(ByteOrder, OutStrm.str(), BaseAddr,
|
|
"0x00000008: EOF found before AdvanceLine value");
|
|
FW.writeU8(20); // AdvanceLine value as offset (LLEB)
|
|
}
|
|
|
|
TEST(GSYMTest, TestLineTableEncodeErrors) {
|
|
const uint64_t BaseAddr = 0x1000;
|
|
const uint32_t FileIdx = 1;
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
LineTable LT;
|
|
checkError("attempted to encode invalid LineTable object",
|
|
LT.encode(FW, BaseAddr));
|
|
|
|
// Try to encode a line table where a line entry has an address that is less
|
|
// than BaseAddr and verify we get an appropriate error.
|
|
LineEntry Line0(BaseAddr+0x000, FileIdx, 10);
|
|
LineEntry Line1(BaseAddr+0x010, FileIdx, 11);
|
|
LT.push(Line0);
|
|
LT.push(Line1);
|
|
checkError("LineEntry has address 0x1000 which is less than the function "
|
|
"start address 0x1010", LT.encode(FW, BaseAddr+0x10));
|
|
LT.clear();
|
|
|
|
// Try to encode a line table where a line entries has an address that is less
|
|
// than BaseAddr and verify we get an appropriate error.
|
|
LT.push(Line1);
|
|
LT.push(Line0);
|
|
checkError("LineEntry in LineTable not in ascending order",
|
|
LT.encode(FW, BaseAddr));
|
|
LT.clear();
|
|
}
|
|
|
|
static void TestHeaderEncodeError(const Header &H,
|
|
std::string ExpectedErrorMsg) {
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = H.encode(FW);
|
|
checkError(ExpectedErrorMsg, std::move(Err));
|
|
}
|
|
|
|
static void TestHeaderDecodeError(StringRef Bytes,
|
|
std::string ExpectedErrorMsg) {
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
uint8_t AddressSize = 4;
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<Header> Decoded = Header::decode(Data);
|
|
// Make sure decoding fails.
|
|
ASSERT_FALSE((bool)Decoded);
|
|
// Make sure decoded object is the same as the one we encoded.
|
|
checkError(ExpectedErrorMsg, Decoded.takeError());
|
|
}
|
|
|
|
// Populate a GSYM header with valid values.
|
|
static void InitHeader(Header &H) {
|
|
H.Magic = GSYM_MAGIC;
|
|
H.Version = GSYM_VERSION;
|
|
H.AddrOffSize = 4;
|
|
H.UUIDSize = 16;
|
|
H.BaseAddress = 0x1000;
|
|
H.NumAddresses = 1;
|
|
H.StrtabOffset= 0x2000;
|
|
H.StrtabSize = 0x1000;
|
|
for (size_t i=0; i<GSYM_MAX_UUID_SIZE; ++i) {
|
|
if (i < H.UUIDSize)
|
|
H.UUID[i] = i;
|
|
else
|
|
H.UUID[i] = 0;
|
|
}
|
|
}
|
|
|
|
TEST(GSYMTest, TestHeaderEncodeErrors) {
|
|
Header H;
|
|
InitHeader(H);
|
|
H.Magic = 12;
|
|
TestHeaderEncodeError(H, "invalid GSYM magic 0x0000000c");
|
|
InitHeader(H);
|
|
H.Version = 12;
|
|
TestHeaderEncodeError(H, "unsupported GSYM version 12");
|
|
InitHeader(H);
|
|
H.AddrOffSize = 12;
|
|
TestHeaderEncodeError(H, "invalid address offset size 12");
|
|
InitHeader(H);
|
|
H.UUIDSize = 128;
|
|
TestHeaderEncodeError(H, "invalid UUID size 128");
|
|
}
|
|
|
|
TEST(GSYMTest, TestHeaderDecodeErrors) {
|
|
const llvm::endianness ByteOrder = llvm::endianness::little;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
Header H;
|
|
InitHeader(H);
|
|
llvm::Error Err = H.encode(FW);
|
|
ASSERT_FALSE(Err);
|
|
FW.fixup32(12, offsetof(Header, Magic));
|
|
TestHeaderDecodeError(OutStrm.str(), "invalid GSYM magic 0x0000000c");
|
|
FW.fixup32(GSYM_MAGIC, offsetof(Header, Magic));
|
|
FW.fixup32(12, offsetof(Header, Version));
|
|
TestHeaderDecodeError(OutStrm.str(), "unsupported GSYM version 12");
|
|
FW.fixup32(GSYM_VERSION, offsetof(Header, Version));
|
|
FW.fixup32(12, offsetof(Header, AddrOffSize));
|
|
TestHeaderDecodeError(OutStrm.str(), "invalid address offset size 12");
|
|
FW.fixup32(4, offsetof(Header, AddrOffSize));
|
|
FW.fixup32(128, offsetof(Header, UUIDSize));
|
|
TestHeaderDecodeError(OutStrm.str(), "invalid UUID size 128");
|
|
}
|
|
|
|
static void TestHeaderEncodeDecode(const Header &H,
|
|
llvm::endianness ByteOrder) {
|
|
uint8_t AddressSize = 4;
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = H.encode(FW);
|
|
ASSERT_FALSE(Err);
|
|
std::string Bytes(OutStrm.str());
|
|
DataExtractor Data(Bytes, ByteOrder == llvm::endianness::little, AddressSize);
|
|
llvm::Expected<Header> Decoded = Header::decode(Data);
|
|
// Make sure decoding succeeded.
|
|
ASSERT_TRUE((bool)Decoded);
|
|
EXPECT_EQ(H, Decoded.get());
|
|
}
|
|
TEST(GSYMTest, TestHeaderEncodeDecode) {
|
|
Header H;
|
|
InitHeader(H);
|
|
TestHeaderEncodeDecode(H, llvm::endianness::little);
|
|
TestHeaderEncodeDecode(H, llvm::endianness::big);
|
|
}
|
|
|
|
static void TestGsymCreatorEncodeError(llvm::endianness ByteOrder,
|
|
const GsymCreator &GC,
|
|
std::string ExpectedErrorMsg) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = GC.encode(FW);
|
|
ASSERT_TRUE(bool(Err));
|
|
checkError(ExpectedErrorMsg, std::move(Err));
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreatorEncodeErrors) {
|
|
const uint8_t ValidUUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
|
14, 15, 16};
|
|
const uint8_t InvalidUUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
|
14, 15, 16, 17, 18, 19, 20, 21};
|
|
// Verify we get an error when trying to encode an GsymCreator with no
|
|
// function infos. We shouldn't be saving a GSYM file in this case since
|
|
// there is nothing inside of it.
|
|
GsymCreator GC;
|
|
TestGsymCreatorEncodeError(llvm::endianness::little, GC,
|
|
"no functions to encode");
|
|
const uint64_t FuncAddr = 0x1000;
|
|
const uint64_t FuncSize = 0x100;
|
|
const uint32_t FuncName = GC.insertString("foo");
|
|
// Verify we get an error trying to encode a GsymCreator that isn't
|
|
// finalized.
|
|
GC.addFunctionInfo(FunctionInfo(FuncAddr, FuncSize, FuncName));
|
|
TestGsymCreatorEncodeError(llvm::endianness::little, GC,
|
|
"GsymCreator wasn't finalized prior to encoding");
|
|
std::string finalizeIssues;
|
|
raw_string_ostream OS(finalizeIssues);
|
|
OutputAggregator Agg(&OS);
|
|
llvm::Error finalizeErr = GC.finalize(Agg);
|
|
ASSERT_FALSE(bool(finalizeErr));
|
|
finalizeErr = GC.finalize(Agg);
|
|
ASSERT_TRUE(bool(finalizeErr));
|
|
checkError("already finalized", std::move(finalizeErr));
|
|
// Verify we get an error trying to encode a GsymCreator with a UUID that is
|
|
// too long.
|
|
GC.setUUID(InvalidUUID);
|
|
TestGsymCreatorEncodeError(llvm::endianness::little, GC,
|
|
"invalid UUID size 21");
|
|
GC.setUUID(ValidUUID);
|
|
// Verify errors are propagated when we try to encoding an invalid line
|
|
// table.
|
|
GC.forEachFunctionInfo([](FunctionInfo &FI) -> bool {
|
|
FI.OptLineTable = LineTable(); // Invalid line table.
|
|
return false; // Stop iterating
|
|
});
|
|
TestGsymCreatorEncodeError(llvm::endianness::little, GC,
|
|
"attempted to encode invalid LineTable object");
|
|
// Verify errors are propagated when we try to encoding an invalid inline
|
|
// info.
|
|
GC.forEachFunctionInfo([](FunctionInfo &FI) -> bool {
|
|
FI.OptLineTable = std::nullopt;
|
|
FI.Inline = InlineInfo(); // Invalid InlineInfo.
|
|
return false; // Stop iterating
|
|
});
|
|
TestGsymCreatorEncodeError(llvm::endianness::little, GC,
|
|
"attempted to encode invalid InlineInfo object");
|
|
}
|
|
|
|
static void Compare(const GsymCreator &GC, const GsymReader &GR) {
|
|
// Verify that all of the data in a GsymCreator is correctly decoded from
|
|
// a GsymReader. To do this, we iterator over
|
|
GC.forEachFunctionInfo([&](const FunctionInfo &FI) -> bool {
|
|
auto DecodedFI = GR.getFunctionInfo(FI.Range.start());
|
|
EXPECT_TRUE(bool(DecodedFI));
|
|
EXPECT_EQ(FI, *DecodedFI);
|
|
return true; // Keep iterating over all FunctionInfo objects.
|
|
});
|
|
}
|
|
|
|
static void TestEncodeDecode(const GsymCreator &GC, llvm::endianness ByteOrder,
|
|
uint16_t Version, uint8_t AddrOffSize,
|
|
uint64_t BaseAddress, uint32_t NumAddresses,
|
|
ArrayRef<uint8_t> UUID) {
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = GC.encode(FW);
|
|
ASSERT_FALSE((bool)Err);
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_TRUE(bool(GR));
|
|
const Header &Hdr = GR->getHeader();
|
|
EXPECT_EQ(Hdr.Version, Version);
|
|
EXPECT_EQ(Hdr.AddrOffSize, AddrOffSize);
|
|
EXPECT_EQ(Hdr.UUIDSize, UUID.size());
|
|
EXPECT_EQ(Hdr.BaseAddress, BaseAddress);
|
|
EXPECT_EQ(Hdr.NumAddresses, NumAddresses);
|
|
EXPECT_EQ(ArrayRef<uint8_t>(Hdr.UUID, Hdr.UUIDSize), UUID);
|
|
Compare(GC, GR.get());
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreator1ByteAddrOffsets) {
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint8_t AddrOffSize = 1;
|
|
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));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(Err);
|
|
TestEncodeDecode(GC, llvm::endianness::little, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
TestEncodeDecode(GC, llvm::endianness::big, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreator2ByteAddrOffsets) {
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint8_t AddrOffSize = 2;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x000, 0x100, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x200, 0x100, Func2Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(Err);
|
|
TestEncodeDecode(GC, llvm::endianness::little, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
TestEncodeDecode(GC, llvm::endianness::big, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreator4ByteAddrOffsets) {
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint8_t AddrOffSize = 4;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x000, 0x100, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x20000, 0x100, Func2Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(Err);
|
|
TestEncodeDecode(GC, llvm::endianness::little, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
TestEncodeDecode(GC, llvm::endianness::big, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreator8ByteAddrOffsets) {
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint8_t AddrOffSize = 8;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x000, 0x100, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr+0x100000000, 0x100, Func2Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(Err);
|
|
TestEncodeDecode(GC, llvm::endianness::little, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
TestEncodeDecode(GC, llvm::endianness::big, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
2, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
}
|
|
|
|
static void VerifyFunctionInfo(const GsymReader &GR, uint64_t Addr,
|
|
const FunctionInfo &FI) {
|
|
auto ExpFI = GR.getFunctionInfo(Addr);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(FI, ExpFI.get());
|
|
}
|
|
|
|
static void VerifyFunctionInfoError(const GsymReader &GR, uint64_t Addr,
|
|
std::string ErrMessage) {
|
|
auto ExpFI = GR.getFunctionInfo(Addr);
|
|
ASSERT_FALSE(bool(ExpFI));
|
|
checkError(ErrMessage, ExpFI.takeError());
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymReader) {
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint64_t Func1Addr = BaseAddr;
|
|
constexpr uint64_t Func2Addr = BaseAddr+0x20;
|
|
constexpr uint64_t FuncSize = 0x10;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
GC.addFunctionInfo(FunctionInfo(Func1Addr, FuncSize, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(Func2Addr, FuncSize, Func2Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error FinalizeErr = GC.finalize(Null);
|
|
ASSERT_FALSE(FinalizeErr);
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = GC.encode(FW);
|
|
ASSERT_FALSE((bool)Err);
|
|
if (auto ExpectedGR = GsymReader::copyBuffer(OutStrm.str())) {
|
|
const GsymReader &GR = ExpectedGR.get();
|
|
VerifyFunctionInfoError(GR, Func1Addr-1, "address 0xfff is not in GSYM");
|
|
|
|
FunctionInfo Func1(Func1Addr, FuncSize, Func1Name);
|
|
VerifyFunctionInfo(GR, Func1Addr, Func1);
|
|
VerifyFunctionInfo(GR, Func1Addr+1, Func1);
|
|
VerifyFunctionInfo(GR, Func1Addr+FuncSize-1, Func1);
|
|
VerifyFunctionInfoError(GR, Func1Addr+FuncSize,
|
|
"address 0x1010 is not in GSYM");
|
|
VerifyFunctionInfoError(GR, Func2Addr-1, "address 0x101f is not in GSYM");
|
|
FunctionInfo Func2(Func2Addr, FuncSize, Func2Name);
|
|
VerifyFunctionInfo(GR, Func2Addr, Func2);
|
|
VerifyFunctionInfo(GR, Func2Addr+1, Func2);
|
|
VerifyFunctionInfo(GR, Func2Addr+FuncSize-1, Func2);
|
|
VerifyFunctionInfoError(GR, Func2Addr+FuncSize,
|
|
"address 0x1030 is not in GSYM");
|
|
}
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymLookups) {
|
|
// Test creating a GSYM file with a function that has a inline information.
|
|
// Verify that lookups work correctly. Lookups do not decode the entire
|
|
// FunctionInfo or InlineInfo, they only extract information needed for the
|
|
// lookup to happen which avoids allocations which can slow down
|
|
// symbolication.
|
|
GsymCreator GC;
|
|
FunctionInfo FI(0x1000, 0x100, GC.insertString("main"));
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FI.OptLineTable = LineTable();
|
|
const uint32_t MainFileIndex = GC.insertFile("/tmp/main.c");
|
|
const uint32_t FooFileIndex = GC.insertFile("/tmp/foo.h");
|
|
FI.OptLineTable->push(LineEntry(0x1000, MainFileIndex, 5));
|
|
FI.OptLineTable->push(LineEntry(0x1010, FooFileIndex, 10));
|
|
FI.OptLineTable->push(LineEntry(0x1012, FooFileIndex, 20));
|
|
FI.OptLineTable->push(LineEntry(0x1014, FooFileIndex, 11));
|
|
FI.OptLineTable->push(LineEntry(0x1016, FooFileIndex, 30));
|
|
FI.OptLineTable->push(LineEntry(0x1018, FooFileIndex, 12));
|
|
FI.OptLineTable->push(LineEntry(0x1020, MainFileIndex, 8));
|
|
FI.Inline = InlineInfo();
|
|
|
|
FI.Inline->Name = GC.insertString("inline1");
|
|
FI.Inline->CallFile = MainFileIndex;
|
|
FI.Inline->CallLine = 6;
|
|
FI.Inline->Ranges.insert(AddressRange(0x1010, 0x1020));
|
|
InlineInfo Inline2;
|
|
Inline2.Name = GC.insertString("inline2");
|
|
Inline2.CallFile = FooFileIndex;
|
|
Inline2.CallLine = 33;
|
|
Inline2.Ranges.insert(AddressRange(0x1012, 0x1014));
|
|
FI.Inline->Children.emplace_back(Inline2);
|
|
InlineInfo Inline3;
|
|
Inline3.Name = GC.insertString("inline3");
|
|
Inline3.CallFile = FooFileIndex;
|
|
Inline3.CallLine = 35;
|
|
Inline3.Ranges.insert(AddressRange(0x1016, 0x1018));
|
|
FI.Inline->Children.emplace_back(Inline3);
|
|
GC.addFunctionInfo(std::move(FI));
|
|
OutputAggregator Null(nullptr);
|
|
Error FinalizeErr = GC.finalize(Null);
|
|
ASSERT_FALSE(FinalizeErr);
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = GC.encode(FW);
|
|
ASSERT_FALSE((bool)Err);
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_TRUE(bool(GR));
|
|
|
|
// Verify inline info is correct when doing lookups.
|
|
auto LR = GR->lookup(0x1000);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 5}));
|
|
LR = GR->lookup(0x100F);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 5, 15}));
|
|
|
|
LR = GR->lookup(0x1010);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "foo.h", 10},
|
|
SourceLocation{"main", "/tmp", "main.c", 6, 16}));
|
|
|
|
LR = GR->lookup(0x1012);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline2", "/tmp", "foo.h", 20},
|
|
SourceLocation{"inline1", "/tmp", "foo.h", 33, 2},
|
|
SourceLocation{"main", "/tmp", "main.c", 6, 18}));
|
|
|
|
LR = GR->lookup(0x1014);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "foo.h", 11, 4},
|
|
SourceLocation{"main", "/tmp", "main.c", 6, 20}));
|
|
|
|
LR = GR->lookup(0x1016);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline3", "/tmp", "foo.h", 30},
|
|
SourceLocation{"inline1", "/tmp", "foo.h", 35, 6},
|
|
SourceLocation{"main", "/tmp", "main.c", 6, 22}));
|
|
|
|
LR = GR->lookup(0x1018);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "foo.h", 12, 8},
|
|
SourceLocation{"main", "/tmp", "main.c", 6, 24}));
|
|
|
|
LR = GR->lookup(0x1020);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 8, 32}));
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestDWARFFunctionWithAddresses) {
|
|
// Create a single compile unit with a single function and make sure it gets
|
|
// converted to DWARF correctly. The function's address range is in where
|
|
// DW_AT_low_pc and DW_AT_high_pc are both addresses.
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- /tmp/main.c
|
|
- main
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000002000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000002000
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFFunctionWithAddressAndOffset) {
|
|
// Create a single compile unit with a single function and make sure it gets
|
|
// converted to DWARF correctly. The function's address range is in where
|
|
// DW_AT_low_pc is an address and the DW_AT_high_pc is an offset.
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- /tmp/main.c
|
|
- main
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFStructMethodNoMangled) {
|
|
// Sometimes the compiler will omit the mangled name in the DWARF for static
|
|
// and member functions of classes and structs. This test verifies that the
|
|
// fully qualified name of the method is computed and used as the string for
|
|
// the function in the GSYM in these cases. Otherwise we might just get a
|
|
// function name like "erase" instead of "std::vector<int>::erase".
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- /tmp/main.c
|
|
- Foo
|
|
- dump
|
|
- this
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_structure_type
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Code: 0x00000003
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Code: 0x00000004
|
|
Tag: DW_TAG_formal_parameter
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_type
|
|
Form: DW_FORM_ref4
|
|
- Attribute: DW_AT_artificial
|
|
Form: DW_FORM_flag_present
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000002000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x0000000000000011
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000002000
|
|
- AbbrCode: 0x00000004
|
|
Values:
|
|
- Value: 0x0000000000000016
|
|
- Value: 0x0000000000000022
|
|
- Value: 0x0000000000000001
|
|
- AbbrCode: 0x00000000
|
|
- AbbrCode: 0x00000000
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "Foo::dump");
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFTextRanges) {
|
|
// Linkers don't understand DWARF, they just like to concatenate and
|
|
// relocate data within the DWARF sections. This means that if a function
|
|
// gets dead stripped, and if those functions use an offset as the
|
|
// DW_AT_high_pc, we can end up with many functions at address zero. The
|
|
// DwarfTransformer allows clients to specify valid .text address ranges
|
|
// and any addresses of any functions must fall within those ranges if any
|
|
// have been specified. This means that an object file can calcuate the
|
|
// address ranges within the binary where code lives and set these ranges
|
|
// as constraints in the DwarfTransformer. ObjectFile instances can
|
|
// add a address ranges of sections that have executable permissions. This
|
|
// keeps bad information from being added to a GSYM file and causing issues
|
|
// when symbolicating.
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- /tmp/main.c
|
|
- main
|
|
- dead_stripped
|
|
- dead_stripped2
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x0000000000000012
|
|
- Value: 0x0000000000000000
|
|
- Value: 0x0000000000000100
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x0000000000000020
|
|
- Value: 0x0000000000000000
|
|
- Value: 0x0000000000000040
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
// Only allow addresses between [0x1000 - 0x2000) to be linked into the
|
|
// GSYM.
|
|
AddressRanges TextRanges;
|
|
TextRanges.insert(AddressRange(0x1000, 0x2000));
|
|
GC.SetValidTextRanges(TextRanges);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "main");
|
|
}
|
|
|
|
TEST(GSYMTest, TestEmptySymbolEndAddressOfTextRanges) {
|
|
// Test that if we have valid text ranges and we have a symbol with no size
|
|
// as the last FunctionInfo entry that the size of the symbol gets set to the
|
|
// end address of the text range.
|
|
GsymCreator GC;
|
|
AddressRanges TextRanges;
|
|
TextRanges.insert(AddressRange(0x1000, 0x2000));
|
|
GC.SetValidTextRanges(TextRanges);
|
|
GC.addFunctionInfo(FunctionInfo(0x1500, 0, GC.insertString("symbol")));
|
|
OutputAggregator Null(nullptr);
|
|
ASSERT_THAT_ERROR(GC.finalize(Null), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1500);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1500, 0x2000));
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "symbol");
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFInlineInfo) {
|
|
// Make sure we parse the line table and inline information correctly from
|
|
// DWARF.
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- /tmp/main.c
|
|
- main
|
|
- inline1
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Code: 0x00000003
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- Value: 0x0000000000000000
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x0000000000000012
|
|
- Value: 0x0000000000001100
|
|
- Value: 0x0000000000000100
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x000000000000000A
|
|
- AbbrCode: 0x00000000
|
|
- AbbrCode: 0x00000000
|
|
debug_line:
|
|
- Length: 96
|
|
Version: 2
|
|
PrologueLength: 46
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- /tmp
|
|
Files:
|
|
- Name: main.c
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
- Name: inline.h
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 4096
|
|
- Opcode: DW_LNS_copy
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 256
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 2
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 10
|
|
Data: 2
|
|
- Opcode: DW_LNS_copy
|
|
Data: 2
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 128
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 128
|
|
- Opcode: DW_LNS_copy
|
|
Data: 128
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 128
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 1
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: -10
|
|
Data: 1
|
|
- Opcode: DW_LNS_copy
|
|
Data: 1
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 3584
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 3584
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 3584
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_TRUE(ExpFI->Inline.has_value());
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "main");
|
|
|
|
// Verify inline info is correct when doing lookups.
|
|
auto LR = GR->lookup(0x1000);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 10}));
|
|
LR = GR->lookup(0x1100-1);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 10, 255}));
|
|
|
|
LR = GR->lookup(0x1100);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "inline.h", 20},
|
|
SourceLocation{"main", "/tmp", "main.c", 10, 256}));
|
|
LR = GR->lookup(0x1180-1);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "inline.h", 20, 127},
|
|
SourceLocation{"main", "/tmp", "main.c", 10, 383}));
|
|
LR = GR->lookup(0x1180);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "inline.h", 21, 128},
|
|
SourceLocation{"main", "/tmp", "main.c", 10, 384}));
|
|
LR = GR->lookup(0x1200-1);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"inline1", "/tmp", "inline.h", 21, 255},
|
|
SourceLocation{"main", "/tmp", "main.c", 10, 511}));
|
|
LR = GR->lookup(0x1200);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
EXPECT_THAT(LR->Locations,
|
|
testing::ElementsAre(SourceLocation{"main", "/tmp", "main.c", 11, 512}));
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestDWARFNoLines) {
|
|
// Check that if a DW_TAG_subprogram doesn't have line table entries that
|
|
// we fall back and use the DW_AT_decl_file and DW_AT_decl_line to at least
|
|
// point to the function definition. This DWARF file has 4 functions:
|
|
// "lines_no_decl": has line table entries, no DW_AT_decl_file/line attrs.
|
|
// "lines_with_decl": has line table entries and has DW_AT_decl_file/line,
|
|
// make sure we don't use DW_AT_decl_file/line and make
|
|
// sure there is a line table.
|
|
// "no_lines_no_decl": no line table entries and no DW_AT_decl_file/line,
|
|
// make sure there is no line table for this function.
|
|
// "no_lines_with_decl": no line table and has DW_AT_decl_file/line, make
|
|
// sure we have one line table entry that starts at
|
|
// the function start address and the decl file and
|
|
// line.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.c")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
// DW_AT_language (DW_LANG_C_plus_plus)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000022: DW_TAG_subprogram
|
|
// DW_AT_name ("lines_no_decl")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
//
|
|
// 0x00000033: DW_TAG_subprogram
|
|
// DW_AT_name ("lines_with_decl")
|
|
// DW_AT_low_pc (0x0000000000002000)
|
|
// DW_AT_high_pc (0x0000000000003000)
|
|
// DW_AT_decl_file ("/tmp/main.c")
|
|
// DW_AT_decl_line (20)
|
|
//
|
|
// 0x00000046: DW_TAG_subprogram
|
|
// DW_AT_name ("no_lines_no_decl")
|
|
// DW_AT_low_pc (0x0000000000003000)
|
|
// DW_AT_high_pc (0x0000000000004000)
|
|
//
|
|
// 0x00000057: DW_TAG_subprogram
|
|
// DW_AT_name ("no_lines_with_decl")
|
|
// DW_AT_low_pc (0x0000000000004000)
|
|
// DW_AT_high_pc (0x0000000000005000)
|
|
// DW_AT_decl_file ("/tmp/main.c")
|
|
// DW_AT_decl_line (40)
|
|
//
|
|
// 0x0000006a: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.c'
|
|
- lines_no_decl
|
|
- lines_with_decl
|
|
- no_lines_no_decl
|
|
- no_lines_with_decl
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Code: 0x00000003
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_decl_file
|
|
Form: DW_FORM_data1
|
|
- Attribute: DW_AT_decl_line
|
|
Form: DW_FORM_data1
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- Value: 0x0000000000000000
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x000000000000001B
|
|
- Value: 0x0000000000002000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000000014
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000002B
|
|
- Value: 0x0000000000003000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x000000000000003C
|
|
- Value: 0x0000000000004000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000000028
|
|
- AbbrCode: 0x00000000
|
|
debug_line:
|
|
- Length: 92
|
|
Version: 2
|
|
PrologueLength: 34
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.c
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 10
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 512
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 3584
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 8192
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 20
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 512
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 3584
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
|
|
EXPECT_EQ(GR->getNumAddresses(), 4u);
|
|
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_TRUE(ExpFI->OptLineTable);
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "lines_no_decl");
|
|
// Make sure have two line table entries and that get the first line entry
|
|
// correct.
|
|
EXPECT_EQ(ExpFI->OptLineTable->size(), 2u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Addr, 0x1000u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Line, 11u);
|
|
|
|
ExpFI = GR->getFunctionInfo(0x2000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x2000, 0x3000));
|
|
EXPECT_TRUE(ExpFI->OptLineTable);
|
|
MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "lines_with_decl");
|
|
// Make sure have two line table entries and that we don't use line 20
|
|
// from the DW_AT_decl_file/line as a line table entry.
|
|
EXPECT_EQ(ExpFI->OptLineTable->size(), 2u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Addr, 0x2000u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Line, 21u);
|
|
|
|
ExpFI = GR->getFunctionInfo(0x3000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x3000, 0x4000));
|
|
// Make sure we have no line table.
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "no_lines_no_decl");
|
|
|
|
ExpFI = GR->getFunctionInfo(0x4000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x4000, 0x5000));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "no_lines_with_decl");
|
|
// Make sure we have one line table entry that uses the DW_AT_decl_file/line
|
|
// as the one and only line entry.
|
|
EXPECT_EQ(ExpFI->OptLineTable->size(), 1u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Addr, 0x4000u);
|
|
EXPECT_EQ(ExpFI->OptLineTable->first()->Line, 40u);
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestDWARFDeadStripAddr4) {
|
|
// Check that various techniques that compilers use for dead code stripping
|
|
// work for 4 byte addresses. Make sure we keep the good functions and
|
|
// strip any functions whose name starts with "stripped".
|
|
//
|
|
// 1 - Compilers might set the low PC to -1 (UINT32_MAX) for compile unit
|
|
// with 4 byte addresses ("stripped1")
|
|
// 2 - Set the low and high PC to the same value ("stripped2")
|
|
// 3 - Have the high PC lower than the low PC ("stripped3")
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.c")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
// DW_AT_language (DW_LANG_C_plus_plus)
|
|
//
|
|
// 0x0000001a: DW_TAG_subprogram
|
|
// DW_AT_name ("main")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
//
|
|
// 0x00000027: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped1")
|
|
// DW_AT_low_pc (0x00000000ffffffff)
|
|
// DW_AT_high_pc (0x0000000100000000)
|
|
//
|
|
// 0x00000034: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped2")
|
|
// DW_AT_low_pc (0x0000000000003000)
|
|
// DW_AT_high_pc (0x0000000000003000)
|
|
//
|
|
// 0x00000041: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped3")
|
|
// DW_AT_low_pc (0x0000000000004000)
|
|
// DW_AT_high_pc (0x0000000000003fff)
|
|
//
|
|
// 0x0000004e: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.c'
|
|
- main
|
|
- stripped1
|
|
- stripped2
|
|
- stripped3
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Code: 0x00000003
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 4
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x0000000000000012
|
|
- Value: 0x00000000FFFFFFFF
|
|
- Value: 0x0000000000000001
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x000000000000001C
|
|
- Value: 0x0000000000003000
|
|
- Value: 0x0000000000003000
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x0000000000000026
|
|
- Value: 0x0000000000004000
|
|
- Value: 0x0000000000003FFF
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 4);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
|
|
// Test that the only function that made it was the "main" function.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "main");
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFDeadStripAddr8) {
|
|
// Check that various techniques that compilers use for dead code stripping
|
|
// work for 4 byte addresses. Make sure we keep the good functions and
|
|
// strip any functions whose name starts with "stripped".
|
|
//
|
|
// 1 - Compilers might set the low PC to -1 (UINT64_MAX) for compile unit
|
|
// with 8 byte addresses ("stripped1")
|
|
// 2 - Set the low and high PC to the same value ("stripped2")
|
|
// 3 - Have the high PC lower than the low PC ("stripped3")
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.c")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
// DW_AT_language (DW_LANG_C_plus_plus)
|
|
//
|
|
// 0x0000001e: DW_TAG_subprogram
|
|
// DW_AT_name ("main")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
//
|
|
// 0x0000002f: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped1")
|
|
// DW_AT_low_pc (0xffffffffffffffff)
|
|
// DW_AT_high_pc (0x0000000000000000)
|
|
//
|
|
// 0x00000040: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped2")
|
|
// DW_AT_low_pc (0x0000000000003000)
|
|
// DW_AT_high_pc (0x0000000000003000)
|
|
//
|
|
// 0x00000055: DW_TAG_subprogram
|
|
// DW_AT_name ("stripped3")
|
|
// DW_AT_low_pc (0x0000000000004000)
|
|
// DW_AT_high_pc (0x0000000000003fff)
|
|
//
|
|
// 0x0000006a: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.c'
|
|
- main
|
|
- stripped1
|
|
- stripped2
|
|
- stripped3
|
|
debug_abbrev:
|
|
- Table:
|
|
- Code: 0x00000001
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_data2
|
|
- Code: 0x00000002
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_data4
|
|
- Code: 0x00000003
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Version: 4
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x00000001
|
|
Values:
|
|
- Value: 0x0000000000000001
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000000004
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x000000000000000D
|
|
- Value: 0x0000000000001000
|
|
- Value: 0x0000000000001000
|
|
- AbbrCode: 0x00000002
|
|
Values:
|
|
- Value: 0x0000000000000012
|
|
- Value: 0xFFFFFFFFFFFFFFFF
|
|
- Value: 0x0000000000000001
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x000000000000001C
|
|
- Value: 0x0000000000003000
|
|
- Value: 0x0000000000003000
|
|
- AbbrCode: 0x00000003
|
|
Values:
|
|
- Value: 0x0000000000000026
|
|
- Value: 0x0000000000004000
|
|
- Value: 0x0000000000003FFF
|
|
- AbbrCode: 0x00000000
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
auto &OS = llvm::nulls();
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
|
|
// Test that the only function that made it was the "main" function.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
StringRef MethodName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(MethodName, "main");
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymCreatorMultipleSymbolsWithNoSize) {
|
|
// Multiple symbols at the same address with zero size were being emitted
|
|
// instead of being combined into a single entry. This function tests to make
|
|
// sure we only get one symbol.
|
|
uint8_t UUID[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
|
GsymCreator GC;
|
|
GC.setUUID(UUID);
|
|
constexpr uint64_t BaseAddr = 0x1000;
|
|
constexpr uint8_t AddrOffSize = 1;
|
|
const uint32_t Func1Name = GC.insertString("foo");
|
|
const uint32_t Func2Name = GC.insertString("bar");
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr, 0, Func1Name));
|
|
GC.addFunctionInfo(FunctionInfo(BaseAddr, 0, Func2Name));
|
|
OutputAggregator Null(nullptr);
|
|
Error Err = GC.finalize(Null);
|
|
ASSERT_FALSE(Err);
|
|
TestEncodeDecode(GC, llvm::endianness::little, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
1, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
TestEncodeDecode(GC, llvm::endianness::big, GSYM_VERSION, AddrOffSize,
|
|
BaseAddr,
|
|
1, // NumAddresses
|
|
ArrayRef<uint8_t>(UUID));
|
|
}
|
|
|
|
// Helper function to quickly create a FunctionInfo in a GsymCreator for testing.
|
|
static void AddFunctionInfo(GsymCreator &GC, const char *FuncName,
|
|
uint64_t FuncAddr, const char *SourcePath,
|
|
const char *HeaderPath) {
|
|
FunctionInfo FI(FuncAddr, 0x30, GC.insertString(FuncName));
|
|
FI.OptLineTable = LineTable();
|
|
const uint32_t SourceFileIdx = GC.insertFile(SourcePath);
|
|
const uint32_t HeaderFileIdx = GC.insertFile(HeaderPath);
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x00, SourceFileIdx, 5));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x10, HeaderFileIdx, 10));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x12, HeaderFileIdx, 20));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x14, HeaderFileIdx, 11));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x16, HeaderFileIdx, 30));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x18, HeaderFileIdx, 12));
|
|
FI.OptLineTable->push(LineEntry(FuncAddr+0x20, SourceFileIdx, 8));
|
|
FI.Inline = InlineInfo();
|
|
|
|
std::string InlineName1(FuncName); InlineName1.append("1");
|
|
std::string InlineName2(FuncName); InlineName2.append("2");
|
|
std::string InlineName3(FuncName); InlineName3.append("3");
|
|
|
|
FI.Inline->Name = GC.insertString(InlineName1);
|
|
FI.Inline->CallFile = SourceFileIdx;
|
|
FI.Inline->CallLine = 6;
|
|
FI.Inline->Ranges.insert(AddressRange(FuncAddr + 0x10, FuncAddr + 0x20));
|
|
InlineInfo Inline2;
|
|
Inline2.Name = GC.insertString(InlineName2);
|
|
Inline2.CallFile = HeaderFileIdx;
|
|
Inline2.CallLine = 33;
|
|
Inline2.Ranges.insert(AddressRange(FuncAddr + 0x12, FuncAddr + 0x14));
|
|
FI.Inline->Children.emplace_back(Inline2);
|
|
InlineInfo Inline3;
|
|
Inline3.Name = GC.insertString(InlineName3);
|
|
Inline3.CallFile = HeaderFileIdx;
|
|
Inline3.CallLine = 35;
|
|
Inline3.Ranges.insert(AddressRange(FuncAddr + 0x16, FuncAddr + 0x18));
|
|
FI.Inline->Children.emplace_back(Inline3);
|
|
GC.addFunctionInfo(std::move(FI));
|
|
}
|
|
|
|
// Finalize a GsymCreator, encode it and decode it and return the error or
|
|
// GsymReader that was successfully decoded.
|
|
static Expected<GsymReader> FinalizeEncodeAndDecode(GsymCreator &GC) {
|
|
OutputAggregator Null(nullptr);
|
|
Error FinalizeErr = GC.finalize(Null);
|
|
if (FinalizeErr)
|
|
return std::move(FinalizeErr);
|
|
SmallString<1024> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
llvm::Error Err = GC.encode(FW);
|
|
if (Err)
|
|
return std::move(Err);
|
|
return GsymReader::copyBuffer(OutStrm.str());
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymSegmenting) {
|
|
// Test creating a GSYM file with function infos and segment the information.
|
|
// We verify segmenting is working by creating a full GSYM and also by
|
|
// encoding multiple segments, then we verify that we get the same information
|
|
// when doing lookups on the full GSYM that was decoded from encoding the
|
|
// entire GSYM and also by decoding information from the segments themselves.
|
|
GsymCreator GC;
|
|
GC.setBaseAddress(0);
|
|
AddFunctionInfo(GC, "main", 0x1000, "/tmp/main.c", "/tmp/main.h");
|
|
AddFunctionInfo(GC, "foo", 0x2000, "/tmp/foo.c", "/tmp/foo.h");
|
|
AddFunctionInfo(GC, "bar", 0x3000, "/tmp/bar.c", "/tmp/bar.h");
|
|
AddFunctionInfo(GC, "baz", 0x4000, "/tmp/baz.c", "/tmp/baz.h");
|
|
Expected<GsymReader> GR = FinalizeEncodeAndDecode(GC);
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
//GR->dump(outs());
|
|
|
|
// Create segmented GSYM files where each file contains 1 function. We will
|
|
// then test doing lookups on the "GR", or the full GSYM file and then test
|
|
// doing lookups on the GsymReader objects for each segment to ensure we get
|
|
// the exact same information. So after all of the code below we will have
|
|
// GsymReader objects that each contain one function. We name the creators
|
|
// and readers to match the one and only address they contain.
|
|
// GC1000 and GR1000 are for [0x1000-0x1030)
|
|
// GC2000 and GR2000 are for [0x2000-0x2030)
|
|
// GC3000 and GR3000 are for [0x3000-0x3030)
|
|
// GC4000 and GR4000 are for [0x4000-0x4030)
|
|
|
|
// Create the segments and verify that FuncIdx, an in/out parameter, gets
|
|
// updated as expected.
|
|
size_t FuncIdx = 0;
|
|
// Make sure we get an error if the segment size is too small to encode a
|
|
// single function info.
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GCError =
|
|
GC.createSegment(57, FuncIdx);
|
|
ASSERT_FALSE((bool)GCError);
|
|
checkError("a segment size of 57 is to small to fit any function infos, "
|
|
"specify a larger value", GCError.takeError());
|
|
// Make sure that the function index didn't get incremented when we didn't
|
|
// encode any values into the segmented GsymCreator.
|
|
ASSERT_EQ(FuncIdx, (size_t)0);
|
|
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC1000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC1000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)1);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC2000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC2000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)2);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC3000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC3000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)3);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC4000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC4000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)4);
|
|
// When there are no function infos left to encode we expect to get no error
|
|
// and get a NULL GsymCreator in the return value from createSegment.
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GCNull =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GCNull, Succeeded());
|
|
ASSERT_TRUE(GC1000.get() != nullptr);
|
|
ASSERT_TRUE(GC2000.get() != nullptr);
|
|
ASSERT_TRUE(GC3000.get() != nullptr);
|
|
ASSERT_TRUE(GC4000.get() != nullptr);
|
|
ASSERT_TRUE(GCNull.get() == nullptr);
|
|
// Encode and decode the GsymReader for each segment and verify they succeed.
|
|
Expected<GsymReader> GR1000 = FinalizeEncodeAndDecode(*GC1000.get());
|
|
ASSERT_THAT_EXPECTED(GR1000, Succeeded());
|
|
Expected<GsymReader> GR2000 = FinalizeEncodeAndDecode(*GC2000.get());
|
|
ASSERT_THAT_EXPECTED(GR2000, Succeeded());
|
|
Expected<GsymReader> GR3000 = FinalizeEncodeAndDecode(*GC3000.get());
|
|
ASSERT_THAT_EXPECTED(GR3000, Succeeded());
|
|
Expected<GsymReader> GR4000 = FinalizeEncodeAndDecode(*GC4000.get());
|
|
ASSERT_THAT_EXPECTED(GR4000, Succeeded());
|
|
|
|
// Verify that all lookups match the range [0x1000-0x1030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR1000.
|
|
for (uint64_t Addr = 0x1000; Addr < 0x1030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR1000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x3000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x4000), Failed());
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x2000-0x2030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR2000.
|
|
for (uint64_t Addr = 0x2000; Addr < 0x2030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR2000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x3000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x4000), Failed());
|
|
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x3000-0x3030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR3000.
|
|
for (uint64_t Addr = 0x3000; Addr < 0x3030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR3000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x4000), Failed());
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x4000-0x4030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR4000.
|
|
for (uint64_t Addr = 0x4000; Addr < 0x4030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
// Lookup in the GsymReader for that contains 0x4000
|
|
auto SegmentLR = GR4000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x3000), Failed());
|
|
}
|
|
}
|
|
|
|
TEST(GSYMTest, TestGsymSegmentingNoBase) {
|
|
// Test creating a GSYM file with function infos and segment the information.
|
|
// We verify segmenting is working by creating a full GSYM and also by
|
|
// encoding multiple segments, then we verify that we get the same information
|
|
// when doing lookups on the full GSYM that was decoded from encoding the
|
|
// entire GSYM and also by decoding information from the segments themselves.
|
|
GsymCreator GC;
|
|
AddFunctionInfo(GC, "main", 0x1000, "/tmp/main.c", "/tmp/main.h");
|
|
AddFunctionInfo(GC, "foo", 0x2000, "/tmp/foo.c", "/tmp/foo.h");
|
|
AddFunctionInfo(GC, "bar", 0x3000, "/tmp/bar.c", "/tmp/bar.h");
|
|
AddFunctionInfo(GC, "baz", 0x4000, "/tmp/baz.c", "/tmp/baz.h");
|
|
Expected<GsymReader> GR = FinalizeEncodeAndDecode(GC);
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
//GR->dump(outs());
|
|
|
|
// Create segmented GSYM files where each file contains 1 function. We will
|
|
// then test doing lookups on the "GR", or the full GSYM file and then test
|
|
// doing lookups on the GsymReader objects for each segment to ensure we get
|
|
// the exact same information. So after all of the code below we will have
|
|
// GsymReader objects that each contain one function. We name the creators
|
|
// and readers to match the one and only address they contain.
|
|
// GC1000 and GR1000 are for [0x1000-0x1030)
|
|
// GC2000 and GR2000 are for [0x2000-0x2030)
|
|
// GC3000 and GR3000 are for [0x3000-0x3030)
|
|
// GC4000 and GR4000 are for [0x4000-0x4030)
|
|
|
|
// Create the segments and verify that FuncIdx, an in/out parameter, gets
|
|
// updated as expected.
|
|
size_t FuncIdx = 0;
|
|
// Make sure we get an error if the segment size is too small to encode a
|
|
// single function info.
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GCError =
|
|
GC.createSegment(57, FuncIdx);
|
|
ASSERT_FALSE((bool)GCError);
|
|
checkError("a segment size of 57 is to small to fit any function infos, "
|
|
"specify a larger value", GCError.takeError());
|
|
// Make sure that the function index didn't get incremented when we didn't
|
|
// encode any values into the segmented GsymCreator.
|
|
ASSERT_EQ(FuncIdx, (size_t)0);
|
|
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC1000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC1000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)1);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC2000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC2000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)2);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC3000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC3000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)3);
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GC4000 =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GC4000, Succeeded());
|
|
ASSERT_EQ(FuncIdx, (size_t)4);
|
|
// When there are no function infos left to encode we expect to get no error
|
|
// and get a NULL GsymCreator in the return value from createSegment.
|
|
llvm::Expected<std::unique_ptr<GsymCreator>> GCNull =
|
|
GC.createSegment(128, FuncIdx);
|
|
ASSERT_THAT_EXPECTED(GCNull, Succeeded());
|
|
ASSERT_TRUE(GC1000.get() != nullptr);
|
|
ASSERT_TRUE(GC2000.get() != nullptr);
|
|
ASSERT_TRUE(GC3000.get() != nullptr);
|
|
ASSERT_TRUE(GC4000.get() != nullptr);
|
|
ASSERT_TRUE(GCNull.get() == nullptr);
|
|
// Encode and decode the GsymReader for each segment and verify they succeed.
|
|
Expected<GsymReader> GR1000 = FinalizeEncodeAndDecode(*GC1000.get());
|
|
ASSERT_THAT_EXPECTED(GR1000, Succeeded());
|
|
Expected<GsymReader> GR2000 = FinalizeEncodeAndDecode(*GC2000.get());
|
|
ASSERT_THAT_EXPECTED(GR2000, Succeeded());
|
|
Expected<GsymReader> GR3000 = FinalizeEncodeAndDecode(*GC3000.get());
|
|
ASSERT_THAT_EXPECTED(GR3000, Succeeded());
|
|
Expected<GsymReader> GR4000 = FinalizeEncodeAndDecode(*GC4000.get());
|
|
ASSERT_THAT_EXPECTED(GR4000, Succeeded());
|
|
|
|
// Verify that all lookups match the range [0x1000-0x1030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR1000.
|
|
for (uint64_t Addr = 0x1000; Addr < 0x1030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR1000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x3000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR1000->lookup(0x4000), Failed());
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x2000-0x2030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR2000.
|
|
for (uint64_t Addr = 0x2000; Addr < 0x2030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR2000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x3000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR2000->lookup(0x4000), Failed());
|
|
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x3000-0x3030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR3000.
|
|
for (uint64_t Addr = 0x3000; Addr < 0x3030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
auto SegmentLR = GR3000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR3000->lookup(0x4000), Failed());
|
|
}
|
|
|
|
// Verify that all lookups match the range [0x4000-0x4030) when doing lookups
|
|
// in the GsymReader that contains all functions and from the segmented
|
|
// GsymReader in GR4000.
|
|
for (uint64_t Addr = 0x4000; Addr < 0x4030; ++Addr) {
|
|
// Lookup in the main GsymReader that contains all function infos
|
|
auto MainLR = GR->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(MainLR, Succeeded());
|
|
// Lookup in the GsymReader for that contains 0x4000
|
|
auto SegmentLR = GR4000->lookup(Addr);
|
|
ASSERT_THAT_EXPECTED(SegmentLR, Succeeded());
|
|
// Make sure the lookup results match.
|
|
EXPECT_EQ(MainLR.get(), SegmentLR.get());
|
|
// Make sure that the lookups on the functions that are not in the segment
|
|
// fail as expected.
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x1000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x2000), Failed());
|
|
ASSERT_THAT_EXPECTED(GR4000->lookup(0x3000), Failed());
|
|
}
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestDWARFInlineRangeScopes) {
|
|
// Test cases where inlined functions address ranges are not contained in the
|
|
// parent ranges and that we can successfully remove them and emit error
|
|
// messages. The DWARF for this looks like the dump below. The inlined
|
|
// functions named "invalid1" and "invalid2" are expected to be removed and
|
|
// an appropriate error message will be emitted.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000002000)
|
|
//
|
|
// 0x0000002a: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("invalid1")
|
|
// DW_AT_low_pc (0x0000000000000fff)
|
|
// DW_AT_high_pc (0x0000000000001001)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (10)
|
|
//
|
|
// 0x00000041: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("valid1")
|
|
// DW_AT_low_pc (0x0000000000001010)
|
|
// DW_AT_high_pc (0x0000000000001100)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (11)
|
|
//
|
|
// 0x00000058: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("invalid2")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001100)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (12)
|
|
//
|
|
// 0x0000006f: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("valid2")
|
|
// DW_AT_low_pc (0x0000000000001020)
|
|
// DW_AT_high_pc (0x0000000000001030)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (13)
|
|
//
|
|
// 0x00000086: NULL
|
|
//
|
|
// 0x00000087: NULL
|
|
//
|
|
// 0x00000088: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- invalid1
|
|
- valid1
|
|
- invalid2
|
|
- valid2
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Code: 0x3
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data1
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data1
|
|
- Code: 0x4
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data1
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data1
|
|
debug_info:
|
|
- Length: 0x85
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x2000
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0xFFF
|
|
- Value: 0x1001
|
|
- Value: 0x1
|
|
- Value: 0xA
|
|
- AbbrCode: 0x4
|
|
Values:
|
|
- Value: 0x1C
|
|
- Value: 0x1010
|
|
- Value: 0x1100
|
|
- Value: 0x1
|
|
- Value: 0xB
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x23
|
|
- Value: 0x1000
|
|
- Value: 0x1100
|
|
- Value: 0x1
|
|
- Value: 0xC
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x2C
|
|
- Value: 0x1020
|
|
- Value: 0x1030
|
|
- Value: 0x1
|
|
- Value: 0xD
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 84
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 4048
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: -1
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x2000));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_TRUE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
std::vector<std::string> ExpectedLogErrors = {
|
|
"error: inlined function DIE at 0x0000002a has a range [0x0000000000000fff "
|
|
"- 0x0000000000001001) that isn't contained in any parent address ranges, "
|
|
"this inline range will be removed.",
|
|
"error: inlined function DIE at 0x00000058 has a range [0x0000000000001000 "
|
|
"- 0x0000000000001100) that isn't contained in any parent address ranges, "
|
|
"this inline range will be removed."
|
|
};
|
|
// Make sure all expected errors are in the error stream for the two invalid
|
|
// inlined functions that we removed due to invalid range scoping.
|
|
for (const auto &Error: ExpectedLogErrors) {
|
|
EXPECT_TRUE(OS.str().find(Error) != std::string::npos);
|
|
}
|
|
// The top level inline info is for the function "foo" itself. Verify that
|
|
// we have only 1 inline function inside of this, even though the DWARF
|
|
// contains two. One of the inline functions in "foo" is invalid, so we must
|
|
// only end up with 1.
|
|
StringRef InlineFuncName = GR->getString(ExpFI->Inline->Name);
|
|
EXPECT_EQ(InlineFuncName, "foo");
|
|
EXPECT_EQ(ExpFI->Inline->CallFile, 0u);
|
|
EXPECT_EQ(ExpFI->Inline->CallLine, 0u);
|
|
EXPECT_EQ(ExpFI->Inline->Children.size(), 1u);
|
|
|
|
|
|
// The first inline function "valid1" contains two inline functions in the
|
|
// DWARF, but one has an address range which isn't contained in any ranges
|
|
// from "foo", so only 1 inline function be parsed.
|
|
InlineInfo &Inline1 = ExpFI->Inline->Children[0];
|
|
StringRef Inline1Name = GR->getString(Inline1.Name);
|
|
EXPECT_EQ(Inline1Name, "valid1");
|
|
EXPECT_EQ(Inline1.CallFile, 1u);
|
|
EXPECT_EQ(Inline1.CallLine, 11u);
|
|
EXPECT_EQ(Inline1.Children.size(), 1u);
|
|
|
|
|
|
// The second inline function "valid2" contains two inline functions in the
|
|
// DWARF, but one has an address range which isn't contained in any ranges
|
|
// from "valid1", so only 1 inline function be parsed.
|
|
InlineInfo &Inline2 = Inline1.Children[0];
|
|
StringRef Inline2Name = GR->getString(Inline2.Name);
|
|
EXPECT_EQ(Inline2Name, "valid2");
|
|
EXPECT_EQ(Inline2.CallFile, 1u);
|
|
EXPECT_EQ(Inline2.CallLine, 13u);
|
|
EXPECT_EQ(Inline2.Children.size(), 0u);
|
|
}
|
|
|
|
TEST(GSYMTest, TestDWARFEmptyInline) {
|
|
// Test cases where we have inline function information in the DWARF that
|
|
// results in us trying to parse the inline info, but since the inline
|
|
// info ends up not adding any valid inline functions due to ranges
|
|
// not being correct, we end up not encoding any inline information. This
|
|
// tests that if we end up creating an empty inline info struct, we end up
|
|
// not encoding it into the GSYM file.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inlineWithInvalidRange")
|
|
// DW_AT_low_pc (0x0000000000001100)
|
|
// DW_AT_high_pc (0x0000000000001200)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (11)
|
|
//
|
|
// 0x00000047: NULL
|
|
//
|
|
// 0x00000048: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- inlineWithInvalidRange
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Code: 0x3
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
debug_info:
|
|
- Length: 0x45
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x1100
|
|
- Value: 0x1200
|
|
- Value: 0x1
|
|
- Value: 0xB
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 76
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 32
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
std::vector<std::string> ExpectedLogErrors = {
|
|
"error: inlined function DIE at 0x0000002a has a range [0x0000000000001100"
|
|
" - 0x0000000000001200) that isn't contained in any parent address ranges,"
|
|
" this inline range will be removed.",
|
|
"warning: DIE contains inline function information that has no valid "
|
|
"ranges, removing inline information:",
|
|
};
|
|
// Make sure all expected errors are in the error stream for the two invalid
|
|
// inlined functions that we removed due to invalid range scoping.
|
|
for (const auto &Error: ExpectedLogErrors) {
|
|
EXPECT_TRUE(OS.str().find(Error) != std::string::npos);
|
|
}
|
|
}
|
|
|
|
TEST(GSYMTest, TestFinalizeForLineTables) {
|
|
// This example has two compile units:
|
|
// - one contains a function "foo" with line table entries and "bar" without
|
|
// - one contains a function "bar" with line table entries and "foo" without
|
|
// This test ensures that no matter what order information gets processed,
|
|
// we want to make sure that we prioritize the entries with the most debug
|
|
// info.
|
|
//
|
|
// The DWARF is the same for the functions, but the first compile unit has
|
|
// lines entries for "foo" and the second one doesn't. And the first compile
|
|
// unit has no line entries for "bar", but the second one does. We expect the
|
|
// resulting gsym file to have a "foo" and "bar" that both have line entries.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: DW_TAG_subprogram
|
|
// DW_AT_name ("bar")
|
|
// DW_AT_low_pc (0x0000000000002000)
|
|
// DW_AT_high_pc (0x0000000000002050)
|
|
//
|
|
// 0x0000003f: NULL
|
|
// 0x00000040: Compile Unit: length = 0x0000003c, format = DWARF32, version = 0x0004, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000080)
|
|
//
|
|
// 0x0000004b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000043)
|
|
//
|
|
// 0x00000055: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000006a: DW_TAG_subprogram
|
|
// DW_AT_name ("bar")
|
|
// DW_AT_low_pc (0x0000000000002000)
|
|
// DW_AT_high_pc (0x0000000000002050)
|
|
//
|
|
// 0x0000007f: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- bar
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Length: 0x3C
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x2000
|
|
- Value: 0x2050
|
|
- AbbrCode: 0x0
|
|
- Length: 0x3C
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x43
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x2000
|
|
- Value: 0x2050
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 63
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 80
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
- Length: 63
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 8192
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 19
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 80
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should only be two functions in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 2u);
|
|
// Verify "foo" is present and has a line table
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
// Verify "foo" is present and has a line table
|
|
auto ExpFI2 = GR->getFunctionInfo(0x2000);
|
|
ASSERT_THAT_EXPECTED(ExpFI2, Succeeded());
|
|
ASSERT_EQ(ExpFI2->Range, AddressRange(0x2000, 0x2050));
|
|
EXPECT_TRUE(ExpFI2->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI2->Inline.has_value());
|
|
StringRef FuncName2 = GR->getString(ExpFI2->Name);
|
|
EXPECT_EQ(FuncName2, "bar");
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestRangeWarnings) {
|
|
// This example has a single compile unit that has a DW_TAG_subprogram that
|
|
// has two discontiguous ranges. We will create two FunctionInfo objects for
|
|
// each range in the function that only contains info for each range. We also
|
|
// want to verify that we only emit errors and warnings for ranges that
|
|
// aren't contained in any parent address ranges if this is true. Prior to
|
|
// this fix we would create two FunctionInfo objects and as each one was
|
|
// being created we would end up warning about all of the ranges that weren't
|
|
// in the current FunctionInfo's range even though the DWARF was well formed.
|
|
// Now we don't incorrectly emit errors when there are none.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_ranges (0x00000000
|
|
// [0x0000000000001000, 0x0000000000001050)
|
|
// [0x0000000000002000, 0x0000000000002050))
|
|
//
|
|
// 0x0000001e: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inline1")
|
|
// DW_AT_ranges (0x00000030
|
|
// [0x0000000000001010, 0x0000000000001040)
|
|
// [0x0000000000002010, 0x0000000000002040))
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (11)
|
|
//
|
|
// 0x0000002f: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inline2")
|
|
// DW_AT_ranges (0x00000060
|
|
// [0x0000000000001015, 0x0000000000001020)
|
|
// [0x0000000000002015, 0x0000000000002020))
|
|
// DW_AT_call_file ("/tmp/inline.h")
|
|
// DW_AT_call_line (21)
|
|
//
|
|
// 0x00000040: NULL
|
|
//
|
|
// 0x00000041: NULL
|
|
//
|
|
// 0x00000042: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- inline1
|
|
- inline2
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_ranges
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x3
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_ranges
|
|
Form: DW_FORM_sec_offset
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
- Code: 0x4
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_ranges
|
|
Form: DW_FORM_sec_offset
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
debug_ranges:
|
|
- Offset: 0x0
|
|
AddrSize: 0x8
|
|
Entries:
|
|
- LowOffset: 0x1000
|
|
HighOffset: 0x1050
|
|
- LowOffset: 0x2000
|
|
HighOffset: 0x2050
|
|
- Offset: 0x30
|
|
AddrSize: 0x8
|
|
Entries:
|
|
- LowOffset: 0x1010
|
|
HighOffset: 0x1040
|
|
- LowOffset: 0x2010
|
|
HighOffset: 0x2040
|
|
- Offset: 0x60
|
|
AddrSize: 0x8
|
|
Entries:
|
|
- LowOffset: 0x1015
|
|
HighOffset: 0x1020
|
|
- LowOffset: 0x2015
|
|
HighOffset: 0x2020
|
|
debug_info:
|
|
- Length: 0x3F
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x0
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x30
|
|
- Value: 0x1
|
|
- Value: 0xB
|
|
- AbbrCode: 0x4
|
|
Values:
|
|
- Value: 0x1B
|
|
- Value: 0x60
|
|
- Value: 0x2
|
|
- Value: 0x15
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 120
|
|
Version: 2
|
|
PrologueLength: 48
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
- Name: inline.h
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 2
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 10
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 48
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 1
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: -10
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 8192
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 19
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 2
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 48
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 1
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be two functions in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 2u);
|
|
// Verify "foo" is present and has a line table
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_TRUE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
// Verify "foo" is present and has a line table
|
|
auto ExpFI2 = GR->getFunctionInfo(0x2000);
|
|
ASSERT_THAT_EXPECTED(ExpFI2, Succeeded());
|
|
ASSERT_EQ(ExpFI2->Range, AddressRange(0x2000, 0x2050));
|
|
EXPECT_TRUE(ExpFI2->OptLineTable.has_value());
|
|
EXPECT_TRUE(ExpFI2->Inline.has_value());
|
|
StringRef FuncName2 = GR->getString(ExpFI2->Name);
|
|
EXPECT_EQ(FuncName2, "foo");
|
|
|
|
// Make sure we don't see spurious errors in the output:
|
|
EXPECT_TRUE(errors.find("error:") == std::string::npos);
|
|
}
|
|
|
|
TEST(GSYMTest, TestEmptyRangeWarnings) {
|
|
// This example has a single compile unit that has a DW_TAG_subprogram that
|
|
// has a function that contains an inlined function that has an empty range.
|
|
// We want to make sure that if we run into only empty inline functions
|
|
// inside of a real function, that we don't end up with inline information
|
|
// in the GSYM and we don't warn about the inline function's range not being
|
|
// contined in the parent ranges since it is ok for inline functions to be
|
|
// elided.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inline1")
|
|
// DW_AT_low_pc (0x0000000000001010)
|
|
// DW_AT_high_pc (0x0000000000001010)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (11)
|
|
//
|
|
// 0x00000047: NULL
|
|
//
|
|
// 0x00000048: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- inline1
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Code: 0x3
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
debug_info:
|
|
- Length: 0x45
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x1010
|
|
- Value: 0x1010
|
|
- Value: 0x1
|
|
- Value: 0xB
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 89
|
|
Version: 2
|
|
PrologueLength: 48
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
- Name: inline.h
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 2
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 10
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 48
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 1
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: -10
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
// Verify "foo" is present and has a line table and no inline info.
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
// Make sure we don't see spurious errors in the output:
|
|
EXPECT_TRUE(errors.find("error:") == std::string::npos);
|
|
}
|
|
|
|
|
|
TEST(GSYMTest, TestEmptyLinkageName) {
|
|
// This example has a single compile unit that has a DW_TAG_subprogram that
|
|
// has a function that has an empty linkage name and a valid normal name.
|
|
// Previously this would cause an encoding error:
|
|
//
|
|
// DWARF conversion failed: attempted to encode invalid FunctionInfo object
|
|
//
|
|
// This was because we would get a valid but empty linkage name and we would
|
|
// try to use this in the GSYM FunctionInfo and that would cause the error
|
|
// as the name was empty.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_linkage_name ("")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002e: NULL
|
|
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- ''
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_linkage_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Length: 0x2B
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x13
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 68
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 256
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 256
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
// Verify "foo" is present and has a line table and no inline info.
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
// Make sure we don't see spurious errors in the output:
|
|
EXPECT_TRUE(errors.find("error:") == std::string::npos);
|
|
}
|
|
|
|
TEST(GSYMTest, TestLineTablesWithEmptyRanges) {
|
|
// Test that lookups find the right line table entry when there are multiple
|
|
// line entries with the same address. When we have multiple line table
|
|
// entries with the same address, we need to pick the last one in the line
|
|
// table. We do this because a line entry's start address in the defined by
|
|
// the line table entry's address and the size is determined by the
|
|
// subtracting the next line table's address. If the current line table
|
|
// entry's address is the same as the next one, then there is no code
|
|
// assiciated with the current line table entry and it should be ignored.
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: NULL
|
|
//
|
|
// The line table has a duplicate entry at 0x1010:
|
|
//
|
|
// Address Line Column File ISA Discriminator Flags
|
|
// ---------- ------ ------ ------ --- ------------- -------------
|
|
// 0x00001000 10 0 1 0 0 is_stmt
|
|
// 0x00001010 11 0 1 0 0 is_stmt
|
|
// 0x00001010 12 0 1 0 0 is_stmt
|
|
// 0x00001050 13 0 1 0 0 is_stmt end_sequence
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Length: 0x27
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 71
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 64
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 1u);
|
|
// Verify "foo" is present and has a line table and no inline info.
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
// Make sure we don't see spurious errors in the output:
|
|
EXPECT_TRUE(errors.find("error:") == std::string::npos);
|
|
|
|
// Make sure that when we lookup address 0x1010, that we get the entry that
|
|
// matches line 12, the second line entry that also has the address of
|
|
// 0x1010.
|
|
auto LR = GR->lookup(0x1010);
|
|
ASSERT_THAT_EXPECTED(LR, Succeeded());
|
|
SourceLocation src_loc = {"foo", "/tmp", "main.cpp", 12, 16};
|
|
EXPECT_THAT(LR->Locations, testing::ElementsAre(src_loc));
|
|
}
|
|
|
|
TEST(GSYMTest, TestHandlingOfInvalidFileIndexes) {
|
|
// Test that llvm-gsymutil can handle invalid file indexes in the following
|
|
// cases:
|
|
// - In line entries in the line table
|
|
// - When parsing inline entries that have a DW_AT_call_file
|
|
// - When parsing function dies with no line table entries and it tries to
|
|
// use the DW_AT_decl_file
|
|
//
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inline_with_invalid_call_file")
|
|
// DW_AT_low_pc (0x0000000000001010)
|
|
// DW_AT_high_pc (0x0000000000001020)
|
|
// DW_AT_call_file (0x0000000a)
|
|
// DW_AT_call_line (11)
|
|
//
|
|
// 0x00000047: DW_TAG_inlined_subroutine
|
|
// DW_AT_name
|
|
// ("inline_inside_parent_with_invalid_call_file")
|
|
// DW_AT_low_pc (0x0000000000001010)
|
|
// DW_AT_high_pc (0x0000000000001015)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (12)
|
|
//
|
|
// 0x00000064: NULL
|
|
//
|
|
// 0x00000065: DW_TAG_inlined_subroutine
|
|
// DW_AT_name ("inline_with_valid_call_file")
|
|
// DW_AT_low_pc (0x0000000000001020)
|
|
// DW_AT_high_pc (0x0000000000001030)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (13)
|
|
//
|
|
// 0x00000082: DW_TAG_inlined_subroutine
|
|
// DW_AT_name
|
|
// ("inline_inside_parent_with_valid_call_file")
|
|
// DW_AT_low_pc (0x0000000000001020)
|
|
// DW_AT_high_pc (0x0000000000001025)
|
|
// DW_AT_call_file ("/tmp/main.cpp")
|
|
// DW_AT_call_line (14)
|
|
//
|
|
// 0x0000009f: NULL
|
|
//
|
|
// 0x000000a0: NULL
|
|
//
|
|
// 0x000000a1: DW_TAG_subprogram
|
|
// DW_AT_name ("func_with_valid_decl_file")
|
|
// DW_AT_decl_file ("/tmp/main.cpp")
|
|
// DW_AT_decl_line (20)
|
|
// DW_AT_low_pc (0x0000000000002000)
|
|
// DW_AT_high_pc (0x0000000000002050)
|
|
//
|
|
// 0x000000b8: DW_TAG_subprogram
|
|
// DW_AT_name ("func_with_invalid_decl_file")
|
|
// DW_AT_decl_file (0x0a)
|
|
// DW_AT_decl_line (20)
|
|
// DW_AT_low_pc (0x0000000000003000)
|
|
// DW_AT_high_pc (0x0000000000003050)
|
|
//
|
|
// 0x000000cf: NULL
|
|
//
|
|
// The table looks has an entry at address 0x0000000000001010 that has an
|
|
// invalid file index that needs to be removed.
|
|
//
|
|
// Address Line Column File ISA Discriminator Flags
|
|
// ---------- ------ ------ ------ --- ------------- -------------
|
|
// 0x00001000 10 0 1 0 0 is_stmt
|
|
// 0x00001010 11 0 10 0 0 is_stmt
|
|
// 0x00001020 11 0 1 0 0 is_stmt
|
|
// 0x00001030 12 0 1 0 0 is_stmt
|
|
// 0x00001050 12 0 1 0 0 is_stmt end_sequence
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- inline_with_invalid_call_file
|
|
- inline_inside_parent_with_invalid_call_file
|
|
- inline_with_valid_call_file
|
|
- inline_inside_parent_with_valid_call_file
|
|
- func_with_valid_decl_file
|
|
- func_with_invalid_decl_file
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Code: 0x3
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
- Code: 0x4
|
|
Tag: DW_TAG_inlined_subroutine
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_call_file
|
|
Form: DW_FORM_data4
|
|
- Attribute: DW_AT_call_line
|
|
Form: DW_FORM_data4
|
|
- Code: 0x5
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_decl_file
|
|
Form: DW_FORM_data1
|
|
- Attribute: DW_AT_decl_line
|
|
Form: DW_FORM_data1
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Length: 0xCC
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x1010
|
|
- Value: 0x1020
|
|
- Value: 0xA
|
|
- Value: 0xB
|
|
- AbbrCode: 0x4
|
|
Values:
|
|
- Value: 0x31
|
|
- Value: 0x1010
|
|
- Value: 0x1015
|
|
- Value: 0x1
|
|
- Value: 0xC
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x3
|
|
Values:
|
|
- Value: 0x5D
|
|
- Value: 0x1020
|
|
- Value: 0x1030
|
|
- Value: 0x1
|
|
- Value: 0xD
|
|
- AbbrCode: 0x4
|
|
Values:
|
|
- Value: 0x79
|
|
- Value: 0x1020
|
|
- Value: 0x1025
|
|
- Value: 0x1
|
|
- Value: 0xE
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x0
|
|
- AbbrCode: 0x5
|
|
Values:
|
|
- Value: 0xA3
|
|
- Value: 0x1
|
|
- Value: 0x14
|
|
- Value: 0x2000
|
|
- Value: 0x2050
|
|
- AbbrCode: 0x5
|
|
Values:
|
|
- Value: 0xBD
|
|
- Value: 0xA
|
|
- Value: 0x14
|
|
- Value: 0x3000
|
|
- Value: 0x3050
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 78
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 10
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_set_file
|
|
Data: 1
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 32
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be one function in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 3u);
|
|
// Verify "foo" is present and has a line table and no inline info.
|
|
auto ExpFI = GR->getFunctionInfo(0x1000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
// Make sure we only have 3 entries to show we removed the line entry with
|
|
// the invalid file index whose address is 0x0000000000001010.
|
|
ASSERT_EQ(ExpFI->OptLineTable->size(), 3u);
|
|
EXPECT_TRUE(ExpFI->Inline.has_value());
|
|
|
|
// Make sure that we only have one inline function, not two. We remove one of
|
|
// the inline functions because it has an invalid DW_AT_call_file attribute.
|
|
ASSERT_EQ(ExpFI->Inline->Children.size(), 1u);
|
|
StringRef InlineName = GR->getString(ExpFI->Inline->Children[0].Name);
|
|
EXPECT_EQ(InlineName, "inline_with_valid_call_file");
|
|
|
|
ExpFI = GR->getFunctionInfo(0x0000000000002000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x2000, 0x2050));
|
|
FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "func_with_valid_decl_file");
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
// Make sure we only have 1 entry in the line table which indicates we were
|
|
// able to parse the DW_AT_decl_file/DW_AT_decl_line correctly.
|
|
EXPECT_TRUE(ExpFI->OptLineTable.has_value());
|
|
ASSERT_EQ(ExpFI->OptLineTable->size(), 1u);
|
|
|
|
ExpFI = GR->getFunctionInfo(0x0000000000003000);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x3000, 0x3050));
|
|
FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "func_with_invalid_decl_file");
|
|
EXPECT_FALSE(ExpFI->Inline.has_value());
|
|
// Make sure we only no line table because there are no line entries in the
|
|
// line table and the DW_AT_decl_file attribute was invalid so we were not
|
|
// able to parse the DW_AT_decl_file/DW_AT_decl_line correctly.
|
|
EXPECT_FALSE(ExpFI->OptLineTable.has_value());
|
|
|
|
// Make sure we don't see spurious errors in the output:
|
|
std::vector<std::string> ExpectedLogErrors = {
|
|
"error: function DIE at 0x00000015 has a line entry with invalid DWARF "
|
|
"file index, this entry will be removed:",
|
|
"error: inlined function DIE at 0x0000002a has an invalid file index 10 "
|
|
"in its DW_AT_call_file attribute, this inline entry and all children "
|
|
"will be removed.",
|
|
"error: function DIE at 0x000000b8 has an invalid file index 10 in its "
|
|
"DW_AT_decl_file attribute, unable to create a single line entry from "
|
|
"the DW_AT_decl_file/DW_AT_decl_line attributes."};
|
|
// Make sure all expected errors are in the error stream for the two invalid
|
|
// inlined functions that we removed due to invalid range scoping.
|
|
for (const auto &Error : ExpectedLogErrors)
|
|
EXPECT_TRUE(errors.find(Error) != std::string::npos);
|
|
}
|
|
|
|
TEST(GSYMTest, TestLookupsOfOverlappingAndUnequalRanges) {
|
|
// Test that llvm-gsymutil lookup the correct funtion info when address
|
|
// ranges overlap. When functions overlap we always want to pick the first
|
|
// function info when symbolicating if there are multiple entries with the
|
|
// same address. Previous to this fix we would just binary search the address
|
|
// table and pick the first function info that matched the address. After
|
|
// this fix we now always select the first matching entry whose address range
|
|
// contains the lookup address to ensure we have the most debug info. We have
|
|
// seen case where the debug info would contain a small range and a symbol
|
|
// would have the same start address but the range was larger and sometimes,
|
|
// depending on how the binary search of the address table happened, we would
|
|
// pick these latter entries. We want the first entries because they always
|
|
// have the most debug info.
|
|
//
|
|
// To repro this case, we just make some simple DWARF that has two
|
|
// overlapping ranges and ensure that any lookups between 0x1000 and 0x104f
|
|
// match "foo", and any ranges between 0x1050 and 0x1fff match "bar".
|
|
//
|
|
// 0x0000000b: DW_TAG_compile_unit
|
|
// DW_AT_name ("/tmp/main.cpp")
|
|
// DW_AT_language (DW_LANG_C)
|
|
// DW_AT_stmt_list (0x00000000)
|
|
//
|
|
// 0x00000015: DW_TAG_subprogram
|
|
// DW_AT_name ("foo")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001050)
|
|
//
|
|
// 0x0000002a: DW_TAG_subprogram
|
|
// DW_AT_name ("bar")
|
|
// DW_AT_low_pc (0x0000000000001000)
|
|
// DW_AT_high_pc (0x0000000000001100)
|
|
//
|
|
// 0x0000003f: NULL
|
|
|
|
StringRef yamldata = R"(
|
|
debug_str:
|
|
- ''
|
|
- '/tmp/main.cpp'
|
|
- foo
|
|
- bar
|
|
debug_abbrev:
|
|
- ID: 0
|
|
Table:
|
|
- Code: 0x1
|
|
Tag: DW_TAG_compile_unit
|
|
Children: DW_CHILDREN_yes
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_language
|
|
Form: DW_FORM_udata
|
|
- Attribute: DW_AT_stmt_list
|
|
Form: DW_FORM_sec_offset
|
|
- Code: 0x2
|
|
Tag: DW_TAG_subprogram
|
|
Children: DW_CHILDREN_no
|
|
Attributes:
|
|
- Attribute: DW_AT_name
|
|
Form: DW_FORM_strp
|
|
- Attribute: DW_AT_low_pc
|
|
Form: DW_FORM_addr
|
|
- Attribute: DW_AT_high_pc
|
|
Form: DW_FORM_addr
|
|
debug_info:
|
|
- Length: 0x3C
|
|
Version: 4
|
|
AbbrevTableID: 0
|
|
AbbrOffset: 0x0
|
|
AddrSize: 8
|
|
Entries:
|
|
- AbbrCode: 0x1
|
|
Values:
|
|
- Value: 0x1
|
|
- Value: 0x2
|
|
- Value: 0x0
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0xF
|
|
- Value: 0x1000
|
|
- Value: 0x1050
|
|
- AbbrCode: 0x2
|
|
Values:
|
|
- Value: 0x13
|
|
- Value: 0x1000
|
|
- Value: 0x1100
|
|
- AbbrCode: 0x0
|
|
debug_line:
|
|
- Length: 71
|
|
Version: 2
|
|
PrologueLength: 36
|
|
MinInstLength: 1
|
|
DefaultIsStmt: 1
|
|
LineBase: 251
|
|
LineRange: 14
|
|
OpcodeBase: 13
|
|
StandardOpcodeLengths: [ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1 ]
|
|
IncludeDirs:
|
|
- '/tmp'
|
|
Files:
|
|
- Name: main.cpp
|
|
DirIdx: 1
|
|
ModTime: 0
|
|
Length: 0
|
|
Opcodes:
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 9
|
|
SubOpcode: DW_LNE_set_address
|
|
Data: 4096
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 9
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 16
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_copy
|
|
Data: 0
|
|
- Opcode: DW_LNS_advance_pc
|
|
Data: 64
|
|
- Opcode: DW_LNS_advance_line
|
|
SData: 1
|
|
Data: 0
|
|
- Opcode: DW_LNS_extended_op
|
|
ExtLen: 1
|
|
SubOpcode: DW_LNE_end_sequence
|
|
Data: 0
|
|
)";
|
|
auto ErrOrSections = DWARFYAML::emitDebugSections(yamldata);
|
|
ASSERT_THAT_EXPECTED(ErrOrSections, Succeeded());
|
|
std::unique_ptr<DWARFContext> DwarfContext =
|
|
DWARFContext::create(*ErrOrSections, 8);
|
|
ASSERT_TRUE(DwarfContext.get() != nullptr);
|
|
std::string errors;
|
|
raw_string_ostream OS(errors);
|
|
OutputAggregator OSAgg(&OS);
|
|
GsymCreator GC;
|
|
DwarfTransformer DT(*DwarfContext, GC);
|
|
const uint32_t ThreadCount = 1;
|
|
ASSERT_THAT_ERROR(DT.convert(ThreadCount, OSAgg), Succeeded());
|
|
ASSERT_THAT_ERROR(GC.finalize(OSAgg), Succeeded());
|
|
SmallString<512> Str;
|
|
raw_svector_ostream OutStrm(Str);
|
|
const auto ByteOrder = llvm::endianness::native;
|
|
FileWriter FW(OutStrm, ByteOrder);
|
|
ASSERT_THAT_ERROR(GC.encode(FW), Succeeded());
|
|
Expected<GsymReader> GR = GsymReader::copyBuffer(OutStrm.str());
|
|
ASSERT_THAT_EXPECTED(GR, Succeeded());
|
|
// There should be two functions in our GSYM.
|
|
EXPECT_EQ(GR->getNumAddresses(), 2u);
|
|
// Verify "foo" is correctly looked up for each of its addresses.
|
|
for (uint64_t Addr = 0x1000; Addr < 0x1050; ++Addr) {
|
|
auto ExpFI = GR->getFunctionInfo(Addr);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1050));
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "foo");
|
|
}
|
|
|
|
// Verify "bar" is correctly looked up for each of its addresses.
|
|
for (uint64_t Addr = 0x1050; Addr < 0x1100; ++Addr) {
|
|
auto ExpFI = GR->getFunctionInfo(Addr);
|
|
ASSERT_THAT_EXPECTED(ExpFI, Succeeded());
|
|
ASSERT_EQ(ExpFI->Range, AddressRange(0x1000, 0x1100));
|
|
StringRef FuncName = GR->getString(ExpFI->Name);
|
|
EXPECT_EQ(FuncName, "bar");
|
|
}
|
|
|
|
// Prior to the fix for this issue when we dumped an entire GSYM file, we
|
|
// were using a function that would extract a FunctionInfo object for a
|
|
// given address which caused us to always dump the first FunctionInfo
|
|
// entry for a given address. We now dump it correctly using an address
|
|
// index. Below we verify that we dump the right FunctionInfo gets dumped.
|
|
|
|
SmallString<512> DumpStr;
|
|
raw_svector_ostream DumpStrm(DumpStr);
|
|
GR->dump(DumpStrm);
|
|
|
|
// Make sure we see both "foo" and "bar" in the output of an entire GSYM
|
|
// dump. Prior to this fix we would two "foo" entries.
|
|
std::vector<std::string> ExpectedDumpLines = {
|
|
"@ 0x00000068: [0x0000000000001000 - 0x0000000000001050) \"foo\"",
|
|
"@ 0x00000088: [0x0000000000001000 - 0x0000000000001100) \"bar\""};
|
|
// Make sure all expected errors are in the error stream for the two invalid
|
|
// inlined functions that we removed due to invalid range scoping.
|
|
for (const auto &Line : ExpectedDumpLines)
|
|
EXPECT_TRUE(DumpStr.find(Line) != std::string::npos);
|
|
}
|