Index: source/Plugins/Process/Windows/DebuggerThread.h =================================================================== --- source/Plugins/Process/Windows/DebuggerThread.h +++ source/Plugins/Process/Windows/DebuggerThread.h @@ -10,6 +10,7 @@ #ifndef liblldb_Plugins_Process_Windows_DebuggerThread_H_ #define liblldb_Plugins_Process_Windows_DebuggerThread_H_ +#include #include #include "ForwardDecl.h" @@ -84,6 +85,9 @@ HANDLE m_debugging_ended_event; // An event which gets signalled by the debugger thread when it // exits the debugger loop and is detached from the inferior. + std::atomic m_pid_to_detach; // Signals the loop to detach from the process (specified by pid). + bool m_detached; // Indicates we've detached from the inferior process and the debug loop can exit. + static lldb::thread_result_t DebuggerThreadLaunchRoutine(void *data); lldb::thread_result_t DebuggerThreadLaunchRoutine(const ProcessLaunchInfo &launch_info); static lldb::thread_result_t DebuggerThreadAttachRoutine(void *data); Index: source/Plugins/Process/Windows/DebuggerThread.cpp =================================================================== --- source/Plugins/Process/Windows/DebuggerThread.cpp +++ source/Plugins/Process/Windows/DebuggerThread.cpp @@ -63,6 +63,8 @@ : m_debug_delegate(debug_delegate) , m_image_file(nullptr) , m_debugging_ended_event(nullptr) + , m_pid_to_detach(0) + , m_detached(false) { m_debugging_ended_event = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); } @@ -153,7 +155,6 @@ else m_debug_delegate->OnDebuggerError(error, 0); - SetEvent(m_debugging_ended_event); return 0; } @@ -193,55 +194,62 @@ "StopDebugging('%s') called (inferior=%I64u).", (terminate ? "true" : "false"), pid); + // Make a copy of the process, since the termination sequence will reset + // DebuggerThread's internal copy and it needs to remain open for the Wait operation. + HostProcess process_copy = m_process; + lldb::process_t handle = m_process.GetNativeProcess().GetSystemHandle(); + if (terminate) { - // Make a copy of the process, since the termination sequence will reset - // DebuggerThread's internal copy and it needs to remain open for the Wait operation. - HostProcess process_copy = m_process; - lldb::process_t handle = m_process.GetNativeProcess().GetSystemHandle(); - // Initiate the termination before continuing the exception, so that the next debug // event we get is the exit process event, and not some other event. BOOL terminate_suceeded = TerminateProcess(handle, 0); WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging called TerminateProcess(0x%p, 0) (inferior=%I64u), success='%s'", handle, pid, (terminate_suceeded ? "true" : "false")); + } - // If we're stuck waiting for an exception to continue (e.g. the user is at a breakpoint - // messing around in the debugger), continue it now. But only AFTER calling TerminateProcess - // to make sure that the very next call to WaitForDebugEvent is an exit process event. - if (m_active_exception.get()) - { - WINLOG_IFANY(WINDOWS_LOG_PROCESS|WINDOWS_LOG_EXCEPTION, - "StopDebugging masking active exception"); + // If we're stuck waiting for an exception to continue (e.g. the user is at a breakpoint + // messing around in the debugger), continue it now. But only AFTER calling TerminateProcess + // to make sure that the very next call to WaitForDebugEvent is an exit process event. + if (m_active_exception.get()) + { + WINLOG_IFANY(WINDOWS_LOG_PROCESS|WINDOWS_LOG_EXCEPTION, + "StopDebugging masking active exception"); - ContinueAsyncException(ExceptionResult::MaskException); - } + ContinueAsyncException(ExceptionResult::MaskException); + } - WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging waiting for detach from process %u to complete.", pid); + if (!terminate) + { + // Indicate that we want to detach. + m_pid_to_detach = GetProcess().GetProcessId(); - DWORD wait_result = WaitForSingleObject(m_debugging_ended_event, 5000); - if (wait_result != WAIT_OBJECT_0) - { - error.SetError(GetLastError(), eErrorTypeWin32); - WINERR_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging WaitForSingleObject(0x%p, 5000) returned %u", - m_debugging_ended_event, wait_result); - } - else + // Force a fresh break so that the detach can happen from the debugger thread. + if (!::DebugBreakProcess(GetProcess().GetNativeProcess().GetSystemHandle())) { - WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging detach from process %u completed successfully.", pid); + error.SetError(::GetLastError(), eErrorTypeWin32); } } + + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging waiting for detach from process %u to complete.", pid); + + DWORD wait_result = WaitForSingleObject(m_debugging_ended_event, 5000); + if (wait_result != WAIT_OBJECT_0) + { + error.SetError(GetLastError(), eErrorTypeWin32); + WINERR_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging WaitForSingleObject(0x%p, 5000) returned %u", + m_debugging_ended_event, wait_result); + } else { - error.SetErrorString("Detach not yet supported on Windows."); - // TODO: Implement detach. + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging detach from process %u completed successfully.", pid); } if (!error.Success()) { WINERR_IFALL(WINDOWS_LOG_PROCESS, - "StopDebugging encountered an error while trying to stop process %u. %s", + "StopDebugging encountered an error while trying to stop process %u. %s", pid, error.AsCString()); } return error; @@ -331,6 +339,11 @@ dbe.dwProcessId, dbe.dwThreadId, continue_status, ::GetCurrentThreadId()); ::ContinueDebugEvent(dbe.dwProcessId, dbe.dwThreadId, continue_status); + + if (m_detached) + { + should_debug = false; + } } else { @@ -344,6 +357,7 @@ FreeProcessHandles(); WINLOG_IFALL(WINDOWS_LOG_EVENT, "WaitForDebugEvent loop completed, exiting."); + SetEvent(m_debugging_ended_event); } ExceptionResult @@ -356,6 +370,18 @@ "HandleExceptionEvent encountered %s chance exception 0x%x on thread 0x%x", first_chance ? "first" : "second", info.ExceptionRecord.ExceptionCode, thread_id); + if (m_pid_to_detach != 0 && m_active_exception->GetExceptionCode() == EXCEPTION_BREAKPOINT) { + WINLOG_IFANY(WINDOWS_LOG_EVENT | WINDOWS_LOG_EXCEPTION | WINDOWS_LOG_PROCESS, + "Breakpoint exception is cue to detach from process 0x%x", + m_pid_to_detach); + if (::DebugActiveProcessStop(m_pid_to_detach)) { + m_detached = true; + return ExceptionResult::MaskException; + } else { + WINLOG_IFANY(WINDOWS_LOG_PROCESS, "Failed to detach, treating as a regular breakpoint"); + } + } + ExceptionResult result = m_debug_delegate->OnDebugException(first_chance, *m_active_exception); m_exception_pred.SetValue(result, eBroadcastNever); Index: source/Plugins/Process/Windows/ProcessWindows.h =================================================================== --- source/Plugins/Process/Windows/ProcessWindows.h +++ source/Plugins/Process/Windows/ProcessWindows.h @@ -92,11 +92,6 @@ bool CanDebug(lldb_private::Target &target, bool plugin_specified_by_name) override; bool - DetachRequiresHalt() override - { - return true; - } - bool DestroyRequiresHalt() override { return false; Index: source/Plugins/Process/Windows/ProcessWindows.cpp =================================================================== --- source/Plugins/Process/Windows/ProcessWindows.cpp +++ source/Plugins/Process/Windows/ProcessWindows.cpp @@ -9,6 +9,7 @@ // Windows includes #include "lldb/Host/windows/windows.h" +#include // C++ Includes #include @@ -54,6 +55,40 @@ #define BOOL_STR(b) ((b) ? "true" : "false") +namespace +{ + +std::string +GetProcessExecutableName(HANDLE process_handle) +{ + std::vector file_name; + DWORD file_name_size = MAX_PATH; // first guess, not an absolute limit + DWORD copied = 0; + do + { + file_name_size *= 2; + file_name.resize(file_name_size); + copied = ::GetModuleFileNameEx(process_handle, NULL, file_name.data(), file_name_size); + } while (copied >= file_name_size); + file_name.resize(copied); + return std::string(file_name.begin(), file_name.end()); +} + +std::string +GetProcessExecutableName(DWORD pid) +{ + std::string file_name; + HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (process_handle != NULL) + { + file_name = GetProcessExecutableName(process_handle); + ::CloseHandle(process_handle); + } + return file_name; +} + +} // anonymous namespace + namespace lldb_private { @@ -417,7 +452,49 @@ Error ProcessWindows::DoDetach(bool keep_stopped) { + DebuggerThreadSP debugger_thread; + StateType private_state; + { + // Acquire the lock only long enough to get the DebuggerThread. + // StopDebugging() will trigger a call back into ProcessWindows which + // will also acquire the lock. Thus we have to release the lock before + // calling StopDebugging(). + llvm::sys::ScopedLock lock(m_mutex); + + private_state = GetPrivateState(); + + if (!m_session_data) + { + WINWARN_IFALL(WINDOWS_LOG_PROCESS, "DoDetach called while state = %u, but there is no active session.", + private_state); + return Error(); + } + + debugger_thread = m_session_data->m_debugger; + } + Error error; + if (private_state != eStateExited && private_state != eStateDetached) + { + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoDetach called for process %I64u while state = %u. Detaching...", + debugger_thread->GetProcess().GetNativeProcess().GetSystemHandle(), private_state); + error = debugger_thread->StopDebugging(false); + if (error.Success()) + { + SetPrivateState(eStateDetached); + } + + // By the time StopDebugging returns, there is no more debugger thread, so + // we can be assured that no other thread will race for the session data. + m_session_data.reset(); + } + else + { + WINERR_IFALL(WINDOWS_LOG_PROCESS, + "DoDetach called for process %I64u while state = %u, but cannot destroy in this state.", + debugger_thread->GetProcess().GetNativeProcess().GetSystemHandle(), private_state); + } + return error; } @@ -451,9 +528,8 @@ debugger_thread->GetProcess().GetNativeProcess().GetSystemHandle(), private_state); error = debugger_thread->StopDebugging(true); - // By the time StopDebugging returns, there is no more debugger thread, so we can be assured that no other - // thread - // will race for the session data. So it's safe to reset it without holding a lock. + // By the time StopDebugging returns, there is no more debugger thread, so + // we can be assured that no other thread will race for the session data. m_session_data.reset(); } else @@ -717,7 +793,8 @@ ModuleSP exe_module_sp(target.GetExecutableModule()); if (exe_module_sp.get()) return exe_module_sp->GetFileSpec().Exists(); - return false; + // However, if there is no executable module, we return true since we might be preparing to attach. + return true; } void @@ -740,10 +817,32 @@ { DebuggerThreadSP debugger = m_session_data->m_debugger; - WINLOG_IFALL(WINDOWS_LOG_PROCESS, "Debugger established connected to process %I64u. Image base = 0x%I64x", + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "Debugger connected to process %I64u. Image base = 0x%I64x", debugger->GetProcess().GetProcessId(), image_base); ModuleSP module = GetTarget().GetExecutableModule(); + if (!module) + { + // During attach, we won't have the executable module, so find it now. + const DWORD pid = debugger->GetProcess().GetProcessId(); + const std::string file_name = GetProcessExecutableName(pid); + if (file_name.empty()) + { + return; + } + + FileSpec executable_file(file_name, true); + ModuleSpec module_spec(executable_file); + Error error; + module = GetTarget().GetSharedModule(module_spec, &error); + if (!module) + { + return; + } + + GetTarget().SetExecutableModule(module, false); + } + bool load_addr_changed; module->SetLoadAddress(GetTarget(), image_base, false, load_addr_changed); @@ -911,3 +1010,4 @@ return; } } +