serhiy.redko created this revision.
serhiy.redko requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

launch.json's fields have ambiguity with how final target is created: 

1. There are dedicated fields to specify target like "program", "pid", "waitFor"
2. There are  "launchCommands" and "attachCommands" fields that might or might 
not create a target.

Current implementation for handling of "launchCommands" and "attachCommands" 
implies
they are rather mutually exclusive with other target defining parameters like 
"program", "pid" etc
in case they create a new target. 

So user can provide in the launch.json fields "program", "pid", "core_file" and 
these parameters 
will be silently ignored by implementation in case "launchCommands" and 
"attachCommands" are provided
as well and create a new target. For attach "core_file" is ignored in case 
"attachCommands"
field is just provided, no matter whether commands create or not target.
At the same time, implementation always creates target by means of 
CreateTargetFromArguments()
API in expectance of target dedicated fields in launch.json like "program", 
"pid", even in case 
"launchCommands" and "attachCommands" (that do create a target)  are provided.

Creation of this first, "auxilary" target, introduces issue with target 
settings specified for second
target and created by "launchCommands" or "attachCommands": target settings for 
the second target are 'hijacked'
by target created by CreateTargetFromArguments() call and not applied to target 
created 
by "launchCommands"/"attachCommands".

For example, for "launchCommands":

settings set target.exec-search-paths /some/path 
target create /some/file

target.exec-search-paths is not applied to the final target created in 
"launchCommands".  
The settings is applied to previously created "auxilary" target which doesn't 
become selected.
This breaks debugging of the target created by 
"launchCommands"/"attachCommands".

A possible workaround for the issue would be a strict requirement to set target 
settings
after target creation command, however it might not be feasible for some 
settings and 
inconsistent with cli lldb usage results where this issue is not reproduced.

This change avoids creation of "auxilary" target in case 
"launchCommands"/"attachCommands" 
are provided. It also uses proper SetTarget() setter to set final target and 
subscribe it
for notifications.

This change potentially can break the case when user provides 
"launchCommands"/"attachCommands"
that don't create a target, but rely on the "auxilary" target that they can 
tune further by commands
in "launchCommands", "attachCommands". This doesn't look like a correct usage 
intent to me
because it requires from users quite thorough inspection of 'launch'/'attach' 
requests implementation and
understanding which launch.json fields are ignored or not by implementation.

In case user needs to tune created target I would recommend to create new 
"postTargetCommands" launch.json
field with clear instructions to not create target and assume that target 
already exists.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D94997

Files:
  lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
  lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py
  lldb/tools/lldb-vscode/lldb-vscode.cpp
  lldb/tools/lldb-vscode/package.json

Index: lldb/tools/lldb-vscode/package.json
===================================================================
--- lldb/tools/lldb-vscode/package.json
+++ lldb/tools/lldb-vscode/package.json
@@ -85,7 +85,7 @@
 						"properties": {
 							"program": {
 								"type": "string",
-								"description": "Path to the program to debug."
+								"description": "Path to the program to debug. Ignored if \"launchCommands\" is provided."
 							},
 							"args": {
 								"type": [
@@ -163,7 +163,7 @@
 							},
 							"launchCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of launching a process. A target will be created with the launch arguments prior to executing these commands. The commands may optionally create a new target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail.",
+								"description": "Custom commands that are executed instead of launching a process. The commands must create a target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail. The following settings will be ignored if provided: \"program\", \"cwd\", \"args\", \"env\", \"disableASLR\", \"disableSTDIO\", \"shellExpandArguments\", \"detachOnError\", \"runInTerminal\", \"targetTriple\", \"platformName\"",
 								"default": []
 							},
 							"stopCommands": {
@@ -187,18 +187,18 @@
 						"properties": {
 							"program": {
 								"type": "string",
-								"description": "Path to the program to attach to."
+								"description": "Path to the program to attach to. Ignored if \"attachCommands\" is provided."
 							},
 							"pid": {
 								"type": [
 									"number",
 									"string"
 								],
-								"description": "System process ID to attach to."
+								"description": "System process ID to attach to. Ignored if \"attachCommands\" is provided."
 							},
 							"waitFor": {
 								"type": "boolean",
-								"description": "If set to true, then wait for the process to launch by looking for a process with a basename that matches `program`. No process ID needs to be specified when using this flag.",
+								"description": "If set to true, then wait for the process to launch by looking for a process with a basename that matches `program`. No process ID needs to be specified when using this flag. Ignored if \"attachCommands\" is provided.",
 								"default": true
 							},
 							"sourcePath": {
@@ -224,7 +224,7 @@
 							},
 							"attachCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.",
+								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands must create a target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail. The following settings will be ignored if provided: \"program\", \"pid\", \"waitFor\", \"coreFile\", \"targetTriple\", \"platformName\"",
 								"default": []
 							},
 							"initCommands": {
Index: lldb/tools/lldb-vscode/lldb-vscode.cpp
===================================================================
--- lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -578,28 +578,43 @@
   // Run any initialize LLDB commands the user specified in the launch.json
   g_vsc.RunInitCommands();
 
-  lldb::SBError status;
-  g_vsc.SetTarget(g_vsc.CreateTargetFromArguments(*arguments, status));
-  if (status.Fail()) {
-    response["success"] = llvm::json::Value(false);
-    EmplaceSafeString(response, "message", status.GetCString());
-    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
-    return;
-  }
-
-  // Run any pre run LLDB commands the user specified in the launch.json
-  g_vsc.RunPreRunCommands();
+  if (!attachCommands.empty()) {
+    // We have "attachCommands" that are a set of commands that are expected
+    // to execute the commands after which a process should be created. If there
+    // is no valid process after running these commands, we have failed.
 
-  if (pid == LLDB_INVALID_PROCESS_ID && wait_for) {
-    char attach_msg[256];
-    auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg),
-                                   "Waiting to attach to \"%s\"...",
-                                   g_vsc.target.GetExecutable().GetFilename());
-    g_vsc.SendOutput(OutputType::Console,
-                     llvm::StringRef(attach_msg, attach_msg_len));
-  }
-  if (attachCommands.empty()) {
+    // Run any pre run LLDB commands the user specified in the launch.json
+    g_vsc.RunPreRunCommands();
+    g_vsc.RunLLDBCommands("Running attachCommands:", attachCommands);
+    // The custom commands are expected to create a new target
+    if (g_vsc.debugger.GetNumTargets() > 0) {
+      g_vsc.SetTarget(g_vsc.debugger.GetSelectedTarget());
+    } else {
+      error.SetErrorString("attachCommands failed to create target");
+    }
+  } else {
     // No "attachCommands", just attach normally.
+    lldb::SBError status;
+    g_vsc.SetTarget(g_vsc.CreateTargetFromArguments(*arguments, status));
+    if (status.Fail()) {
+      response["success"] = llvm::json::Value(false);
+      EmplaceSafeString(response, "message", status.GetCString());
+      g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+      return;
+    }
+
+    // Run any pre run LLDB commands the user specified in the launch.json
+    g_vsc.RunPreRunCommands();
+
+    if (pid == LLDB_INVALID_PROCESS_ID && wait_for) {
+      char attach_msg[256];
+      auto attach_msg_len = snprintf(
+          attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...",
+          g_vsc.target.GetExecutable().GetFilename());
+      g_vsc.SendOutput(OutputType::Console,
+                       llvm::StringRef(attach_msg, attach_msg_len));
+    }
+
     // Disable async events so the attach will be successful when we return from
     // the launch call and the launch will happen synchronously
     g_vsc.debugger.SetAsync(false);
@@ -609,14 +624,6 @@
       g_vsc.target.LoadCore(core_file.data(), error);
     // Reenable async events
     g_vsc.debugger.SetAsync(true);
-  } else {
-    // We have "attachCommands" that are a set of commands that are expected
-    // to execute the commands after which a process should be created. If there
-    // is no valid process after running these commands, we have failed.
-    g_vsc.RunLLDBCommands("Running attachCommands:", attachCommands);
-    // The custom commands might have created a new target so we should use the
-    // selected target after these commands are run.
-    g_vsc.target = g_vsc.debugger.GetSelectedTarget();
   }
 
   SetSourceMapFromArguments(*arguments);
@@ -1547,67 +1554,77 @@
 
   SetSourceMapFromArguments(*arguments);
 
-  lldb::SBError status;
-  g_vsc.SetTarget(g_vsc.CreateTargetFromArguments(*arguments, status));
-  if (status.Fail()) {
-    response["success"] = llvm::json::Value(false);
-    EmplaceSafeString(response, "message", status.GetCString());
-    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
-    return;
-  }
+  if (!launchCommands.empty()) {
+    // if "launchCommands" are provided, then they are expected to make the
+    // launch happen for launch requests and they replace the normal logic that
+    // would implement the launch. Run any pre run LLDB commands the user
+    // specified in the launch.json
+    g_vsc.RunPreRunCommands();
+    g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
+    // The custom commands are expected to create a new target
+    if (g_vsc.debugger.GetNumTargets() > 0) {
+      g_vsc.SetTarget(g_vsc.debugger.GetSelectedTarget());
+    } else {
+      error.SetErrorString("launchCommands failed to create target");
+    }
+  } else {
+    // the normal logic that would implement the launch
+    lldb::SBError status;
+    g_vsc.SetTarget(g_vsc.CreateTargetFromArguments(*arguments, status));
+    if (status.Fail()) {
+      response["success"] = llvm::json::Value(false);
+      EmplaceSafeString(response, "message", status.GetCString());
+      g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+      return;
+    }
 
-  if (GetBoolean(arguments, "runInTerminal", false)) {
-    request_runInTerminal(request, response);
-    g_vsc.SendJSON(llvm::json::Value(std::move(response)));
-    return;
-  }
+    if (GetBoolean(arguments, "runInTerminal", false)) {
+      request_runInTerminal(request, response);
+      g_vsc.SendJSON(llvm::json::Value(std::move(response)));
+      return;
+    }
+
+    // Instantiate a launch info instance for the target.
+    auto launch_info = g_vsc.target.GetLaunchInfo();
+
+    // Grab the current working directory if there is one and set it in the
+    // launch info.
+    const auto cwd = GetString(arguments, "cwd");
+    if (!cwd.empty())
+      launch_info.SetWorkingDirectory(cwd.data());
+
+    // Extract any extra arguments and append them to our program arguments for
+    // when we launch
+    auto args = GetStrings(arguments, "args");
+    if (!args.empty())
+      launch_info.SetArguments(MakeArgv(args).data(), true);
+
+    // Pass any environment variables along that the user specified.
+    auto envs = GetStrings(arguments, "env");
+    if (!envs.empty())
+      launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
+
+    auto flags = launch_info.GetLaunchFlags();
+
+    if (GetBoolean(arguments, "disableASLR", true))
+      flags |= lldb::eLaunchFlagDisableASLR;
+    if (GetBoolean(arguments, "disableSTDIO", false))
+      flags |= lldb::eLaunchFlagDisableSTDIO;
+    if (GetBoolean(arguments, "shellExpandArguments", false))
+      flags |= lldb::eLaunchFlagShellExpandArguments;
+    const bool detachOnError = GetBoolean(arguments, "detachOnError", false);
+    launch_info.SetDetachOnError(detachOnError);
+    launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
+                               lldb::eLaunchFlagStopAtEntry);
+
+    // Run any pre run LLDB commands the user specified in the launch.json
+    g_vsc.RunPreRunCommands();
 
-  // Instantiate a launch info instance for the target.
-  auto launch_info = g_vsc.target.GetLaunchInfo();
-
-  // Grab the current working directory if there is one and set it in the
-  // launch info.
-  const auto cwd = GetString(arguments, "cwd");
-  if (!cwd.empty())
-    launch_info.SetWorkingDirectory(cwd.data());
-
-  // Extract any extra arguments and append them to our program arguments for
-  // when we launch
-  auto args = GetStrings(arguments, "args");
-  if (!args.empty())
-    launch_info.SetArguments(MakeArgv(args).data(), true);
-
-  // Pass any environment variables along that the user specified.
-  auto envs = GetStrings(arguments, "env");
-  if (!envs.empty())
-    launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
-
-  auto flags = launch_info.GetLaunchFlags();
-
-  if (GetBoolean(arguments, "disableASLR", true))
-    flags |= lldb::eLaunchFlagDisableASLR;
-  if (GetBoolean(arguments, "disableSTDIO", false))
-    flags |= lldb::eLaunchFlagDisableSTDIO;
-  if (GetBoolean(arguments, "shellExpandArguments", false))
-    flags |= lldb::eLaunchFlagShellExpandArguments;
-  const bool detatchOnError = GetBoolean(arguments, "detachOnError", false);
-  launch_info.SetDetachOnError(detatchOnError);
-  launch_info.SetLaunchFlags(flags | lldb::eLaunchFlagDebug |
-                             lldb::eLaunchFlagStopAtEntry);
-
-  // Run any pre run LLDB commands the user specified in the launch.json
-  g_vsc.RunPreRunCommands();
-  if (launchCommands.empty()) {
     // Disable async events so the launch will be successful when we return from
     // the launch call and the launch will happen synchronously
     g_vsc.debugger.SetAsync(false);
     g_vsc.target.Launch(launch_info, error);
     g_vsc.debugger.SetAsync(true);
-  } else {
-    g_vsc.RunLLDBCommands("Running launchCommands:", launchCommands);
-    // The custom commands might have created a new target so we should use the
-    // selected target after these commands are run.
-    g_vsc.target = g_vsc.debugger.GetSelectedTarget();
   }
 
   if (error.Fail()) {
Index: lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py
===================================================================
--- lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py
+++ lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py
@@ -373,6 +373,7 @@
         '''
         self.build_and_create_debug_adaptor()
         program = self.getBuildArtifact("a.out")
+        execSearchPaths = '/unique/path/to/symbols/launch/test'
 
         source = 'main.c'
         first_line = line_number(source, '// breakpoint 1')
@@ -382,6 +383,7 @@
         # also we can verify that "stopCommands" get run as the
         # breakpoints get hit
         launchCommands = [
+            'settings set target.exec-search-paths "%s"' % (execSearchPaths),
             'target create "%s"' % (program),
             'br s -f main.c -l %d' % first_line,
             'br s -f main.c -l %d' % second_line,
@@ -391,7 +393,7 @@
         initCommands = ['target list', 'platform list']
         preRunCommands = ['image list a.out', 'image dump sections a.out']
         stopCommands = ['frame variable', 'bt']
-        exitCommands = ['expr 2+3', 'expr 3+4']
+        exitCommands = ['expr 2+3', 'expr 3+4', 'settings show target.exec-search-paths']
         self.launch(program,
                     initCommands=initCommands,
                     preRunCommands=preRunCommands,
@@ -428,6 +430,8 @@
         # "exitCommands" that were run after the second breakpoint was hit
         output = self.get_console(timeout=1.0)
         self.verify_commands('exitCommands', output, exitCommands)
+        # confirm that output contains correct target.exec-search-paths value
+        self.verify_contains_text('exitCommands', output, execSearchPaths)
 
     @skipIfWindows
     @skipIfNetBSD # Hangs on NetBSD as well
@@ -440,7 +444,7 @@
         '''
         self.build_and_create_debug_adaptor()
         program = self.getBuildArtifact("a.out")
-        
+
         terminateCommands = ['expr 4+2']
         self.launch(program=program,
                     terminateCommands=terminateCommands)
Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
+++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
@@ -126,6 +126,13 @@
                             "verify '%s' found in console output for '%s'" % (
                                 cmd, flavor))
 
+    def verify_contains_text(self, flavor, output, text):
+        self.assertTrue(output and len(output) > 0, "expect console output")
+        found = text in output
+        self.assertTrue(found,
+                        "verify '%s' found in console output for '%s'" % (
+                            text, flavor))
+
     def get_dict_value(self, d, key_path):
         '''Verify each key in the key_path array is in contained in each
            dictionary within "d". Assert if any key isn't in the
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to