[Lldb-commits] [lldb] a2c267e - [lldb] Fix race condition between lldb-vscode and stop hooks executor
Pavel Labath via lldb-commits
lldb-commits at lists.llvm.org
Tue Feb 22 03:54:35 PST 2022
Author: Ilya Nozhkin
Date: 2022-02-22T12:53:55+01:00
New Revision: a2c267e0c9d9b9963f4022caf455327a7d96dfbf
URL: https://github.com/llvm/llvm-project/commit/a2c267e0c9d9b9963f4022caf455327a7d96dfbf
DIFF: https://github.com/llvm/llvm-project/commit/a2c267e0c9d9b9963f4022caf455327a7d96dfbf.diff
LOG: [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
Added:
lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile
lldb/test/API/tools/lldb-vscode/stop-hooks/TestVSCode_stop_hooks.py
lldb/test/API/tools/lldb-vscode/stop-hooks/main.c
Modified:
lldb/include/lldb/Target/Process.h
lldb/packages/Python/lldbsuite/test/lldbtest.py
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp
lldb/source/Target/Process.cpp
lldb/source/Target/Target.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 7911dac40b705..adceec619ff04 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -3073,6 +3073,9 @@ void PruneThreadPlans();
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;
};
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 950dd41666fd6..98b0922e9cfe2 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -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)
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
index 255a4805a9737..b0fb17ffa9719 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
@@ -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(),
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index 177332b6cbc96..a25fd1f2678eb 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -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();
diff --git a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
index 6e49faee4da7a..927daed08d34b 100644
--- a/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
+++ b/lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp
@@ -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;
}
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 220a153f5c3b6..708268ff900bc 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -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);
+ ProcessSP process_sp =
+ target.CreateProcess(launch_info.GetListener(),
+ launch_info.GetProcessPluginName(), nullptr, false);
- return process_sp;
- }
+ 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,
diff --git a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp
index 219d7c7e37b27..2438661886291 100644
--- a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp
+++ b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp
@@ -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())
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 8e1857ce4bfa9..c70bfcfc448de 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -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 (error.Fail()) {
+ std::string local_exec_file_path = exe_spec_to_use.GetPath();
+ return Status("file doesn't exist: '%s'", local_exec_file_path.c_str());
+ }
- 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");
- }
+ const bool restarted = false;
+ SetPublicState(eStateLaunching, restarted);
+ m_should_detach = false;
- 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;
+ 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");
+ }
- // 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);
- }
+ 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 {
- 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 error;
}
- 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() {
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index d7023a159046c..298db3bca6803 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -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))
- return error;
+ 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();
- 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);
+ if (rebroadcast_first_stop) {
+ assert(first_stop_event_sp);
+ m_process_sp->BroadcastEvent(first_stop_event_sp);
+ return error;
}
- 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(
diff --git a/lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile b/lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-vscode/stop-hooks/TestVSCode_stop_hooks.py b/lldb/test/API/tools/lldb-vscode/stop-hooks/TestVSCode_stop_hooks.py
new file mode 100644
index 0000000000000..c6be1a013a80a
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/stop-hooks/TestVSCode_stop_hooks.py
@@ -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()
diff --git a/lldb/test/API/tools/lldb-vscode/stop-hooks/main.c b/lldb/test/API/tools/lldb-vscode/stop-hooks/main.c
new file mode 100644
index 0000000000000..76e8197013aab
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/stop-hooks/main.c
@@ -0,0 +1 @@
+int main() { return 0; }
More information about the lldb-commits
mailing list