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/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -631,7 +631,7 @@ self.initialize_body = response['body'] return response - def request_launch(self, program, args=None, cwd=None, env=None, + def request_launch(self, program=None, args=None, cwd=None, env=None, stopOnEntry=False, disableASLR=True, disableSTDIO=False, shellExpandArguments=False, trace=False, initCommands=None, preRunCommands=None, @@ -640,9 +640,9 @@ debuggerRoot=None, launchCommands=None, sourceMap=None, runInTerminal=False, expectFailure=False, postRunCommands=None): - args_dict = { - 'program': program - } + args_dict = {} + if program: + args_dict['program'] = program if args: args_dict['args'] = args if cwd: Index: lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py =================================================================== --- lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py +++ lldb/test/API/tools/lldb-vscode/attach/TestVSCode_attach.py @@ -153,8 +153,7 @@ stopCommands = ['frame variable', 'bt'] exitCommands = ['expr 2+3', 'expr 3+4'] terminateCommands = ['expr 4+2'] - self.attach(program=program, - attachCommands=attachCommands, + self.attach(attachCommands=attachCommands, initCommands=initCommands, preRunCommands=preRunCommands, stopCommands=stopCommands, @@ -217,8 +216,7 @@ 'process launch' ] terminateCommands = ['expr 4+2'] - self.attach(program=program, - attachCommands=attachCommands, + self.attach(attachCommands=attachCommands, terminateCommands=terminateCommands, disconnectAutomatically=False) self.get_console() 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,13 +398,13 @@ 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'] - self.launch(program, - initCommands=initCommands, + exitCommands = ['expr 2+3', 'expr 3+4', 'settings show target.exec-search-paths'] + self.launch(initCommands=initCommands, preRunCommands=preRunCommands, stopCommands=stopCommands, exitCommands=exitCommands, - launchCommands=launchCommands) + launchCommands=launchCommands, + disableASLR=None) # Get output from the console. This should contain both the # "initCommands" and the "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 @@ -618,28 +618,67 @@ 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 (!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); @@ -649,14 +688,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(); } if (error.Success() && core_file.empty()) { @@ -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": {