request_handlers[request] = callback;
}
+lldb::SBError VSCode::WaitForProcessToStop(uint32_t seconds) {
+ // Wait for the process hit a stopped state. When running a launch (with or
+ // without "launchCommands") or attach (with or without)= "attachCommands"),
+ // the calls might take some time to stop at the entry point since the command
+ // is asynchronous. So we need to sync up with the process and make sure it is
+ // stopped before we proceed to do anything else as we will soon be asked to
+ // set breakpoints and other things that require the process to be stopped.
+ // We must use polling because attach doesn't send a process state change
+ // event for the first stop, while launching does. Since both "attachCommands"
+ // and "launchCommands" could end up using any combination of LLDB commands,
+ // we must ensure we can also catch when the process stops, so we must poll
+ // the process to make sure we handle all cases.
+
+ lldb::SBError error;
+ lldb::SBProcess process = target.GetProcess();
+ if (!process.IsValid()) {
+ error.SetErrorString("invalid process");
+ return error;
+ }
+ auto timeout_time =
+ std::chrono::high_resolution_clock::now() + std::chrono::seconds(seconds);
+ while (std::chrono::high_resolution_clock::now() < timeout_time) {
+ const auto state = process.GetState();
+ switch (state) {
+ case lldb::eStateAttaching:
+ case lldb::eStateConnected:
+ case lldb::eStateInvalid:
+ case lldb::eStateLaunching:
+ case lldb::eStateRunning:
+ case lldb::eStateStepping:
+ case lldb::eStateSuspended:
+ break;
+ case lldb::eStateDetached:
+ error.SetErrorString("process detached during launch or attach");
+ return error;
+ case lldb::eStateExited:
+ error.SetErrorString("process exited during launch or attach");
+ return error;
+ case lldb::eStateUnloaded:
+ error.SetErrorString("process unloaded during launch or attach");
+ return error;
+ case lldb::eStateCrashed:
+ case lldb::eStateStopped:
+ return lldb::SBError(); // Success!
+ }
+ std::this_thread::sleep_for(std::chrono::microseconds(250));
+ }
+ error.SetErrorStringWithFormat("process failed to stop within %u seconds",
+ seconds);
+ return error;
+}
+
void Variables::Clear() {
locals.Clear();
globals.Clear();
/// Debuggee will continue from stopped state.
void WillContinue() { variables.Clear(); }
+ /// Poll the process to wait for it to reach the eStateStopped state.
+ ///
+ /// We need to ensure the process is stopped and ready to resume before we
+ /// continue with the launch or attach. This is needed since we no longer play
+ /// with the synchronous mode in the debugger for launching (with or without
+ /// "launchCommands") or attaching (with or without "attachCommands").
+ ///
+ /// \param[in] seconds
+ /// The number of seconds to poll the process to wait until it is stopped.
+ ///
+ /// \return Error if waiting for the process fails, no error if succeeds.
+ lldb::SBError WaitForProcessToStop(uint32_t seconds);
+
private:
// Send the JSON in "json_str" to the "out" stream. Correctly send the
// "Content-Length:" field followed by the length, followed by the raw
case lldb::eStateSuspended:
break;
case lldb::eStateStopped:
- // Only report a stopped event if the process was not restarted.
- if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
- SendStdOutStdErr(process);
- SendThreadStoppedEvent();
+ // Now that we don't mess with the async setting in the debugger
+ // when launching or attaching we will get the first process stop
+ // event which we do not want to send an event for. This is because
+ // we either manually deliver the event in by calling the
+ // SendThreadStoppedEvent() from request_configuarationDone() if we
+ // want to stop on entry, or we resume from that function.
+ if (process.GetStopID() > 1) {
+ // Only report a stopped event if the process was not restarted.
+ if (!lldb::SBProcess::GetRestartedFromEvent(event)) {
+ SendStdOutStdErr(process);
+ SendThreadStoppedEvent();
+ }
}
break;
case lldb::eStateRunning:
g_vsc.terminate_commands = GetStrings(arguments, "terminateCommands");
auto attachCommands = GetStrings(arguments, "attachCommands");
llvm::StringRef core_file = GetString(arguments, "coreFile");
+ const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
g_vsc.stop_at_entry =
core_file.empty() ? GetBoolean(arguments, "stopOnEntry", false) : true;
std::vector<std::string> postRunCommands =
}
if (attachCommands.empty()) {
// No "attachCommands", just attach normally.
- // 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);
if (core_file.empty())
g_vsc.target.Attach(attach_info, error);
else
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
// selected target after these commands are run.
g_vsc.target = g_vsc.debugger.GetSelectedTarget();
}
+ // Make sure the process is attached and stopped before proceeding.
+ if (error.Success())
+ error = g_vsc.WaitForProcessToStop(timeout_seconds);
if (error.Success() && core_file.empty()) {
auto attached_pid = g_vsc.target.GetProcess().GetProcessID();
GetStrings(arguments, "postRunCommands");
g_vsc.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false);
const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot");
+ const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30);
// This is a hack for loading DWARF in .o files on Mac where the .o files
// in the debug map of the main executable have relative paths which require
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 {
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();
}
+ // Make sure the process is launched and stopped at the entry point before
+ // proceeding.
+ if (error.Success())
+ error = g_vsc.WaitForProcessToStop(timeout_seconds);
if (error.Fail()) {
response["success"] = llvm::json::Value(false);