llvm-project/lldb/unittests/Symbol/LineTableTest.cpp
royitaqi 967434aa32
[lldb] Remerge #136236 (Avoid force loading symbols in statistics collection (#136795)
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.
2025-04-24 17:23:41 -07:00

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));
}