Skip to content

[lldb] refactor Target::Install function #108996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

dlav-sc
Copy link
Contributor

@dlav-sc dlav-sc commented Sep 17, 2024

refactor Target::Install function

@llvmbot
Copy link
Member

llvmbot commented Sep 17, 2024

@llvm/pr-subscribers-lldb

Author: None (dlav-sc)

Changes

Lldb documentation says that during remote debugging lldb client should check the existence of the desired executable file on the remote target and only if the file is missing copy it there. There is no such check in fact, so during an attempt to copy the executable ETXTBSY error can occur. This patch adds the check using a MD5 file hash value.

Fixed tests:
TestProcessHandle.py
TestFdLeak.py


Full diff: https://github.com/llvm/llvm-project/pull/108996.diff

1 Files Affected:

  • (modified) lldb/source/Target/Target.cpp (+125-40)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index f1659aae0800db..8f1cfc7b4f0b67 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -76,6 +76,106 @@
 using namespace lldb;
 using namespace lldb_private;
 
+namespace {
+
+struct ExecutableInstaller {
+
+  ExecutableInstaller(PlatformSP platform, ModuleSP module)
+      : m_platform{platform}, m_module{module},
+        m_local_file{m_module->GetFileSpec()},
+        m_remote_file{m_module->GetRemoteInstallFileSpec()} {}
+
+  void setRemoteFile() const { m_module->SetPlatformFileSpec(m_remote_file); }
+
+  PlatformSP m_platform;
+  ModuleSP m_module;
+  const FileSpec m_local_file;
+  const FileSpec m_remote_file;
+};
+
+struct MainExecutableInstaller {
+
+  MainExecutableInstaller(PlatformSP platform, TargetSP target, ModuleSP module,
+                          ProcessLaunchInfo *launch_info)
+      : m_platform{platform}, m_target{target}, m_module{module},
+        m_local_file{m_module->GetFileSpec()},
+        m_remote_file{
+            getRemoteFileSpec(m_platform, m_target, m_module, m_local_file)},
+        m_launch_info{launch_info} {}
+
+  void setRemoteFile() const {
+    m_module->SetPlatformFileSpec(m_remote_file);
+    m_launch_info->SetExecutableFile(m_remote_file,
+                                     false /*add_exe_file_as_first_arg*/);
+    m_platform->SetFilePermissions(m_remote_file, 0700 /*-rwx------*/);
+  }
+
+  PlatformSP m_platform;
+  TargetSP m_target;
+  ModuleSP m_module;
+  const FileSpec m_local_file;
+  const FileSpec m_remote_file;
+  ProcessLaunchInfo *m_launch_info;
+
+private:
+  // Creates a filename "<local_file_name>_<local_file_md5_hash>" for a file
+  // on a remote target. This is needed to check existance of the file on a
+  // remote machine and then install it if the file is missing.
+  static std::optional<std::string>
+  getRemoteFileName(const FileSpec &local_file) {
+    auto local_md5 = llvm::sys::fs::md5_contents(local_file.GetPath());
+    if (!local_md5)
+      return std::nullopt;
+
+    auto [high, low] = local_md5->words();
+
+    std::stringstream ss;
+    ss << local_file.GetFilename().GetCString() << "_" << high << low;
+    return ss.str();
+  }
+
+  static FileSpec getRemoteFileSpec(PlatformSP platform, TargetSP target,
+                                    ModuleSP module,
+                                    const FileSpec &local_file) {
+    FileSpec remote_file = module->GetRemoteInstallFileSpec();
+    if (remote_file || !target->GetAutoInstallMainExecutable())
+      return remote_file;
+
+    if (!local_file)
+      return {};
+
+    auto remote_filename = getRemoteFileName(local_file);
+    if (!remote_filename)
+      return {};
+
+    remote_file = platform->GetRemoteWorkingDirectory();
+    remote_file.AppendPathComponent(remote_filename.value());
+
+    return remote_file;
+  }
+};
+} // namespace
+
+template <typename Installer>
+static Status installExecutable(const Installer &installer) {
+  // Firstly check is the file already exists on a remote machine
+  if (installer.m_platform->GetFileExists(installer.m_remote_file)) {
+    installer.setRemoteFile();
+    return Status();
+  }
+
+  if (!installer.m_local_file)
+    return Status();
+
+  Status error = installer.m_platform->Install(installer.m_local_file,
+                                               installer.m_remote_file);
+  if (error.Fail())
+    return error;
+
+  installer.setRemoteFile();
+  return Status();
+}
+
 constexpr std::chrono::milliseconds EvaluateExpressionOptions::default_timeout;
 
 Target::Arch::Arch(const ArchSpec &spec)
@@ -3076,48 +3176,33 @@ TargetProperties &Target::GetGlobalProperties() {
 Status Target::Install(ProcessLaunchInfo *launch_info) {
   Status error;
   PlatformSP platform_sp(GetPlatform());
-  if (platform_sp) {
-    if (platform_sp->IsRemote()) {
-      if (platform_sp->IsConnected()) {
-        // Install all files that have an install path when connected to a
-        // remote platform. If target.auto-install-main-executable is set then
-        // also install the main executable even if it does not have an explicit
-        // install path specified.
-        const ModuleList &modules = GetImages();
-        const size_t num_images = modules.GetSize();
-        for (size_t idx = 0; idx < num_images; ++idx) {
-          ModuleSP module_sp(modules.GetModuleAtIndex(idx));
-          if (module_sp) {
-            const bool is_main_executable = module_sp == GetExecutableModule();
-            FileSpec local_file(module_sp->GetFileSpec());
-            if (local_file) {
-              FileSpec remote_file(module_sp->GetRemoteInstallFileSpec());
-              if (!remote_file) {
-                if (is_main_executable && GetAutoInstallMainExecutable()) {
-                  // Automatically install the main executable.
-                  remote_file = platform_sp->GetRemoteWorkingDirectory();
-                  remote_file.AppendPathComponent(
-                      module_sp->GetFileSpec().GetFilename().GetCString());
-                }
-              }
-              if (remote_file) {
-                error = platform_sp->Install(local_file, remote_file);
-                if (error.Success()) {
-                  module_sp->SetPlatformFileSpec(remote_file);
-                  if (is_main_executable) {
-                    platform_sp->SetFilePermissions(remote_file, 0700);
-                    if (launch_info)
-                      launch_info->SetExecutableFile(remote_file, false);
-                  }
-                } else
-                  break;
-              }
-            }
-          }
-        }
-      }
+  if (!platform_sp || !platform_sp->IsRemote() || !platform_sp->IsConnected())
+    return error;
+
+  // Install all files that have an install path when connected to a
+  // remote platform. If target.auto-install-main-executable is set then
+  // also install the main executable even if it does not have an explicit
+  // install path specified.
+
+  for (auto module_sp : GetImages().Modules()) {
+    if (module_sp == GetExecutableModule()) {
+      MainExecutableInstaller installer{platform_sp, shared_from_this(),
+                                        module_sp, launch_info};
+      if (installer.m_remote_file)
+        error = installExecutable(installer);
+      else if (GetAutoInstallMainExecutable())
+        error.SetErrorStringWithFormatv("Target::{0}() can't get a remote file",
+                                        __FUNCTION__);
+    } else {
+      ExecutableInstaller installer{platform_sp, module_sp};
+      if (installer.m_remote_file)
+        error = installExecutable(installer);
     }
+
+    if (error.Fail())
+      return error;
   }
+
   return error;
 }
 

@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Sep 17, 2024

Will look at this properly tomorrow but just looking at the title, I'm surprised that #88812 did not also fix this. Perhaps putfile and install are different code paths.

@dlav-sc
Copy link
Contributor Author

dlav-sc commented Sep 18, 2024

I'm surprised that #88812 did not also fix this. Perhaps putfile and install are different code paths.

Nope, I simply didn't have this commit, my bad. Maybe we can consider this PR as the refactoring one, if you don't mind.

@DavidSpickett
Copy link
Collaborator

Sure, please update the PR title and description to reflect that, then I'll take a look.

@dlav-sc dlav-sc force-pushed the dlav-sc/lldb-remote-file-install-check branch from a647e41 to 5161bba Compare September 18, 2024 10:43
Copy link

github-actions bot commented Sep 18, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@dlav-sc dlav-sc force-pushed the dlav-sc/lldb-remote-file-install-check branch from 33feed8 to 5161bba Compare September 18, 2024 12:05
@dlav-sc dlav-sc changed the title [lldb] add a check using an MD5 hash for whether a file needs to be installed on the remote target [lldb] refactor Target::Install function Sep 18, 2024
@dlav-sc
Copy link
Contributor Author

dlav-sc commented Sep 18, 2024

Sure, please update the PR title and description to reflect that, then I'll take a look.

Thanks

@dlav-sc dlav-sc force-pushed the dlav-sc/lldb-remote-file-install-check branch 2 times, most recently from bbf9688 to a10668a Compare September 20, 2024 11:56
Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@dlav-sc
Copy link
Contributor Author

dlav-sc commented Sep 20, 2024

Looks good to me.

Thank you for the review. I haven't merge rights yet, so could you press the button, please.

@DavidSpickett DavidSpickett merged commit 6ad268e into llvm:main Sep 23, 2024
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants