| //===-- NativeProcessWindows.cpp ------------------------------------------===// |
| // |
| // 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 "lldb/Host/windows/windows.h" |
| #include <psapi.h> |
| |
| #include "NativeProcessWindows.h" |
| #include "NativeThreadWindows.h" |
| #include "lldb/Host/FileSystem.h" |
| #include "lldb/Host/HostNativeProcessBase.h" |
| #include "lldb/Host/HostProcess.h" |
| #include "lldb/Host/ProcessLaunchInfo.h" |
| #include "lldb/Host/windows/AutoHandle.h" |
| #include "lldb/Host/windows/HostThreadWindows.h" |
| #include "lldb/Host/windows/ProcessLauncherWindows.h" |
| #include "lldb/Target/MemoryRegionInfo.h" |
| #include "lldb/Target/Process.h" |
| #include "lldb/Utility/State.h" |
| #include "llvm/Support/ConvertUTF.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/Threading.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include "DebuggerThread.h" |
| #include "ExceptionRecord.h" |
| #include "ProcessWindowsLog.h" |
| |
| #include <tlhelp32.h> |
| |
| #pragma warning(disable : 4005) |
| #include "winternl.h" |
| #include <ntstatus.h> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace llvm; |
| |
| namespace lldb_private { |
| |
| NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info, |
| NativeDelegate &delegate, |
| llvm::Error &E) |
| : NativeProcessProtocol(LLDB_INVALID_PROCESS_ID, |
| launch_info.GetPTY().ReleasePrimaryFileDescriptor(), |
| delegate), |
| ProcessDebugger(), m_arch(launch_info.GetArchitecture()) { |
| ErrorAsOutParameter EOut(&E); |
| DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); |
| E = LaunchProcess(launch_info, delegate_sp).ToError(); |
| if (E) |
| return; |
| |
| SetID(GetDebuggedProcessId()); |
| } |
| |
| NativeProcessWindows::NativeProcessWindows(lldb::pid_t pid, int terminal_fd, |
| NativeDelegate &delegate, |
| llvm::Error &E) |
| : NativeProcessProtocol(pid, terminal_fd, delegate), ProcessDebugger() { |
| ErrorAsOutParameter EOut(&E); |
| DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); |
| ProcessAttachInfo attach_info; |
| attach_info.SetProcessID(pid); |
| E = AttachProcess(pid, attach_info, delegate_sp).ToError(); |
| if (E) |
| return; |
| |
| SetID(GetDebuggedProcessId()); |
| |
| ProcessInstanceInfo info; |
| if (!Host::GetProcessInfo(pid, info)) { |
| E = createStringError(inconvertibleErrorCode(), |
| "Cannot get process information"); |
| return; |
| } |
| m_arch = info.GetArchitecture(); |
| } |
| |
| Status NativeProcessWindows::Resume(const ResumeActionList &resume_actions) { |
| Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); |
| Status error; |
| llvm::sys::ScopedLock lock(m_mutex); |
| |
| StateType state = GetState(); |
| if (state == eStateStopped || state == eStateCrashed) { |
| LLDB_LOG(log, "process {0} is in state {1}. Resuming...", |
| GetDebuggedProcessId(), state); |
| LLDB_LOG(log, "resuming {0} threads.", m_threads.size()); |
| |
| bool failed = false; |
| for (uint32_t i = 0; i < m_threads.size(); ++i) { |
| auto thread = static_cast<NativeThreadWindows *>(m_threads[i].get()); |
| const ResumeAction *const action = |
| resume_actions.GetActionForThread(thread->GetID(), true); |
| if (action == nullptr) |
| continue; |
| |
| switch (action->state) { |
| case eStateRunning: |
| case eStateStepping: { |
| Status result = thread->DoResume(action->state); |
| if (result.Fail()) { |
| failed = true; |
| LLDB_LOG(log, |
| "Trying to resume thread at index {0}, but failed with " |
| "error {1}.", |
| i, result); |
| } |
| break; |
| } |
| case eStateSuspended: |
| case eStateStopped: |
| llvm_unreachable("Unexpected state"); |
| |
| default: |
| return Status( |
| "NativeProcessWindows::%s (): unexpected state %s specified " |
| "for pid %" PRIu64 ", tid %" PRIu64, |
| __FUNCTION__, StateAsCString(action->state), GetID(), |
| thread->GetID()); |
| } |
| } |
| |
| if (failed) { |
| error.SetErrorString("NativeProcessWindows::DoResume failed"); |
| } else { |
| SetState(eStateRunning); |
| } |
| |
| // Resume the debug loop. |
| ExceptionRecordSP active_exception = |
| m_session_data->m_debugger->GetActiveException().lock(); |
| if (active_exception) { |
| // Resume the process and continue processing debug events. Mask the |
| // exception so that from the process's view, there is no indication that |
| // anything happened. |
| m_session_data->m_debugger->ContinueAsyncException( |
| ExceptionResult::MaskException); |
| } |
| } else { |
| LLDB_LOG(log, "error: process {0} is in state {1}. Returning...", |
| GetDebuggedProcessId(), GetState()); |
| } |
| |
| return error; |
| } |
| |
| NativeThreadWindows * |
| NativeProcessWindows::GetThreadByID(lldb::tid_t thread_id) { |
| return static_cast<NativeThreadWindows *>( |
| NativeProcessProtocol::GetThreadByID(thread_id)); |
| } |
| |
| Status NativeProcessWindows::Halt() { |
| bool caused_stop = false; |
| StateType state = GetState(); |
| if (state != eStateStopped) |
| return HaltProcess(caused_stop); |
| return Status(); |
| } |
| |
| Status NativeProcessWindows::Detach() { |
| Status error; |
| Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); |
| StateType state = GetState(); |
| if (state != eStateExited && state != eStateDetached) { |
| error = DetachProcess(); |
| if (error.Success()) |
| SetState(eStateDetached); |
| else |
| LLDB_LOG(log, "Detaching process error: {0}", error); |
| } else { |
| error.SetErrorStringWithFormatv("error: process {0} in state = {1}, but " |
| "cannot detach it in this state.", |
| GetID(), state); |
| LLDB_LOG(log, "error: {0}", error); |
| } |
| return error; |
| } |
| |
| Status NativeProcessWindows::Signal(int signo) { |
| Status error; |
| error.SetErrorString("Windows does not support sending signals to processes"); |
| return error; |
| } |
| |
| Status NativeProcessWindows::Interrupt() { return Halt(); } |
| |
| Status NativeProcessWindows::Kill() { |
| StateType state = GetState(); |
| return DestroyProcess(state); |
| } |
| |
| Status NativeProcessWindows::IgnoreSignals(llvm::ArrayRef<int> signals) { |
| return Status(); |
| } |
| |
| Status NativeProcessWindows::GetMemoryRegionInfo(lldb::addr_t load_addr, |
| MemoryRegionInfo &range_info) { |
| return ProcessDebugger::GetMemoryRegionInfo(load_addr, range_info); |
| } |
| |
| Status NativeProcessWindows::ReadMemory(lldb::addr_t addr, void *buf, |
| size_t size, size_t &bytes_read) { |
| return ProcessDebugger::ReadMemory(addr, buf, size, bytes_read); |
| } |
| |
| Status NativeProcessWindows::WriteMemory(lldb::addr_t addr, const void *buf, |
| size_t size, size_t &bytes_written) { |
| return ProcessDebugger::WriteMemory(addr, buf, size, bytes_written); |
| } |
| |
| llvm::Expected<lldb::addr_t> |
| NativeProcessWindows::AllocateMemory(size_t size, uint32_t permissions) { |
| lldb::addr_t addr; |
| Status ST = ProcessDebugger::AllocateMemory(size, permissions, addr); |
| if (ST.Success()) |
| return addr; |
| return ST.ToError(); |
| } |
| |
| llvm::Error NativeProcessWindows::DeallocateMemory(lldb::addr_t addr) { |
| return ProcessDebugger::DeallocateMemory(addr).ToError(); |
| } |
| |
| lldb::addr_t NativeProcessWindows::GetSharedLibraryInfoAddress() { return 0; } |
| |
| bool NativeProcessWindows::IsAlive() const { |
| StateType state = GetState(); |
| switch (state) { |
| case eStateCrashed: |
| case eStateDetached: |
| case eStateExited: |
| case eStateInvalid: |
| case eStateUnloaded: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| void NativeProcessWindows::SetStopReasonForThread(NativeThreadWindows &thread, |
| lldb::StopReason reason, |
| std::string description) { |
| SetCurrentThreadID(thread.GetID()); |
| |
| ThreadStopInfo stop_info; |
| stop_info.reason = reason; |
| |
| // No signal support on Windows but required to provide a 'valid' signum. |
| if (reason == StopReason::eStopReasonException) { |
| stop_info.details.exception.type = 0; |
| stop_info.details.exception.data_count = 0; |
| } else { |
| stop_info.details.signal.signo = SIGTRAP; |
| } |
| |
| thread.SetStopReason(stop_info, description); |
| } |
| |
| void NativeProcessWindows::StopThread(lldb::tid_t thread_id, |
| lldb::StopReason reason, |
| std::string description) { |
| NativeThreadWindows *thread = GetThreadByID(thread_id); |
| if (!thread) |
| return; |
| |
| for (uint32_t i = 0; i < m_threads.size(); ++i) { |
| auto t = static_cast<NativeThreadWindows *>(m_threads[i].get()); |
| Status error = t->DoStop(); |
| if (error.Fail()) |
| exit(1); |
| } |
| SetStopReasonForThread(*thread, reason, description); |
| } |
| |
| size_t NativeProcessWindows::UpdateThreads() { return m_threads.size(); } |
| |
| llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> |
| NativeProcessWindows::GetAuxvData() const { |
| // Not available on this target. |
| return llvm::errc::not_supported; |
| } |
| |
| bool NativeProcessWindows::FindSoftwareBreakpoint(lldb::addr_t addr) { |
| auto it = m_software_breakpoints.find(addr); |
| if (it == m_software_breakpoints.end()) |
| return false; |
| return true; |
| } |
| |
| Status NativeProcessWindows::SetBreakpoint(lldb::addr_t addr, uint32_t size, |
| bool hardware) { |
| if (hardware) |
| return SetHardwareBreakpoint(addr, size); |
| return SetSoftwareBreakpoint(addr, size); |
| } |
| |
| Status NativeProcessWindows::RemoveBreakpoint(lldb::addr_t addr, |
| bool hardware) { |
| if (hardware) |
| return RemoveHardwareBreakpoint(addr); |
| return RemoveSoftwareBreakpoint(addr); |
| } |
| |
| Status NativeProcessWindows::CacheLoadedModules() { |
| Status error; |
| if (!m_loaded_modules.empty()) |
| return Status(); |
| |
| // Retrieve loaded modules by a Target/Module free implemenation. |
| AutoHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetID())); |
| if (snapshot.IsValid()) { |
| MODULEENTRY32W me; |
| me.dwSize = sizeof(MODULEENTRY32W); |
| if (Module32FirstW(snapshot.get(), &me)) { |
| do { |
| std::string path; |
| if (!llvm::convertWideToUTF8(me.szExePath, path)) |
| continue; |
| |
| FileSpec file_spec(path); |
| FileSystem::Instance().Resolve(file_spec); |
| m_loaded_modules[file_spec] = (addr_t)me.modBaseAddr; |
| } while (Module32Next(snapshot.get(), &me)); |
| } |
| |
| if (!m_loaded_modules.empty()) |
| return Status(); |
| } |
| |
| error.SetError(::GetLastError(), lldb::ErrorType::eErrorTypeWin32); |
| return error; |
| } |
| |
| Status NativeProcessWindows::GetLoadedModuleFileSpec(const char *module_path, |
| FileSpec &file_spec) { |
| Status error = CacheLoadedModules(); |
| if (error.Fail()) |
| return error; |
| |
| FileSpec module_file_spec(module_path); |
| FileSystem::Instance().Resolve(module_file_spec); |
| for (auto &it : m_loaded_modules) { |
| if (it.first == module_file_spec) { |
| file_spec = it.first; |
| return Status(); |
| } |
| } |
| return Status("Module (%s) not found in process %" PRIu64 "!", |
| module_file_spec.GetCString(), GetID()); |
| } |
| |
| Status |
| NativeProcessWindows::GetFileLoadAddress(const llvm::StringRef &file_name, |
| lldb::addr_t &load_addr) { |
| Status error = CacheLoadedModules(); |
| if (error.Fail()) |
| return error; |
| |
| load_addr = LLDB_INVALID_ADDRESS; |
| FileSpec file_spec(file_name); |
| FileSystem::Instance().Resolve(file_spec); |
| for (auto &it : m_loaded_modules) { |
| if (it.first == file_spec) { |
| load_addr = it.second; |
| return Status(); |
| } |
| } |
| return Status("Can't get loaded address of file (%s) in process %" PRIu64 "!", |
| file_spec.GetCString(), GetID()); |
| } |
| |
| void NativeProcessWindows::OnExitProcess(uint32_t exit_code) { |
| Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); |
| LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); |
| |
| ProcessDebugger::OnExitProcess(exit_code); |
| |
| // No signal involved. It is just an exit event. |
| WaitStatus wait_status(WaitStatus::Exit, exit_code); |
| SetExitStatus(wait_status, true); |
| |
| // Notify the native delegate. |
| SetState(eStateExited, true); |
| } |
| |
| void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) { |
| Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_PROCESS); |
| LLDB_LOG(log, "Debugger connected to process {0}. Image base = {1:x}", |
| GetDebuggedProcessId(), image_base); |
| |
| // This is the earliest chance we can resolve the process ID and |
| // architecture if we don't know them yet. |
| if (GetID() == LLDB_INVALID_PROCESS_ID) |
| SetID(GetDebuggedProcessId()); |
| |
| if (GetArchitecture().GetMachine() == llvm::Triple::UnknownArch) { |
| ProcessInstanceInfo process_info; |
| if (!Host::GetProcessInfo(GetDebuggedProcessId(), process_info)) { |
| LLDB_LOG(log, "Cannot get process information during debugger connecting " |
| "to process"); |
| return; |
| } |
| SetArchitecture(process_info.GetArchitecture()); |
| } |
| |
| // The very first one shall always be the main thread. |
| assert(m_threads.empty()); |
| m_threads.push_back(std::make_unique<NativeThreadWindows>( |
| *this, m_session_data->m_debugger->GetMainThread())); |
| } |
| |
| ExceptionResult |
| NativeProcessWindows::OnDebugException(bool first_chance, |
| const ExceptionRecord &record) { |
| Log *log = ProcessWindowsLog::GetLogIfAny(WINDOWS_LOG_EXCEPTION); |
| llvm::sys::ScopedLock lock(m_mutex); |
| |
| // Let the debugger establish the internal status. |
| ProcessDebugger::OnDebugException(first_chance, record); |
| |
| static bool initial_stop = false; |
| if (!first_chance) { |
| SetState(eStateStopped, false); |
| } |
| |
| ExceptionResult result = ExceptionResult::SendToApplication; |
| switch (record.GetExceptionCode()) { |
| case DWORD(STATUS_SINGLE_STEP): |
| case STATUS_WX86_SINGLE_STEP: { |
| uint32_t wp_id = LLDB_INVALID_INDEX32; |
| if (NativeThreadWindows *thread = GetThreadByID(record.GetThreadID())) { |
| NativeRegisterContextWindows ®_ctx = thread->GetRegisterContext(); |
| Status error = |
| reg_ctx.GetWatchpointHitIndex(wp_id, record.GetExceptionAddress()); |
| if (error.Fail()) |
| LLDB_LOG(log, |
| "received error while checking for watchpoint hits, pid = " |
| "{0}, error = {1}", |
| thread->GetID(), error); |
| if (wp_id != LLDB_INVALID_INDEX32) { |
| addr_t wp_addr = reg_ctx.GetWatchpointAddress(wp_id); |
| addr_t wp_hit_addr = reg_ctx.GetWatchpointHitAddress(wp_id); |
| std::string desc = |
| formatv("{0} {1} {2}", wp_addr, wp_id, wp_hit_addr).str(); |
| StopThread(record.GetThreadID(), StopReason::eStopReasonWatchpoint, |
| desc); |
| } |
| } |
| if (wp_id == LLDB_INVALID_INDEX32) |
| StopThread(record.GetThreadID(), StopReason::eStopReasonTrace); |
| |
| SetState(eStateStopped, true); |
| |
| // Continue the debugger. |
| return ExceptionResult::MaskException; |
| } |
| case DWORD(STATUS_BREAKPOINT): |
| case STATUS_WX86_BREAKPOINT: |
| if (FindSoftwareBreakpoint(record.GetExceptionAddress())) { |
| LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.", |
| record.GetExceptionAddress()); |
| |
| StopThread(record.GetThreadID(), StopReason::eStopReasonBreakpoint); |
| |
| if (NativeThreadWindows *stop_thread = |
| GetThreadByID(record.GetThreadID())) { |
| auto ®ister_context = stop_thread->GetRegisterContext(); |
| // The current EIP is AFTER the BP opcode, which is one byte '0xCC' |
| uint64_t pc = register_context.GetPC() - 1; |
| register_context.SetPC(pc); |
| } |
| |
| SetState(eStateStopped, true); |
| return ExceptionResult::MaskException; |
| } |
| |
| if (!initial_stop) { |
| initial_stop = true; |
| LLDB_LOG(log, |
| "Hit loader breakpoint at address {0:x}, setting initial stop " |
| "event.", |
| record.GetExceptionAddress()); |
| |
| // We are required to report the reason for the first stop after |
| // launching or being attached. |
| if (NativeThreadWindows *thread = GetThreadByID(record.GetThreadID())) |
| SetStopReasonForThread(*thread, StopReason::eStopReasonBreakpoint); |
| |
| // Do not notify the native delegate (e.g. llgs) since at this moment |
| // the program hasn't returned from Factory::Launch() and the delegate |
| // might not have an valid native process to operate on. |
| SetState(eStateStopped, false); |
| |
| // Hit the initial stop. Continue the application. |
| return ExceptionResult::BreakInDebugger; |
| } |
| |
| LLVM_FALLTHROUGH; |
| default: |
| LLDB_LOG(log, |
| "Debugger thread reported exception {0:x} at address {1:x} " |
| "(first_chance={2})", |
| record.GetExceptionCode(), record.GetExceptionAddress(), |
| first_chance); |
| |
| { |
| std::string desc; |
| llvm::raw_string_ostream desc_stream(desc); |
| desc_stream << "Exception " |
| << llvm::format_hex(record.GetExceptionCode(), 8) |
| << " encountered at address " |
| << llvm::format_hex(record.GetExceptionAddress(), 8); |
| StopThread(record.GetThreadID(), StopReason::eStopReasonException, |
| desc_stream.str().c_str()); |
| |
| SetState(eStateStopped, true); |
| } |
| |
| // For non-breakpoints, give the application a chance to handle the |
| // exception first. |
| if (first_chance) |
| result = ExceptionResult::SendToApplication; |
| else |
| result = ExceptionResult::BreakInDebugger; |
| } |
| |
| return result; |
| } |
| |
| void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) { |
| llvm::sys::ScopedLock lock(m_mutex); |
| |
| auto thread = std::make_unique<NativeThreadWindows>(*this, new_thread); |
| thread->GetRegisterContext().ClearAllHardwareWatchpoints(); |
| for (const auto &pair : GetWatchpointMap()) { |
| const NativeWatchpoint &wp = pair.second; |
| thread->SetWatchpoint(wp.m_addr, wp.m_size, wp.m_watch_flags, |
| wp.m_hardware); |
| } |
| |
| m_threads.push_back(std::move(thread)); |
| } |
| |
| void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id, |
| uint32_t exit_code) { |
| llvm::sys::ScopedLock lock(m_mutex); |
| NativeThreadWindows *thread = GetThreadByID(thread_id); |
| if (!thread) |
| return; |
| |
| for (auto t = m_threads.begin(); t != m_threads.end();) { |
| if ((*t)->GetID() == thread_id) { |
| t = m_threads.erase(t); |
| } else { |
| ++t; |
| } |
| } |
| } |
| |
| void NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec, |
| lldb::addr_t module_addr) { |
| // Simply invalidate the cached loaded modules. |
| if (!m_loaded_modules.empty()) |
| m_loaded_modules.clear(); |
| } |
| |
| void NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr) { |
| if (!m_loaded_modules.empty()) |
| m_loaded_modules.clear(); |
| } |
| |
| llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| NativeProcessWindows::Factory::Launch( |
| ProcessLaunchInfo &launch_info, |
| NativeProcessProtocol::NativeDelegate &native_delegate, |
| MainLoop &mainloop) const { |
| Error E = Error::success(); |
| auto process_up = std::unique_ptr<NativeProcessWindows>( |
| new NativeProcessWindows(launch_info, native_delegate, E)); |
| if (E) |
| return std::move(E); |
| return std::move(process_up); |
| } |
| |
| llvm::Expected<std::unique_ptr<NativeProcessProtocol>> |
| NativeProcessWindows::Factory::Attach( |
| lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate, |
| MainLoop &mainloop) const { |
| Error E = Error::success(); |
| // Set pty master fd invalid since it is not available. |
| auto process_up = std::unique_ptr<NativeProcessWindows>( |
| new NativeProcessWindows(pid, -1, native_delegate, E)); |
| if (E) |
| return std::move(E); |
| return std::move(process_up); |
| } |
| } // namespace lldb_private |