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 @@ -125,6 +125,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 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 @@ -378,6 +378,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') @@ -387,6 +388,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), 'breakpoint s -f main.c -l %d' % first_line, 'breakpoint s -f main.c -l %d' % second_line, @@ -396,7 +398,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, @@ -433,6 +435,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 Index: lldb/tools/lldb-vscode/lldb-vscode.cpp =================================================================== --- lldb/tools/lldb-vscode/lldb-vscode.cpp +++ lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -616,28 +616,67 @@ // 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; - } + 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. - // Run any pre run LLDB commands the user specified in the launch.json - g_vsc.RunPreRunCommands(); + std::vector incompatible_fields; + for (llvm::StringRef arg : {"program", "pid", "waitFor", "coreFile", + "targetTriple", "platformName"}) { + if (arguments->get(arg) != nullptr) { + incompatible_fields.push_back(arg); + } + } - 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 (!incompatible_fields.empty()) { + std::string str; + llvm::raw_string_ostream strm(str); + strm << "Incorrect debug configuration: " << + "'attachCommands' is not compatible with "; + for (llvm::StringRef field : incompatible_fields) { + strm << " '" << field << "' "; + } + response["success"] = llvm::json::Value(false); + EmplaceSafeString(response, "message", strm.str()); + 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(); + 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"); + } } - if (attachCommands.empty()) { + 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); @@ -647,14 +686,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); @@ -1667,65 +1698,101 @@ 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 + + std::vector incompatible_fields; + for (llvm::StringRef arg : {"program", "cwd", "args", "env", "disableASLR", "disableSTDIO", + "shellExpandArguments", "detachOnError", "runInTerminal", "targetTriple", + "platformName"}) { + if (arguments->get(arg) != nullptr) { + incompatible_fields.push_back(arg); + } + } - // 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 (GetBoolean(arguments, "runInTerminal", false)) { - if (llvm::Error err = request_runInTerminal(request)) - error.SetErrorString(llvm::toString(std::move(err)).c_str()); - } else 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 { + if (!incompatible_fields.empty()) { + std::string str; + llvm::raw_string_ostream strm(str); + strm << "Incorrect debug configuration: " << + "'launchCommands' is not compatible with "; + for (llvm::StringRef field : incompatible_fields) { + strm << " '" << field << "' "; + } + response["success"] = llvm::json::Value(false); + EmplaceSafeString(response, "message", strm.str()); + g_vsc.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + g_vsc.RunPreRunCommands(); 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(); + // 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; + } + + // 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 (GetBoolean(arguments, "runInTerminal", false)) { + if (llvm::Error err = request_runInTerminal(request)) + error.SetErrorString(llvm::toString(std::move(err)).c_str()); + } else { + // 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); + } } if (error.Fail()) { Index: lldb/tools/lldb-vscode/package.json =================================================================== --- lldb/tools/lldb-vscode/package.json +++ lldb/tools/lldb-vscode/package.json @@ -215,7 +215,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": { @@ -276,7 +276,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": {