Implement attach to process on Windows.

Differential Revision: http://reviews.llvm.org/D9801
Reviewed by: Adrian McCarthy

llvm-svn: 237817
This commit is contained in:
Zachary Turner 2015-05-20 18:31:17 +00:00
parent fd28abcf15
commit c62733b0de
8 changed files with 279 additions and 91 deletions

View File

@ -526,51 +526,88 @@ PlatformWindows::LaunchProcess (ProcessLaunchInfo &launch_info)
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 do it from the same the thread
// that will go into a permanent loop which will then receive debug events from the process. In particular,
// this means we can't use any of LLDB's generic mechanisms to do it for us, because it doesn't have the
// special knowledge required for setting up the background thread or passing the right flags.
//
// Another problem is that that LLDB's standard model for debugging a process is to first launch it, have
// it 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 in 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);
}
else
{
ProcessSP process_sp = target->CreateProcess(launch_info.GetListenerForProcess(debugger),
launch_info.GetProcessPluginName(),
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 @@ PlatformWindows::GetStatus (Stream &strm)
<< " Build: " << update << '\n';
#endif
}
bool
PlatformWindows::CanDebugProcess()
{
return true;
}

View File

@ -122,10 +122,12 @@ public:
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;
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,
@ -145,12 +147,7 @@ public:
void
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

View File

@ -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 @@ struct DebugLaunchContext
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 @@ DebuggerThread::DebugLaunch(const ProcessLaunchInfo &launch_info)
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 @@ DebuggerThread::DebugLaunch(const ProcessLaunchInfo &launch_info)
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<DebugLaunchContext *>(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::DebuggerThreadRoutine(const ProcessLaunchInfo &launch_info)
DebuggerThread::DebuggerThreadAttachRoutine(void *data)
{
DebugAttachContext *context = static_cast<DebugAttachContext *>(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::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<DebuggerThread> 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 @@ DebuggerThread::DebuggerThreadRoutine(const ProcessLaunchInfo &launch_info)
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<DebuggerThread> 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)
{

View File

@ -34,6 +34,7 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread>
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 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread>
// 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);
};
}

View File

@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "DynamicLoaderWindows.h"
#include "ProcessWindowsLog.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Target/Process.h"
@ -65,6 +66,7 @@ DynamicLoader *DynamicLoaderWindows::CreateInstance(Process *process, bool force
if (should_create)
return new DynamicLoaderWindows (process);
return nullptr;
}

View File

@ -62,8 +62,8 @@ namespace lldb_private
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 @@ class ProcessWindowsData
~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<lldb::tid_t, HostThread> m_new_threads;
std::set<lldb::tid_t> m_exited_threads;
@ -257,7 +257,8 @@ ProcessWindows::DoLaunch(Module *exe_module,
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,44 +267,94 @@ ProcessWindows::DoLaunch(Module *exe_module,
// Kick off the DebugLaunch asynchronously and wait for it to complete.
result = debugger->DebugLaunch(launch_info);
HostProcess process;
if (result.Success())
{
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);
}
if (result.Success())
{
WINLOG_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch successfully launched '%s'",
launch_info.GetExecutableFile().GetPath().c_str());
}
else
if (result.Fail())
{
WINERR_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch failed launching '%s'. %s",
launch_info.GetExecutableFile().GetPath().c_str(), result.AsCString());
return result;
}
// 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.
HostProcess process;
Error error = WaitForDebuggerConnection(debugger, process);
if (error.Fail())
{
WINERR_IFALL(WINDOWS_LOG_PROCESS, "DoLaunch failed launching '%s'. %s",
launch_info.GetExecutableFile().GetPath().c_str(), error.AsCString());
return error;
}
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<DWORD>(pid);
Error error = debugger->DebugAttach(process_id, attach_info);
if (error.Fail())
{
WINLOG_IFALL(WINDOWS_LOG_PROCESS,
"DoAttachToProcessWithID encountered an error occurred initiating the asynchronous attach. %s",
error.AsCString());
return error;
}
HostProcess process;
error = WaitForDebuggerConnection(debugger, process);
if (error.Fail())
{
WINLOG_IFALL(WINDOWS_LOG_PROCESS,
"DoAttachToProcessWithID encountered an error waiting for the debugger to connect. %s",
error.AsCString());
return error;
}
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;
}
Error
ProcessWindows::WaitForDebuggerConnection(DebuggerThreadSP debugger, HostProcess &process)
{
Error result;
WINLOG_IFANY(WINDOWS_LOG_PROCESS|WINDOWS_LOG_BREAKPOINTS, "WaitForDebuggerConnection Waiting for loader breakpoint.");
// 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)
{
WINLOG_IFANY(WINDOWS_LOG_PROCESS|WINDOWS_LOG_BREAKPOINTS, "WaitForDebuggerConnection hit loader breakpoint, returning.");
process = debugger->GetProcess();
return m_session_data->m_launch_error;
}
else
return Error(::GetLastError(), eErrorTypeWin32);
}
Error
ProcessWindows::DoResume()
{
@ -520,12 +571,17 @@ ProcessWindows::DoHalt(bool &caused_stop)
}
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();
}
@ -557,11 +613,13 @@ size_t
ProcessWindows::DoWriteMemory(lldb::addr_t vm_addr, const void *buf, size_t size, Error &error)
{
llvm::sys::ScopedLock lock(m_mutex);
WINLOG_IFALL(WINDOWS_LOG_MEMORY, "DoWriteMemory attempting to write %u bytes into address 0x%I64x", size, vm_addr);
if (!m_session_data)
{
WINERR_IFANY(WINDOWS_LOG_MEMORY, "DoWriteMemory cannot write, there is no active debugger connection.");
return 0;
WINLOG_IFALL(WINDOWS_LOG_MEMORY, "DoWriteMemory attempting to write %u bytes into address 0x%I64x", size, vm_addr);
}
HostProcess process = m_session_data->m_debugger->GetProcess();
void *addr = reinterpret_cast<void *>(vm_addr);
@ -672,20 +730,18 @@ ProcessWindows::OnDebuggerConnected(lldb::addr_t image_base)
WINLOG_IFALL(WINDOWS_LOG_PROCESS, "Debugger established connected to process %I64u. Image base = 0x%I64x",
debugger->GetProcess().GetProcessId(), image_base);
// Either we successfully attached to an existing process, or we successfully launched a new
// process under the debugger.
ModuleSP module = GetTarget().GetExecutableModule();
bool load_addr_changed;
module->SetLoadAddress(GetTarget(), image_base, false, load_addr_changed);
// Notify the target that the executable module has loaded. This will cause any pending
// breakpoints to be resolved to explicit brekapoint sites.
ModuleList loaded_modules;
loaded_modules.Append(module);
GetTarget().ModulesDidLoad(loaded_modules);
// Add the main executable module to the list of pending module loads. We can't call
// GetTarget().ModulesDidLoad() here because we still haven't returned from DoLaunch() / DoAttach() yet
// so the target may not have set the process instance to `this` yet.
llvm::sys::ScopedLock lock(m_mutex);
const HostThreadWindows &wmain_thread = debugger->GetMainThread().GetNativeThread();
m_session_data->m_new_threads[wmain_thread.GetThreadId()] = debugger->GetMainThread();
}
@ -723,9 +779,18 @@ ProcessWindows::OnDebugException(bool first_chance, const ExceptionRecord &recor
if (!m_session_data->m_initial_stop_received)
{
WINLOG_IFANY(WINDOWS_LOG_BREAKPOINTS,
"Hit loader breakpoint at address 0x%I64x, setting initial stop event.",
record.GetExceptionAddress());
m_session_data->m_initial_stop_received = true;
::SetEvent(m_session_data->m_initial_stop_event);
}
else
{
WINLOG_IFANY(WINDOWS_LOG_BREAKPOINTS,
"Hit non-loader breakpoint at address 0x%I64x.",
record.GetExceptionAddress());
}
SetPrivateState(eStateStopped);
break;
case EXCEPTION_SINGLE_STEP:

View File

@ -30,6 +30,7 @@ class ProcessMonitor;
namespace lldb_private
{
class HostProcess;
class ProcessWindowsData;
}
@ -77,11 +78,14 @@ public:
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 @@ public:
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.

View File

@ -3336,6 +3336,9 @@ Process::AttachCompletionHandler::PerformAction (lldb::EventSP &event_sp)
switch (state)
{
case eStateAttaching:
return eEventActionSuccess;
case eStateRunning:
case eStateConnected:
return eEventActionRetry;
@ -4457,7 +4460,8 @@ Process::HandlePrivateEvent (EventSP &event_sp)
// 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);
}