| //===-- ProcessWindows.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 "ProcessWindows.h" | |
| // Windows includes | |
| #include "lldb/Host/windows/windows.h" | |
| #include <dbghelp.h> | |
| #include <excpt.h> | |
| #include <psapi.h> | |
| #include "lldb/Breakpoint/Watchpoint.h" | |
| #include "lldb/Core/Address.h" | |
| #include "lldb/Core/IOHandler.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/Host/Config.h" | |
| #include "lldb/Host/FileSystem.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Host/HostNativeProcessBase.h" | |
| #include "lldb/Host/HostProcess.h" | |
| #include "lldb/Host/Pipe.h" | |
| #include "lldb/Host/PseudoTerminal.h" | |
| #include "lldb/Host/windows/ConnectionConPTYWindows.h" | |
| #include "lldb/Host/windows/HostThreadWindows.h" | |
| #include "lldb/Host/windows/PathUtils.h" | |
| #include "lldb/Symbol/ObjectFile.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Target/MemoryRegionInfo.h" | |
| #include "lldb/Target/ProcessIOHandler.h" | |
| #include "lldb/Target/StopInfo.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/State.h" | |
| #include "llvm/Support/ConvertUTF.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include "llvm/Support/Format.h" | |
| #include "llvm/Support/Threading.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include "DebuggerThread.h" | |
| #include "ExceptionRecord.h" | |
| #include "ForwardDecl.h" | |
| #include "LocalDebugDelegate.h" | |
| #include "ProcessWindowsLog.h" | |
| #include "TargetThreadWindows.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| LLDB_PLUGIN_DEFINE_ADV(ProcessWindows, ProcessWindowsCommon) | |
| namespace { | |
| std::string GetProcessExecutableName(HANDLE process_handle) { | |
| std::vector<wchar_t> 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 = ::GetModuleFileNameExW(process_handle, nullptr, file_name.data(), | |
| file_name_size); | |
| } while (copied >= file_name_size); | |
| file_name.resize(copied); | |
| std::string result; | |
| llvm::convertWideToUTF8(file_name.data(), result); | |
| // A process launched through an extended-length path has the "\\?\" prefix. | |
| return StripExtendedLengthPrefix(result); | |
| } | |
| std::string GetProcessExecutableName(DWORD pid) { | |
| std::string file_name; | |
| HANDLE process_handle = | |
| ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); | |
| if (process_handle != nullptr) { | |
| file_name = GetProcessExecutableName(process_handle); | |
| ::CloseHandle(process_handle); | |
| } | |
| return file_name; | |
| } | |
| } // anonymous namespace | |
| namespace lldb_private { | |
| ProcessSP ProcessWindows::CreateInstance(lldb::TargetSP target_sp, | |
| lldb::ListenerSP listener_sp, | |
| const FileSpec *crash_file_path, | |
| bool can_connect) { | |
| if (crash_file_path) | |
| return nullptr; // Cannot create a Windows process from a crash_file. | |
| return ProcessSP(new ProcessWindows(target_sp, listener_sp)); | |
| } | |
| static bool ShouldUseLLDBServer() { | |
| if (const char *env = ::getenv("LLDB_USE_LLDB_SERVER")) { | |
| llvm::StringRef use_lldb_server(env); | |
| return use_lldb_server.equals_insensitive("on") || | |
| use_lldb_server.equals_insensitive("yes") || | |
| use_lldb_server.equals_insensitive("1") || | |
| use_lldb_server.equals_insensitive("true"); | |
| } | |
| return LLDB_ENABLE_LIBXML2; | |
| } | |
| void ProcessWindows::Initialize() { | |
| if (!ShouldUseLLDBServer()) { | |
| PluginManager::RegisterPlugin(GetPluginNameStatic(), | |
| GetPluginDescriptionStatic(), CreateInstance); | |
| } | |
| } | |
| void ProcessWindows::Terminate() { | |
| if (!ShouldUseLLDBServer()) | |
| PluginManager::UnregisterPlugin(CreateInstance); | |
| } | |
| llvm::StringRef ProcessWindows::GetPluginDescriptionStatic() { | |
| return "Process plugin for Windows"; | |
| } | |
| // Constructors and destructors. | |
| ProcessWindows::ProcessWindows(lldb::TargetSP target_sp, | |
| lldb::ListenerSP listener_sp) | |
| : lldb_private::Process(target_sp, listener_sp), | |
| m_watchpoint_ids( | |
| RegisterContextWindows::GetNumHardwareBreakpointSlots(), | |
| LLDB_INVALID_BREAK_ID) {} | |
| ProcessWindows::~ProcessWindows() = default; | |
| Status ProcessWindows::EnableBreakpointSite(BreakpointSite *bp_site) { | |
| if (bp_site->HardwareRequired()) | |
| return Status::FromErrorString("Hardware breakpoints are not supported."); | |
| Log *log = GetLog(WindowsLog::Breakpoints); | |
| LLDB_LOG(log, "bp_site = {0:x}, id={1}, addr={2:x}", bp_site, | |
| bp_site->GetID(), bp_site->GetLoadAddress()); | |
| Status error = EnableSoftwareBreakpoint(bp_site); | |
| if (!error.Success()) | |
| LLDB_LOG(log, "error: {0}", error); | |
| return error; | |
| } | |
| Status ProcessWindows::DisableBreakpointSite(BreakpointSite *bp_site) { | |
| Log *log = GetLog(WindowsLog::Breakpoints); | |
| LLDB_LOG(log, "bp_site = {0:x}, id={1}, addr={2:x}", bp_site, | |
| bp_site->GetID(), bp_site->GetLoadAddress()); | |
| Status error = DisableSoftwareBreakpoint(bp_site); | |
| if (!error.Success()) | |
| LLDB_LOG(log, "error: {0}", error); | |
| return error; | |
| } | |
| Status ProcessWindows::DoDetach(bool keep_stopped) { | |
| Status error; | |
| Log *log = GetLog(WindowsLog::Process); | |
| StateType private_state = GetPrivateState(); | |
| if (private_state != eStateExited && private_state != eStateDetached) { | |
| if (!keep_stopped) { | |
| // if the thread is suspended by lldb, we have to resume threads before | |
| // detaching process. When we do after DetachProcess(), thread handles | |
| // become invalid so we do before detach. | |
| if (private_state == eStateStopped || private_state == eStateCrashed) { | |
| LLDB_LOG(log, "process {0} is in state {1}. Resuming for detach...", | |
| m_session_data->m_debugger->GetProcess().GetProcessId(), | |
| GetPrivateState()); | |
| LLDB_LOG(log, "resuming {0} threads for detach.", | |
| m_thread_list.GetSize()); | |
| bool failed = false; | |
| for (uint32_t i = 0; i < m_thread_list.GetSize(); ++i) { | |
| auto thread = std::static_pointer_cast<TargetThreadWindows>( | |
| m_thread_list.GetThreadAtIndex(i)); | |
| Status result = thread->DoResume(); | |
| if (result.Fail()) { | |
| failed = true; | |
| LLDB_LOG(log, | |
| "Trying to resume thread at index {0}, but failed with " | |
| "error {1}.", | |
| i, result); | |
| } | |
| } | |
| if (failed) { | |
| error = Status::FromErrorString("Resuming Threads for Detach failed"); | |
| } | |
| } | |
| } | |
| error = DetachProcess(); | |
| if (error.Success()) | |
| SetPrivateState(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(), private_state); | |
| LLDB_LOG(log, "error: {0}", error); | |
| } | |
| return error; | |
| } | |
| Status ProcessWindows::DoLaunch(Module *exe_module, | |
| ProcessLaunchInfo &launch_info) { | |
| Status error; | |
| DebugDelegateSP delegate(new LocalDebugDelegate(shared_from_this())); | |
| error = LaunchProcess(launch_info, delegate); | |
| if (error.Success()) | |
| SetID(launch_info.GetProcessID()); | |
| m_pty = launch_info.TakePTY(); | |
| return error; | |
| } | |
| Status | |
| ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid, | |
| const ProcessAttachInfo &attach_info) { | |
| DebugDelegateSP delegate(new LocalDebugDelegate(shared_from_this())); | |
| Status error = AttachProcess(pid, attach_info, delegate); | |
| if (error.Success()) | |
| SetID(GetDebuggedProcessId()); | |
| m_expecting_loader_int3 = true; | |
| return error; | |
| } | |
| Status ProcessWindows::DoResume(RunDirection direction) { | |
| Log *log = GetLog(WindowsLog::Process); | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| if (direction == RunDirection::eRunReverse) { | |
| return Status::FromErrorStringWithFormatv( | |
| "{0} does not support reverse execution of processes", GetPluginName()); | |
| } | |
| Status error; | |
| StateType private_state = GetPrivateState(); | |
| if (private_state == eStateStopped || private_state == eStateCrashed) { | |
| LLDB_LOG(log, "process {0} is in state {1}. Resuming...", | |
| m_session_data->m_debugger->GetProcess().GetProcessId(), | |
| GetPrivateState()); | |
| LLDB_LOG(log, "resuming {0} threads.", m_thread_list.GetSize()); | |
| bool failed = false; | |
| for (uint32_t i = 0; i < m_thread_list.GetSize(); ++i) { | |
| auto thread = std::static_pointer_cast<TargetThreadWindows>( | |
| m_thread_list.GetThreadAtIndex(i)); | |
| Status result = thread->DoResume(); | |
| if (result.Fail()) { | |
| failed = true; | |
| LLDB_LOG( | |
| log, | |
| "Trying to resume thread at index {0}, but failed with error {1}.", | |
| i, result); | |
| } | |
| } | |
| if (failed) { | |
| error = Status::FromErrorString("ProcessWindows::DoResume failed"); | |
| } else { | |
| SetPrivateState(eStateRunning); | |
| } | |
| 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 { | |
| LLDB_LOG(log, "error: process {0} is in state {1}. Returning...", | |
| m_session_data->m_debugger->GetProcess().GetProcessId(), | |
| GetPrivateState()); | |
| } | |
| return error; | |
| } | |
| Status ProcessWindows::DoDestroy() { | |
| StateType private_state = GetPrivateState(); | |
| return DestroyProcess(private_state); | |
| } | |
| Status ProcessWindows::DoHalt(bool &caused_stop) { | |
| StateType state = GetPrivateState(); | |
| if (state != eStateStopped) { | |
| m_pending_halt = true; | |
| Status error = HaltProcess(caused_stop); | |
| if (error.Fail() || !caused_stop) | |
| m_pending_halt = false; | |
| } | |
| caused_stop = false; | |
| return Status(); | |
| } | |
| void ProcessWindows::DidLaunch() { | |
| ArchSpec arch_spec; | |
| DidAttach(arch_spec); | |
| } | |
| 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_stop_at_entry) | |
| RefreshStateAfterStop(); | |
| } | |
| void ProcessWindows::RefreshStateAfterStop() { | |
| Log *log = GetLog(WindowsLog::Exception); | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| if (!m_session_data) { | |
| LLDB_LOG(log, "no active session. Returning..."); | |
| return; | |
| } | |
| m_thread_list.RefreshStateAfterStop(); | |
| ExceptionRecordSP active_exception = | |
| m_session_data->m_debugger->GetActiveException(); | |
| if (!active_exception) { | |
| LLDB_LOG(log, | |
| "there is no active exception in process {0}. Why is the " | |
| "process stopped?", | |
| m_session_data->m_debugger->GetProcess().GetProcessId()); | |
| return; | |
| } | |
| StopInfoSP stop_info; | |
| m_thread_list.SetSelectedThreadByID(active_exception->GetThreadID()); | |
| ThreadSP stop_thread = m_thread_list.GetSelectedThread(); | |
| if (!stop_thread) | |
| return; | |
| RegisterContextSP register_context = stop_thread->GetRegisterContext(); | |
| uint64_t pc = register_context->GetPC(); | |
| // If we're at a BreakpointSite, mark this as an Unexecuted Breakpoint. | |
| // We'll clear that state if we've actually executed the breakpoint. | |
| BreakpointSiteSP site(GetBreakpointSiteList().FindByAddress(pc)); | |
| if (site && IsBreakpointSitePhysicallyEnabled(*site)) | |
| stop_thread->SetThreadStoppedAtUnexecutedBP(pc); | |
| switch (active_exception->GetExceptionValue()) { | |
| case EXCEPTION_SINGLE_STEP: { | |
| auto *reg_ctx = static_cast<RegisterContextWindows *>( | |
| stop_thread->GetRegisterContext().get()); | |
| uint32_t slot_id = reg_ctx->GetTriggeredHardwareBreakpointSlotId(); | |
| if (slot_id != LLDB_INVALID_INDEX32) { | |
| int id = m_watchpoint_ids[slot_id]; | |
| LLDB_LOG(log, | |
| "Single-stepped onto a watchpoint in process {0} at address " | |
| "{1:x} with watchpoint {2}", | |
| m_session_data->m_debugger->GetProcess().GetProcessId(), pc, id); | |
| stop_info = StopInfo::CreateStopReasonWithWatchpointID(*stop_thread, id); | |
| stop_thread->SetStopInfo(stop_info); | |
| return; | |
| } | |
| LLDB_LOG(log, "single stepping thread {0}", stop_thread->GetID()); | |
| stop_info = StopInfo::CreateStopReasonToTrace(*stop_thread); | |
| stop_thread->SetStopInfo(stop_info); | |
| return; | |
| } | |
| case EXCEPTION_BREAKPOINT: { | |
| int breakpoint_size = 1; | |
| switch (GetTarget().GetArchitecture().GetMachine()) { | |
| case llvm::Triple::aarch64: | |
| breakpoint_size = 4; | |
| break; | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| breakpoint_size = 2; | |
| break; | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| breakpoint_size = 1; | |
| break; | |
| default: | |
| LLDB_LOG(log, "Unknown breakpoint size for architecture"); | |
| break; | |
| } | |
| // The current PC is AFTER the BP opcode, on all architectures. | |
| pc = register_context->GetPC() - breakpoint_size; | |
| site = GetBreakpointSiteList().FindByAddress(pc); | |
| if (site) { | |
| LLDB_LOG(log, | |
| "detected breakpoint in process {0} at address {1:x} with " | |
| "breakpoint site {2}", | |
| m_session_data->m_debugger->GetProcess().GetProcessId(), pc, | |
| site->GetID()); | |
| stop_thread->SetThreadHitBreakpointSite(); | |
| if (site->ValidForThisThread(*stop_thread)) { | |
| LLDB_LOG(log, | |
| "Breakpoint site {0} is valid for this thread ({1:x}), " | |
| "creating stop info.", | |
| site->GetID(), stop_thread->GetID()); | |
| stop_info = StopInfo::CreateStopReasonWithBreakpointSiteID( | |
| *stop_thread, site->GetID()); | |
| register_context->SetPC(pc); | |
| } else { | |
| LLDB_LOG(log, | |
| "Breakpoint site {0} is not valid for this thread, " | |
| "creating empty stop info.", | |
| site->GetID()); | |
| } | |
| stop_thread->SetStopInfo(stop_info); | |
| return; | |
| } else { | |
| // The thread hit a hard-coded breakpoint like an `int 3` or | |
| // `__debugbreak()`. | |
| LLDB_LOG(log, | |
| "No breakpoint site matches for this thread. __debugbreak()? " | |
| "Creating stop info with the exception."); | |
| // FALLTHROUGH: We'll treat this as a generic exception record in the | |
| // default case. | |
| [[fallthrough]]; | |
| } | |
| } | |
| default: { | |
| std::string desc; | |
| llvm::raw_string_ostream desc_stream(desc); | |
| desc_stream << "Exception " | |
| << llvm::format_hex(active_exception->GetExceptionValue(), 8) | |
| << " encountered at address " | |
| << llvm::format_hex(active_exception->GetExceptionAddress(), 8); | |
| active_exception->Dump(desc_stream); | |
| stop_info = | |
| StopInfo::CreateStopReasonWithException(*stop_thread, desc.c_str()); | |
| stop_thread->SetStopInfo(stop_info); | |
| LLDB_LOG(log, "{0}", desc); | |
| return; | |
| } | |
| } | |
| } | |
| bool ProcessWindows::CanDebug(lldb::TargetSP target_sp, | |
| bool plugin_specified_by_name) { | |
| if (plugin_specified_by_name) | |
| return true; | |
| // For now we are just making sure the file exists for a given module | |
| ModuleSP exe_module_sp(target_sp->GetExecutableModule()); | |
| if (exe_module_sp.get()) | |
| return FileSystem::Instance().Exists(exe_module_sp->GetFileSpec()); | |
| // However, if there is no executable module, we return true since we might | |
| // be preparing to attach. | |
| return true; | |
| } | |
| bool ProcessWindows::DoUpdateThreadList(ThreadList &old_thread_list, | |
| ThreadList &new_thread_list) { | |
| Log *log = GetLog(WindowsLog::Thread); | |
| // Add all the threads that were previously running and for which we did not | |
| // detect a thread exited event. | |
| int new_size = 0; | |
| int continued_threads = 0; | |
| int exited_threads = 0; | |
| int new_threads = 0; | |
| for (ThreadSP old_thread : old_thread_list.Threads()) { | |
| lldb::tid_t old_thread_id = old_thread->GetID(); | |
| auto exited_thread_iter = | |
| m_session_data->m_exited_threads.find(old_thread_id); | |
| if (exited_thread_iter == m_session_data->m_exited_threads.end()) { | |
| new_thread_list.AddThread(old_thread); | |
| ++new_size; | |
| ++continued_threads; | |
| LLDB_LOG_VERBOSE(log, "Thread {0} was running and is still running.", | |
| old_thread_id); | |
| } else { | |
| LLDB_LOG_VERBOSE(log, "Thread {0} was running and has exited.", | |
| old_thread_id); | |
| ++exited_threads; | |
| } | |
| } | |
| // Also add all the threads that are new since the last time we broke into | |
| // the debugger. | |
| for (const auto &thread_info : m_session_data->m_new_threads) { | |
| new_thread_list.AddThread(thread_info.second); | |
| ++new_size; | |
| ++new_threads; | |
| LLDB_LOG_VERBOSE(log, "Thread {0} is new since last update.", | |
| thread_info.first); | |
| } | |
| LLDB_LOG(log, "{0} new threads, {1} old threads, {2} exited threads.", | |
| new_threads, continued_threads, exited_threads); | |
| m_session_data->m_new_threads.clear(); | |
| m_session_data->m_exited_threads.clear(); | |
| return new_size > 0; | |
| } | |
| bool ProcessWindows::IsAlive() { | |
| StateType state = GetPrivateState(); | |
| switch (state) { | |
| case eStateCrashed: | |
| case eStateDetached: | |
| case eStateUnloaded: | |
| case eStateExited: | |
| case eStateInvalid: | |
| return false; | |
| default: | |
| return true; | |
| } | |
| } | |
| ArchSpec ProcessWindows::GetSystemArchitecture() { | |
| return HostInfo::GetArchitecture(); | |
| } | |
| size_t ProcessWindows::DoReadMemory(const ProcessAddress &process_addr, | |
| void *buf, size_t size, Status &error) { | |
| lldb::addr_t vm_addr = process_addr.GetValue(); | |
| size_t bytes_read = 0; | |
| error = ProcessDebugger::ReadMemory(vm_addr, buf, size, bytes_read); | |
| return bytes_read; | |
| } | |
| size_t ProcessWindows::DoWriteMemory(lldb::addr_t vm_addr, const void *buf, | |
| size_t size, Status &error) { | |
| size_t bytes_written = 0; | |
| error = ProcessDebugger::WriteMemory(vm_addr, buf, size, bytes_written); | |
| return bytes_written; | |
| } | |
| lldb::addr_t ProcessWindows::DoAllocateMemory(size_t size, uint32_t permissions, | |
| Status &error) { | |
| lldb::addr_t vm_addr = LLDB_INVALID_ADDRESS; | |
| error = ProcessDebugger::AllocateMemory(size, permissions, vm_addr); | |
| return vm_addr; | |
| } | |
| Status ProcessWindows::DoDeallocateMemory(lldb::addr_t ptr) { | |
| return ProcessDebugger::DeallocateMemory(ptr); | |
| } | |
| Status ProcessWindows::DoGetMemoryRegionInfo(lldb::addr_t vm_addr, | |
| MemoryRegionInfo &info) { | |
| return ProcessDebugger::GetMemoryRegionInfo(vm_addr, info); | |
| } | |
| lldb::addr_t ProcessWindows::GetImageInfoAddress() { | |
| Target &target = GetTarget(); | |
| ModuleSP executable_sp = target.GetExecutableModule(); | |
| if (!executable_sp) | |
| return LLDB_INVALID_ADDRESS; | |
| ObjectFile *obj_file = executable_sp->GetObjectFile(); | |
| Address addr = obj_file->GetImageInfoAddress(&target); | |
| if (addr.IsValid()) | |
| return addr.GetLoadAddress(&target); | |
| else | |
| return LLDB_INVALID_ADDRESS; | |
| } | |
| DynamicLoaderWindowsDYLD *ProcessWindows::GetDynamicLoader() { | |
| if (m_dyld_up.get() == nullptr) | |
| m_dyld_up.reset(DynamicLoader::FindPlugin( | |
| this, DynamicLoaderWindowsDYLD::GetPluginNameStatic())); | |
| return static_cast<DynamicLoaderWindowsDYLD *>(m_dyld_up.get()); | |
| } | |
| void ProcessWindows::OnExitProcess(uint32_t exit_code) { | |
| // No need to acquire the lock since m_session_data isn't accessed. | |
| Log *log = GetLog(WindowsLog::Process); | |
| LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code); | |
| if (m_pty) { | |
| DrainProcessStdout(); | |
| m_pty->SetStopping(true); | |
| m_pty->Close(); | |
| m_stdio_communication.InterruptRead(); | |
| m_stdio_communication.StopReadThread(); | |
| } | |
| TargetSP target = CalculateTarget(); | |
| if (target) { | |
| ModuleSP executable_module = target->GetExecutableModule(); | |
| ModuleList unloaded_modules; | |
| unloaded_modules.Append(executable_module); | |
| target->ModulesDidUnload(unloaded_modules, false); | |
| } | |
| SetExitStatus(exit_code, /*exit_string=*/""); | |
| SetPrivateState(eStateExited); | |
| ProcessDebugger::OnExitProcess(exit_code); | |
| } | |
| void ProcessWindows::DrainProcessStdout() { | |
| if (!m_stdio_communication.ReadThreadIsRunning()) | |
| return; | |
| m_stdio_communication.SynchronizeWithReadThread(); | |
| if (!m_pty || m_pty->GetMode() != PseudoConsole::Mode::ConPTY) | |
| return; | |
| HANDLE pipe = m_pty->GetSTDOUTHandle(); | |
| for (int consec_empty = 0; consec_empty < 3;) { | |
| if (!m_stdio_communication.ReadThreadIsRunning()) | |
| break; | |
| DWORD avail = 0; | |
| // PeekNamedPipe is thread safe. | |
| if (!::PeekNamedPipe(pipe, nullptr, 0, nullptr, &avail, nullptr)) | |
| break; | |
| if (avail > 0) { | |
| consec_empty = 0; | |
| m_stdio_communication.SynchronizeWithReadThread(); | |
| } else { | |
| ++consec_empty; | |
| if (consec_empty < 3) | |
| ::SleepEx(1, FALSE); | |
| } | |
| } | |
| } | |
| void ProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) { | |
| DebuggerThreadSP debugger = m_session_data->m_debugger; | |
| Log *log = GetLog(WindowsLog::Process); | |
| LLDB_LOG(log, "Debugger connected to process {0}. Image base = {1:x}", | |
| debugger->GetProcess().GetProcessId(), image_base); | |
| ModuleSP module = GetTarget().GetExecutableModule(); | |
| if (!module) { | |
| const DWORD pid = debugger->GetProcess().GetProcessId(); | |
| const std::string file_name = GetProcessExecutableName(pid); | |
| if (file_name.empty()) | |
| return; | |
| FileSpec executable_file(file_name); | |
| FileSystem::Instance().Resolve(executable_file); | |
| ModuleSpec module_spec(executable_file); | |
| Status error; | |
| module = | |
| GetTarget().GetOrCreateModule(module_spec, /*notify=*/true, &error); | |
| if (!module) | |
| return; | |
| GetTarget().RebuildModuleListWithExecutable(module, eLoadDependentsNo); | |
| } | |
| if (auto dyld = GetDynamicLoader()) | |
| dyld->OnLoadModule(module, ModuleSpec(), image_base); | |
| // 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 HostThread &host_main_thread = debugger->GetMainThread(); | |
| ThreadSP main_thread = | |
| std::make_shared<TargetThreadWindows>(*this, host_main_thread); | |
| tid_t id = host_main_thread.GetNativeThread().GetThreadId(); | |
| main_thread->SetID(id); | |
| m_session_data->m_new_threads[id] = main_thread; | |
| } | |
| ExceptionResult | |
| ProcessWindows::OnDebugException(bool first_chance, | |
| const ExceptionRecord &record) { | |
| Log *log = GetLog(WindowsLog::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.GetExceptionValue(), record.GetExceptionAddress()); | |
| return ExceptionResult::SendToApplication; | |
| } | |
| if (!first_chance) { | |
| // Not any second chance exception is an application crash by definition. | |
| // It may be an expression evaluation crash. | |
| DrainProcessStdout(); | |
| SetPrivateState(eStateStopped); | |
| } | |
| ExceptionResult result = ExceptionResult::SendToApplication; | |
| switch (record.GetExceptionValue()) { | |
| case EXCEPTION_BREAKPOINT: { | |
| const lldb::addr_t bp_addr = record.GetExceptionAddress(); | |
| if (m_pending_halt) { | |
| m_pending_halt = false; | |
| } else if (m_expecting_loader_int3 && first_chance && | |
| m_session_data->m_initial_stop_received && | |
| !GetBreakpointSiteList().FindByAddress(bp_addr) && | |
| IsSystemModuleAddress(bp_addr)) { | |
| m_expecting_loader_int3 = false; | |
| LLDB_LOG(log, | |
| "Skipping expected loader breakpoint at address {0:x} in a " | |
| "system module.", | |
| bp_addr); | |
| return ExceptionResult::MaskException; | |
| } | |
| // Handle breakpoints at the first chance. | |
| result = ExceptionResult::BreakInDebugger; | |
| if (!m_session_data->m_initial_stop_received) { | |
| 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); | |
| } else { | |
| LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.", | |
| record.GetExceptionAddress()); | |
| } | |
| // Drain any in-flight process output before announcing the stop. The I/O | |
| // reader thread and this debug-event thread run concurrently. Without | |
| // synchronization the eBroadcastBitStateChanged(Stopped) event can reach | |
| // the Debugger event thread before the preceding eBroadcastBitSTDOUT | |
| // events. | |
| DrainProcessStdout(); | |
| SetPrivateState(eStateStopped); | |
| break; | |
| } | |
| case EXCEPTION_SINGLE_STEP: | |
| result = ExceptionResult::BreakInDebugger; | |
| DrainProcessStdout(); | |
| SetPrivateState(eStateStopped); | |
| break; | |
| default: | |
| LLDB_LOG(log, | |
| "Debugger thread reported exception {0:x} at address {1:x} " | |
| "(first_chance={2})", | |
| record.GetExceptionValue(), record.GetExceptionAddress(), | |
| first_chance); | |
| // 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 ProcessWindows::OnCreateThread(const HostThread &new_thread) { | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| ThreadSP thread = std::make_shared<TargetThreadWindows>(*this, new_thread); | |
| const HostNativeThread &native_new_thread = new_thread.GetNativeThread(); | |
| tid_t id = native_new_thread.GetThreadId(); | |
| thread->SetID(id); | |
| m_session_data->m_new_threads[id] = thread; | |
| for (const std::map<int, WatchpointInfo>::value_type &p : m_watchpoints) { | |
| auto *reg_ctx = static_cast<RegisterContextWindows *>( | |
| thread->GetRegisterContext().get()); | |
| reg_ctx->AddHardwareBreakpoint(p.second.slot_id, p.second.address, | |
| p.second.size, p.second.read, | |
| p.second.write); | |
| } | |
| } | |
| void ProcessWindows::OnExitThread(lldb::tid_t thread_id, uint32_t exit_code) { | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| // On a forced termination, we may get exit thread events after the session | |
| // data has been cleaned up. | |
| if (!m_session_data) | |
| return; | |
| // A thread may have started and exited before the debugger stopped allowing a | |
| // refresh. | |
| // Just remove it from the new threads list in that case. | |
| auto iter = m_session_data->m_new_threads.find(thread_id); | |
| if (iter != m_session_data->m_new_threads.end()) | |
| m_session_data->m_new_threads.erase(iter); | |
| else | |
| m_session_data->m_exited_threads.insert(thread_id); | |
| } | |
| DllEventAction ProcessWindows::OnLoadDll(const ModuleSpec &module_spec, | |
| lldb::addr_t module_addr, | |
| lldb::tid_t thread_id) { | |
| if (auto dyld = GetDynamicLoader()) | |
| dyld->OnLoadModule(nullptr, module_spec, module_addr); | |
| return DllEventAction::ContinueDebugLoop; | |
| } | |
| DllEventAction ProcessWindows::OnUnloadDll(lldb::addr_t module_addr, | |
| lldb::tid_t thread_id) { | |
| if (auto dyld = GetDynamicLoader()) | |
| dyld->OnUnloadModule(module_addr); | |
| return DllEventAction::ContinueDebugLoop; | |
| } | |
| void ProcessWindows::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; | |
| llvm::Error err = | |
| ReadDebugString(debug_string_addr, is_unicode, length_lower_word, buffer); | |
| if (err) { | |
| LLDB_LOG_ERROR(log, std::move(err), | |
| "Failed to read debug string at {1:x} (size & 0xffff={2}, " | |
| "unicode={3}): {0}", | |
| debug_string_addr, length_lower_word, is_unicode); | |
| 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; | |
| } | |
| AppendSTDOUT(out.data(), out.size()); | |
| } else { | |
| AppendSTDOUT(buffer.data(), buffer.size()); | |
| } | |
| } | |
| void ProcessWindows::OnDebuggerError(const Status &error, uint32_t type) { | |
| llvm::sys::ScopedLock lock(m_mutex); | |
| Log *log = GetLog(WindowsLog::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.Clone(); | |
| ::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; | |
| } | |
| } | |
| std::optional<uint32_t> ProcessWindows::GetWatchpointSlotCount() { | |
| return RegisterContextWindows::GetNumHardwareBreakpointSlots(); | |
| } | |
| std::optional<DWORD> ProcessWindows::GetActiveExceptionCode() const { | |
| if (!m_session_data || !m_session_data->m_debugger) | |
| return std::nullopt; | |
| auto exc = m_session_data->m_debugger->GetActiveException(); | |
| if (!exc) | |
| return std::nullopt; | |
| return exc->GetExceptionValue(); | |
| } | |
| Status ProcessWindows::EnableWatchpoint(WatchpointSP wp_sp, bool notify) { | |
| Status error; | |
| if (wp_sp->IsEnabled()) { | |
| wp_sp->SetEnabled(true, notify); | |
| return error; | |
| } | |
| WatchpointInfo info; | |
| for (info.slot_id = 0; | |
| info.slot_id < RegisterContextWindows::GetNumHardwareBreakpointSlots(); | |
| info.slot_id++) | |
| if (m_watchpoint_ids[info.slot_id] == LLDB_INVALID_BREAK_ID) | |
| break; | |
| if (info.slot_id == RegisterContextWindows::GetNumHardwareBreakpointSlots()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Can't find free slot for watchpoint %i", wp_sp->GetID()); | |
| return error; | |
| } | |
| info.address = wp_sp->GetLoadAddress(); | |
| info.size = wp_sp->GetByteSize(); | |
| info.read = wp_sp->WatchpointRead(); | |
| info.write = wp_sp->WatchpointWrite() || wp_sp->WatchpointModify(); | |
| for (unsigned i = 0U; i < m_thread_list.GetSize(); i++) { | |
| Thread *thread = m_thread_list.GetThreadAtIndex(i).get(); | |
| auto *reg_ctx = static_cast<RegisterContextWindows *>( | |
| thread->GetRegisterContext().get()); | |
| if (!reg_ctx->AddHardwareBreakpoint(info.slot_id, info.address, info.size, | |
| info.read, info.write)) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Can't enable watchpoint %i on thread 0x%llx", wp_sp->GetID(), | |
| thread->GetID()); | |
| break; | |
| } | |
| } | |
| if (error.Fail()) { | |
| for (unsigned i = 0U; i < m_thread_list.GetSize(); i++) { | |
| Thread *thread = m_thread_list.GetThreadAtIndex(i).get(); | |
| auto *reg_ctx = static_cast<RegisterContextWindows *>( | |
| thread->GetRegisterContext().get()); | |
| reg_ctx->RemoveHardwareBreakpoint(info.slot_id); | |
| } | |
| return error; | |
| } | |
| m_watchpoints[wp_sp->GetID()] = info; | |
| m_watchpoint_ids[info.slot_id] = wp_sp->GetID(); | |
| wp_sp->SetEnabled(true, notify); | |
| return error; | |
| } | |
| Status ProcessWindows::DisableWatchpoint(WatchpointSP wp_sp, bool notify) { | |
| Status error; | |
| if (!wp_sp->IsEnabled()) { | |
| wp_sp->SetEnabled(false, notify); | |
| return error; | |
| } | |
| auto it = m_watchpoints.find(wp_sp->GetID()); | |
| if (it == m_watchpoints.end()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Info about watchpoint %i is not found", wp_sp->GetID()); | |
| return error; | |
| } | |
| for (unsigned i = 0U; i < m_thread_list.GetSize(); i++) { | |
| Thread *thread = m_thread_list.GetThreadAtIndex(i).get(); | |
| auto *reg_ctx = static_cast<RegisterContextWindows *>( | |
| thread->GetRegisterContext().get()); | |
| if (!reg_ctx->RemoveHardwareBreakpoint(it->second.slot_id)) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Can't disable watchpoint %i on thread 0x%llx", wp_sp->GetID(), | |
| thread->GetID()); | |
| break; | |
| } | |
| } | |
| if (error.Fail()) | |
| return error; | |
| m_watchpoint_ids[it->second.slot_id] = LLDB_INVALID_BREAK_ID; | |
| m_watchpoints.erase(it); | |
| wp_sp->SetEnabled(false, notify); | |
| return error; | |
| } | |
| size_t ProcessWindows::PutSTDIN(const char *src, size_t src_len, | |
| Status &error) { | |
| if (!m_stdio_communication.IsConnected()) { | |
| error = Status::FromErrorString("stdin not connected"); | |
| return 0; | |
| } | |
| ConnectionStatus status; | |
| return m_stdio_communication.WriteAll(src, src_len, status, &error); | |
| } | |
| void ProcessWindows::SetPseudoConsoleHandle() { | |
| if (m_pty == nullptr) | |
| return; | |
| m_stdio_communication.SetConnection( | |
| std::make_unique<ConnectionConPTY>(m_pty)); | |
| if (m_stdio_communication.IsConnected()) { | |
| m_stdio_communication.SetReadThreadBytesReceivedCallback( | |
| STDIOReadThreadBytesReceived, this); | |
| m_stdio_communication.StartReadThread(); | |
| // Now read thread is set up, set up input reader. | |
| { | |
| std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); | |
| if (!m_process_input_reader) | |
| m_process_input_reader = | |
| std::make_shared<IOHandlerProcessSTDIOWindows>(this); | |
| } | |
| } | |
| } | |
| } // namespace lldb_private |