splhack updated this revision to Diff 538696.
splhack added a comment.

rebase


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())
@@ -2306,6 +2338,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
@@ -2167,3 +2167,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
@@ -583,6 +583,11 @@
     return m_source_file_cache;
   }
 
+  /// 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;
@@ -734,11 +739,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

Reply via email to