diff options
Diffstat (limited to 'lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp')
-rw-r--r-- | lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp new file mode 100644 index 00000000000..a79250b350e --- /dev/null +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp @@ -0,0 +1,572 @@ +//===-- ProcessDebugger.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ProcessDebugger.h" + +// Windows includes +#include "lldb/Host/windows/windows.h" +#include <psapi.h> + +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostNativeProcessBase.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/Process.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Error.h" + +#include "DebuggerThread.h" +#include "ExceptionRecord.h" +#include "ProcessWindowsLog.h" + +using namespace lldb; +using namespace lldb_private; + +static DWORD ConvertLldbToWinApiProtect(uint32_t protect) { + // We also can process a read / write permissions here, but if the debugger + // will make later a write into the allocated memory, it will fail. To get + // around it is possible inside DoWriteMemory to remember memory permissions, + // allow write, write and restore permissions, but for now we process only + // the executable permission. + // + // TODO: Process permissions other than executable + if (protect & ePermissionsExecutable) + return PAGE_EXECUTE_READWRITE; + + return PAGE_READWRITE; +} + +// The Windows page protection bits are NOT independent masks that can be +// bitwise-ORed together. For example, PAGE_EXECUTE_READ is not (PAGE_EXECUTE +// | PAGE_READ). To test for an access type, it's necessary to test for any of +// the bits that provide that access type. +static bool IsPageReadable(uint32_t protect) { + return (protect & PAGE_NOACCESS) == 0; +} + +static bool IsPageWritable(uint32_t protect) { + return (protect & (PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | + PAGE_READWRITE | PAGE_WRITECOPY)) != 0; +} + +static bool IsPageExecutable(uint32_t protect) { + return (protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | + PAGE_EXECUTE_WRITECOPY)) != 0; +} + +namespace lldb_private { + +lldb::pid_t ProcessDebugger::GetDebuggedProcessId() const { + if (m_session_data) + return m_session_data->m_debugger->GetProcess().GetProcessId(); + return LLDB_INVALID_PROCESS_ID; +} + +Status ProcessDebugger::DetachProcess() { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + DebuggerThreadSP debugger_thread; + { + // Acquire the lock only long enough to get the DebuggerThread. + // StopDebugging() will trigger a call back into ProcessDebugger which will + // also acquire the lock. Thus we have to release the lock before calling + // StopDebugging(). + llvm::sys::ScopedLock lock(m_mutex); + + if (!m_session_data) { + LLDB_LOG(log, "there is no active session."); + return Status(); + } + + debugger_thread = m_session_data->m_debugger; + } + + Status error; + + LLDB_LOG(log, "detaching from process {0}.", + debugger_thread->GetProcess().GetNativeProcess().GetSystemHandle()); + error = debugger_thread->StopDebugging(false); + + // 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(); + + return error; +} + +Status ProcessDebugger::LaunchProcess(ProcessLaunchInfo &launch_info, + DebugDelegateSP delegate) { + // Even though m_session_data is accessed here, it is before a debugger + // thread has been kicked off. So there's no race conditions, and it + // shouldn't be necessary to acquire the mutex. + + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + Status result; + + FileSpec working_dir = launch_info.GetWorkingDirectory(); + namespace fs = llvm::sys::fs; + if (working_dir) { + FileSystem::Instance().Resolve(working_dir); + if (!FileSystem::Instance().IsDirectory(working_dir)) { + result.SetErrorStringWithFormat("No such file or directory: %s", + working_dir.GetCString()); + return result; + } + } + + if (!launch_info.GetFlags().Test(eLaunchFlagDebug)) { + StreamString stream; + stream.Printf("ProcessDebugger unable to launch '%s'. ProcessDebugger can " + "only be used for debug launches.", + launch_info.GetExecutableFile().GetPath().c_str()); + std::string message = stream.GetString(); + result.SetErrorString(message.c_str()); + + LLDB_LOG(log, "error: {0}", message); + return result; + } + + bool stop_at_entry = launch_info.GetFlags().Test(eLaunchFlagStopAtEntry); + m_session_data.reset(new ProcessWindowsData(stop_at_entry)); + m_session_data->m_debugger.reset(new DebuggerThread(delegate)); + DebuggerThreadSP debugger = m_session_data->m_debugger; + + // Kick off the DebugLaunch asynchronously and wait for it to complete. + result = debugger->DebugLaunch(launch_info); + if (result.Fail()) { + LLDB_LOG(log, "failed launching '{0}'. {1}", + launch_info.GetExecutableFile().GetPath(), result); + return result; + } + + HostProcess process; + Status error = WaitForDebuggerConnection(debugger, process); + if (error.Fail()) { + LLDB_LOG(log, "failed launching '{0}'. {1}", + launch_info.GetExecutableFile().GetPath(), error); + return error; + } + + LLDB_LOG(log, "successfully launched '{0}'", + launch_info.GetExecutableFile().GetPath()); + + // 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()); + + return result; +} + +Status ProcessDebugger::AttachProcess(const ProcessAttachInfo &attach_info, + DebugDelegateSP delegate) { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + m_session_data.reset( + new ProcessWindowsData(!attach_info.GetContinueOnceAttached())); + DebuggerThreadSP debugger(new DebuggerThread(delegate)); + + m_session_data->m_debugger = debugger; + + DWORD process_id = static_cast<DWORD>(attach_info.GetProcessID()); + Status error = debugger->DebugAttach(process_id, attach_info); + if (error.Fail()) { + LLDB_LOG( + log, + "encountered an error occurred initiating the asynchronous attach. {0}", + error); + return error; + } + + HostProcess process; + error = WaitForDebuggerConnection(debugger, process); + if (error.Fail()) { + LLDB_LOG(log, + "encountered an error waiting for the debugger to connect. {0}", + error); + return error; + } + + LLDB_LOG(log, "successfully attached to process with pid={0}", 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. + + return error; +} + +Status ProcessDebugger::DestroyProcess() { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + DebuggerThreadSP debugger_thread; + { + // Acquire this lock inside an inner scope, only long enough to get the + // DebuggerThread. StopDebugging() will trigger a call back into + // ProcessDebugger which will acquire the lock again, so we need to not + // deadlock. + llvm::sys::ScopedLock lock(m_mutex); + + if (!m_session_data) { + LLDB_LOG(log, "warning: there is no active session."); + return Status(); + } + + debugger_thread = m_session_data->m_debugger; + } + + LLDB_LOG(log, "Shutting down process {0}.", + debugger_thread->GetProcess().GetNativeProcess().GetSystemHandle()); + Status 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. + m_session_data.reset(); + + return error; +} + +Status ProcessDebugger::HaltProcess(bool &caused_stop) { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + Status error; + llvm::sys::ScopedLock lock(m_mutex); + caused_stop = ::DebugBreakProcess(m_session_data->m_debugger->GetProcess() + .GetNativeProcess() + .GetSystemHandle()); + if (!caused_stop) { + error.SetError(::GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, "DebugBreakProcess failed with error {0}", error); + } + + return error; +} + +Status ProcessDebugger::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, + size_t &bytes_read) { + Status error; + bytes_read = 0; + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_MEMORY); + llvm::sys::ScopedLock lock(m_mutex); + + if (!m_session_data) { + error.SetErrorString( + "cannot read, there is no active debugger connection."); + LLDB_LOG(log, "error: {0}", error); + return error; + } + + LLDB_LOG(log, "attempting to read {0} bytes from address {1:x}", size, + vm_addr); + + HostProcess process = m_session_data->m_debugger->GetProcess(); + void *addr = reinterpret_cast<void *>(vm_addr); + SIZE_T num_of_bytes_read = 0; + if (!::ReadProcessMemory(process.GetNativeProcess().GetSystemHandle(), addr, + buf, size, &num_of_bytes_read)) { + // Reading from the process can fail for a number of reasons - set the + // error code and make sure that the number of bytes read is set back to 0 + // because in some scenarios the value of bytes_read returned from the API + // is garbage. + error.SetError(GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, "reading failed with error: {0}", error); + } else { + bytes_read = num_of_bytes_read; + } + return error; +} + +Status ProcessDebugger::WriteMemory(lldb::addr_t vm_addr, const void *buf, + size_t size, size_t &bytes_written) { + Status error; + bytes_written = 0; + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_MEMORY); + llvm::sys::ScopedLock lock(m_mutex); + LLDB_LOG(log, "attempting to write {0} bytes into address {1:x}", size, + vm_addr); + + if (!m_session_data) { + error.SetErrorString( + "cannot write, there is no active debugger connection."); + LLDB_LOG(log, "error: {0}", error); + return error; + } + + HostProcess process = m_session_data->m_debugger->GetProcess(); + void *addr = reinterpret_cast<void *>(vm_addr); + SIZE_T num_of_bytes_written = 0; + lldb::process_t handle = process.GetNativeProcess().GetSystemHandle(); + if (::WriteProcessMemory(handle, addr, buf, size, &num_of_bytes_written)) { + FlushInstructionCache(handle, addr, num_of_bytes_written); + bytes_written = num_of_bytes_written; + } else { + error.SetError(GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, "writing failed with error: {0}", error); + } + return error; +} + +Status ProcessDebugger::AllocateMemory(size_t size, uint32_t permissions, + lldb::addr_t &addr) { + Status error; + addr = LLDB_INVALID_ADDRESS; + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_MEMORY); + llvm::sys::ScopedLock lock(m_mutex); + LLDB_LOG(log, "attempting to allocate {0} bytes with permissions {1}", size, + permissions); + + if (!m_session_data) { + error.SetErrorString( + "cannot allocate, there is no active debugger connection"); + LLDB_LOG(log, "error: {0}", error); + return error; + } + + HostProcess process = m_session_data->m_debugger->GetProcess(); + lldb::process_t handle = process.GetNativeProcess().GetSystemHandle(); + auto protect = ConvertLldbToWinApiProtect(permissions); + auto result = ::VirtualAllocEx(handle, nullptr, size, MEM_COMMIT, protect); + if (!result) { + error.SetError(GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, "allocating failed with error: {0}", error); + } else { + addr = reinterpret_cast<addr_t>(result); + } + return error; +} + +Status ProcessDebugger::DeallocateMemory(lldb::addr_t vm_addr) { + Status result; + + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_MEMORY); + llvm::sys::ScopedLock lock(m_mutex); + LLDB_LOG(log, "attempting to deallocate bytes at address {0}", vm_addr); + + if (!m_session_data) { + result.SetErrorString( + "cannot deallocate, there is no active debugger connection"); + LLDB_LOG(log, "error: {0}", result); + return result; + } + + HostProcess process = m_session_data->m_debugger->GetProcess(); + lldb::process_t handle = process.GetNativeProcess().GetSystemHandle(); + if (!::VirtualFreeEx(handle, reinterpret_cast<LPVOID>(vm_addr), 0, + MEM_RELEASE)) { + result.SetError(GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, "deallocating failed with error: {0}", result); + } + + return result; +} + +Status ProcessDebugger::GetMemoryRegionInfo(lldb::addr_t vm_addr, + MemoryRegionInfo &info) { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_MEMORY); + Status error; + llvm::sys::ScopedLock lock(m_mutex); + info.Clear(); + + if (!m_session_data) { + error.SetErrorString( + "GetMemoryRegionInfo called with no debugging session."); + LLDB_LOG(log, "error: {0}", error); + return error; + } + HostProcess process = m_session_data->m_debugger->GetProcess(); + lldb::process_t handle = process.GetNativeProcess().GetSystemHandle(); + if (handle == nullptr || handle == LLDB_INVALID_PROCESS) { + error.SetErrorString( + "GetMemoryRegionInfo called with an invalid target process."); + LLDB_LOG(log, "error: {0}", error); + return error; + } + + LLDB_LOG(log, "getting info for address {0:x}", vm_addr); + + void *addr = reinterpret_cast<void *>(vm_addr); + MEMORY_BASIC_INFORMATION mem_info = {}; + SIZE_T result = ::VirtualQueryEx(handle, addr, &mem_info, sizeof(mem_info)); + if (result == 0) { + if (::GetLastError() == ERROR_INVALID_PARAMETER) { + // ERROR_INVALID_PARAMETER is returned if VirtualQueryEx is called with + // an address past the highest accessible address. We should return a + // range from the vm_addr to LLDB_INVALID_ADDRESS + info.GetRange().SetRangeBase(vm_addr); + info.GetRange().SetRangeEnd(LLDB_INVALID_ADDRESS); + info.SetReadable(MemoryRegionInfo::eNo); + info.SetExecutable(MemoryRegionInfo::eNo); + info.SetWritable(MemoryRegionInfo::eNo); + info.SetMapped(MemoryRegionInfo::eNo); + return error; + } else { + error.SetError(::GetLastError(), eErrorTypeWin32); + LLDB_LOG(log, + "VirtualQueryEx returned error {0} while getting memory " + "region info for address {1:x}", + error, vm_addr); + return error; + } + } + + // Protect bits are only valid for MEM_COMMIT regions. + if (mem_info.State == MEM_COMMIT) { + const bool readable = IsPageReadable(mem_info.Protect); + const bool executable = IsPageExecutable(mem_info.Protect); + const bool writable = IsPageWritable(mem_info.Protect); + info.SetReadable(readable ? MemoryRegionInfo::eYes : MemoryRegionInfo::eNo); + info.SetExecutable(executable ? MemoryRegionInfo::eYes + : MemoryRegionInfo::eNo); + info.SetWritable(writable ? MemoryRegionInfo::eYes : MemoryRegionInfo::eNo); + } else { + info.SetReadable(MemoryRegionInfo::eNo); + info.SetExecutable(MemoryRegionInfo::eNo); + info.SetWritable(MemoryRegionInfo::eNo); + } + + // AllocationBase is defined for MEM_COMMIT and MEM_RESERVE but not MEM_FREE. + if (mem_info.State != MEM_FREE) { + info.GetRange().SetRangeBase( + reinterpret_cast<addr_t>(mem_info.AllocationBase)); + info.GetRange().SetRangeEnd(reinterpret_cast<addr_t>(mem_info.BaseAddress) + + mem_info.RegionSize); + info.SetMapped(MemoryRegionInfo::eYes); + } else { + // In the unmapped case we need to return the distance to the next block of + // memory. VirtualQueryEx nearly does that except that it gives the + // distance from the start of the page containing vm_addr. + SYSTEM_INFO data; + ::GetSystemInfo(&data); + DWORD page_offset = vm_addr % data.dwPageSize; + info.GetRange().SetRangeBase(vm_addr); + info.GetRange().SetByteSize(mem_info.RegionSize - page_offset); + info.SetMapped(MemoryRegionInfo::eNo); + } + + error.SetError(::GetLastError(), eErrorTypeWin32); + LLDB_LOGV(log, + "Memory region info for address {0}: readable={1}, " + "executable={2}, writable={3}", + vm_addr, info.GetReadable(), info.GetExecutable(), + info.GetWritable()); + return error; +} + +void ProcessDebugger::OnExitProcess(uint32_t exit_code) { + // If the process exits before any initial stop then notify the debugger + // of the error otherwise WaitForDebuggerConnection() will be blocked. + // An example of this issue is when a process fails to load a dependent DLL. + if (m_session_data && !m_session_data->m_initial_stop_received) { + Status error(exit_code, eErrorTypeWin32); + OnDebuggerError(error, 0); + } +} + +void ProcessDebugger::OnDebuggerConnected(lldb::addr_t image_base) {} + +ExceptionResult +ProcessDebugger::OnDebugException(bool first_chance, + const ExceptionRecord &record) { + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_EXCEPTION); + llvm::sys::ScopedLock lock(m_mutex); + // FIXME: Without this check, occasionally when running the test suite + // there is an issue where m_session_data can be null. It's not clear how + // this could happen but it only surfaces while running the test suite. In + // order to properly diagnose this, we probably need to first figure allow the + // test suite to print out full lldb logs, and then add logging to the process + // plugin. + if (!m_session_data) { + LLDB_LOG(log, + "Debugger thread reported exception {0:x} at address {1:x}, but " + "there is no session.", + record.GetExceptionCode(), record.GetExceptionAddress()); + return ExceptionResult::SendToApplication; + } + + ExceptionResult result = ExceptionResult::SendToApplication; + if ((record.GetExceptionCode() == EXCEPTION_BREAKPOINT || + record.GetExceptionCode() == + 0x4000001FL /*WOW64 STATUS_WX86_BREAKPOINT*/) && + !m_session_data->m_initial_stop_received) { + // Handle breakpoints at the first chance. + result = ExceptionResult::BreakInDebugger; + LLDB_LOG( + log, + "Hit loader breakpoint at address {0:x}, setting initial stop event.", + record.GetExceptionAddress()); + m_session_data->m_initial_stop_received = true; + ::SetEvent(m_session_data->m_initial_stop_event); + } + return result; +} + +void ProcessDebugger::OnCreateThread(const HostThread &thread) { + // Do nothing by default +} + +void ProcessDebugger::OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) { + // Do nothing by default +} + +void ProcessDebugger::OnLoadDll(const ModuleSpec &module_spec, + lldb::addr_t module_addr) { + // Do nothing by default +} + +void ProcessDebugger::OnUnloadDll(lldb::addr_t module_addr) { + // Do nothing by default +} + +void ProcessDebugger::OnDebugString(const std::string &string) {} + +void ProcessDebugger::OnDebuggerError(const Status &error, uint32_t type) { + llvm::sys::ScopedLock lock(m_mutex); + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); + + if (m_session_data->m_initial_stop_received) { + // This happened while debugging. Do we shutdown the debugging session, + // try to continue, or do something else? + LLDB_LOG(log, + "Error {0} occurred during debugging. Unexpected behavior " + "may result. {1}", + error.GetError(), error); + } else { + // If we haven't actually launched the process yet, this was an error + // launching the process. Set the internal error and signal the initial + // stop event so that the DoLaunch method wakes up and returns a failure. + m_session_data->m_launch_error = error; + ::SetEvent(m_session_data->m_initial_stop_event); + LLDB_LOG(log, + "Error {0} occurred launching the process before the initial " + "stop. {1}", + error.GetError(), error); + return; + } +} + +Status ProcessDebugger::WaitForDebuggerConnection(DebuggerThreadSP debugger, + HostProcess &process) { + Status result; + Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS | + WINDOWS_LOG_BREAKPOINTS); + LLDB_LOG(log, "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) { + LLDB_LOG(log, "hit loader breakpoint, returning."); + + process = debugger->GetProcess(); + return m_session_data->m_launch_error; + } else + return Status(::GetLastError(), eErrorTypeWin32); +} + +} // namespace lldb_private |