This revision was automatically updated to reflect the committed changes. Closed by commit rGa2c267e0c9d9: [lldb] Fix race condition between lldb-vscode and stop hooks executor (authored by ilya-nozhkin, committed by labath).
Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D119548/new/ https://reviews.llvm.org/D119548 Files: 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 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
Index: lldb/test/API/tools/lldb-vscode/stop-hooks/main.c =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-vscode/stop-hooks/main.c @@ -0,0 +1 @@ +int main() { return 0; } Index: lldb/test/API/tools/lldb-vscode/stop-hooks/TestVSCode_stop_hooks.py =================================================================== --- /dev/null +++ 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() Index: lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-vscode/stop-hooks/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -3026,6 +3026,14 @@ 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 @@ } // 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 @@ 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( Index: lldb/source/Target/Process.cpp =================================================================== --- lldb/source/Target/Process.cpp +++ lldb/source/Target/Process.cpp @@ -2429,6 +2429,39 @@ } 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 @@ // 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 @@ 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 @@ 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 @@ 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() { Index: lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp =================================================================== --- lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp +++ lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp @@ -428,6 +428,8 @@ "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()) Index: lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp =================================================================== --- lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -488,18 +488,20 @@ // 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, Index: lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp =================================================================== --- lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp +++ lldb/source/Plugins/Platform/QemuUser/PlatformQemuUser.cpp @@ -217,11 +217,12 @@ 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 @@ process_sp->SetSTDIOFileDescriptor( launch_info.GetPTY().ReleasePrimaryFileDescriptor()); - process_sp->WaitForProcessToStop(llvm::None, nullptr, false, listener_sp); return process_sp; } Index: lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp =================================================================== --- lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp +++ lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp @@ -443,9 +443,8 @@ // 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_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 @@ // 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(); Index: lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py =================================================================== --- lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py +++ lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py @@ -11,8 +11,8 @@ 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(), Index: lldb/packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbtest.py +++ lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -231,6 +231,11 @@ 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) Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -3073,6 +3073,9 @@ 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; };
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits