splhack updated this revision to Diff 534803. splhack added a comment. Move SetTargetGetModuleCallback from Debugger to Platform. https://discourse.llvm.org/t/rfc-python-callback-for-target-get-module/71580/4?u=splhack
Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D153734/new/ https://reviews.llvm.org/D153734 Files: lldb/include/lldb/Core/Debugger.h lldb/include/lldb/Interpreter/ScriptInterpreter.h lldb/include/lldb/Target/Platform.h lldb/include/lldb/Target/Target.h lldb/source/Core/Debugger.cpp lldb/source/Target/Platform.cpp lldb/source/Target/Target.cpp lldb/unittests/Target/CMakeLists.txt lldb/unittests/Target/GetModuleCallbackTest.cpp lldb/unittests/Target/Inputs/AndroidModule.c lldb/unittests/Target/Inputs/AndroidModule.so lldb/unittests/Target/Inputs/AndroidModule.so.sym lldb/unittests/Target/Inputs/AndroidModule.unstripped.so
Index: lldb/unittests/Target/Inputs/AndroidModule.so.sym =================================================================== --- /dev/null +++ lldb/unittests/Target/Inputs/AndroidModule.so.sym @@ -0,0 +1,21 @@ +MODULE Linux arm64 38830080A082E5515922C905D23890DA0 AndroidModule.so +INFO CODE_ID 8000833882A051E55922C905D23890DABDDEFECC +FILE 0 /private/tmp/test/AndroidModule.c +FUNC 162c 8 0 boom +162c 8 8 0 +FUNC 1634 8 0 boom_hidden +1634 8 12 0 +PUBLIC 15cc 0 __on_dlclose +PUBLIC 15dc 0 __emutls_unregister_key +PUBLIC 15e4 0 __on_dlclose_late +PUBLIC 15ec 0 __atexit_handler_wrapper +PUBLIC 1600 0 atexit +PUBLIC 161c 0 pthread_atfork +STACK CFI INIT 15cc 10 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 15dc 8 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 15e4 8 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 15ec 14 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 1600 1c .cfa: sp 0 + .ra: x30 +STACK CFI INIT 161c 10 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 162c 8 .cfa: sp 0 + .ra: x30 +STACK CFI INIT 1634 8 .cfa: sp 0 + .ra: x30 Index: lldb/unittests/Target/Inputs/AndroidModule.c =================================================================== --- /dev/null +++ lldb/unittests/Target/Inputs/AndroidModule.c @@ -0,0 +1,13 @@ +// aarch64-linux-android29-clang -shared -Os -glldb -g3 -Wl,--build-id=sha1 \ +// AndroidModule.c -o AndroidModule.so +// dump_syms AndroidModule.so > AndroidModule.so.sym +// cp AndroidModule.so AndroidModule.unstripped.so +// llvm-strip --strip-unneeded AndroidModule.so + +int boom(void) { + return 47; +} + +__attribute__((visibility("hidden"))) int boom_hidden(void) { + return 48; +} Index: lldb/unittests/Target/GetModuleCallbackTest.cpp =================================================================== --- /dev/null +++ lldb/unittests/Target/GetModuleCallbackTest.cpp @@ -0,0 +1,675 @@ +//===-- GetModuleCallbackTest.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/Breakpad/ObjectFileBreakpad.h" +#include "Plugins/ObjectFile/ELF/ObjectFileELF.h" +#include "Plugins/Platform/Android/PlatformAndroid.h" +#include "Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h" +#include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/Target.h" +#include "gmock/gmock.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::platform_android; +using namespace lldb_private::breakpad; +using namespace testing; + +namespace { + +constexpr llvm::StringLiteral k_process_plugin("mock-process-plugin"); +constexpr llvm::StringLiteral k_platform_dir("remote-android"); +constexpr llvm::StringLiteral k_cache_dir(".cache"); +constexpr llvm::StringLiteral k_module_file("AndroidModule.so"); +constexpr llvm::StringLiteral k_symbol_file("AndroidModule.unstripped.so"); +constexpr llvm::StringLiteral k_breakpad_symbol_file("AndroidModule.so.sym"); +constexpr llvm::StringLiteral k_arch("aarch64-none-linux"); +constexpr llvm::StringLiteral + k_module_uuid("80008338-82A0-51E5-5922-C905D23890DA-BDDEFECC"); +constexpr llvm::StringLiteral k_function_symbol("boom"); +constexpr llvm::StringLiteral k_hidden_function_symbol("boom_hidden"); +const size_t k_module_size = 3784; + +class MockDebugger : public Debugger { +public: + MockDebugger() : Debugger(nullptr, nullptr) {} + + MOCK_METHOD4(CallTargetGetModuleCallback, + Status(void *, const ModuleSpec &, FileSpec &, FileSpec &)); +}; + +ModuleSpec GetTestModuleSpec(); + +class MockProcess : public Process { +public: + MockProcess(TargetSP target_sp, ListenerSP listener_sp) + : Process(target_sp, listener_sp) {} + + llvm::StringRef GetPluginName() override { return k_process_plugin; }; + + bool CanDebug(TargetSP target, bool plugin_specified_by_name) override { + return true; + } + + Status DoDestroy() override { return Status(); } + + void RefreshStateAfterStop() override {} + + bool DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) override { + return false; + } + + size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size, + Status &error) override { + return 0; + } + + bool GetModuleSpec(const FileSpec &module_file_spec, const ArchSpec &arch, + ModuleSpec &module_spec) override { + module_spec = GetTestModuleSpec(); + return true; + } +}; + +FileSpec GetTestDir() { + const auto *info = UnitTest::GetInstance()->current_test_info(); + FileSpec test_dir = HostInfo::GetProcessTempDir(); + test_dir.AppendPathComponent(std::string(info->test_case_name()) + "-" + + info->name()); + std::error_code ec = llvm::sys::fs::create_directory(test_dir.GetPath()); + EXPECT_FALSE(ec); + return test_dir; +} + +FileSpec GetRemotePath() { + FileSpec fs("/", FileSpec::Style::posix); + fs.AppendPathComponent("bin"); + fs.AppendPathComponent(k_module_file); + return fs; +} + +FileSpec GetUuidView(FileSpec spec) { + spec.AppendPathComponent(k_platform_dir); + spec.AppendPathComponent(k_cache_dir); + spec.AppendPathComponent(k_module_uuid); + spec.AppendPathComponent(k_module_file); + return spec; +} + +void BuildEmptyCacheDir(const FileSpec &test_dir) { + FileSpec cache_dir(test_dir); + cache_dir.AppendPathComponent(k_platform_dir); + cache_dir.AppendPathComponent(k_cache_dir); + std::error_code ec = llvm::sys::fs::create_directories(cache_dir.GetPath()); + EXPECT_FALSE(ec); +} + +FileSpec BuildCacheDir(const FileSpec &test_dir) { + FileSpec uuid_view = GetUuidView(test_dir); + std::error_code ec = + llvm::sys::fs::create_directories(uuid_view.GetDirectory().GetCString()); + EXPECT_FALSE(ec); + ec = llvm::sys::fs::copy_file(GetInputFilePath(k_module_file), + uuid_view.GetPath().c_str()); + EXPECT_FALSE(ec); + return uuid_view; +} + +FileSpec GetSymFileSpec(const FileSpec &uuid_view) { + return FileSpec(uuid_view.GetPath() + ".sym"); +} + +FileSpec BuildCacheDirWithSymbol(const FileSpec &test_dir) { + FileSpec uuid_view = BuildCacheDir(test_dir); + std::error_code ec = + llvm::sys::fs::copy_file(GetInputFilePath(k_symbol_file), + GetSymFileSpec(uuid_view).GetPath().c_str()); + EXPECT_FALSE(ec); + return uuid_view; +} + +FileSpec BuildCacheDirWithBreakpadSymbol(const FileSpec &test_dir) { + FileSpec uuid_view = BuildCacheDir(test_dir); + std::error_code ec = + llvm::sys::fs::copy_file(GetInputFilePath(k_breakpad_symbol_file), + GetSymFileSpec(uuid_view).GetPath().c_str()); + EXPECT_FALSE(ec); + return uuid_view; +} + +ModuleSpec GetTestModuleSpec() { + ModuleSpec module_spec(GetRemotePath(), ArchSpec(k_arch)); + module_spec.GetUUID().SetFromStringRef(k_module_uuid); + module_spec.SetObjectSize(k_module_size); + return module_spec; +} + +void CheckModule(const ModuleSP &module_sp) { + ASSERT_TRUE(module_sp); + ASSERT_EQ(module_sp->GetUUID().GetAsString(), k_module_uuid); + ASSERT_EQ(module_sp->GetObjectOffset(), 0U); + ASSERT_EQ(module_sp->GetPlatformFileSpec(), GetRemotePath()); +} + +SymbolContextList FindFunctions(const ModuleSP &module_sp, + const llvm::StringRef &name) { + SymbolContextList sc_list; + ModuleFunctionSearchOptions function_options; + function_options.include_symbols = true; + function_options.include_inlines = true; + FunctionNameType type = static_cast<FunctionNameType>(eSymbolTypeCode); + module_sp->FindFunctions(ConstString(name), CompilerDeclContext(), type, + function_options, sc_list); + return sc_list; +} + +void CheckStrippedSymbol(const ModuleSP &module_sp) { + SymbolContextList sc_list = FindFunctions(module_sp, k_function_symbol); + EXPECT_EQ(1U, sc_list.GetSize()); + + sc_list = FindFunctions(module_sp, k_hidden_function_symbol); + EXPECT_EQ(0U, sc_list.GetSize()); +} + +void CheckUnstrippedSymbol(const ModuleSP &module_sp) { + SymbolContextList sc_list = FindFunctions(module_sp, k_function_symbol); + EXPECT_EQ(1U, sc_list.GetSize()); + + sc_list = FindFunctions(module_sp, k_hidden_function_symbol); + EXPECT_EQ(1U, sc_list.GetSize()); +} + +ProcessSP MockProcessCreateInstance(TargetSP target_sp, ListenerSP listener_sp, + const FileSpec *crash_file_path, + bool can_connect) { + return std::make_shared<MockProcess>(target_sp, listener_sp); +} + +class GetModuleCallbackTest : public testing::Test { + SubsystemRAII<FileSystem, HostInfo, ObjectFileBreakpad, ObjectFileELF, + PlatformAndroid, SymbolFileBreakpad, SymbolFileSymtab> + subsystems; + +public: + void SetUp() override { + m_test_dir = GetTestDir(); + + // Set module cache directory for PlatformAndroid. + PlatformAndroid::GetGlobalPlatformProperties().SetModuleCacheDirectory( + m_test_dir); + + // Create PlatformAndroid. + ArchSpec arch(k_arch); + m_platform_sp = PlatformAndroid::CreateInstance(true, &arch); + EXPECT_TRUE(m_platform_sp); + + // Create Target. + m_debugger.GetTargetList().CreateTarget( + m_debugger, "", arch, eLoadDependentsNo, m_platform_sp, m_target_sp); + EXPECT_TRUE(m_target_sp); + + // Create MockProcess. + PluginManager::RegisterPlugin(k_process_plugin, "", + MockProcessCreateInstance); + m_process_sp = + m_target_sp->CreateProcess(Listener::MakeListener("test-listener"), + k_process_plugin, /*crash_file=*/nullptr, + /*can_connect=*/true); + EXPECT_TRUE(m_process_sp); + + m_module_spec = GetTestModuleSpec(); + m_check_module_spec = [this](auto &module_spec) { + EXPECT_TRUE(m_module_spec.Matches(module_spec, + /*exact_arch_match=*/true)); + }; + + // Any non-nullptr value works. + m_target_get_module_callback = static_cast<void *>(this); + } + +protected: + std::function<void(const ModuleSpec &)> m_check_module_spec; + + FileSpec m_test_dir; + MockDebugger m_debugger; + PlatformSP m_platform_sp; + TargetSP m_target_sp; + ProcessSP m_process_sp; + ModuleSpec m_module_spec; + void *m_target_get_module_callback; +}; + +} // namespace + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleWithCachedModule) { + // The module file is cached, and the get module callback is not set. + // GetOrCreateModule should succeed to return the module from the cache. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(_, _, _, _)).Times(0); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleWithCachedModuleAndSymbol) { + // The module and symbol files are cached, and the get module callback is not + // set. GetOrCreateModule should succeed to return the module from the cache + // with the symbol. + FileSpec uuid_view = BuildCacheDirWithSymbol(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(_, _, _, _)).Times(0); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), GetSymFileSpec(uuid_view)); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleWithCachedModuleAndBreakpadSymbol) { + // The module file and breakpad symbol file are cached, and the get module + // callback is not set. GetOrCreateModule should succeed to return the module + // from the cache with the symbol. + FileSpec uuid_view = BuildCacheDirWithBreakpadSymbol(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(_, _, _, _)).Times(0); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), GetSymFileSpec(uuid_view)); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleFailure) { + // The cache dir is empty, and the get module callback is not set. + // GetOrCreateModule should fail because PlatformAndroid tries to download the + // module and fails. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(_, _, _, _)).Times(0); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + ASSERT_FALSE(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleInterpreterFailureNoCache) { + // The cache dir is empty, also the get module callback fails for some reason. + // GetOrCreateModule should fail because PlatformAndroid tries to download the + // module and fails. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback( + m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + Return(Status("The get module callback failed")))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + ASSERT_FALSE(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackFailureCached) { + // The module file is cached, so GetOrCreateModule should succeed to return + // the module from the cache even though the get module callback fails for + // some reason. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback( + m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + Return(Status("The get module callback failed")))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackNoFiles) { + // The module file is cached, so GetOrCreateModule should succeed to return + // the module from the cache even though the get module callback returns + // no files. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + // The get module callback succeeds but it does not set + // module_file_spec nor symbol_file_spec. + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackNonExistentModule) { + // The module file is cached, so GetOrCreateModule should succeed to return + // the module from the cache even though the get module callback returns + // non-existent module file. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath("/this path does not exist"); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackNonExistentSymbol) { + // The module file is cached, so GetOrCreateModule should succeed to return + // the module from the cache even though the get module callback returns + // non-existent symbol file. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + // The get module callback returns a right module file. + module_file_spec.SetPath( + GetInputFilePath(k_module_file)); + }), + WithArg<3>([](auto &symbol_file_spec) { + // But it returns non-existent symbols file. + symbol_file_spec.SetPath("/this path does not exist"); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_TRUE(module_sp->GetSymbolFileFileSpec().GetPath().empty()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackSuccessWithModule) { + // The get module callback returns a module file, GetOrCreateModule should + // succeed to return the module from the Inputs directory. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath( + GetInputFilePath(k_module_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), + FileSpec(GetInputFilePath(k_module_file))); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckStrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleCallbackSuccessWithSymbolAsModule) { + // The get module callback returns the symbol file as a module file. It + // should work since the sections and UUID of the symbol file are the exact + // same with the module file, GetOrCreateModule should succeed to return the + // module with the symbol file from Inputs directory. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath( + GetInputFilePath(k_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), + FileSpec(GetInputFilePath(k_symbol_file))); + ASSERT_FALSE(module_sp->GetSymbolFileFileSpec()); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleCallbackSuccessWithSymbolAsModuleAndSymbol) { + // The get module callback returns a symbol file as both a module file and a + // symbol file. It should work since the sections and UUID of the symbol file + // are the exact same with the module file, GetOrCreateModule should succeed + // to return the module with the symbol file from Inputs directory. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce( + DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath(GetInputFilePath(k_symbol_file)); + }), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath(GetInputFilePath(k_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), + FileSpec(GetInputFilePath(k_symbol_file))); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), + FileSpec(GetInputFilePath(k_symbol_file))); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleCallbackSuccessWithModuleAndSymbol) { + // The get module callback returns a module file and a symbol file, + // GetOrCreateModule should succeed to return the module from Inputs + // directory, along with the symbol file. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce( + DoAll(WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath(GetInputFilePath(k_module_file)); + }), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath(GetInputFilePath(k_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), + FileSpec(GetInputFilePath(k_module_file))); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), + FileSpec(GetInputFilePath(k_symbol_file))); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleCallbackSuccessWithModuleAndBreakpadSymbol) { + // The get module callback returns a module file and a breakpad symbol file, + // GetOrCreateModule should succeed to return the module with the symbol file + // from Inputs directory. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll( + WithArg<1>(m_check_module_spec), + WithArg<2>([](auto &module_file_spec) { + module_file_spec.SetPath(GetInputFilePath(k_module_file)); + }), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath(GetInputFilePath(k_breakpad_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), + FileSpec(GetInputFilePath(k_module_file))); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), + FileSpec(GetInputFilePath(k_breakpad_symbol_file))); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleCallbackSuccessWithOnlySymbol) { + // The get callback returns only a symbol file, and the module is cached, + // GetOrCreateModule should succeed to return the module from the cache + // along with the symbol file from the Inputs directory. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath( + GetInputFilePath(k_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), + FileSpec(GetInputFilePath(k_symbol_file))); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleCallbackSuccessWithOnlyBreakpadSymbol) { + // The get callback returns only a breakpad symbol file, and the module is + // cached, GetOrCreateModule should succeed to return the module from the + // cache along with the symbol file from the Inputs directory. + FileSpec uuid_view = BuildCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath( + GetInputFilePath(k_breakpad_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + CheckModule(module_sp); + ASSERT_EQ(module_sp->GetFileSpec(), uuid_view); + ASSERT_EQ(module_sp->GetSymbolFileFileSpec(), + FileSpec(GetInputFilePath(k_breakpad_symbol_file))); + CheckUnstrippedSymbol(module_sp); +} + +TEST_F(GetModuleCallbackTest, GetOrCreateModuleNoCacheWithCallbackOnlySymbol) { + // The get callback returns only a symbol file, but the module is not + // cached, GetOrCreateModule should fail because of the missing module. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath( + GetInputFilePath(k_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + ASSERT_FALSE(module_sp); +} + +TEST_F(GetModuleCallbackTest, + GetOrCreateModuleNoCacheWithCallbackOnlyBreakpadSymbol) { + // The get callback returns only a breakpad symbol file, but the module is not + // cached, GetOrCreateModule should fail because of the missing module. + BuildEmptyCacheDir(m_test_dir); + + EXPECT_CALL(m_debugger, CallTargetGetModuleCallback(m_target_get_module_callback, _, _, _)) + .Times(1) + .WillOnce(DoAll(WithArg<1>(m_check_module_spec), + WithArg<3>([](auto &symbol_file_spec) { + symbol_file_spec.SetPath( + GetInputFilePath(k_breakpad_symbol_file)); + }), + Return(Status()))); + + m_platform_sp->SetTargetGetModuleCallback(m_target_get_module_callback); + + ModuleSP module_sp = + m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); + ASSERT_FALSE(module_sp); +} Index: lldb/unittests/Target/CMakeLists.txt =================================================================== --- lldb/unittests/Target/CMakeLists.txt +++ lldb/unittests/Target/CMakeLists.txt @@ -2,6 +2,7 @@ ABITest.cpp DynamicRegisterInfoTest.cpp ExecutionContextTest.cpp + GetModuleCallbackTest.cpp MemoryRegionInfoTest.cpp MemoryTest.cpp MemoryTagMapTest.cpp @@ -15,9 +16,12 @@ LINK_LIBS lldbCore lldbHost + lldbPluginObjectFileBreakpad lldbPluginObjectFileELF lldbPluginPlatformLinux lldbPluginPlatformMacOSX + lldbPluginPlatformAndroid + lldbPluginSymbolFileBreakpad lldbPluginSymbolFileSymtab lldbTarget lldbSymbol @@ -27,4 +31,10 @@ Support ) -add_unittest_inputs(TargetTests TestModule.so) +set(test_inputs + AndroidModule.so + AndroidModule.so.sym + AndroidModule.unstripped.so + TestModule.so + ) +add_unittest_inputs(TargetTests "${test_inputs}") Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -2129,19 +2129,46 @@ // of the library bool did_create_module = false; FileSpecList search_paths = GetExecutableSearchPaths(); - // If there are image search path entries, try to use them first to acquire - // a suitable image. - if (m_image_search_paths.GetSize()) { - ModuleSpec transformed_spec(module_spec); - ConstString transformed_dir; - if (m_image_search_paths.RemapPath( - module_spec.GetFileSpec().GetDirectory(), transformed_dir)) { - transformed_spec.GetFileSpec().SetDirectory(transformed_dir); - transformed_spec.GetFileSpec().SetFilename( - module_spec.GetFileSpec().GetFilename()); - error = ModuleList::GetSharedModule(transformed_spec, module_sp, - &search_paths, &old_modules, - &did_create_module); + FileSpec symbol_file_spec; + + // Call get module callback if set. This allows users to implement their own + // module cache system. For example, to leverage build system artifacts, to + // bypass pulling files from remote platform, or to search symbol files from + // symbol servers. + CallGetModuleCallbackIfSet(module_spec, module_sp, symbol_file_spec, + did_create_module); + + // The result of this CallGetModuleCallbackIfSet is one of the following. + // 1. module_sp:loaded, symbol_file_spec:set + // The callback found a module file and a symbol file for the + // module_spec. We will call module_sp->SetSymbolFileFileSpec with + // the symbol_file_spec later. + // 2. module_sp:loaded, symbol_file_spec:empty + // The callback only found a module file for the module_spec. + // 3. module_sp:empty, symbol_file_spec:set + // The callback only found a symbol file for the module. We continue + // to find a module file for this module_spec and we will call + // module_sp->SetSymbolFileFileSpec with the symbol_file_spec later. + // 4. module_sp:empty, symbol_file_spec:empty + // The callback is not set. Or the callback did not find any module + // files nor any symbol files. Or the callback failed, or something + // went wrong. We continue to find a module file for this module_spec. + + if (!module_sp) { + // If there are image search path entries, try to use them to acquire a + // suitable image. + if (m_image_search_paths.GetSize()) { + ModuleSpec transformed_spec(module_spec); + ConstString transformed_dir; + if (m_image_search_paths.RemapPath( + module_spec.GetFileSpec().GetDirectory(), transformed_dir)) { + transformed_spec.GetFileSpec().SetDirectory(transformed_dir); + transformed_spec.GetFileSpec().SetFilename( + module_spec.GetFileSpec().GetFilename()); + error = ModuleList::GetSharedModule(transformed_spec, module_sp, + &search_paths, &old_modules, + &did_create_module); + } } } @@ -2232,6 +2259,11 @@ }); } + // If the get module callback had found a symbol file, set it to the + // module_sp before preloading symbols. + if (symbol_file_spec) + module_sp->SetSymbolFileFileSpec(symbol_file_spec); + // Preload symbols outside of any lock, so hopefully we can do this for // each library in parallel. if (GetPreloadSymbols()) @@ -2307,6 +2339,110 @@ return module_sp; } +void Target::CallGetModuleCallbackIfSet(const ModuleSpec &module_spec, + lldb::ModuleSP &module_sp, + FileSpec &symbol_file_spec, + bool &did_create_module) { + if (!m_platform_sp) + return; + + void *target_get_module_callback_baton = + m_platform_sp->GetTargetGetModuleCallback(); + if (!target_get_module_callback_baton) + return; + + FileSpec module_file_spec; + Status error = GetDebugger().CallTargetGetModuleCallback( + target_get_module_callback_baton, module_spec, module_file_spec, + symbol_file_spec); + + // Target get module callback is set and called. Check the error. + Log *log = GetLog(LLDBLog::Target); + if (error.Fail()) { + LLDB_LOGF(log, "%s: get module callback failed: %s", + LLVM_PRETTY_FUNCTION, error.AsCString()); + return; + } + + // The get module callback was succeeded. It should returned + // 1. a combination of a module file and a symbol file. + // 2. or only a module file. + // 3. or only a symbol file. For example, a breakpad symbol text file. + // + // Check the module_file_spec and symbol_file_spec values. + // 1. module:empty symbol:empty -> Fail + // 2. module:exists symbol:exists -> Success + // 3. module:exists symbol:empty -> Success + // 4. module:empty symbol:exists -> Success + if (!module_file_spec && !symbol_file_spec) { + // This is 4. module:empty symbol:empty -> Fail + LLDB_LOGF(log, + "%s: get module callback did not set both " + "module_file_spec and symbol_file_spec", + LLVM_PRETTY_FUNCTION); + return; + } + + if (module_file_spec && !FileSystem::Instance().Exists(module_file_spec)) { + LLDB_LOGF(log, + "%s: get module callback set a non-existent file to " + "module_file_spec: %s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str()); + // Clear symbol_file_spec for the error. + symbol_file_spec.Clear(); + return; + } + + if (symbol_file_spec && !FileSystem::Instance().Exists(symbol_file_spec)) { + LLDB_LOGF(log, + "%s: get module callback set a non-existent file to " + "symbol_file_spec: %s", + LLVM_PRETTY_FUNCTION, symbol_file_spec.GetPath().c_str()); + // Clear symbol_file_spec for the error. + symbol_file_spec.Clear(); + return; + } + + if (!module_file_spec && symbol_file_spec) { + // The get module callback returned + // 3. only a symbol file. For example, a breakpad symbol text file. + // GetOrCreateModule will use this returned symbol_file_spec. + LLDB_LOGF(log, "%s: get module callback succeeded: symbol=%s", + LLVM_PRETTY_FUNCTION, symbol_file_spec.GetPath().c_str()); + return; + } + + // The get module callback returned + // 1. a combination of a module file and a symbol file. + // 2. or only a module file. + // Load it. + auto cached_module_spec(module_spec); + cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 + // content hash instead of real UUID. + cached_module_spec.GetFileSpec() = module_file_spec; + cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); + cached_module_spec.SetObjectOffset(0); + + error = ModuleList::GetSharedModule(cached_module_spec, module_sp, nullptr, + nullptr, &did_create_module, false); + if (error.Success() && module_sp) { + // Succeeded to load the module file. + LLDB_LOGF(log, + "%s: get module callback succeeded: module=%s symbol=%s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str(), + symbol_file_spec.GetPath().c_str()); + } else { + LLDB_LOGF(log, + "%s: get module callback succeeded but failed to load: " + "module=%s symbol=%s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str(), + symbol_file_spec.GetPath().c_str()); + // Clear module_sp and symbol_file_spec for the error. + module_sp.reset(); + symbol_file_spec.Clear(); + } +} + TargetSP Target::CalculateTarget() { return shared_from_this(); } ProcessSP Target::CalculateProcess() { return m_process_sp; } Index: lldb/source/Target/Platform.cpp =================================================================== --- lldb/source/Target/Platform.cpp +++ lldb/source/Target/Platform.cpp @@ -1971,6 +1971,19 @@ return {}; } +void Platform::SetTargetGetModuleCallback(void *callback_baton) { + // NOTE: When m_target_get_module_callback_baton is already set with + // non-nullptr, this will leak the Python callable object behind the pointer + // as other callbacks, because this does not call Py_DECREF the object. But it + // should be almost zero impact since this method is expected to be called + // only once. + m_target_get_module_callback_baton = callback_baton; +} + +void *Platform::GetTargetGetModuleCallback() const { + return m_target_get_module_callback_baton; +} + PlatformSP PlatformList::GetOrCreate(llvm::StringRef name) { std::lock_guard<std::recursive_mutex> guard(m_mutex); for (const PlatformSP &platform_sp : m_platforms) { Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -2141,3 +2141,15 @@ "Debugger::GetThreadPool called before Debugger::Initialize"); return *g_thread_pool; } + +Status Debugger::CallTargetGetModuleCallback( + void *target_get_module_callback_baton, const ModuleSpec &module_spec, + FileSpec &module_file_spec, FileSpec &symbol_file_spec) { + ScriptInterpreter *script_interpreter = GetScriptInterpreter(); + if (!script_interpreter) + return Status("Cannot find ScriptInterpreter"); + + return script_interpreter->CallTargetGetModuleCallback( + target_get_module_callback_baton, module_spec, module_file_spec, + symbol_file_spec); +} Index: lldb/include/lldb/Target/Target.h =================================================================== --- lldb/include/lldb/Target/Target.h +++ lldb/include/lldb/Target/Target.h @@ -1625,6 +1625,12 @@ Target(const Target &) = delete; const Target &operator=(const Target &) = delete; + +private: + void CallGetModuleCallbackIfSet(const ModuleSpec &module_spec, + lldb::ModuleSP &module_sp, + FileSpec &symbol_file_spec, + bool &did_create_module); }; } // namespace lldb_private Index: lldb/include/lldb/Target/Platform.h =================================================================== --- lldb/include/lldb/Target/Platform.h +++ lldb/include/lldb/Target/Platform.h @@ -881,6 +881,14 @@ virtual Args GetExtraStartupCommands(); + /// Set target get module callback. This allows users to implement their own + /// module cache system. For example, to leverage artifacts of build system, + /// to bypass pulling files from remote platform, or to search symbol files + /// from symbol servers. + void SetTargetGetModuleCallback(void *callback_baton); + + void *GetTargetGetModuleCallback() const; + protected: /// Create a list of ArchSpecs with the given OS and a architectures. The /// vendor field is left as an "unspecified unknown". @@ -928,6 +936,7 @@ std::vector<ConstString> m_trap_handlers; bool m_calculated_trap_handlers; const std::unique_ptr<ModuleCache> m_module_cache; + void *m_target_get_module_callback_baton = nullptr; /// Ask the Platform subclass to fill in the list of trap handler names /// Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -427,6 +427,16 @@ const char *user_input, bool is_callback) {} + /// Call the target get module callback to get a module. + virtual Status CallTargetGetModuleCallback(void *callback_baton, + const ModuleSpec &module_spec, + FileSpec &module_file_spec, + FileSpec &symbol_file_spec) { + Status error; + error.SetErrorString("unimplemented"); + return error; + } + virtual bool GetScriptedSummary(const char *function_name, lldb::ValueObjectSP valobj, StructuredData::ObjectSP &callee_wrapper_sp, Index: lldb/include/lldb/Core/Debugger.h =================================================================== --- lldb/include/lldb/Core/Debugger.h +++ lldb/include/lldb/Core/Debugger.h @@ -515,6 +515,11 @@ void FlushProcessOutput(Process &process, bool flush_stdout, bool flush_stderr); + /// Call target get module callback if set. + virtual Status CallTargetGetModuleCallback( + void *target_get_module_callback_baton, const ModuleSpec &module_spec, + FileSpec &module_file_spec, FileSpec &symbol_file_spec); + protected: friend class CommandInterpreter; friend class REPL; @@ -670,11 +675,12 @@ eBroadcastBitEventThreadIsListening = (1 << 0), }; -private: +protected: // Use Debugger::CreateInstance() to get a shared pointer to a new debugger // object Debugger(lldb::LogOutputCallback m_log_callback, void *baton); +private: Debugger(const Debugger &) = delete; const Debugger &operator=(const Debugger &) = delete; };
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits