| //===-- 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 <dbghelp.h> | |
| #include <excpt.h> | |
| #include <pathcch.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/PseudoTerminal.h" | |
| #include "lldb/Host/windows/AutoHandle.h" | |
| #include "lldb/Host/windows/ConnectionConPTYWindows.h" | |
| #include "lldb/Host/windows/HostThreadWindows.h" | |
| #include "lldb/Host/windows/PathUtils.h" | |
| #include "lldb/Host/windows/ProcessLauncherWindows.h" | |
| #include "lldb/Host/windows/PseudoConsole.h" | |
| #include "lldb/Target/MemoryRegionInfo.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Utility/State.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/StringRef.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, | |
| PseudoTerminal::invalid_fd, // NativeProcessWindows owns the ConPTY. | |
| delegate), | |
| ProcessDebugger(), m_arch(launch_info.GetArchitecture()), | |
| m_stdio_communication("lldb.NativeProcessWindows.stdio") { | |
| ErrorAsOutParameter EOut(&E); | |
| DebugDelegateSP delegate_sp(new NativeDebugDelegate(*this)); | |
| E = LaunchProcess(launch_info, delegate_sp).ToError(); | |
| if (E) | |
| return; | |
| SetID(GetDebuggedProcessId()); | |
| m_pty = launch_info.TakePTY(); | |
| StartStdioForwarding(); | |
| } | |
| NativeProcessWindows::NativeProcessWindows(lldb::pid_t pid, int terminal_fd, | |
| NativeDelegate &delegate, | |
| llvm::Error &E) | |
| : NativeProcessProtocol(pid, terminal_fd, delegate), ProcessDebugger(), | |
| m_stdio_communication("lldb.NativeProcessWindows.stdio") { | |
| 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; | |
| m_expecting_loader_int3 = true; | |
| 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 = GetLog(WindowsLog::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); | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| LLDB_LOG(log, "resuming {0} threads.", m_threads.size()); | |
| m_pending_library_events = false; | |
| 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: | |
| break; | |
| default: | |
| return Status::FromErrorStringWithFormat( | |
| "NativeProcessWindows::%s (): unexpected state %s specified " | |
| "for pid %" PRIu64 ", tid %" PRIu64, | |
| __FUNCTION__, StateAsCString(action->state), GetID(), | |
| thread->GetID()); | |
| } | |
| } | |
| if (failed) { | |
| error = Status::FromErrorString("NativeProcessWindows::DoResume failed"); | |
| } else { | |
| SetState(eStateRunning); | |
| } | |
| // Resume the debug loop. | |
| ExceptionRecordSP active_exception = | |
| m_session_data->m_debugger->GetActiveException(); | |
| 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 { | |
| m_session_data->m_debugger->ContinueAsyncDllEvent(); | |
| } | |
| } 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) { | |
| m_pending_halt = true; | |
| Status err = HaltProcess(caused_stop); | |
| if (err.Fail() || !caused_stop) | |
| m_pending_halt = false; | |
| return err; | |
| } | |
| return Status(); | |
| } | |
| Status NativeProcessWindows::Detach() { | |
| Status error; | |
| Log *log = GetLog(WindowsLog::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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorString( | |
| "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(const ProcessAddress &process_addr, | |
| void *buf, size_t size, | |
| size_t &bytes_read) { | |
| lldb::addr_t addr = process_addr.GetValue(); | |
| return ProcessDebugger::ReadMemory(addr, buf, size, bytes_read); | |
| } | |
| Status NativeProcessWindows::DoWriteMemory(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. | |
| stop_info.signo = SIGTRAP; | |
| if (reason == StopReason::eStopReasonException) { | |
| stop_info.details.exception.type = 0; | |
| stop_info.details.exception.data_count = 0; | |
| } | |
| 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; | |
| Log *log = GetLog(WindowsLog::Thread); | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| for (uint32_t i = 0; i < m_threads.size(); ++i) { | |
| auto t = static_cast<NativeThreadWindows *>(m_threads[i].get()); | |
| if (Status error = t->DoStop(); error.Fail()) | |
| LLDB_LOG(log, "failed to stop thread {0}: {1}", t->GetID(), error); | |
| } | |
| SetStopReasonForThread(*thread, reason, description); | |
| } | |
| size_t NativeProcessWindows::UpdateThreads() { | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| return m_threads.size(); | |
| } | |
| llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> | |
| NativeProcessWindows::GetAuxvData() const { | |
| // Not available on this target. | |
| return llvm::errc::not_supported; | |
| } | |
| llvm::Expected<llvm::ArrayRef<uint8_t>> | |
| NativeProcessWindows::GetSoftwareBreakpointTrapOpcode(size_t size_hint) { | |
| static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e, | |
| 0xd4}; // brk #0xf000 | |
| static const uint8_t g_thumb_opcode[] = {0xfe, 0xde}; // udf #0xfe | |
| switch (GetArchitecture().GetMachine()) { | |
| case llvm::Triple::aarch64: | |
| return llvm::ArrayRef(g_aarch64_opcode); | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| return llvm::ArrayRef(g_thumb_opcode); | |
| default: | |
| return NativeProcessProtocol::GetSoftwareBreakpointTrapOpcode(size_hint); | |
| } | |
| } | |
| size_t NativeProcessWindows::GetSoftwareBreakpointPCOffset() { | |
| // Windows always reports an incremented PC after a breakpoint is hit, | |
| // even on ARM. | |
| return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size(); | |
| } | |
| bool NativeProcessWindows::FindSoftwareBreakpoint(lldb::addr_t addr) { | |
| return m_software_breakpoints.find(addr) != m_software_breakpoints.end(); | |
| } | |
| 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); | |
| } | |
| // Get the path of a module loaded in the target process, as the loader recorded | |
| // it. | |
| // | |
| // Keep the loader's spelling. | |
| static bool GetLoadedModulePath(HANDLE process, HMODULE module, | |
| std::string &path) { | |
| std::vector<wchar_t> name(MAX_PATH); | |
| DWORD len = 0; | |
| while (true) { | |
| len = ::GetModuleFileNameExW(process, module, name.data(), | |
| static_cast<DWORD>(name.size())); | |
| if (len == 0) | |
| return false; | |
| if (len < name.size()) | |
| break; | |
| if (name.size() >= PATHCCH_MAX_CCH) | |
| return false; | |
| name.resize(name.size() * 2); | |
| } | |
| std::wstring wpath(name.data(), len); | |
| if (!llvm::convertWideToUTF8(wpath, path)) | |
| return false; | |
| path = StripExtendedLengthPrefix(path); | |
| // The loader's path often differs from the on-disk one only in case (e.g. | |
| // C:\windows\System32\KERNEL32.DLL). Open the image file to get its on-disk | |
| // spelling, and use it if that's the only difference. | |
| AutoHandle file(::CreateFileW( | |
| wpath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
| nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); | |
| if (!file.IsValid()) | |
| return true; | |
| // Unlike GetModuleFileNameExW, GetFinalPathNameByHandleW reports the buffer | |
| // size it needs instead of truncating, so start empty and let the first call | |
| // size the buffer rather than guessing. | |
| std::vector<wchar_t> full; | |
| while (true) { | |
| DWORD needed = ::GetFinalPathNameByHandleW( | |
| file.get(), full.data(), static_cast<DWORD>(full.size()), | |
| FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | |
| if (needed == 0) | |
| break; | |
| if (needed < full.size()) { | |
| std::string canonical; | |
| if (!llvm::convertWideToUTF8(std::wstring_view(full.data(), needed), | |
| canonical)) | |
| break; | |
| // GetFinalPathNameByHandleW returns an extended-length ("\\?\") path. | |
| canonical = StripExtendedLengthPrefix(canonical); | |
| if (llvm::StringRef(canonical).equals_insensitive(path)) | |
| path = std::move(canonical); | |
| break; | |
| } | |
| full.resize(needed); | |
| } | |
| return true; | |
| } | |
| Status NativeProcessWindows::CacheLoadedModules() { | |
| Status error; | |
| if (!m_loaded_modules.IsEmpty()) | |
| return Status(); | |
| AutoHandle process(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, | |
| FALSE, GetID()), | |
| nullptr); | |
| if (!process.IsValid()) | |
| return Status(::GetLastError(), eErrorTypeWin32); | |
| AutoHandle snapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetID())); | |
| if (!snapshot.IsValid()) | |
| return Status(::GetLastError(), eErrorTypeWin32); | |
| MODULEENTRY32W me; | |
| me.dwSize = sizeof(MODULEENTRY32W); | |
| if (::Module32FirstW(snapshot.get(), &me)) { | |
| do { | |
| std::string path; | |
| if (!GetLoadedModulePath(process.get(), me.hModule, path)) | |
| continue; | |
| FileSpec file_spec(path); | |
| FileSystem::Instance().Resolve(file_spec); | |
| m_loaded_modules.Add(file_spec, reinterpret_cast<addr_t>(me.modBaseAddr)); | |
| } while (::Module32NextW(snapshot.get(), &me)); | |
| } | |
| if (!m_loaded_modules.IsEmpty()) | |
| return Status(); | |
| error = Status(::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); | |
| if (const FileSpec *found = m_loaded_modules.FindFile(module_file_spec)) { | |
| file_spec = *found; | |
| return Status(); | |
| } | |
| return Status::FromErrorStringWithFormat( | |
| "Module (%s) not found in process %" PRIu64 "!", | |
| module_file_spec.GetPath().c_str(), 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); | |
| if (std::optional<addr_t> base = m_loaded_modules.GetBaseAddress(file_spec)) { | |
| load_addr = *base; | |
| return Status(); | |
| } | |
| return Status::FromErrorStringWithFormat( | |
| "Can't get loaded address of file (%s) in process %" PRIu64 "!", | |
| file_spec.GetPath().c_str(), GetID()); | |
| } | |
| llvm::Expected<std::vector<LoadedLibraryInfo>> | |
| NativeProcessWindows::GetLoadedLibraries() { | |
| if (Status error = CacheLoadedModules(); error.Fail()) | |
| return error.ToError(); | |
| std::vector<LoadedLibraryInfo> libs; | |
| libs.reserve(m_loaded_modules.GetSize()); | |
| for (const auto &[file_spec, base_addrs] : m_loaded_modules) { | |
| LoadedLibraryInfo info; | |
| info.name = file_spec.GetPath(); | |
| info.base_addr = base_addrs.front(); | |
| libs.push_back(std::move(info)); | |
| } | |
| return libs; | |
| } | |
| bool NativeProcessWindows::HasPendingLibraryEvents() { | |
| return m_pending_library_events; | |
| } | |
| void NativeProcessWindows::OnExitProcess(uint32_t exit_code) { | |
| Log *log = GetLog(WindowsLog::Process); | |
| LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); | |
| // Closing the ConPTY signals EOF on the parent-side STDOUT pipe so the | |
| // read thread can exit. Tear it down before the debuggee is destroyed. | |
| StopStdioForwarding(); | |
| 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 = GetLog(WindowsLog::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()); | |
| ProcessInstanceInfo info; | |
| bool got_info = Host::GetProcessInfo(GetDebuggedProcessId(), info); | |
| if (GetArchitecture().GetMachine() == llvm::Triple::UnknownArch) { | |
| if (!got_info) { | |
| LLDB_LOG(log, "Cannot get process information during debugger connecting " | |
| "to process"); | |
| return; | |
| } | |
| SetArchitecture(info.GetArchitecture()); | |
| } | |
| if (got_info) { | |
| FileSpec exe = info.GetExecutableFile(); | |
| if (const std::string &image_path = | |
| m_session_data->m_debugger->GetImagePath(); | |
| !image_path.empty() && | |
| !llvm::StringRef(image_path).equals_insensitive(exe.GetPath())) | |
| exe = FileSpec(image_path); | |
| if (exe) { | |
| FileSystem::Instance().Resolve(exe); | |
| m_loaded_modules.Add(exe, image_base); | |
| } | |
| } | |
| // The very first one shall always be the main thread. | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| assert(m_threads.empty()); | |
| m_threads.push_back(std::make_unique<NativeThreadWindows>( | |
| *this, m_session_data->m_debugger->GetMainThread())); | |
| } | |
| ExceptionResult | |
| NativeProcessWindows::HandleSingleStepException(const ExceptionRecord &record) { | |
| uint32_t wp_id = LLDB_INVALID_INDEX32; | |
| #ifndef __aarch64__ | |
| Log *log = GetLog(WindowsLog::Exception); | |
| 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); | |
| } | |
| } | |
| #endif | |
| if (wp_id == LLDB_INVALID_INDEX32) | |
| StopThread(record.GetThreadID(), StopReason::eStopReasonTrace); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::MaskException; | |
| } | |
| ExceptionResult | |
| NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) { | |
| Log *log = GetLog(WindowsLog::Exception); | |
| const auto exception_addr = record.GetExceptionAddress(); | |
| const auto thread_id = record.GetThreadID(); | |
| if (NativeThreadWindows *stop_thread = GetThreadByID(thread_id)) { | |
| auto ®_ctx = stop_thread->GetRegisterContext(); | |
| if (FindSoftwareBreakpoint(exception_addr)) { | |
| LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.", | |
| exception_addr); | |
| StopThread(thread_id, StopReason::eStopReasonBreakpoint); | |
| // The current PC is AFTER the BP opcode, on all architectures. | |
| reg_ctx.SetPC(reg_ctx.GetPC() - GetSoftwareBreakpointPCOffset()); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::MaskException; | |
| } | |
| // This block of code will only be entered in case of a hardware | |
| // watchpoint or breakpoint hit on AArch64. However, we only handle | |
| // hardware watchpoints below as breakpoints are not yet supported. | |
| const ArrayRef<uint64_t> args = record.GetExceptionArguments(); | |
| // Check that the ExceptionInformation array of EXCEPTION_RECORD | |
| // contains at least two elements: the first is a read-write flag | |
| // indicating the type of data access operation (read or write) while | |
| // the second contains the virtual address of the accessed data. | |
| if (args.size() >= 2) { | |
| uint32_t hw_id = LLDB_INVALID_INDEX32; | |
| Status error = reg_ctx.GetWatchpointHitIndex(hw_id, args[1]); | |
| if (error.Fail()) | |
| LLDB_LOG(log, | |
| "received error while checking for watchpoint hits, pid = " | |
| "{0}, error = {1}", | |
| thread_id, error); | |
| if (hw_id != LLDB_INVALID_INDEX32) { | |
| std::string desc = | |
| formatv("{0} {1} {2}", reg_ctx.GetWatchpointAddress(hw_id), hw_id, | |
| exception_addr) | |
| .str(); | |
| StopThread(thread_id, StopReason::eStopReasonWatchpoint, desc); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::MaskException; | |
| } | |
| } | |
| } | |
| if (!m_initial_stop_seen) { | |
| m_initial_stop_seen = true; | |
| LLDB_LOG(log, | |
| "Hit loader breakpoint at address {0:x}, setting initial stop " | |
| "event.", | |
| exception_addr); | |
| // We are required to report the reason for the first stop after | |
| // launching or being attached. | |
| if (NativeThreadWindows *thread = GetThreadByID(thread_id)) | |
| SetStopReasonForThread(*thread, StopReason::eStopReasonBreakpoint); | |
| // Do not notify the native delegate (e.g. llgs) since at this moment | |
| // the program hasn't returned from Manager::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; | |
| } | |
| // Our own DebugBreakProcess() injection, used to implement | |
| // Halt()/Interrupt(). | |
| if (m_pending_halt) { | |
| LLDB_LOG(log, | |
| "DebugBreakProcess injection treated as Halt SIGSTOP for tid " | |
| "{0:x}", | |
| thread_id); | |
| m_pending_halt = false; | |
| ThreadStopInfo signal_info; | |
| signal_info.reason = StopReason::eStopReasonSignal; | |
| signal_info.signo = 19; // SIGSTOP on POSIX | |
| // Halt all threads at the kernel level. | |
| { | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| for (uint32_t i = 0; i < m_threads.size(); ++i) { | |
| auto t = static_cast<NativeThreadWindows *>(m_threads[i].get()); | |
| if (Status err = t->DoStop(); err.Fail()) { | |
| LLDB_LOG(log, "Failed to stop thread {1:x}: {0}", t->GetID(), | |
| err.GetError()); | |
| exit(1); | |
| } | |
| } | |
| } | |
| SetCurrentThreadID(thread_id); | |
| if (NativeThreadWindows *injected = GetThreadByID(thread_id)) | |
| injected->SetStopReason(signal_info, "interrupt"); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::BreakInDebugger; | |
| } | |
| if (m_expecting_loader_int3 && IsSystemModuleAddress(exception_addr)) { | |
| m_expecting_loader_int3 = false; | |
| LLDB_LOG(log, | |
| "Skipping expected loader breakpoint at address {0:x} in a " | |
| "system module.", | |
| exception_addr); | |
| return ExceptionResult::MaskException; | |
| } | |
| std::string desc = formatv("Exception {0:x8} encountered at address {1:x8}", | |
| record.GetExceptionValue(), exception_addr) | |
| .str(); | |
| StopThread(thread_id, StopReason::eStopReasonException, std::move(desc)); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::MaskException; | |
| } | |
| ExceptionResult | |
| NativeProcessWindows::HandleGenericException(bool first_chance, | |
| const ExceptionRecord &record) { | |
| Log *log = GetLog(WindowsLog::Exception); | |
| LLDB_LOG(log, | |
| "Debugger thread reported exception {0:x} at address {1:x} " | |
| "(first_chance={2})", | |
| record.GetExceptionValue(), record.GetExceptionAddress(), | |
| first_chance); | |
| if (first_chance) | |
| return ExceptionResult::SendToApplication; | |
| std::string desc; | |
| llvm::raw_string_ostream desc_stream(desc); | |
| desc_stream << "Exception " << llvm::format_hex(record.GetExceptionValue(), 8) | |
| << " encountered at address " | |
| << llvm::format_hex(record.GetExceptionAddress(), 8); | |
| record.Dump(desc_stream); | |
| StopThread(record.GetThreadID(), StopReason::eStopReasonException, | |
| std::move(desc)); | |
| SetState(eStateStopped, true); | |
| return ExceptionResult::BreakInDebugger; | |
| } | |
| ExceptionResult | |
| NativeProcessWindows::OnDebugException(bool first_chance, | |
| const ExceptionRecord &record) { | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| // Handle the exception first to keep track of the stop reason. | |
| ExceptionResult result; | |
| switch (record.GetExceptionValue()) { | |
| case DWORD(STATUS_SINGLE_STEP): | |
| case STATUS_WX86_SINGLE_STEP: | |
| result = HandleSingleStepException(record); | |
| break; | |
| case DWORD(STATUS_BREAKPOINT): | |
| case STATUS_WX86_BREAKPOINT: | |
| result = HandleBreakpointException(record); | |
| break; | |
| default: | |
| result = HandleGenericException(first_chance, record); | |
| break; | |
| } | |
| // Let the debugger establish the internal status. | |
| ProcessDebugger::OnDebugException(first_chance, record); | |
| 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); | |
| } | |
| if (StateType state = GetState(); | |
| state == eStateStopped || state == eStateCrashed) { | |
| if (Status error = thread->DoStop(); error.Fail()) { | |
| Log *log = GetLog(WindowsLog::Thread); | |
| LLDB_LOG(log, "failed to suspend newly-created thread {0}: {1}", | |
| thread->GetID(), error); | |
| } | |
| ThreadStopInfo stop_info; | |
| stop_info.reason = lldb::eStopReasonNone; | |
| thread->SetStopReason(stop_info, ""); | |
| } | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| m_threads.push_back(std::move(thread)); | |
| } | |
| void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id, | |
| uint32_t exit_code) { | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| llvm::erase_if(m_threads, [thread_id](const auto &t) { | |
| return t->GetID() == thread_id; | |
| }); | |
| } | |
| DllEventAction NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec, | |
| lldb::addr_t module_addr, | |
| lldb::tid_t thread_id) { | |
| Log *log = GetLog(WindowsLog::Process); | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| FileSpec resolved = module_spec.GetFileSpec(); | |
| if (resolved) { | |
| FileSystem::Instance().Resolve(resolved); | |
| m_loaded_modules.Add(resolved, module_addr); | |
| } | |
| m_pending_library_events = true; | |
| if (!m_initial_stop_seen || !m_client_supports_libraries_read) | |
| return DllEventAction::ContinueDebugLoop; | |
| // Can't resolve a breakpoint in a system DLL. | |
| if (!resolved || ProcessDebugger::IsSystemDLL(resolved.GetPath())) | |
| return DllEventAction::ContinueDebugLoop; | |
| { | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| NativeThreadWindows *loader_thread = GetThreadByID(thread_id); | |
| if (!loader_thread && !m_threads.empty()) { | |
| LLDB_LOG(log, | |
| "LOAD_DLL on unknown tid {0:x}. Falling back to main thread.", | |
| thread_id); | |
| loader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get()); | |
| } | |
| if (loader_thread) { | |
| SetCurrentThreadID(loader_thread->GetID()); | |
| if (loader_thread->DoStop().Fail()) | |
| LLDB_LOG(log, "Failed to suspend thread {0} on LOAD_DLL.", | |
| loader_thread->GetID()); | |
| ThreadStopInfo info; | |
| info.reason = lldb::eStopReasonNone; | |
| info.signo = 0; | |
| loader_thread->SetStopReason(info, ""); | |
| } | |
| } | |
| SetState(eStateStopped, true); | |
| return DllEventAction::ParkDebugLoop; | |
| } | |
| DllEventAction NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr, | |
| lldb::tid_t thread_id) { | |
| Log *log = GetLog(WindowsLog::Process); | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| FileSpec unloaded_spec = m_loaded_modules.Remove(module_addr); | |
| m_pending_library_events = true; | |
| if (!m_initial_stop_seen || !m_client_supports_libraries_read) | |
| return DllEventAction::ContinueDebugLoop; | |
| if (!unloaded_spec || ProcessDebugger::IsSystemDLL(unloaded_spec.GetPath())) | |
| return DllEventAction::ContinueDebugLoop; | |
| { | |
| std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); | |
| NativeThreadWindows *unloader_thread = GetThreadByID(thread_id); | |
| if (!unloader_thread && !m_threads.empty()) { | |
| LLDB_LOG(log, | |
| "UNLOAD_DLL on unknown tid {0:x}. Falling back to main thread.", | |
| thread_id); | |
| unloader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get()); | |
| } | |
| if (unloader_thread) { | |
| SetCurrentThreadID(unloader_thread->GetID()); | |
| if (unloader_thread->DoStop().Fail()) | |
| LLDB_LOG(log, "Failed to suspend thread {0} on UNLOAD_DLL.", | |
| unloader_thread->GetID()); | |
| ThreadStopInfo info; | |
| info.reason = lldb::eStopReasonNone; | |
| info.signo = 0; | |
| unloader_thread->SetStopReason(info, ""); | |
| } | |
| } | |
| SetState(eStateStopped, true); | |
| return DllEventAction::ParkDebugLoop; | |
| } | |
| void NativeProcessWindows::OnDebugString(lldb::addr_t debug_string_addr, | |
| bool is_unicode, | |
| uint16_t length_lower_word) { | |
| Log *log = GetLog(WindowsLog::Process); | |
| llvm::SmallVector<char, 256> buffer; | |
| if (llvm::Error err = ProcessDebugger::ReadDebugString( | |
| debug_string_addr, is_unicode, length_lower_word, buffer)) { | |
| std::string err_str = llvm::toString(std::move(err)); | |
| std::string msg = | |
| llvm::formatv("Failed to read debug string at {0:x} " | |
| "(size & 0xffff={1}, unicode={2}): {3}\n", | |
| debug_string_addr, length_lower_word, is_unicode, err_str) | |
| .str(); | |
| LLDB_LOG(log, "{0}", msg); | |
| m_delegate.NewProcessOutput(this, llvm::StringRef(msg)); | |
| return; | |
| } | |
| if (buffer.empty()) | |
| return; | |
| if (is_unicode) { | |
| assert(buffer.size() % 2 == 0); | |
| llvm::ArrayRef<unsigned short> utf16( | |
| reinterpret_cast<const unsigned short *>(buffer.data()), | |
| buffer.size() / 2); | |
| std::string out; | |
| if (!llvm::convertUTF16ToUTF8String(utf16, out)) { | |
| LLDB_LOG(log, "Debug string is not valid Utf 16"); | |
| return; | |
| } | |
| m_delegate.NewProcessOutput(this, llvm::StringRef(out.data(), out.size())); | |
| } else { | |
| m_delegate.NewProcessOutput(this, | |
| llvm::StringRef(buffer.data(), buffer.size())); | |
| } | |
| } | |
| llvm::Expected<std::unique_ptr<NativeProcessProtocol>> | |
| NativeProcessWindows::Manager::Launch( | |
| ProcessLaunchInfo &launch_info, | |
| NativeProcessProtocol::NativeDelegate &native_delegate) { | |
| 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::Manager::Attach( | |
| lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) { | |
| Error E = Error::success(); | |
| // Set pty primary 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); | |
| } | |
| NativeProcessWindows::~NativeProcessWindows() { StopStdioForwarding(); } | |
| void NativeProcessWindows::StartStdioForwarding() { | |
| if (!m_pty || !m_pty->IsConnected()) | |
| return; | |
| m_stdio_communication.SetConnection( | |
| std::make_unique<ConnectionConPTY>(m_pty)); | |
| if (!m_stdio_communication.IsConnected()) | |
| return; | |
| m_stdio_communication.SetReadThreadBytesReceivedCallback( | |
| &NativeProcessWindows::STDIOReadThreadBytesReceived, this); | |
| m_stdio_communication.StartReadThread(); | |
| } | |
| void NativeProcessWindows::StopStdioForwarding() { | |
| if (!m_stdio_communication.HasConnection()) | |
| return; | |
| if (m_pty) | |
| m_pty->Close(); | |
| if (m_stdio_communication.ReadThreadIsRunning()) | |
| m_stdio_communication.JoinReadThread(); | |
| if (m_stdio_communication.HasConnection()) | |
| m_stdio_communication.Disconnect(); | |
| } | |
| void NativeProcessWindows::STDIOReadThreadBytesReceived(void *baton, | |
| const void *src, | |
| size_t src_len) { | |
| auto *self = static_cast<NativeProcessWindows *>(baton); | |
| if (src_len == 0) | |
| return; | |
| self->m_delegate.NewProcessOutput( | |
| self, llvm::StringRef(static_cast<const char *>(src), src_len)); | |
| } | |
| size_t NativeProcessWindows::WriteStdin(const void *buf, size_t len, | |
| Status &error) { | |
| if (!m_stdio_communication.HasConnection()) { | |
| error = Status::FromErrorString( | |
| "no ConPTY connection on this NativeProcessWindows"); | |
| return 0; | |
| } | |
| ConnectionStatus status; | |
| size_t written = m_stdio_communication.Write(buf, len, status, &error); | |
| if (status != eConnectionStatusSuccess && error.Success()) | |
| error = Status::FromErrorStringWithFormatv( | |
| "ConPTY stdin write returned status {0}", static_cast<int>(status)); | |
| return written; | |
| } | |
| } // namespace lldb_private |