Skip to content

Commit a2c267e

Browse files
ilya-nozhkinlabath
authored andcommitted
[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
1 parent 3c0096a commit a2c267e

File tree

12 files changed

+205
-138
lines changed

12 files changed

+205
-138
lines changed

lldb/include/lldb/Target/Process.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,6 +3073,9 @@ void PruneThreadPlans();
30733073

30743074
void ControlPrivateStateThread(uint32_t signal);
30753075

3076+
Status LaunchPrivate(ProcessLaunchInfo &launch_info, lldb::StateType &state,
3077+
lldb::EventSP &event_sp);
3078+
30763079
Process(const Process &) = delete;
30773080
const Process &operator=(const Process &) = delete;
30783081
};

lldb/packages/Python/lldbsuite/test/lldbtest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ def pointer_size():
231231

232232
def is_exe(fpath):
233233
"""Returns true if fpath is an executable."""
234+
if fpath == None:
235+
return False
236+
if sys.platform == 'win32':
237+
if not fpath.endswith(".exe"):
238+
fpath += ".exe"
234239
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
235240

236241

lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class VSCodeTestCaseBase(TestBase):
1111

1212
def create_debug_adaptor(self, lldbVSCodeEnv=None):
1313
'''Create the Visual Studio Code debug adaptor'''
14-
self.assertTrue(os.path.exists(self.lldbVSCodeExec),
15-
'lldb-vscode must exist')
14+
self.assertTrue(is_exe(self.lldbVSCodeExec),
15+
'lldb-vscode must exist and be executable')
1616
log_file_path = self.getBuildArtifact('vscode.txt')
1717
self.vscode = vscode.DebugAdaptor(
1818
executable=self.lldbVSCodeExec, init_commands=self.setUpCommands(),

lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,8 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
443443

444444
// Now create the gdb-remote process.
445445
LLDB_LOG(log, "having target create process with gdb-remote plugin");
446-
process_sp =
447-
target.CreateProcess(launch_info.GetListener(), "gdb-remote", nullptr,
448-
true);
446+
process_sp = target.CreateProcess(launch_info.GetListener(), "gdb-remote",
447+
nullptr, true);
449448

450449
if (!process_sp) {
451450
error.SetErrorString("CreateProcess() failed for gdb-remote process");
@@ -454,15 +453,8 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
454453
}
455454

456455
LLDB_LOG(log, "successfully created process");
457-
// Adjust launch for a hijacker.
458-
ListenerSP listener_sp;
459-
if (!launch_info.GetHijackListener()) {
460-
LLDB_LOG(log, "setting up hijacker");
461-
listener_sp =
462-
Listener::MakeListener("lldb.PlatformLinux.DebugProcess.hijack");
463-
launch_info.SetHijackListener(listener_sp);
464-
process_sp->HijackProcessEvents(listener_sp);
465-
}
456+
457+
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
466458

467459
// Log file actions.
468460
if (log) {
@@ -480,14 +472,6 @@ lldb::ProcessSP PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info,
480472
// Do the launch.
481473
error = process_sp->Launch(launch_info);
482474
if (error.Success()) {
483-
// Handle the hijacking of process events.
484-
if (listener_sp) {
485-
const StateType state = process_sp->WaitForProcessToStop(
486-
llvm::None, nullptr, false, listener_sp);
487-
488-
LLDB_LOG(log, "pid {0} state {0}", process_sp->GetID(), state);
489-
}
490-
491475
// Hook up process PTY if we have one (which we should for local debugging
492476
// with llgs).
493477
int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor();

lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,12 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
217217
launch_info.GetListener(),
218218
process_gdb_remote::ProcessGDBRemote::GetPluginNameStatic(), nullptr,
219219
true);
220+
if (!process_sp) {
221+
error.SetErrorString("Failed to create GDB process");
222+
return nullptr;
223+
}
220224

221-
ListenerSP listener_sp =
222-
Listener::MakeListener("lldb.platform_qemu_user.debugprocess");
223-
launch_info.SetHijackListener(listener_sp);
224-
Process::ProcessEventHijacker hijacker(*process_sp, listener_sp);
225+
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
225226

226227
error = process_sp->ConnectRemote(("unix-connect://" + socket_path).str());
227228
if (error.Fail())
@@ -232,7 +233,6 @@ lldb::ProcessSP PlatformQemuUser::DebugProcess(ProcessLaunchInfo &launch_info,
232233
process_sp->SetSTDIOFileDescriptor(
233234
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
234235

235-
process_sp->WaitForProcessToStop(llvm::None, nullptr, false, listener_sp);
236236
return process_sp;
237237
}
238238

lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -488,18 +488,20 @@ ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info,
488488
// This is a process attach. Don't need to launch anything.
489489
ProcessAttachInfo attach_info(launch_info);
490490
return Attach(attach_info, debugger, &target, error);
491-
} else {
492-
ProcessSP process_sp = target.CreateProcess(
493-
launch_info.GetListener(), launch_info.GetProcessPluginName(), nullptr,
494-
false);
491+
}
495492

496-
// We need to launch and attach to the process.
497-
launch_info.GetFlags().Set(eLaunchFlagDebug);
498-
if (process_sp)
499-
error = process_sp->Launch(launch_info);
493+
ProcessSP process_sp =
494+
target.CreateProcess(launch_info.GetListener(),
495+
launch_info.GetProcessPluginName(), nullptr, false);
500496

501-
return process_sp;
502-
}
497+
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
498+
499+
// We need to launch and attach to the process.
500+
launch_info.GetFlags().Set(eLaunchFlagDebug);
501+
if (process_sp)
502+
error = process_sp->Launch(launch_info);
503+
504+
return process_sp;
503505
}
504506

505507
lldb::ProcessSP PlatformWindows::Attach(ProcessAttachInfo &attach_info,

lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ PlatformRemoteGDBServer::DebugProcess(ProcessLaunchInfo &launch_info,
428428
"gdb-remote", nullptr, true);
429429

430430
if (process_sp) {
431+
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
432+
431433
error = process_sp->ConnectRemote(connect_url.c_str());
432434
// Retry the connect remote one time...
433435
if (error.Fail())

lldb/source/Target/Process.cpp

Lines changed: 105 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,6 +2429,39 @@ void Process::LoadOperatingSystemPlugin(bool flush) {
24292429
}
24302430

24312431
Status Process::Launch(ProcessLaunchInfo &launch_info) {
2432+
StateType state_after_launch = eStateInvalid;
2433+
EventSP first_stop_event_sp;
2434+
Status status =
2435+
LaunchPrivate(launch_info, state_after_launch, first_stop_event_sp);
2436+
if (status.Fail())
2437+
return status;
2438+
2439+
if (state_after_launch != eStateStopped &&
2440+
state_after_launch != eStateCrashed)
2441+
return Status();
2442+
2443+
// Note, the stop event was consumed above, but not handled. This
2444+
// was done to give DidLaunch a chance to run. The target is either
2445+
// stopped or crashed. Directly set the state. This is done to
2446+
// prevent a stop message with a bunch of spurious output on thread
2447+
// status, as well as not pop a ProcessIOHandler.
2448+
SetPublicState(state_after_launch, false);
2449+
2450+
if (PrivateStateThreadIsValid())
2451+
ResumePrivateStateThread();
2452+
else
2453+
StartPrivateStateThread();
2454+
2455+
// Target was stopped at entry as was intended. Need to notify the
2456+
// listeners about it.
2457+
if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
2458+
HandlePrivateEvent(first_stop_event_sp);
2459+
2460+
return Status();
2461+
}
2462+
2463+
Status Process::LaunchPrivate(ProcessLaunchInfo &launch_info, StateType &state,
2464+
EventSP &event_sp) {
24322465
Status error;
24332466
m_abi_sp.reset();
24342467
m_dyld_up.reset();
@@ -2445,7 +2478,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
24452478
// be a way to express this path, without actually having a module.
24462479
// The way to do that is to set the ExecutableFile in the LaunchInfo.
24472480
// Figure that out here:
2448-
2481+
24492482
FileSpec exe_spec_to_use;
24502483
if (!exe_module) {
24512484
if (!launch_info.GetExecutableFile()) {
@@ -2455,7 +2488,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
24552488
exe_spec_to_use = launch_info.GetExecutableFile();
24562489
} else
24572490
exe_spec_to_use = exe_module->GetFileSpec();
2458-
2491+
24592492
if (exe_module && FileSystem::Instance().Exists(exe_module->GetFileSpec())) {
24602493
// Install anything that might need to be installed prior to launching.
24612494
// For host systems, this will do nothing, but if we are connected to a
@@ -2464,6 +2497,7 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
24642497
if (error.Fail())
24652498
return error;
24662499
}
2500+
24672501
// Listen and queue events that are broadcasted during the process launch.
24682502
ListenerSP listener_sp(Listener::MakeListener("LaunchEventHijack"));
24692503
HijackProcessEvents(listener_sp);
@@ -2473,93 +2507,81 @@ Status Process::Launch(ProcessLaunchInfo &launch_info) {
24732507
PausePrivateStateThread();
24742508

24752509
error = WillLaunch(exe_module);
2476-
if (error.Success()) {
2477-
const bool restarted = false;
2478-
SetPublicState(eStateLaunching, restarted);
2479-
m_should_detach = false;
2510+
if (error.Fail()) {
2511+
std::string local_exec_file_path = exe_spec_to_use.GetPath();
2512+
return Status("file doesn't exist: '%s'", local_exec_file_path.c_str());
2513+
}
24802514

2481-
if (m_public_run_lock.TrySetRunning()) {
2482-
// Now launch using these arguments.
2483-
error = DoLaunch(exe_module, launch_info);
2484-
} else {
2485-
// This shouldn't happen
2486-
error.SetErrorString("failed to acquire process run lock");
2487-
}
2515+
const bool restarted = false;
2516+
SetPublicState(eStateLaunching, restarted);
2517+
m_should_detach = false;
24882518

2489-
if (error.Fail()) {
2490-
if (GetID() != LLDB_INVALID_PROCESS_ID) {
2491-
SetID(LLDB_INVALID_PROCESS_ID);
2492-
const char *error_string = error.AsCString();
2493-
if (error_string == nullptr)
2494-
error_string = "launch failed";
2495-
SetExitStatus(-1, error_string);
2496-
}
2497-
} else {
2498-
EventSP event_sp;
2519+
if (m_public_run_lock.TrySetRunning()) {
2520+
// Now launch using these arguments.
2521+
error = DoLaunch(exe_module, launch_info);
2522+
} else {
2523+
// This shouldn't happen
2524+
error.SetErrorString("failed to acquire process run lock");
2525+
}
24992526

2500-
// Now wait for the process to launch and return control to us, and then
2501-
// call DidLaunch:
2502-
StateType state = WaitForProcessStopPrivate(event_sp, seconds(10));
2503-
2504-
if (state == eStateInvalid || !event_sp) {
2505-
// We were able to launch the process, but we failed to catch the
2506-
// initial stop.
2507-
error.SetErrorString("failed to catch stop after launch");
2508-
SetExitStatus(0, "failed to catch stop after launch");
2509-
Destroy(false);
2510-
} else if (state == eStateStopped || state == eStateCrashed) {
2511-
DidLaunch();
2512-
2513-
DynamicLoader *dyld = GetDynamicLoader();
2514-
if (dyld)
2515-
dyld->DidLaunch();
2516-
2517-
GetJITLoaders().DidLaunch();
2518-
2519-
SystemRuntime *system_runtime = GetSystemRuntime();
2520-
if (system_runtime)
2521-
system_runtime->DidLaunch();
2522-
2523-
if (!m_os_up)
2524-
LoadOperatingSystemPlugin(false);
2525-
2526-
// We successfully launched the process and stopped, now it the
2527-
// right time to set up signal filters before resuming.
2528-
UpdateAutomaticSignalFiltering();
2529-
2530-
// Note, the stop event was consumed above, but not handled. This
2531-
// was done to give DidLaunch a chance to run. The target is either
2532-
// stopped or crashed. Directly set the state. This is done to
2533-
// prevent a stop message with a bunch of spurious output on thread
2534-
// status, as well as not pop a ProcessIOHandler.
2535-
// We are done with the launch hijack listener, and this stop should
2536-
// go to the public state listener:
2537-
RestoreProcessEvents();
2538-
SetPublicState(state, false);
2539-
2540-
if (PrivateStateThreadIsValid())
2541-
ResumePrivateStateThread();
2542-
else
2543-
StartPrivateStateThread();
2544-
2545-
// Target was stopped at entry as was intended. Need to notify the
2546-
// listeners about it.
2547-
if (state == eStateStopped &&
2548-
launch_info.GetFlags().Test(eLaunchFlagStopAtEntry))
2549-
HandlePrivateEvent(event_sp);
2550-
} else if (state == eStateExited) {
2551-
// We exited while trying to launch somehow. Don't call DidLaunch
2552-
// as that's not likely to work, and return an invalid pid.
2553-
HandlePrivateEvent(event_sp);
2554-
}
2527+
if (error.Fail()) {
2528+
if (GetID() != LLDB_INVALID_PROCESS_ID) {
2529+
SetID(LLDB_INVALID_PROCESS_ID);
2530+
const char *error_string = error.AsCString();
2531+
if (error_string == nullptr)
2532+
error_string = "launch failed";
2533+
SetExitStatus(-1, error_string);
25552534
}
2556-
} else {
2557-
std::string local_exec_file_path = exe_spec_to_use.GetPath();
2558-
error.SetErrorStringWithFormat("file doesn't exist: '%s'",
2559-
local_exec_file_path.c_str());
2535+
return error;
25602536
}
25612537

2562-
return error;
2538+
// Now wait for the process to launch and return control to us, and then
2539+
// call DidLaunch:
2540+
state = WaitForProcessStopPrivate(event_sp, seconds(10));
2541+
2542+
if (state == eStateInvalid || !event_sp) {
2543+
// We were able to launch the process, but we failed to catch the
2544+
// initial stop.
2545+
error.SetErrorString("failed to catch stop after launch");
2546+
SetExitStatus(0, error.AsCString());
2547+
Destroy(false);
2548+
return error;
2549+
}
2550+
2551+
if (state == eStateExited) {
2552+
// We exited while trying to launch somehow. Don't call DidLaunch
2553+
// as that's not likely to work, and return an invalid pid.
2554+
HandlePrivateEvent(event_sp);
2555+
return Status();
2556+
}
2557+
2558+
if (state == eStateStopped || state == eStateCrashed) {
2559+
DidLaunch();
2560+
2561+
DynamicLoader *dyld = GetDynamicLoader();
2562+
if (dyld)
2563+
dyld->DidLaunch();
2564+
2565+
GetJITLoaders().DidLaunch();
2566+
2567+
SystemRuntime *system_runtime = GetSystemRuntime();
2568+
if (system_runtime)
2569+
system_runtime->DidLaunch();
2570+
2571+
if (!m_os_up)
2572+
LoadOperatingSystemPlugin(false);
2573+
2574+
// We successfully launched the process and stopped, now it the
2575+
// right time to set up signal filters before resuming.
2576+
UpdateAutomaticSignalFiltering();
2577+
return Status();
2578+
}
2579+
2580+
return Status("Unexpected process state after the launch: %s, expected %s, "
2581+
"%s, %s or %s",
2582+
StateAsCString(state), StateAsCString(eStateInvalid),
2583+
StateAsCString(eStateExited), StateAsCString(eStateStopped),
2584+
StateAsCString(eStateCrashed));
25632585
}
25642586

25652587
Status Process::LoadCore() {

0 commit comments

Comments
 (0)