Index: source/Plugins/Platform/Windows/PlatformWindows.h =================================================================== --- source/Plugins/Platform/Windows/PlatformWindows.h +++ source/Plugins/Platform/Windows/PlatformWindows.h @@ -121,11 +121,11 @@ lldb_private::Error LaunchProcess(lldb_private::ProcessLaunchInfo &launch_info) override; - lldb::ProcessSP - Attach(lldb_private::ProcessAttachInfo &attach_info, - lldb_private::Debugger &debugger, - lldb_private::Target *target, - lldb_private::Error &error) override; + lldb::ProcessSP DebugProcess(lldb_private::ProcessLaunchInfo &launch_info, lldb_private::Debugger &debugger, + lldb_private::Target *target, lldb_private::Error &error) override; + + lldb::ProcessSP Attach(lldb_private::ProcessAttachInfo &attach_info, lldb_private::Debugger &debugger, + lldb_private::Target *target, lldb_private::Error &error) override; lldb_private::Error GetFileWithUUID(const lldb_private::FileSpec &platform_file, @@ -146,11 +146,7 @@ GetStatus(lldb_private::Stream &strm) override; // Local debugging not yet supported - bool - CanDebugProcess(void) override - { - return false; - } + bool CanDebugProcess() override; // FIXME not sure what the _sigtramp equivalent would be on this platform void Index: source/Plugins/Platform/Windows/PlatformWindows.cpp =================================================================== --- source/Plugins/Platform/Windows/PlatformWindows.cpp +++ source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -29,6 +29,8 @@ #include "lldb/Breakpoint/BreakpointSite.h" #include "lldb/Target/Process.h" +#include "Plugins/Process/Windows/ProcessWindows.h" + using namespace lldb; using namespace lldb_private; @@ -526,51 +528,86 @@ return error; } +ProcessSP +PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, Debugger &debugger, Target *target, Error &error) +{ + // Windows has special considerations that must be followed when launching or attaching to a process. The + // key requirement is that when launching or attaching to a process, you must physically spawn the process + // from the same thread that goes into a permanent loop receiving debug events from the process. In particular, + // this means we can't do it from one of LLDB's threads, we have to spawn a background thread to do it, and + // have this thread coordinate with LLDB's threads. + // + // One problem this creates is that LLDB's standard model for debugging a process is to first launch it, have + // it to stop at the entry point, and then attach to it. In Windows this doesn't quite work, you have to + // specify as an argument to CreateProcess() that you're going to debug the process. So we override DebugProcess + // here to handle this. Launch operations go directly to the process plugin, and attach operations almost go + // directly to the process plugin (but we hijack the events first). In essence, we encapsulate all the logic + // of Launching and Attaching into the process plugin, and PlatformWindows::DebugProcess is just a pass-through + // to get to the process plugin. + + if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) + { + // This is a process attach. Don't need to launch anything. + ProcessAttachInfo attach_info(launch_info); + return Attach(attach_info, debugger, target, error); + } + + const char *plugin_name = launch_info.GetProcessPluginName(); + ProcessSP process_sp = Process::FindPlugin(*target, plugin_name, + launch_info.GetListenerForProcess(debugger), nullptr); + + // 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, Debugger &debugger, Target *target, Error &error) { + error.Clear(); lldb::ProcessSP process_sp; - if (IsHost()) - { - if (target == NULL) - { - TargetSP new_target_sp; - FileSpec emptyFileSpec; - ArchSpec emptyArchSpec; - - error = debugger.GetTargetList().CreateTarget (debugger, - NULL, - NULL, - false, - NULL, - new_target_sp); - target = new_target_sp.get(); - } - else - error.Clear(); - - if (target && error.Success()) - { - debugger.GetTargetList().SetSelectedTarget(target); - // The Windows platform always currently uses the GDB remote debugger plug-in - // so even when debugging locally we are debugging remotely! - // Just like the darwin plugin. - process_sp = target->CreateProcess (attach_info.GetListenerForProcess(debugger), "gdb-remote", NULL); - - if (process_sp) - error = process_sp->Attach (attach_info); - } - } - else + if (!IsHost()) { if (m_remote_platform_sp) process_sp = m_remote_platform_sp->Attach (attach_info, debugger, target, error); else error.SetErrorString ("the platform is not currently connected"); + return process_sp; } + + if (target == NULL) + { + TargetSP new_target_sp; + FileSpec emptyFileSpec; + ArchSpec emptyArchSpec; + + error = debugger.GetTargetList().CreateTarget (debugger, + NULL, + NULL, + false, + NULL, + new_target_sp); + target = new_target_sp.get(); + } + + if (!target || error.Fail()) + return process_sp; + + debugger.GetTargetList().SetSelectedTarget(target); + + const char *plugin_name = attach_info.GetProcessPluginName(); + process_sp = target->CreateProcess(attach_info.GetListenerForProcess(debugger), plugin_name, NULL); + + process_sp->HijackProcessEvents(attach_info.GetHijackListener().get()); + if (process_sp) + error = process_sp->Attach (attach_info); + return process_sp; } @@ -687,3 +724,9 @@ << " Build: " << update << '\n'; #endif } + +bool +PlatformWindows::CanDebugProcess() +{ + return true; +} Index: source/Plugins/Process/Windows/DebuggerThread.h =================================================================== --- source/Plugins/Process/Windows/DebuggerThread.h +++ source/Plugins/Process/Windows/DebuggerThread.h @@ -34,6 +34,7 @@ virtual ~DebuggerThread(); Error DebugLaunch(const ProcessLaunchInfo &launch_info); + Error DebugAttach(lldb::pid_t pid, const ProcessAttachInfo &attach_info); HostProcess GetProcess() const @@ -80,8 +81,10 @@ // is finished processing and the debug loop can be // continued. - static lldb::thread_result_t DebuggerThreadRoutine(void *data); - lldb::thread_result_t DebuggerThreadRoutine(const ProcessLaunchInfo &launch_info); + 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); + lldb::thread_result_t DebuggerThreadAttachRoutine(lldb::pid_t pid, const ProcessAttachInfo &launch_info); }; } Index: source/Plugins/Process/Windows/DebuggerThread.cpp =================================================================== --- source/Plugins/Process/Windows/DebuggerThread.cpp +++ source/Plugins/Process/Windows/DebuggerThread.cpp @@ -22,6 +22,7 @@ #include "lldb/Host/windows/HostThreadWindows.h" #include "lldb/Host/windows/ProcessLauncherWindows.h" #include "lldb/Target/ProcessLaunchInfo.h" +#include "lldb/Target/Process.h" #include "Plugins/Process/Windows/ProcessWindowsLog.h" @@ -43,6 +44,19 @@ DebuggerThread *m_thread; ProcessLaunchInfo m_launch_info; }; + +struct DebugAttachContext +{ + DebugAttachContext(DebuggerThread *thread, lldb::pid_t pid, const ProcessAttachInfo &attach_info) + : m_thread(thread) + , m_pid(pid) + , m_attach_info(attach_info) + { + } + DebuggerThread *m_thread; + lldb::pid_t m_pid; + ProcessAttachInfo m_attach_info; +}; } DebuggerThread::DebuggerThread(DebugDelegateSP debug_delegate) @@ -64,7 +78,7 @@ Error error; DebugLaunchContext *context = new DebugLaunchContext(this, launch_info); HostThread slave_thread(ThreadLauncher::LaunchThread("lldb.plugin.process-windows.slave[?]", - DebuggerThreadRoutine, context, &error)); + DebuggerThreadLaunchRoutine, context, &error)); if (!error.Success()) { @@ -75,25 +89,53 @@ return error; } +Error +DebuggerThread::DebugAttach(lldb::pid_t pid, const ProcessAttachInfo &attach_info) +{ + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DebuggerThread::DebugAttach attaching to '%u'", (DWORD)pid); + + Error error; + DebugAttachContext *context = new DebugAttachContext(this, pid, attach_info); + HostThread slave_thread(ThreadLauncher::LaunchThread("lldb.plugin.process-windows.slave[?]", + DebuggerThreadAttachRoutine, context, &error)); + + if (!error.Success()) + { + WINERR_IFALL(WINDOWS_LOG_PROCESS, "DebugAttach couldn't attach to process '%u'. %s", (DWORD)pid, + error.AsCString()); + } + + return error; +} + lldb::thread_result_t -DebuggerThread::DebuggerThreadRoutine(void *data) +DebuggerThread::DebuggerThreadLaunchRoutine(void *data) { DebugLaunchContext *context = static_cast(data); - lldb::thread_result_t result = context->m_thread->DebuggerThreadRoutine(context->m_launch_info); + lldb::thread_result_t result = context->m_thread->DebuggerThreadLaunchRoutine(context->m_launch_info); + delete context; + return result; +} + +lldb::thread_result_t +DebuggerThread::DebuggerThreadAttachRoutine(void *data) +{ + DebugAttachContext *context = static_cast(data); + lldb::thread_result_t result = + context->m_thread->DebuggerThreadAttachRoutine(context->m_pid, context->m_attach_info); delete context; return result; } lldb::thread_result_t -DebuggerThread::DebuggerThreadRoutine(const ProcessLaunchInfo &launch_info) +DebuggerThread::DebuggerThreadLaunchRoutine(const ProcessLaunchInfo &launch_info) { // Grab a shared_ptr reference to this so that we know it won't get deleted until after the // thread routine has exited. std::shared_ptr this_ref(shared_from_this()); - WINLOG_IFALL(WINDOWS_LOG_PROCESS, - "DebuggerThread preparing to launch '%s'.", - launch_info.GetExecutableFile().GetPath().c_str()); + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DebuggerThread preparing to launch '%s' on background thread.", + launch_info.GetExecutableFile().GetPath().c_str()); Error error; ProcessLauncherWindows launcher; @@ -111,6 +153,31 @@ return 0; } +lldb::thread_result_t +DebuggerThread::DebuggerThreadAttachRoutine(lldb::pid_t pid, const ProcessAttachInfo &attach_info) +{ + // Grab a shared_ptr reference to this so that we know it won't get deleted until after the + // thread routine has exited. + std::shared_ptr this_ref(shared_from_this()); + + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DebuggerThread preparing to attach to process '%u' on background thread.", + (DWORD)pid); + + if (!DebugActiveProcess((DWORD)pid)) + { + Error error(::GetLastError(), eErrorTypeWin32); + m_debug_delegate->OnDebuggerError(error, 0); + return 0; + } + + // The attach was successful, enter the debug loop. From here on out, this is no different than + // a create process operation, so all the same comments in DebugLaunch should apply from this + // point out. + DebugLoop(); + + return 0; +} + Error DebuggerThread::StopDebugging(bool terminate) { Index: source/Plugins/Process/Windows/ProcessWindows.h =================================================================== --- source/Plugins/Process/Windows/ProcessWindows.h +++ source/Plugins/Process/Windows/ProcessWindows.h @@ -30,6 +30,7 @@ namespace lldb_private { +class HostProcess; class ProcessWindowsData; } @@ -77,11 +78,14 @@ lldb_private::Error DoDetach(bool keep_stopped) override; lldb_private::Error DoLaunch(lldb_private::Module *exe_module, lldb_private::ProcessLaunchInfo &launch_info) override; + lldb_private::Error DoAttachToProcessWithID(lldb::pid_t pid, + const lldb_private::ProcessAttachInfo &attach_info) override; lldb_private::Error DoResume() override; lldb_private::Error DoDestroy() override; lldb_private::Error DoHalt(bool &caused_stop) override; void DidLaunch() override; + void DidAttach(lldb_private::ArchSpec &arch_spec) override; void RefreshStateAfterStop() override; lldb::addr_t GetImageInfoAddress() override; @@ -116,6 +120,9 @@ void OnDebuggerError(const lldb_private::Error &error, uint32_t type) override; private: + lldb_private::Error WaitForDebuggerConnection(lldb_private::DebuggerThreadSP debugger, + lldb_private::HostProcess &process); + llvm::sys::Mutex m_mutex; // Data for the active debugging session. Index: source/Plugins/Process/Windows/ProcessWindows.cpp =================================================================== --- source/Plugins/Process/Windows/ProcessWindows.cpp +++ source/Plugins/Process/Windows/ProcessWindows.cpp @@ -62,8 +62,8 @@ class ProcessWindowsData { public: - ProcessWindowsData(const ProcessLaunchInfo &launch_info) - : m_launch_info(launch_info) + ProcessWindowsData(bool stop_at_entry) + : m_stop_at_entry(stop_at_entry) , m_initial_stop_event(nullptr) , m_initial_stop_received(false) { @@ -72,11 +72,11 @@ ~ProcessWindowsData() { ::CloseHandle(m_initial_stop_event); } - ProcessLaunchInfo m_launch_info; lldb_private::Error m_launch_error; lldb_private::DebuggerThreadSP m_debugger; StopInfoSP m_pending_stop_info; HANDLE m_initial_stop_event; + bool m_stop_at_entry; bool m_initial_stop_received; std::map m_new_threads; std::set m_exited_threads; @@ -257,7 +257,8 @@ return result; } - m_session_data.reset(new ProcessWindowsData(launch_info)); + bool stop_at_entry = launch_info.GetFlags().Test(eLaunchFlagStopAtEntry); + m_session_data.reset(new ProcessWindowsData(stop_at_entry)); SetPrivateState(eStateLaunching); DebugDelegateSP delegate(new LocalDebugDelegate(shared_from_this())); @@ -266,42 +267,90 @@ // Kick off the DebugLaunch asynchronously and wait for it to complete. result = debugger->DebugLaunch(launch_info); + if (result.Fail()) + { + WINERR_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch failed launching '%s'. %s", + launch_info.GetExecutableFile().GetPath().c_str(), result.AsCString()); + return result; + } HostProcess process; - if (result.Success()) + Error error = WaitForDebuggerConnection(debugger, process); + if (error.Fail()) { - WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch started asynchronous launch of '%s'. Waiting for initial stop.", - launch_info.GetExecutableFile().GetPath().c_str()); - - // Block this function until we receive the initial stop from the process. - if (::WaitForSingleObject(m_session_data->m_initial_stop_event, INFINITE) == WAIT_OBJECT_0) - { - process = debugger->GetProcess(); - if (m_session_data->m_launch_error.Fail()) - result = m_session_data->m_launch_error; - } - else - result.SetError(::GetLastError(), eErrorTypeWin32); + WINERR_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch failed launching '%s'. %s", + launch_info.GetExecutableFile().GetPath().c_str(), error.AsCString()); + return error; } - if (result.Success()) + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch successfully launched '%s'", + launch_info.GetExecutableFile().GetPath().c_str()); + + // We've hit the initial stop. If eLaunchFlagsStopAtEntry was specified, the private state + // should already be set to eStateStopped as a result of hitting the initial breakpoint. If + // it was not set, the breakpoint should have already been resumed from and the private state + // should already be eStateRunning. + launch_info.SetProcessID(process.GetProcessId()); + SetID(process.GetProcessId()); + + return result; +} + +Error +ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid, const ProcessAttachInfo &attach_info) +{ + m_session_data.reset(new ProcessWindowsData(!attach_info.GetContinueOnceAttached())); + + DebugDelegateSP delegate(new LocalDebugDelegate(shared_from_this())); + DebuggerThreadSP debugger(new DebuggerThread(delegate)); + + m_session_data->m_debugger = debugger; + + DWORD process_id = static_cast(pid); + Error error = debugger->DebugAttach(process_id, attach_info); + if (error.Fail()) { - WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch successfully launched '%s'", - launch_info.GetExecutableFile().GetPath().c_str()); + WINLOG_IFALL(WINDOWS_LOG_PROCESS, + "DoAttachToProcessWithID encountered an error occurred initiating the asynchronous attach. %s", + error.AsCString()); + return error; } - else + + HostProcess process; + error = WaitForDebuggerConnection(debugger, process); + if (error.Fail()) { - WINERR_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch failed launching '%s'. %s", - launch_info.GetExecutableFile().GetPath().c_str(), result.AsCString()); - return result; + WINLOG_IFALL(WINDOWS_LOG_PROCESS, + "DoAttachToProcessWithID encountered an error waiting for the debugger to connect. %s", + error.AsCString()); + return error; } - // We've hit the initial stop. The private state should already be set to stopped as a result - // of encountering the breakpoint exception in ProcessWindows::OnDebugException. - launch_info.SetProcessID(process.GetProcessId()); + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoAttachToProcessWithID successfully attached to process with pid=%u", + process_id); + + // We've hit the initial stop. If eLaunchFlagsStopAtEntry was specified, the private state + // should already be set to eStateStopped as a result of hitting the initial breakpoint. If + // it was not set, the breakpoint should have already been resumed from and the private state + // should already be eStateRunning. SetID(process.GetProcessId()); + return error; +} - return result; +Error +ProcessWindows::WaitForDebuggerConnection(DebuggerThreadSP debugger, HostProcess &process) +{ + Error result; + WINLOG_IFALL(WINDOWS_LOG_PROCESS, "WaitForDebuggerConnection Waiting for initial stop."); + + // Block this function until we receive the initial stop from the process. + if (::WaitForSingleObject(m_session_data->m_initial_stop_event, INFINITE) == WAIT_OBJECT_0) + { + process = debugger->GetProcess(); + return m_session_data->m_launch_error; + } + else + return Error(::GetLastError(), eErrorTypeWin32); } Error @@ -521,11 +570,16 @@ void ProcessWindows::DidLaunch() { + DidAttach(ArchSpec()); +} + +void +ProcessWindows::DidAttach(ArchSpec &arch_spec) +{ llvm::sys::ScopedLock lock(m_mutex); // The initial stop won't broadcast the state change event, so account for that here. - if (m_session_data && GetPrivateState() == eStateStopped && - m_session_data->m_launch_info.GetFlags().Test(eLaunchFlagStopAtEntry)) + if (m_session_data && GetPrivateState() == eStateStopped && m_session_data->m_stop_at_entry) RefreshStateAfterStop(); } Index: source/Target/Process.cpp =================================================================== --- source/Target/Process.cpp +++ source/Target/Process.cpp @@ -3318,6 +3318,9 @@ switch (state) { + case eStateAttaching: + return eEventActionSuccess; + case eStateRunning: case eStateConnected: return eEventActionRetry; @@ -4439,7 +4442,8 @@ // Only push the input handler if we aren't fowarding events, // as this means the curses GUI is in use... // Or don't push it if we are launching since it will come up stopped. - if (!GetTarget().GetDebugger().IsForwardingEvents() && new_state != eStateLaunching) + if (!GetTarget().GetDebugger().IsForwardingEvents() && new_state != eStateLaunching && + new_state != eStateAttaching) PushProcessIOHandler (); m_iohandler_sync.SetValue(true, eBroadcastAlways); }