diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -286,7 +286,7 @@ stopCommands=None, exitCommands=None, terminateCommands=None, sourcePath=None, debuggerRoot=None, sourceInitFile=False, launchCommands=None, sourceMap=None, disconnectAutomatically=True, runInTerminal=False, - expectFailure=False, postRunCommands=None): + expectFailure=False, postRunCommands=None, runToBinaryEntry=False): '''Sending launch request to vscode ''' @@ -323,7 +323,8 @@ sourceMap=sourceMap, runInTerminal=runInTerminal, expectFailure=expectFailure, - postRunCommands=postRunCommands) + postRunCommands=postRunCommands, + runToBinaryEntry=runToBinaryEntry) if expectFailure: return response @@ -346,7 +347,7 @@ terminateCommands=None, sourcePath=None, debuggerRoot=None, sourceInitFile=False, runInTerminal=False, disconnectAutomatically=True, postRunCommands=None, - lldbVSCodeEnv=None): + lldbVSCodeEnv=None, runToBinaryEntry=False): '''Build the default Makefile target, create the VSCode debug adaptor, and launch the process. ''' @@ -359,4 +360,5 @@ terminateCommands, sourcePath, debuggerRoot, sourceInitFile, runInTerminal=runInTerminal, disconnectAutomatically=disconnectAutomatically, - postRunCommands=postRunCommands) + postRunCommands=postRunCommands, + runToBinaryEntry=runToBinaryEntry) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py @@ -644,7 +644,7 @@ terminateCommands=None ,sourcePath=None, debuggerRoot=None, launchCommands=None, sourceMap=None, runInTerminal=False, expectFailure=False, - postRunCommands=None): + postRunCommands=None, runToBinaryEntry=False): args_dict = { 'program': program } @@ -656,6 +656,8 @@ args_dict['env'] = env if stopOnEntry: args_dict['stopOnEntry'] = stopOnEntry + if runToBinaryEntry: + args_dict['runToBinaryEntry'] = runToBinaryEntry if disableASLR: args_dict['disableASLR'] = disableASLR if disableSTDIO: diff --git a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py --- a/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py +++ b/lldb/test/API/tools/lldb-vscode/launch/TestVSCode_launch.py @@ -78,6 +78,29 @@ reason != 'breakpoint', 'verify stop isn\'t "main" breakpoint') + @skipIfWindows + @skipIfRemote + def test_runToBinaryEntry(self): + ''' + Tests the runToBinaryEntry option can successfully launch a simple + program and hit a breakpoint and does not interupt the launch. + ''' + program = self.getBuildArtifact("a.out") + self.build_and_launch(program, runToBinaryEntry=True) + self.set_function_breakpoints(['main']) + stopped_events = self.continue_to_next_stop() + console_output = self.get_console() + self.assertIn("Process stopped successfully at the binary's entry point", console_output) + + for stopped_event in stopped_events: + if 'body' in stopped_event: + body = stopped_event['body'] + if 'reason' in body: + reason = body['reason'] + self.assertTrue( + reason == 'breakpoint', + 'verify successfully stop at "main" breakpoint') + @skipIfWindows @skipIfRemote def test_cwd(self): diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h --- a/lldb/tools/lldb-vscode/VSCode.h +++ b/lldb/tools/lldb-vscode/VSCode.h @@ -144,6 +144,7 @@ lldb::tid_t focus_tid; bool sent_terminated_event; bool stop_at_entry; + bool run_to_binary_entry = false; bool is_attach; bool configuration_done_sent; uint32_t reverse_request_seq; @@ -261,9 +262,14 @@ /// /// \param[in] seconds /// The number of seconds to poll the process to wait until it is stopped. + /// \param[in] old_stop_id + /// Optional old stop id which we should only check for stopped state if + /// the new stop id is greater than it. This is needed if the previous state + /// is stopped so that we can ensure we are checking new stopped state not + /// the old one in async mode. /// /// \return Error if waiting for the process fails, no error if succeeds. - lldb::SBError WaitForProcessToStop(uint32_t seconds); + lldb::SBError WaitForProcessToStop(uint32_t seconds, uint32_t old_stop_id = 0); private: // Send the JSON in "json_str" to the "out" stream. Correctly send the diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp --- a/lldb/tools/lldb-vscode/VSCode.cpp +++ b/lldb/tools/lldb-vscode/VSCode.cpp @@ -528,7 +528,7 @@ request_handlers[request] = callback; } -lldb::SBError VSCode::WaitForProcessToStop(uint32_t seconds) { +lldb::SBError VSCode::WaitForProcessToStop(uint32_t seconds, uint32_t old_stop_id) { lldb::SBError error; lldb::SBProcess process = target.GetProcess(); if (!process.IsValid()) { @@ -538,6 +538,10 @@ auto timeout_time = std::chrono::steady_clock::now() + std::chrono::seconds(seconds); while (std::chrono::steady_clock::now() < timeout_time) { + // Wait for stop id changed before checking for stopped state. + // This is needed to ensure we are checking old stopped state in async mode. + if (old_stop_id > 0 && process.GetStopID() <= old_stop_id) + continue; const auto state = process.GetState(); switch (state) { case lldb::eStateAttaching: diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp --- a/lldb/tools/lldb-vscode/lldb-vscode.cpp +++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp @@ -1610,6 +1610,68 @@ error.GetCString()); } +lldb::SBError RunToBinaryEntry() { + lldb::SBError error; + if (!g_vsc.run_to_binary_entry) + return error; + + if (g_vsc.stop_at_entry) { + g_vsc.SendOutput(OutputType::Console, + "RunToBinaryEntry is ignored due to StopOnEntry\n"); + return error; + } + + lldb::SBTarget target = g_vsc.debugger.GetSelectedTarget(); + if (!target.IsValid()) + return error; + lldb::SBFileSpec exe_file = target.GetExecutable(); + if (!exe_file.IsValid()) + return error; + lldb::SBModule exe_module = target.FindModule(exe_file); + if (!exe_module.IsValid()) { + g_vsc.SendOutput(OutputType::Console, + "RunToBinaryEntry failed: invalid executable module\n"); + return error; + } + + lldb::SBAddress entry_point = exe_module.GetObjectFileEntryPointAddress(); + if (!entry_point.IsValid()) { + g_vsc.SendOutput(OutputType::Console, + "RunToBinaryEntry failed: can't find entry point\n"); + return error; + } + lldb::SBBreakpoint entry_breakpoint = + target.BreakpointCreateBySBAddress(entry_point); + if (!entry_breakpoint.IsValid() || entry_breakpoint.GetNumLocations() == 0) { + g_vsc.SendOutput(OutputType::Console, + "RunToBinaryEntry failed: can't place the breakpoint\n"); + return error; + } + + uint32_t old_stop_id = target.GetProcess().GetStopID(); + entry_breakpoint.SetOneShot(true); + error = target.GetProcess().Continue(); + if (error.Fail()) + return error; + + const uint64_t timeout_seconds = 600; + error = g_vsc.WaitForProcessToStop(timeout_seconds, old_stop_id); + if (error.Fail()) + return error; + + // Successfully got a process stop; we still need to check if the stop is what + // we expected. + if (entry_breakpoint.GetHitCount() == 0) + g_vsc.SendOutput(OutputType::Telemetry, + "RunToBinaryEntry failed: process stopped not at the " + "binary's entry point\n"); + else + g_vsc.SendOutput(OutputType::Telemetry, + "RunToBinaryEntry success: Process stopped successfully " + "at the binary's entry point\n"); + return error; +} + // "LaunchRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", @@ -1659,6 +1721,7 @@ std::vector postRunCommands = GetStrings(arguments, "postRunCommands"); g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); + g_vsc.run_to_binary_entry = GetBoolean(arguments, "runToBinaryEntry", false); const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot"); const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30); @@ -1741,6 +1804,9 @@ error = g_vsc.WaitForProcessToStop(timeout_seconds); } + if (error.Success()) + error = RunToBinaryEntry(); + if (error.Fail()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", std::string(error.GetCString())); diff --git a/lldb/tools/lldb-vscode/package.json b/lldb/tools/lldb-vscode/package.json --- a/lldb/tools/lldb-vscode/package.json +++ b/lldb/tools/lldb-vscode/package.json @@ -157,6 +157,11 @@ "description": "Automatically stop after launch.", "default": false }, + "runToBinaryEntry": { + "type": "boolean", + "description": "run to program entry one-shot breakpoint during launch to ensure dependency modules are loaded.", + "default": false + }, "disableASLR": { "type": "boolean", "description": "Enable or disable Address space layout randomization if the debugger supports it.",