
Fix a [test failure](https://github.com/llvm/llvm-project/pull/136236#issuecomment-2819772879) in #136236, apply a minor renaming of statistics, and remerge. See details below. # Changes in #136236 Currently, `DebuggerStats::ReportStatistics()` calls `Module::GetSymtab(/*can_create=*/false)`, but then the latter calls `SymbolFile::GetSymtab()`. This will load symbols if haven't yet. See stacktrace below. The problem is that `DebuggerStats::ReportStatistics` should be read-only. This is especially important because it reports stats for symtab parsing/indexing time, which could be affected by the reporting itself if it's not read-only. This patch fixes this problem by adding an optional parameter `SymbolFile::GetSymtab(bool can_create = true)` and receiving the `false` value passed down from `Module::GetSymtab(/*can_create=*/false)` when the call is initiated from `DebuggerStats::ReportStatistics()`. --- Notes about the following stacktrace: 1. This can be reproduced. Create a helloworld program on **macOS** with dSYM, add `settings set target.preload-symbols false` to `~/.lldbinit`, do `lldb a.out`, then `statistics dump`. 2. `ObjectFile::GetSymtab` has `llvm::call_once`. So the fact that it called into `ObjectFileMachO::ParseSymtab` means that the symbol table is actually being parsed. ``` (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = step over frame #0: 0x0000000124c4d5a0 LLDB`ObjectFileMachO::ParseSymtab(this=0x0000000111504e40, symtab=0x0000600000a05e00) at ObjectFileMachO.cpp:2259:44 * frame #1: 0x0000000124fc50a0 LLDB`lldb_private::ObjectFile::GetSymtab()::$_0::operator()(this=0x000000016d35c858) const at ObjectFile.cpp:761:9 frame #5: 0x0000000124fc4e68 LLDB`void std::__1::__call_once_proxy[abi:v160006]<std::__1::tuple<lldb_private::ObjectFile::GetSymtab()::$_0&&>>(__vp=0x000000016d35c7f0) at mutex:652:5 frame #6: 0x0000000198afb99c libc++.1.dylib`std::__1::__call_once(unsigned long volatile&, void*, void (*)(void*)) + 196 frame #7: 0x0000000124fc4dd0 LLDB`void std::__1::call_once[abi:v160006]<lldb_private::ObjectFile::GetSymtab()::$_0>(__flag=0x0000600003920080, __func=0x000000016d35c858) at mutex:670:9 frame #8: 0x0000000124fc3cb0 LLDB`void llvm::call_once<lldb_private::ObjectFile::GetSymtab()::$_0>(flag=0x0000600003920080, F=0x000000016d35c858) at Threading.h:88:5 frame #9: 0x0000000124fc2bc4 LLDB`lldb_private::ObjectFile::GetSymtab(this=0x0000000111504e40) at ObjectFile.cpp:755:5 frame #10: 0x0000000124fe0a28 LLDB`lldb_private::SymbolFileCommon::GetSymtab(this=0x0000000104865200) at SymbolFile.cpp:158:39 frame #11: 0x0000000124d8fedc LLDB`lldb_private::Module::GetSymtab(this=0x00000001113041a8, can_create=false) at Module.cpp:1027:21 frame #12: 0x0000000125125bdc LLDB`lldb_private::DebuggerStats::ReportStatistics(debugger=0x000000014284d400, target=0x0000000115808200, options=0x000000014195d6d1) at Statistics.cpp:329:30 frame #13: 0x0000000125672978 LLDB`CommandObjectStatsDump::DoExecute(this=0x000000014195d540, command=0x000000016d35d820, result=0x000000016d35e150) at CommandObjectStats.cpp:144:18 frame #14: 0x0000000124f29b40 LLDB`lldb_private::CommandObjectParsed::Execute(this=0x000000014195d540, args_string="", result=0x000000016d35e150) at CommandObject.cpp:832:9 frame #15: 0x0000000124efbd70 LLDB`lldb_private::CommandInterpreter::HandleCommand(this=0x0000000141b22f30, command_line="statistics dump", lazy_add_to_history=eLazyBoolCalculate, result=0x000000016d35e150, force_repeat_command=false) at CommandInterpreter.cpp:2134:14 frame #16: 0x0000000124f007f4 LLDB`lldb_private::CommandInterpreter::IOHandlerInputComplete(this=0x0000000141b22f30, io_handler=0x00000001419b2aa8, line="statistics dump") at CommandInterpreter.cpp:3251:3 frame #17: 0x0000000124d7b5ec LLDB`lldb_private::IOHandlerEditline::Run(this=0x00000001419b2aa8) at IOHandler.cpp:588:22 frame #18: 0x0000000124d1e8fc LLDB`lldb_private::Debugger::RunIOHandlers(this=0x000000014284d400) at Debugger.cpp:1225:16 frame #19: 0x0000000124f01f74 LLDB`lldb_private::CommandInterpreter::RunCommandInterpreter(this=0x0000000141b22f30, options=0x000000016d35e63c) at CommandInterpreter.cpp:3543:16 frame #20: 0x0000000122840294 LLDB`lldb::SBDebugger::RunCommandInterpreter(this=0x000000016d35ebd8, auto_handle_events=true, spawn_thread=false) at SBDebugger.cpp:1212:42 frame #21: 0x0000000102aa6d28 lldb`Driver::MainLoop(this=0x000000016d35ebb8) at Driver.cpp:621:18 frame #22: 0x0000000102aa75b0 lldb`main(argc=1, argv=0x000000016d35f548) at Driver.cpp:829:26 frame #23: 0x0000000198858274 dyld`start + 2840 ``` # Changes in this PR top of the above Fix a [test failure](https://github.com/llvm/llvm-project/pull/136236#issuecomment-2819772879) in `TestStats.py`. The original version of the added test checks that all modules have symbol count zero when `target.preload-symbols == false`. The test failed on macOS. Due to various reasons, on macOS, symbols can be loaded for dylibs even with that setting, but not for the main module. For now, the fix of the test is to limit the assertion to only the main module. The test now passes on macOS. In the future, when we have a way to control a specific list of plug-ins to be loaded, there may be a configuration that this test can use to assert that all modules have symbol count zero. Apply a minor renaming of statistics, per the [suggestion](https://github.com/llvm/llvm-project/pull/136226#issuecomment-2825080275) in #136226 after merge.
303 lines
11 KiB
C++
303 lines
11 KiB
C++
//===-- LineTableTest.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 "Plugins/ObjectFile/ELF/ObjectFileELF.h"
|
|
#include "TestingSupport/SubsystemRAII.h"
|
|
#include "TestingSupport/TestUtilities.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/SymbolFile.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
|
|
using namespace lldb;
|
|
using namespace llvm;
|
|
using namespace lldb_private;
|
|
|
|
namespace {
|
|
|
|
// A fake symbol file class to allow us to create the line table "the right
|
|
// way". Pretty much all methods except for GetCompileUnitAtIndex and
|
|
// GetNumCompileUnits are stubbed out.
|
|
class FakeSymbolFile : public SymbolFile {
|
|
public:
|
|
/// LLVM RTTI support.
|
|
/// \{
|
|
bool isA(const void *ClassID) const override {
|
|
return ClassID == &ID || SymbolFile::isA(ClassID);
|
|
}
|
|
static bool classof(const SymbolFile *obj) { return obj->isA(&ID); }
|
|
/// \}
|
|
|
|
static void Initialize() {
|
|
PluginManager::RegisterPlugin("FakeSymbolFile", "", CreateInstance,
|
|
DebuggerInitialize);
|
|
}
|
|
static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
|
|
|
|
void InjectCompileUnit(std::unique_ptr<CompileUnit> cu_up) {
|
|
m_cu_sp = std::move(cu_up);
|
|
}
|
|
|
|
private:
|
|
/// LLVM RTTI support.
|
|
static char ID;
|
|
|
|
static SymbolFile *CreateInstance(ObjectFileSP objfile_sp) {
|
|
return new FakeSymbolFile(std::move(objfile_sp));
|
|
}
|
|
static void DebuggerInitialize(Debugger &) {}
|
|
|
|
StringRef GetPluginName() override { return "FakeSymbolFile"; }
|
|
uint32_t GetAbilities() override { return UINT32_MAX; }
|
|
uint32_t CalculateAbilities() override { return UINT32_MAX; }
|
|
uint32_t GetNumCompileUnits() override { return 1; }
|
|
CompUnitSP GetCompileUnitAtIndex(uint32_t) override { return m_cu_sp; }
|
|
Symtab *GetSymtab(bool can_create = true) override { return nullptr; }
|
|
LanguageType ParseLanguage(CompileUnit &) override { return eLanguageTypeC; }
|
|
size_t ParseFunctions(CompileUnit &) override { return 0; }
|
|
bool ParseLineTable(CompileUnit &) override { return true; }
|
|
bool ParseDebugMacros(CompileUnit &) override { return true; }
|
|
bool ParseSupportFiles(CompileUnit &, SupportFileList &) override {
|
|
return true;
|
|
}
|
|
size_t ParseTypes(CompileUnit &) override { return 0; }
|
|
bool ParseImportedModules(const SymbolContext &,
|
|
std::vector<SourceModule> &) override {
|
|
return false;
|
|
}
|
|
size_t ParseBlocksRecursive(Function &) override { return 0; }
|
|
size_t ParseVariablesForContext(const SymbolContext &) override { return 0; }
|
|
Type *ResolveTypeUID(user_id_t) override { return nullptr; }
|
|
std::optional<ArrayInfo>
|
|
GetDynamicArrayInfoForUID(user_id_t, const ExecutionContext *) override {
|
|
return std::nullopt;
|
|
}
|
|
bool CompleteType(CompilerType &) override { return true; }
|
|
uint32_t ResolveSymbolContext(const Address &, SymbolContextItem,
|
|
SymbolContext &) override {
|
|
return 0;
|
|
}
|
|
void GetTypes(SymbolContextScope *, TypeClass, TypeList &) override {}
|
|
Expected<TypeSystemSP> GetTypeSystemForLanguage(LanguageType) override {
|
|
return createStringError(std::errc::not_supported, "");
|
|
}
|
|
const ObjectFile *GetObjectFile() const override {
|
|
return m_objfile_sp.get();
|
|
}
|
|
ObjectFile *GetObjectFile() override { return m_objfile_sp.get(); }
|
|
ObjectFile *GetMainObjectFile() override { return m_objfile_sp.get(); }
|
|
void SectionFileAddressesChanged() override {}
|
|
void Dump(Stream &) override {}
|
|
uint64_t GetDebugInfoSize(bool) override { return 0; }
|
|
bool GetDebugInfoIndexWasLoadedFromCache() const override { return false; }
|
|
void SetDebugInfoIndexWasLoadedFromCache() override {}
|
|
bool GetDebugInfoIndexWasSavedToCache() const override { return false; }
|
|
void SetDebugInfoIndexWasSavedToCache() override {}
|
|
bool GetDebugInfoHadFrameVariableErrors() const override { return false; }
|
|
void SetDebugInfoHadFrameVariableErrors() override {}
|
|
TypeSP MakeType(user_id_t, ConstString, std::optional<uint64_t>,
|
|
SymbolContextScope *, user_id_t, Type::EncodingDataType,
|
|
const Declaration &, const CompilerType &, Type::ResolveState,
|
|
uint32_t) override {
|
|
return nullptr;
|
|
}
|
|
TypeSP CopyType(const TypeSP &) override { return nullptr; }
|
|
|
|
FakeSymbolFile(ObjectFileSP objfile_sp)
|
|
: m_objfile_sp(std::move(objfile_sp)) {}
|
|
|
|
ObjectFileSP m_objfile_sp;
|
|
CompUnitSP m_cu_sp;
|
|
};
|
|
|
|
struct FakeModuleFixture {
|
|
TestFile file;
|
|
ModuleSP module_sp;
|
|
SectionSP text_sp;
|
|
LineTable *line_table;
|
|
};
|
|
|
|
class LineTableTest : public testing::Test {
|
|
SubsystemRAII<ObjectFileELF, FakeSymbolFile> subsystems;
|
|
};
|
|
|
|
class LineSequenceBuilder {
|
|
public:
|
|
std::vector<LineTable::Sequence> Build() { return std::move(m_sequences); }
|
|
enum Terminal : bool { Terminal = true };
|
|
void Entry(addr_t addr, bool terminal = false) {
|
|
LineTable::AppendLineEntryToSequence(
|
|
m_sequence, addr, /*line=*/1, /*column=*/0,
|
|
/*file_idx=*/0,
|
|
/*is_start_of_statement=*/false, /*is_start_of_basic_block=*/false,
|
|
/*is_prologue_end=*/false, /*is_epilogue_begin=*/false, terminal);
|
|
if (terminal)
|
|
m_sequences.push_back(std::move(m_sequence));
|
|
}
|
|
|
|
private:
|
|
std::vector<LineTable::Sequence> m_sequences;
|
|
LineTable::Sequence m_sequence;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
char FakeSymbolFile::ID;
|
|
|
|
static llvm::Expected<FakeModuleFixture>
|
|
CreateFakeModule(std::vector<LineTable::Sequence> line_sequences) {
|
|
Expected<TestFile> file = TestFile::fromYaml(R"(
|
|
--- !ELF
|
|
FileHeader:
|
|
Class: ELFCLASS64
|
|
Data: ELFDATA2LSB
|
|
Type: ET_EXEC
|
|
Machine: EM_386
|
|
Sections:
|
|
- Name: .text
|
|
Type: SHT_PROGBITS
|
|
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
|
|
AddressAlign: 0x0010
|
|
Address: 0x0000
|
|
Size: 0x1000
|
|
)");
|
|
if (!file)
|
|
return file.takeError();
|
|
|
|
auto module_sp = std::make_shared<Module>(file->moduleSpec());
|
|
SectionSP text_sp =
|
|
module_sp->GetSectionList()->FindSectionByName(ConstString(".text"));
|
|
if (!text_sp)
|
|
return createStringError("No .text");
|
|
|
|
auto cu_up = std::make_unique<CompileUnit>(module_sp, /*user_data=*/nullptr,
|
|
/*support_file_sp=*/nullptr,
|
|
/*uid=*/0, eLanguageTypeC,
|
|
/*is_optimized=*/eLazyBoolNo);
|
|
LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences));
|
|
cu_up->SetLineTable(line_table);
|
|
cast<FakeSymbolFile>(module_sp->GetSymbolFile())
|
|
->InjectCompileUnit(std::move(cu_up));
|
|
|
|
return FakeModuleFixture{std::move(*file), std::move(module_sp),
|
|
std::move(text_sp), line_table};
|
|
}
|
|
|
|
TEST_F(LineTableTest, lower_bound) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
builder.Entry(20); // Starts right after the previous sequence.
|
|
builder.Entry(30, LineSequenceBuilder::Terminal);
|
|
builder.Entry(40); // Gap after the previous sequence.
|
|
builder.Entry(50, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto make_addr = [&](addr_t addr) { return Address(fixture->text_sp, addr); };
|
|
|
|
EXPECT_EQ(table->lower_bound(make_addr(0)), 0u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(9)), 0u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(10)), 1u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(19)), 1u);
|
|
|
|
// Skips over the terminal entry.
|
|
EXPECT_EQ(table->lower_bound(make_addr(20)), 3u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(29)), 3u);
|
|
|
|
// In case there's no "real" entry at this address, the function returns the
|
|
// first real entry.
|
|
EXPECT_EQ(table->lower_bound(make_addr(30)), 5u);
|
|
EXPECT_EQ(table->lower_bound(make_addr(40)), 5u);
|
|
|
|
// In a gap, return the first entry after the gap.
|
|
EXPECT_EQ(table->lower_bound(make_addr(39)), 5u);
|
|
|
|
// And if there's no such entry, return the size of the list.
|
|
EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize());
|
|
EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize());
|
|
}
|
|
|
|
TEST_F(LineTableTest, GetLineEntryIndexRange) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto make_range = [&](addr_t addr, addr_t size) {
|
|
return AddressRange(fixture->text_sp, addr, size);
|
|
};
|
|
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 10)),
|
|
testing::Pair(0, 1));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 20)),
|
|
testing::Pair(0, 3)); // Includes the terminal entry.
|
|
// Partial overlap on one side.
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(3, 7)),
|
|
testing::Pair(0, 1));
|
|
// On the other side
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 15)),
|
|
testing::Pair(0, 2));
|
|
// On both sides
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(2, 3)),
|
|
testing::Pair(0, 1));
|
|
// Empty ranges
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 0)),
|
|
testing::Pair(0, 0));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(5, 0)),
|
|
testing::Pair(0, 0));
|
|
EXPECT_THAT(table->GetLineEntryIndexRange(make_range(10, 0)),
|
|
testing::Pair(1, 1));
|
|
}
|
|
|
|
TEST_F(LineTableTest, FindLineEntryByAddress) {
|
|
LineSequenceBuilder builder;
|
|
builder.Entry(0);
|
|
builder.Entry(10);
|
|
builder.Entry(20, LineSequenceBuilder::Terminal);
|
|
builder.Entry(20); // Starts right after the previous sequence.
|
|
builder.Entry(30, LineSequenceBuilder::Terminal);
|
|
builder.Entry(40); // Gap after the previous sequence.
|
|
builder.Entry(50, LineSequenceBuilder::Terminal);
|
|
|
|
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
|
|
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
|
|
|
|
LineTable *table = fixture->line_table;
|
|
|
|
auto find = [&](addr_t addr) -> std::tuple<addr_t, addr_t, bool> {
|
|
LineEntry entry;
|
|
if (!table->FindLineEntryByAddress(Address(fixture->text_sp, addr), entry))
|
|
return {LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false};
|
|
return {entry.range.GetBaseAddress().GetFileAddress(),
|
|
entry.range.GetByteSize(),
|
|
static_cast<bool>(entry.is_terminal_entry)};
|
|
};
|
|
|
|
EXPECT_THAT(find(0), testing::FieldsAre(0, 10, false));
|
|
EXPECT_THAT(find(9), testing::FieldsAre(0, 10, false));
|
|
EXPECT_THAT(find(10), testing::FieldsAre(10, 10, false));
|
|
EXPECT_THAT(find(19), testing::FieldsAre(10, 10, false));
|
|
EXPECT_THAT(find(20), testing::FieldsAre(20, 10, false));
|
|
EXPECT_THAT(find(30), testing::FieldsAre(LLDB_INVALID_ADDRESS,
|
|
LLDB_INVALID_ADDRESS, false));
|
|
EXPECT_THAT(find(40), testing::FieldsAre(40, 10, false));
|
|
EXPECT_THAT(find(50), testing::FieldsAre(LLDB_INVALID_ADDRESS,
|
|
LLDB_INVALID_ADDRESS, false));
|
|
}
|