forked from OSchip/llvm-project
Load executable module when attaching to process; implement detach from process.
llvm-svn: 240157
This commit is contained in:
parent
2a20bd1a94
commit
a59a7211f0
|
@ -63,6 +63,8 @@ DebuggerThread::DebuggerThread(DebugDelegateSP debug_delegate)
|
|||
: 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 @@ DebuggerThread::DebuggerThreadLaunchRoutine(const ProcessLaunchInfo &launch_info
|
|||
else
|
||||
m_debug_delegate->OnDebuggerError(error, 0);
|
||||
|
||||
SetEvent(m_debugging_ended_event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -193,55 +194,62 @@ DebuggerThread::StopDebugging(bool terminate)
|
|||
"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())
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (!terminate)
|
||||
{
|
||||
// Indicate that we want to detach.
|
||||
m_pid_to_detach = GetProcess().GetProcessId();
|
||||
|
||||
// Force a fresh break so that the detach can happen from the debugger thread.
|
||||
if (!::DebugBreakProcess(GetProcess().GetNativeProcess().GetSystemHandle()))
|
||||
{
|
||||
WINLOG_IFANY(WINDOWS_LOG_PROCESS|WINDOWS_LOG_EXCEPTION,
|
||||
"StopDebugging masking active exception");
|
||||
|
||||
ContinueAsyncException(ExceptionResult::MaskException);
|
||||
error.SetError(::GetLastError(), eErrorTypeWin32);
|
||||
}
|
||||
}
|
||||
|
||||
WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging waiting for detach from process %u to complete.", pid);
|
||||
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
|
||||
{
|
||||
WINLOG_IFALL(WINDOWS_LOG_PROCESS, "StopDebugging detach from process %u completed successfully.", 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 @@ DebuggerThread::DebugLoop()
|
|||
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 @@ DebuggerThread::DebugLoop()
|
|||
FreeProcessHandles();
|
||||
|
||||
WINLOG_IFALL(WINDOWS_LOG_EVENT, "WaitForDebugEvent loop completed, exiting.");
|
||||
SetEvent(m_debugging_ended_event);
|
||||
}
|
||||
|
||||
ExceptionResult
|
||||
|
@ -356,6 +370,18 @@ DebuggerThread::HandleExceptionEvent(const EXCEPTION_DEBUG_INFO &info, DWORD thr
|
|||
"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);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#ifndef liblldb_Plugins_Process_Windows_DebuggerThread_H_
|
||||
#define liblldb_Plugins_Process_Windows_DebuggerThread_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "ForwardDecl.h"
|
||||
|
@ -84,6 +85,9 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread>
|
|||
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<DWORD> 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);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
// Windows includes
|
||||
#include "lldb/Host/windows/windows.h"
|
||||
#include <psapi.h>
|
||||
|
||||
// C++ Includes
|
||||
#include <list>
|
||||
|
@ -54,6 +55,40 @@ using namespace lldb_private;
|
|||
|
||||
#define BOOL_STR(b) ((b) ? "true" : "false")
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string
|
||||
GetProcessExecutableName(HANDLE process_handle)
|
||||
{
|
||||
std::vector<char> 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 @@ ProcessWindows::GetPluginVersion()
|
|||
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 @@ ProcessWindows::DoDestroy()
|
|||
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
|
||||
|
@ -718,7 +794,8 @@ ProcessWindows::CanDebug(Target &target, bool plugin_specified_by_name)
|
|||
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
|
||||
|
@ -741,10 +818,32 @@ ProcessWindows::OnDebuggerConnected(lldb::addr_t image_base)
|
|||
{
|
||||
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,4 +1010,4 @@ ProcessWindows::OnDebuggerError(const Error &error, uint32_t type)
|
|||
error.GetError(), error.AsCString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,11 +92,6 @@ public:
|
|||
|
||||
bool CanDebug(lldb_private::Target &target, bool plugin_specified_by_name) override;
|
||||
bool
|
||||
DetachRequiresHalt() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool
|
||||
DestroyRequiresHalt() override
|
||||
{
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue