forked from OSchip/llvm-project
[lldb] Fix race condition between lldb-vscode and stop hooks executor
The race is between these two pieces of code that are executed in two separate lldb-vscode threads (the first is in the main thread and another is in the event-handling thread): ``` // lldb-vscode.cpp g_vsc.debugger.SetAsync(false); g_vsc.target.Launch(launch_info, error); g_vsc.debugger.SetAsync(true); ``` ``` // Target.cpp bool old_async = debugger.GetAsyncExecution(); debugger.SetAsyncExecution(true); debugger.GetCommandInterpreter().HandleCommands(GetCommands(), exc_ctx, options, result); debugger.SetAsyncExecution(old_async); ``` The sequence that leads to the bug is this one: 1. Main thread enables synchronous mode and launches the process. 2. When the process is launched, it generates the first stop event. 3. This stop event is catched by the event-handling thread and DoOnRemoval is invoked. 4. Inside DoOnRemoval, this thread runs stop hooks. And before running stop hooks, the current synchronization mode is stored into old_async (and right now it is equal to "false"). 5. The main thread finishes the launch and returns to lldb-vscode, the synchronization mode is restored to asynchronous by lldb-vscode. 6. Event-handling thread finishes stop hooks processing and restores the synchronization mode according to old_async (i.e. makes the mode synchronous) 7. And now the mode is synchronous while lldb-vscode expects it to be asynchronous. Synchronous mode forbids the process to broadcast public stop events, so, VS Code just hangs because lldb-vscode doesn't notify it about stops. So, this diff makes the target intercept the first stop event if the process is launched in the synchronous mode, thus preventing stop hooks execution. The bug is only present on Windows because other platforms already intercept this event using their own hijacking listeners. So, this diff also fixes some problems with lldb-vscode tests on Windows to make it possible to run the related test. Other tests still can't be enabled because the debugged program prints something into stdout and LLDB can't intercept this output and redirect it to lldb-vscode properly. Reviewed By: jingham Differential Revision: https://reviews.llvm.org/D119548
This commit is contained in:
parent
3c0096a1d4
commit
a2c267e0c9
|
@ -3073,6 +3073,9 @@ private:
|
|||
|
||||
void ControlPrivateStateThread(uint32_t signal);
|
||||
|
||||
Status LaunchPrivate(ProcessLaunchInfo &launch_info, lldb::StateType &state,
|
||||
lldb::EventSP &event_sp);
|
||||
|
||||
Process(const Process &) = delete;
|
||||
const Process &operator=(const Process &) = delete;
|
||||
};
|
||||
|
|
|
@ -231,6 +231,11 @@ def pointer_size():
|
|||
|
||||
def is_exe(fpath):
|
||||
"""Returns true if fpath is an executable."""
|
||||
if fpath == None:
|
||||
return False
|
||||
if sys.platform == 'win32':
|
||||
if not fpath.endswith(".exe"):
|
||||
fpath += ".exe"
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ class VSCodeTestCaseBase(TestBase):
|
|||
|
||||
def create_debug_adaptor(self, lldbVSCodeEnv=None):
|
||||
'''Create the Visual Studio Code debug adaptor'''
|
||||
self.assertTrue(os.path.exists(self.lldbVSCodeExec),
|
||||
'lldb-vscode must exist')
|
||||
self.assertTrue(is_exe(self.lldbVSCodeExec),
|
||||
'lldb-vscode must exist and be executable')
|
||||
log_file_path = self.getBuildArtifact('vscode.txt')
|
||||
self.vscode = vscode.DebugAdaptor(
|
||||
executable=self.lldbVSCodeExec, init_commands=self.setUpCommands(),
|
||||
|
|
|
@ -443,9 +443,8 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
|
||||
// Now create the gdb-remote process.
|
||||
LLDB_LOG(log, "having target create process with gdb-remote plugin");
|
||||
process_sp =
|
||||
target.CreateProcess(launch_info.GetListener(), "gdb-remote", nullptr,
|
||||
true);
|
||||
process_sp = target.CreateProcess(launch_info.GetListener(), "gdb-remote",
|
||||
nullptr, true);
|
||||
|
||||
if (!process_sp) {
|
||||
error.SetErrorString("CreateProcess() failed for gdb-remote process");
|
||||
|
@ -454,15 +453,8 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
}
|
||||
|
||||
LLDB_LOG(log, "successfully created process");
|
||||
// Adjust launch for a hijacker.
|
||||
ListenerSP listener_sp;
|
||||
if (!launch_info.GetHijackListener()) {
|
||||
LLDB_LOG(log, "setting up hijacker");
|
||||
listener_sp =
|
||||
Listener::MakeListener("lldb.PlatformLinux.DebugProcess.hijack");
|
||||
launch_info.SetHijackListener(listener_sp);
|
||||
process_sp->HijackProcessEvents(listener_sp);
|
||||
}
|
||||
|
||||
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
|
||||
|
||||
// Log file actions.
|
||||
if (log) {
|
||||
|
@ -480,14 +472,6 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
// Do the launch.
|
||||
error = process_sp->Launch(launch_info);
|
||||
if (error.Success()) {
|
||||
// Handle the hijacking of process events.
|
||||
if (listener_sp) {
|
||||
const StateType state = process_sp->WaitForProcessToStop(
|
||||
llvm::None, nullptr, false, listener_sp);
|
||||
|
||||
LLDB_LOG(log, "pid {0} state {0}", process_sp->GetID(), state);
|
||||
}
|
||||
|
||||
// Hook up process PTY if we have one (which we should for local debugging
|
||||
// with llgs).
|
||||
int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();
|
||||
|
|
|
@ -217,11 +217,12 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
launch_info.GetListener(),
|
||||
process_gdb_remote::ProcessGDBRemote::GetPluginNameStatic(), nullptr,
|
||||
true);
|
||||
if (!process_sp) {
|
||||
error.SetErrorString("Failed to create GDB process");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ListenerSP listener_sp =
|
||||
Listener::MakeListener("lldb.platform_qemu_user.debugprocess");
|
||||
launch_info.SetHijackListener(listener_sp);
|
||||
Process::ProcessEventHijacker hijacker(*process_sp, listener_sp);
|
||||
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
|
||||
|
||||
error = process_sp->ConnectRemote(("unix-connect://" + socket_path).str());
|
||||
if (error.Fail())
|
||||
|
@ -232,7 +233,6 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
process_sp->SetSTDIOFileDescriptor(
|
||||
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
|
||||
|
||||
process_sp->WaitForProcessToStop(llvm::None, nullptr, false, listener_sp);
|
||||
return process_sp;
|
||||
}
|
||||
|
||||
|
|
|
@ -488,18 +488,20 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
// This is a process attach. Don't need to launch anything.
|
||||
ProcessAttachInfo attach_info(launch_info);
|
||||
return Attach(attach_info, debugger, &target, error);
|
||||
} else {
|
||||
ProcessSP process_sp = target.CreateProcess(
|
||||
launch_info.GetListener(), launch_info.GetProcessPluginName(), nullptr,
|
||||
false);
|
||||
|
||||
// We need to launch and attach to the process.
|
||||
launch_info.GetFlags().Set(eLaunchFlagDebug);
|
||||
if (process_sp)
|
||||
error = process_sp->Launch(launch_info);
|
||||
|
||||
return process_sp;
|
||||
}
|
||||
|
||||
ProcessSP process_sp =
|
||||
target.CreateProcess(launch_info.GetListener(),
|
||||
launch_info.GetProcessPluginName(), nullptr, false);
|
||||
|
||||
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
|
||||
|
||||
// We need to launch and attach to the process.
|
||||
launch_info.GetFlags().Set(eLaunchFlagDebug);
|
||||
if (process_sp)
|
||||
error = process_sp->Launch(launch_info);
|
||||
|
||||
return process_sp;
|
||||
}
|
||||
|
||||
lldb::ProcessSP PlatformWindows::Attach(ProcessAttachInfo &attach_info,
|
||||
|
|
|
@ -428,6 +428,8 @@ PlatformRemoteGDBServer::DebugProcess(ProcessLaunchInfo &launch_info,
|
|||
"gdb-remote", nullptr, true);
|
||||
|
||||
if (process_sp) {
|
||||
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
|
||||
|
||||
error = process_sp->ConnectRemote(connect_url.c_str());
|
||||
// Retry the connect remote one time...
|
||||
if (error.Fail())
|
||||
|
|
|
@ -2429,6 +2429,39 @@ void Process::LoadOperatingSystemPlugin(bool flush) {
|
|||
}
|
||||
|
||||
Status Process::Launch(ProcessLaunchInfo &launch_info) {
|
||||
StateType state_after_launch = eStateInvalid;
|
||||
EventSP first_stop_event_sp;
|
||||
Status status =
|
||||
LaunchPrivate(launch_info, state_after_launch, first_stop_event_sp);
|
||||
if (status.Fail())
|
||||
return status;
|
||||
|
||||
if (state_after_launch != eStateStopped &&
|
||||
state_after_launch != eStateCrashed)
|
||||
return Status();
|
||||
|
||||
// Note, the stop event was consumed above, but not handled. This
|
||||
// was done to give DidLaunch a chance to run. The target is either
|
||||
// stopped or crashed. Directly set the state. This is done to
|
||||
// prevent a stop message with a bunch of spurious output on thread
|
||||
// status, as well as not pop a ProcessIOHandler.
|
||||
SetPublicState(state_after_launch, false);
|
||||
|
||||
if (PrivateStateThreadIsValid())
|
||||
ResumePrivateStateThread();
|
||||
else
|
||||
StartPrivateStateThread();
|
||||
|
||||
// Target was stopped at entry as was intended. Need to notify the
|
||||
// listeners about it.
|
||||
if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
|
||||
HandlePrivateEvent(first_stop_event_sp);
|
||||
|
||||
return Status();
|
||||
}
|
||||
|
||||
Status Process::LaunchPrivate(ProcessLaunchInfo &launch_info, StateType &state,
|
||||
EventSP &event_sp) {
|
||||
Status error;
|
||||
m_abi_sp.reset();
|
||||
m_dyld_up.reset();
|
||||
|
@ -2445,7 +2478,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
|
|||
// be a way to express this path, without actually having a module.
|
||||
// The way to do that is to set the ExecutableFile in the LaunchInfo.
|
||||
// Figure that out here:
|
||||
|
||||
|
||||
FileSpec exe_spec_to_use;
|
||||
if (!exe_module) {
|
||||
if (!launch_info.GetExecutableFile()) {
|
||||
|
@ -2455,7 +2488,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
|
|||
exe_spec_to_use = launch_info.GetExecutableFile();
|
||||
} else
|
||||
exe_spec_to_use = exe_module->GetFileSpec();
|
||||
|
||||
|
||||
if (exe_module && FileSystem::Instance().Exists(exe_module->GetFileSpec())) {
|
||||
// Install anything that might need to be installed prior to launching.
|
||||
// For host systems, this will do nothing, but if we are connected to a
|
||||
|
@ -2464,6 +2497,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
|
|||
if (error.Fail())
|
||||
return error;
|
||||
}
|
||||
|
||||
// Listen and queue events that are broadcasted during the process launch.
|
||||
ListenerSP listener_sp(Listener::MakeListener("LaunchEventHijack"));
|
||||
HijackProcessEvents(listener_sp);
|
||||
|
@ -2473,93 +2507,81 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
|
|||
PausePrivateStateThread();
|
||||
|
||||
error = WillLaunch(exe_module);
|
||||
if (error.Success()) {
|
||||
const bool restarted = false;
|
||||
SetPublicState(eStateLaunching, restarted);
|
||||
m_should_detach = false;
|
||||
|
||||
if (m_public_run_lock.TrySetRunning()) {
|
||||
// Now launch using these arguments.
|
||||
error = DoLaunch(exe_module, launch_info);
|
||||
} else {
|
||||
// This shouldn't happen
|
||||
error.SetErrorString("failed to acquire process run lock");
|
||||
}
|
||||
|
||||
if (error.Fail()) {
|
||||
if (GetID() != LLDB_INVALID_PROCESS_ID) {
|
||||
SetID(LLDB_INVALID_PROCESS_ID);
|
||||
const char *error_string = error.AsCString();
|
||||
if (error_string == nullptr)
|
||||
error_string = "launch failed";
|
||||
SetExitStatus(-1, error_string);
|
||||
}
|
||||
} else {
|
||||
EventSP event_sp;
|
||||
|
||||
// Now wait for the process to launch and return control to us, and then
|
||||
// call DidLaunch:
|
||||
StateType state = WaitForProcessStopPrivate(event_sp, seconds(10));
|
||||
|
||||
if (state == eStateInvalid || !event_sp) {
|
||||
// We were able to launch the process, but we failed to catch the
|
||||
// initial stop.
|
||||
error.SetErrorString("failed to catch stop after launch");
|
||||
SetExitStatus(0, "failed to catch stop after launch");
|
||||
Destroy(false);
|
||||
} else if (state == eStateStopped || state == eStateCrashed) {
|
||||
DidLaunch();
|
||||
|
||||
DynamicLoader *dyld = GetDynamicLoader();
|
||||
if (dyld)
|
||||
dyld->DidLaunch();
|
||||
|
||||
GetJITLoaders().DidLaunch();
|
||||
|
||||
SystemRuntime *system_runtime = GetSystemRuntime();
|
||||
if (system_runtime)
|
||||
system_runtime->DidLaunch();
|
||||
|
||||
if (!m_os_up)
|
||||
LoadOperatingSystemPlugin(false);
|
||||
|
||||
// We successfully launched the process and stopped, now it the
|
||||
// right time to set up signal filters before resuming.
|
||||
UpdateAutomaticSignalFiltering();
|
||||
|
||||
// Note, the stop event was consumed above, but not handled. This
|
||||
// was done to give DidLaunch a chance to run. The target is either
|
||||
// stopped or crashed. Directly set the state. This is done to
|
||||
// prevent a stop message with a bunch of spurious output on thread
|
||||
// status, as well as not pop a ProcessIOHandler.
|
||||
// We are done with the launch hijack listener, and this stop should
|
||||
// go to the public state listener:
|
||||
RestoreProcessEvents();
|
||||
SetPublicState(state, false);
|
||||
|
||||
if (PrivateStateThreadIsValid())
|
||||
ResumePrivateStateThread();
|
||||
else
|
||||
StartPrivateStateThread();
|
||||
|
||||
// Target was stopped at entry as was intended. Need to notify the
|
||||
// listeners about it.
|
||||
if (state == eStateStopped &&
|
||||
launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
|
||||
HandlePrivateEvent(event_sp);
|
||||
} else if (state == eStateExited) {
|
||||
// We exited while trying to launch somehow. Don't call DidLaunch
|
||||
// as that's not likely to work, and return an invalid pid.
|
||||
HandlePrivateEvent(event_sp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (error.Fail()) {
|
||||
std::string local_exec_file_path = exe_spec_to_use.GetPath();
|
||||
error.SetErrorStringWithFormat("file doesn't exist: '%s'",
|
||||
local_exec_file_path.c_str());
|
||||
return Status("file doesn't exist: '%s'", local_exec_file_path.c_str());
|
||||
}
|
||||
|
||||
return error;
|
||||
const bool restarted = false;
|
||||
SetPublicState(eStateLaunching, restarted);
|
||||
m_should_detach = false;
|
||||
|
||||
if (m_public_run_lock.TrySetRunning()) {
|
||||
// Now launch using these arguments.
|
||||
error = DoLaunch(exe_module, launch_info);
|
||||
} else {
|
||||
// This shouldn't happen
|
||||
error.SetErrorString("failed to acquire process run lock");
|
||||
}
|
||||
|
||||
if (error.Fail()) {
|
||||
if (GetID() != LLDB_INVALID_PROCESS_ID) {
|
||||
SetID(LLDB_INVALID_PROCESS_ID);
|
||||
const char *error_string = error.AsCString();
|
||||
if (error_string == nullptr)
|
||||
error_string = "launch failed";
|
||||
SetExitStatus(-1, error_string);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Now wait for the process to launch and return control to us, and then
|
||||
// call DidLaunch:
|
||||
state = WaitForProcessStopPrivate(event_sp, seconds(10));
|
||||
|
||||
if (state == eStateInvalid || !event_sp) {
|
||||
// We were able to launch the process, but we failed to catch the
|
||||
// initial stop.
|
||||
error.SetErrorString("failed to catch stop after launch");
|
||||
SetExitStatus(0, error.AsCString());
|
||||
Destroy(false);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (state == eStateExited) {
|
||||
// We exited while trying to launch somehow. Don't call DidLaunch
|
||||
// as that's not likely to work, and return an invalid pid.
|
||||
HandlePrivateEvent(event_sp);
|
||||
return Status();
|
||||
}
|
||||
|
||||
if (state == eStateStopped || state == eStateCrashed) {
|
||||
DidLaunch();
|
||||
|
||||
DynamicLoader *dyld = GetDynamicLoader();
|
||||
if (dyld)
|
||||
dyld->DidLaunch();
|
||||
|
||||
GetJITLoaders().DidLaunch();
|
||||
|
||||
SystemRuntime *system_runtime = GetSystemRuntime();
|
||||
if (system_runtime)
|
||||
system_runtime->DidLaunch();
|
||||
|
||||
if (!m_os_up)
|
||||
LoadOperatingSystemPlugin(false);
|
||||
|
||||
// We successfully launched the process and stopped, now it the
|
||||
// right time to set up signal filters before resuming.
|
||||
UpdateAutomaticSignalFiltering();
|
||||
return Status();
|
||||
}
|
||||
|
||||
return Status("Unexpected process state after the launch: %s, expected %s, "
|
||||
"%s, %s or %s",
|
||||
StateAsCString(state), StateAsCString(eStateInvalid),
|
||||
StateAsCString(eStateExited), StateAsCString(eStateStopped),
|
||||
StateAsCString(eStateCrashed));
|
||||
}
|
||||
|
||||
Status Process::LoadCore() {
|
||||
|
|
|
@ -3026,6 +3026,14 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
|
|||
if (!launch_info.GetArchitecture().IsValid())
|
||||
launch_info.GetArchitecture() = GetArchitecture();
|
||||
|
||||
// Hijacking events of the process to be created to be sure that all events
|
||||
// until the first stop are intercepted (in case if platform doesn't define
|
||||
// its own hijacking listener or if the process is created by the target
|
||||
// manually, without the platform).
|
||||
if (!launch_info.GetHijackListener())
|
||||
launch_info.SetHijackListener(
|
||||
Listener::MakeListener("lldb.Target.Launch.hijack"));
|
||||
|
||||
// If we're not already connected to the process, and if we have a platform
|
||||
// that can launch a process for debugging, go ahead and do that here.
|
||||
if (state != eStateConnected && platform_sp &&
|
||||
|
@ -3057,8 +3065,10 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
|
|||
}
|
||||
|
||||
// Since we didn't have a platform launch the process, launch it here.
|
||||
if (m_process_sp)
|
||||
if (m_process_sp) {
|
||||
m_process_sp->HijackProcessEvents(launch_info.GetHijackListener());
|
||||
error = m_process_sp->Launch(launch_info);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_process_sp && error.Success())
|
||||
|
@ -3067,35 +3077,35 @@ Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) {
|
|||
if (!error.Success())
|
||||
return error;
|
||||
|
||||
auto at_exit =
|
||||
llvm::make_scope_exit([&]() { m_process_sp->RestoreProcessEvents(); });
|
||||
bool rebroadcast_first_stop =
|
||||
!synchronous_execution &&
|
||||
launch_info.GetFlags().Test(eLaunchFlagStopAtEntry);
|
||||
|
||||
if (!synchronous_execution &&
|
||||
launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
|
||||
assert(launch_info.GetHijackListener());
|
||||
|
||||
EventSP first_stop_event_sp;
|
||||
state = m_process_sp->WaitForProcessToStop(llvm::None, &first_stop_event_sp,
|
||||
rebroadcast_first_stop,
|
||||
launch_info.GetHijackListener());
|
||||
m_process_sp->RestoreProcessEvents();
|
||||
|
||||
if (rebroadcast_first_stop) {
|
||||
assert(first_stop_event_sp);
|
||||
m_process_sp->BroadcastEvent(first_stop_event_sp);
|
||||
return error;
|
||||
|
||||
ListenerSP hijack_listener_sp(launch_info.GetHijackListener());
|
||||
if (!hijack_listener_sp) {
|
||||
hijack_listener_sp = Listener::MakeListener("lldb.Target.Launch.hijack");
|
||||
launch_info.SetHijackListener(hijack_listener_sp);
|
||||
m_process_sp->HijackProcessEvents(hijack_listener_sp);
|
||||
}
|
||||
|
||||
switch (m_process_sp->WaitForProcessToStop(llvm::None, nullptr, false,
|
||||
hijack_listener_sp, nullptr)) {
|
||||
switch (state) {
|
||||
case eStateStopped: {
|
||||
if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
|
||||
break;
|
||||
if (synchronous_execution) {
|
||||
if (synchronous_execution)
|
||||
// Now we have handled the stop-from-attach, and we are just
|
||||
// switching to a synchronous resume. So we should switch to the
|
||||
// SyncResume hijacker.
|
||||
m_process_sp->RestoreProcessEvents();
|
||||
m_process_sp->ResumeSynchronous(stream);
|
||||
} else {
|
||||
m_process_sp->RestoreProcessEvents();
|
||||
else
|
||||
error = m_process_sp->PrivateResume();
|
||||
}
|
||||
if (!error.Success()) {
|
||||
Status error2;
|
||||
error2.SetErrorStringWithFormat(
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Test stop hooks
|
||||
"""
|
||||
|
||||
|
||||
from lldbsuite.test.decorators import *
|
||||
from lldbsuite.test.lldbtest import *
|
||||
import lldbvscode_testcase
|
||||
|
||||
|
||||
class TestVSCode_stop_hooks(lldbvscode_testcase.VSCodeTestCaseBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
|
||||
@skipIfRemote
|
||||
def test_stop_hooks_before_run(self):
|
||||
'''
|
||||
Test that there is no race condition between lldb-vscode and
|
||||
stop hooks executor
|
||||
'''
|
||||
program = self.getBuildArtifact("a.out")
|
||||
preRunCommands = ['target stop-hook add -o help']
|
||||
self.build_and_launch(program, stopOnEntry=True, preRunCommands=preRunCommands)
|
||||
|
||||
# The first stop is on entry.
|
||||
self.continue_to_next_stop()
|
||||
|
||||
breakpoint_ids = self.set_function_breakpoints(['main'])
|
||||
# This request hangs if the race happens, because, in that case, the
|
||||
# command interpreter is in synchronous mode while lldb-vscode expects
|
||||
# it to be in asynchronous mode, so, the process doesn't send the stop
|
||||
# event to "lldb.Debugger" listener (which is monitored by lldb-vscode).
|
||||
self.continue_to_breakpoints(breakpoint_ids)
|
||||
|
||||
self.continue_to_exit()
|
|
@ -0,0 +1 @@
|
|||
int main() { return 0; }
|
Loading…
Reference in New Issue