Skip to content

[LLDB] Add a target.launch-working-dir setting #113521

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
merged 1 commit into from
Nov 5, 2024

Conversation

walter-erquinigo
Copy link
Member

@walter-erquinigo walter-erquinigo commented Oct 24, 2024

Internally we use bazel in a way in which it can drop you in a LLDB session with the target launched in a particular cwd, which is needed for things to work. We've been making this automation work via process launch -w. However, if later the user wants to restart the process with r, then they end up using a different cwd for relaunching the process. As a way to fix this, I'm adding a target-level setting that allows configuring a default cwd used for launching the process without needing the user to specify it manually.

@llvmbot
Copy link
Member

llvmbot commented Oct 24, 2024

@llvm/pr-subscribers-lldb

Author: Walter Erquinigo (walter-erquinigo)

Changes

Internally we use bazel in a way in which it can drop you in a LLDB session with the target launched in a particular cwd, which is needed for things to work. We've been making this automation work via process launch -w. However, if later the user wants to restart the process with r, then they end up using a different cwd for relaunching the process. As a way to fix this, I'm adding a target-level setting that allows overriding the cwd used for launching the process without needing the user to specify it manually.


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

5 Files Affected:

  • (modified) lldb/include/lldb/Target/Target.h (+3)
  • (modified) lldb/source/Commands/CommandObjectProcess.cpp (+5)
  • (modified) lldb/source/Target/Target.cpp (+9)
  • (modified) lldb/source/Target/TargetProperties.td (+4)
  • (modified) lldb/test/API/commands/process/launch/TestProcessLaunch.py (+45)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index e4848f19e64d62..0e8bbb41f29941 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -37,6 +37,7 @@
 #include "lldb/Utility/RealpathPrefixes.h"
 #include "lldb/Utility/Timeout.h"
 #include "lldb/lldb-public.h"
+#include "llvm/ADT/StringRef.h"
 
 namespace lldb_private {
 
@@ -114,6 +115,8 @@ class TargetProperties : public Properties {
 
   void SetDisableSTDIO(bool b);
 
+  std::optional<llvm::StringRef> GetLaunchWorkingDirectory() const;
+
   const char *GetDisassemblyFlavor() const;
 
   InlineStrategy GetInlineStrategy() const;
diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp
index e7c7d07ad47722..297c94c1f0a055 100644
--- a/lldb/source/Commands/CommandObjectProcess.cpp
+++ b/lldb/source/Commands/CommandObjectProcess.cpp
@@ -201,6 +201,11 @@ class CommandObjectProcessLaunch : public CommandObjectProcessLaunchOrAttach {
     if (target->GetDisableSTDIO())
       m_options.launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
 
+    if (std::optional<llvm::StringRef> wd =
+            target->GetLaunchWorkingDirectory()) {
+      m_options.launch_info.SetWorkingDirectory(FileSpec(*wd));
+    }
+
     // Merge the launch info environment with the target environment.
     Environment target_env = target->GetEnvironment();
     m_options.launch_info.GetEnvironment().insert(target_env.begin(),
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 04395e37f0425d..ef4dabf00c1a9e 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4428,6 +4428,15 @@ void TargetProperties::SetDisableSTDIO(bool b) {
   const uint32_t idx = ePropertyDisableSTDIO;
   SetPropertyAtIndex(idx, b);
 }
+std::optional<llvm::StringRef>
+TargetProperties::GetLaunchWorkingDirectory() const {
+  const uint32_t idx = ePropertyLaunchWorkingDir;
+  llvm::StringRef value = GetPropertyAtIndexAs<llvm::StringRef>(
+      idx, g_target_properties[idx].default_cstr_value);
+  if (value.empty())
+    return {};
+  return value;
+}
 
 const char *TargetProperties::GetDisassemblyFlavor() const {
   const uint32_t idx = ePropertyDisassemblyFlavor;
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index fb61478fb752dc..613442501d6b6d 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -201,6 +201,10 @@ let Definition = "target" in {
   def DebugUtilityExpression: Property<"debug-utility-expression", "Boolean">,
     DefaultFalse,
     Desc<"Enable debugging of LLDB-internal utility expressions.">;
+  def LaunchWorkingDir: Property<"launch-working-dir", "String">,
+    DefaultStringValue<"">,
+    Desc<"An override for the working directory to use when launching processes. "
+         "It's not used when empty.">;
 }
 
 let Definition = "process_experimental" in {
diff --git a/lldb/test/API/commands/process/launch/TestProcessLaunch.py b/lldb/test/API/commands/process/launch/TestProcessLaunch.py
index 45f9f494ab8f5c..f3afc385a56086 100644
--- a/lldb/test/API/commands/process/launch/TestProcessLaunch.py
+++ b/lldb/test/API/commands/process/launch/TestProcessLaunch.py
@@ -8,6 +8,7 @@
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test import lldbutil
+from pathlib import Path
 
 
 class ProcessLaunchTestCase(TestBase):
@@ -206,3 +207,47 @@ def test_environment_with_special_char(self):
         self.assertEqual(value, evil_var)
         process.Continue()
         self.assertState(process.GetState(), lldb.eStateExited, PROCESS_EXITED)
+
+    def test_target_launch_working_dir_prop(self):
+        """Test that the setting `target.launch-working-dir` is correctly used when launching a process."""
+        d = {"CXX_SOURCES": "print_cwd.cpp"}
+        self.build(dictionary=d)
+        self.setTearDownCleanup(d)
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("file " + exe)
+
+        mywd = "my_working_dir"
+        out_file_name = "my_working_dir_test.out"
+
+        my_working_dir_path = self.getBuildArtifact(mywd)
+        lldbutil.mkdir_p(my_working_dir_path)
+        out_file_path = os.path.join(my_working_dir_path, out_file_name)
+        another_working_dir_path = Path(
+            os.path.join(my_working_dir_path, "..")
+        ).resolve()
+
+        # Check that we correctly set the working dir
+        self.runCmd(
+            f"settings set target.launch-working-dir {another_working_dir_path}"
+        )
+        launch_command = f"process launch -o {out_file_path}"
+
+        self.expect(
+            launch_command,
+            patterns=["Process .* launched: .*a.out"],
+        )
+
+        out = lldbutil.read_file_on_target(self, out_file_path)
+        self.assertIn(f"stdout: {another_working_dir_path}", out)
+
+        # Check that we can unset the setting
+        self.runCmd(f"settings set target.launch-working-dir ''")
+        launch_command = f"process launch -o {out_file_path}"
+
+        self.expect(
+            launch_command,
+            patterns=["Process .* launched: .*a.out"],
+        )
+
+        out = lldbutil.read_file_on_target(self, out_file_path)
+        self.assertNotIn(f"stdout: {another_working_dir_path}", out)

@walter-erquinigo walter-erquinigo force-pushed the users/walter-erquinigo/run branch from be5c3c3 to 4c80e26 Compare October 24, 2024 04:40
@walter-erquinigo
Copy link
Member Author

Friendly ping, otherwise I'll merge it in a couple of days

@DavidSpickett
Copy link
Collaborator

launch-working-dir sounds like working-dir to me. Do we need the launch prefix? Should this be target.process not target?

This issue could be re-stated as "a process launched with -w does not use the same working dir if run again". Which I think is certainly unexpected behaviour as a user. Did you look into making run of the process just re-use the working dir that was set up front?

The way I see it this splits in two:

  • One use case is saying "I'm running lldb from X but I want to launch all processes as if they were in Y" - where that might be the same program or many different programs across a single session.
    • Which you could also automate with cd <workingdir> && <path-to>/lldb/. Would that work in your system?
  • process launch -w'd processes should not forget their initial working dir when re-run.

Feels like you are solving one problem using the solution of a different problem.

@walter-erquinigo
Copy link
Member Author

launch-working-dir sounds like working-dir to me.

That's a good idea

Should this be target.process not target?

Another good idea

"a process launched with -w does not use the same working dir if run again"

That was just a symptom, but what we want is users not having to specify process launch -w at all. We want our users to simply do run and that things just work.

Which you could also automate with cd && /lldb/. Would that work in your system?

Sadly it seems that this is not possible with the way bazel spawns tools, which respects the cwd from the user invocation. We can invoke LLDB with any init flags we want, but we can't change its actual cwd. That's how I thought of adding the working-dir as a setting via the command line invocation.

What do you think?

@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Oct 31, 2024

That was just a symptom, but what we want is users not having to specify process launch -w at all. We want our users to simply do run and that things just work.

We have:

  • process launch -w does not carry the working dir to subsequent launches.
    • Please open an issue for that.
    • Maybe it can be fixed with the setting, but you don't need to deal with that (and the more I think about that, the more I think it should not be fixed with the setting).
  • Your users want to use run as they would normally, I think I would too.
    • Adding a setting is a reasonable approach for this part.
    • It's kinda like git -C but for all targets launched by lldb.

You will have to decide whether process launch -w or the setting wins, I would say process launch -w wins and if -w is not passed then the setting is used.

launch-working-dir sounds like working-dir to me.

Although one might think that working-dir actually changes the working directory of a running target if you change it at runtime. We already have a few settings that actually only apply on first launch but aren't named as such.

So let's leave the launch bit in and document that changing it at runtime will not change the process' working dir, to be extra clear.

Should this be target.process not target?

On second thought, if this is on target then the launch workdir dir should be too:

target.run-args -- A list containing all the arguments to be passed to the executable when it is run. Note that this does NOT include the argv[0] which is in target.arg0.

...so ignore advice from me on a Wednesday I guess :)

Also you could reference how this setting interacts with the other ways to pass arguments like lldb program -- arg1 arg2, whether it's additive or one wins. For a working dir, one has to win of course.

@DavidSpickett
Copy link
Collaborator

Internally we use bazel in a way in which

Even if this way of using it (I have no experience with Bazel myself) is unusual, I think the pitch for the setting is ok anyway.

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.

I think you already did what I asked for in my comment just now, just docs and comments to update.

@@ -4428,6 +4428,15 @@ void TargetProperties::SetDisableSTDIO(bool b) {
const uint32_t idx = ePropertyDisableSTDIO;
SetPropertyAtIndex(idx, b);
}
std::optional<llvm::StringRef>
TargetProperties::GetLaunchWorkingDirectory() const {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If it's not used when empty should this just return llvm::StringRef?

I get the logical purity of using optional, but if we're defining some value of it as the "null" value anyway we don't have to use optional.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that makes sense, also because of consistency with the rest of the code

def LaunchWorkingDir: Property<"launch-working-dir", "String">,
DefaultStringValue<"">,
Desc<"A default value for the working directory to use when launching processes. "
"It's not used when empty.">;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here we should say how it interacts with commands that can set the working dir, and that changing this at runtime does not change the running process' working dir.

"This setting is only used when the target is launched. If you change this setting, the new value will only apply to subsequent launches."

os.path.join(my_working_dir_path, "..")
).resolve()

# Check that we correctly override the working dir
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please rewrite this so it's obvious what "correct" means. Like:

# If -w is passed to process launch, that value will be used instead of the setting.

out = lldbutil.read_file_on_target(self, out_file_path)
self.assertNotIn(f"stdout: {another_working_dir_path}", out)

# Check that we correctly set the working dir
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, state what you expect to happen.

out = lldbutil.read_file_on_target(self, out_file_path)
self.assertIn(f"stdout: {another_working_dir_path}", out)

# Check that we can unset the setting
Copy link
Collaborator

Choose a reason for hiding this comment

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

and here.

@DavidSpickett
Copy link
Collaborator

Also please add a release note to the LLDB section in the llvm release notes doc. These sort of settings people don't know they could use, until they realise they exist.

@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Oct 31, 2024

process launch -w does not carry the working dir to subsequent launches.

  • Please open an issue for that.

Actually this is not a bug. process launch and run are two separate (at least from the user perpsective) ways to run a program.

I think if you should add to this help text:

       -w <directory> ( --working-dir <directory> )
            Set the current working directory to <path> when running the inferior.

To note that it only applies to that launch and that anyone who wants a sticky setting should use this new setting.

@labath
Copy link
Collaborator

labath commented Oct 31, 2024

Friendly ping, otherwise I'll merge it in a couple of days

One week is the minimum time for a ping (you pinged after six days). It's not the maximum time for a review (there isn't one).

(Practically speaking, if you feel your reviews are going slower than usual, it could be because a lot of the devs were at the conference last week. I'm still catching up with the backlog.)

@walter-erquinigo
Copy link
Member Author

walter-erquinigo commented Nov 1, 2024

Sorry, I thought it had been 1 week already. I'll be mindful of that next time. And yeah, it's natural that the conference slowed things down. I just thought that people weren't caring about this patch, which happens very occasionally :)

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.

We only have one request for this feature, but the logic of it is clear and GDB as an equivalent in set cwd: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Working-Directory.html

So this looks good to me once the docs are updated.

Internally we use bazel in a way in which it can drop you in a LLDB session with the target launched in a particular cwd, which is needed for things to work. We've been making this automation work via `process launch -w`. However, if later the user wants to restart the process with `r`, then they end up using a different cwd for relaunching the process.
As a way to fix this, I'm adding a target-level setting that allows setting a default cwd used for launching the process without needing the user to specify it manually.
@walter-erquinigo
Copy link
Member Author

@DavidSpickett , thanks for the thorough review! It was great to see that gdb has a similar functionality as well.

@walter-erquinigo walter-erquinigo merged commit 6620cd2 into main Nov 5, 2024
9 checks passed
@walter-erquinigo walter-erquinigo deleted the users/walter-erquinigo/run branch November 5, 2024 11:33
@llvm-ci
Copy link
Collaborator

llvm-ci commented Nov 5, 2024

LLVM Buildbot has detected a new failure on builder lldb-remote-linux-ubuntu running on as-builder-9 while building lldb,llvm at step 16 "test-check-lldb-api".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/195/builds/622

Here is the relevant piece of the build log for the reference
Step 16 (test-check-lldb-api) failure: Test just built components: check-lldb-api completed (failure)
******************** TEST 'lldb-api :: commands/process/launch/TestProcessLaunch.py' FAILED ********************
Script:
--
/usr/bin/python3.12 /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./bin --libcxx-include-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/include/c++/v1 --libcxx-include-target-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/include/aarch64-unknown-linux-gnu/c++/v1 --libcxx-library-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./lib/aarch64-unknown-linux-gnu --arch aarch64 --build-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./bin/lldb --compiler /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang --dsymutil /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./bin/dsymutil --make /usr/bin/make --llvm-tools-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./bin --lldb-obj-root /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/tools/lldb --lldb-libs-dir /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/./lib --platform-url connect://jetson-agx-2198.lab.llvm.org:1234 --platform-working-dir /home/ubuntu/lldb-tests --sysroot /mnt/fs/jetson-agx-ubuntu --env ARCH_CFLAGS=-mcpu=cortex-a78 --platform-name remote-linux /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/llvm-project/lldb/test/API/commands/process/launch -p TestProcessLaunch.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 20.0.0git (https://github.com/llvm/llvm-project.git revision 6620cd25234a42ca4b51490627afcb93fa443dc3)
  clang revision 6620cd25234a42ca4b51490627afcb93fa443dc3
  llvm revision 6620cd25234a42ca4b51490627afcb93fa443dc3
Setting up remote platform 'remote-linux'
Connecting to remote platform 'remote-linux' at 'connect://jetson-agx-2198.lab.llvm.org:1234'...
Connected.
Setting remote platform working directory to '/home/ubuntu/lldb-tests'...
Skipping the following test categories: ['dsym', 'gmodules', 'debugserver', 'objc', 'lldb-dap']

--
Command Output (stderr):
--
WARNING:root:Custom libc++ is not supported for remote runs: ignoring --libcxx arguments
PASS: LLDB (/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang-aarch64) :: test_environment_with_special_char (TestProcessLaunch.ProcessLaunchTestCase.test_environment_with_special_char)
UNSUPPORTED: LLDB (/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang-aarch64) :: test_io (TestProcessLaunch.ProcessLaunchTestCase.test_io) (skip on remote platform) 
UNSUPPORTED: LLDB (/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang-aarch64) :: test_set_working_dir_existing (TestProcessLaunch.ProcessLaunchTestCase.test_set_working_dir_existing) (skip on remote platform) 
UNSUPPORTED: LLDB (/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang-aarch64) :: test_set_working_dir_nonexisting (TestProcessLaunch.ProcessLaunchTestCase.test_set_working_dir_nonexisting) (skip on remote platform) 
FAIL: LLDB (/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang-aarch64) :: test_target_launch_working_dir_prop (TestProcessLaunch.ProcessLaunchTestCase.test_target_launch_working_dir_prop)
======================================================================
FAIL: test_target_launch_working_dir_prop (TestProcessLaunch.ProcessLaunchTestCase.test_target_launch_working_dir_prop)
   Test that the setting `target.launch-working-dir` is correctly used when launching a process.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/llvm-project/lldb/test/API/commands/process/launch/TestProcessLaunch.py", line 235, in test_target_launch_working_dir_prop
    self.expect(
  File "/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 2370, in expect
    self.runCmd(
  File "/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1000, in runCmd
    self.assertTrue(self.res.Succeeded(), msg + output)
AssertionError: False is not true : Command 'process launch -o /home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/lldb-test-build.noindex/commands/process/launch/TestProcessLaunch.test_target_launch_working_dir_prop/my_working_dir/my_working_dir_test.out' did not return successfully
Error output:
error: Cannot launch '/home/ubuntu/lldb-tests/commands/process/launch/5/TestProcessLaunch.test_target_launch_working_dir_prop/a.out': DupDescriptor-open failed: No such file or directory

Config=aarch64-/home/buildbot/worker/as-builder-9/lldb-remote-linux-ubuntu/build/bin/clang
----------------------------------------------------------------------
Ran 5 tests in 1.964s

FAILED (failures=1, skipped=3)

--

...

walter-erquinigo added a commit that referenced this pull request Nov 5, 2024
walter-erquinigo added a commit that referenced this pull request Nov 5, 2024
Reverts #113521 due to build bot failures mentioned in
the original PR.
PhilippRados pushed a commit to PhilippRados/llvm-project that referenced this pull request Nov 6, 2024
Internally we use bazel in a way in which it can drop you in a LLDB
session with the target launched in a particular cwd, which is needed
for things to work. We've been making this automation work via `process
launch -w`. However, if later the user wants to restart the process with
`r`, then they end up using a different cwd for relaunching the process.
As a way to fix this, I'm adding a target-level setting that allows
configuring a default cwd used for launching the process without needing
the user to specify it manually.
PhilippRados pushed a commit to PhilippRados/llvm-project that referenced this pull request Nov 6, 2024
Reverts llvm#113521 due to build bot failures mentioned in
the original PR.
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.

5 participants