| //===-- PlatformWindows.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 "PlatformWindows.h" | |
| #include <chrono> | |
| #include <cstdio> | |
| #include <optional> | |
| #if defined(_WIN32) | |
| #include "lldb/Host/windows/windows.h" | |
| #include <pathcch.h> | |
| #include <winsock2.h> | |
| #else | |
| #define MAX_PATH 260 | |
| #define PATHCCH_MAX_CCH 0x8000 | |
| #endif | |
| #include "Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h" | |
| #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" | |
| #include "lldb/Breakpoint/BreakpointLocation.h" | |
| #include "lldb/Breakpoint/BreakpointSite.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Expression/DiagnosticManager.h" | |
| #include "lldb/Expression/FunctionCaller.h" | |
| #include "lldb/Expression/UserExpression.h" | |
| #include "lldb/Expression/UtilityFunction.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Utility/ErrorMessages.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/Support/ConvertUTF.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/FormatAdapters.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| LLDB_PLUGIN_DEFINE(PlatformWindows) | |
| static uint32_t g_initialize_count = 0; | |
| // Upper bound on the timeout used when running a utility expression with | |
| // only one thread allowed to run. On expiry, RunThreadPlan retries with all | |
| // threads, so this does not shorten the overall timeout. | |
| static std::chrono::microseconds GetLoaderOneThreadTimeout(Process *process) { | |
| constexpr std::chrono::seconds preferred(5); | |
| return std::min<std::chrono::microseconds>( | |
| preferred, | |
| std::chrono::microseconds(process->GetUtilityExpressionTimeout()) / 2); | |
| } | |
| namespace { | |
| #define LLDB_PROPERTIES_windows | |
| #include "PlatformWindowsProperties.inc" | |
| enum { | |
| #define LLDB_PROPERTIES_windows | |
| #include "PlatformWindowsPropertiesEnum.inc" | |
| }; | |
| class PluginProperties : public Properties { | |
| public: | |
| PluginProperties() { | |
| m_collection_sp = std::make_shared<OptionValueProperties>("windows"); | |
| m_collection_sp->Initialize(g_windows_properties_def); | |
| } | |
| bool DisableDebugHeap() const { | |
| return GetPropertyAtIndexAs<bool>(ePropertyDisableDebugHeap, true); | |
| } | |
| }; | |
| static PluginProperties &GetGlobalProperties() { | |
| static PluginProperties g_settings; | |
| return g_settings; | |
| } | |
| } // end of anonymous namespace | |
| PlatformSP PlatformWindows::CreateInstance(bool force, | |
| const lldb_private::ArchSpec *arch) { | |
| // The only time we create an instance is when we are creating a remote | |
| // windows platform | |
| const bool is_host = false; | |
| bool create = force; | |
| if (!create && arch && arch->IsValid()) { | |
| const llvm::Triple &triple = arch->GetTriple(); | |
| switch (triple.getVendor()) { | |
| case llvm::Triple::PC: | |
| create = true; | |
| break; | |
| case llvm::Triple::UnknownVendor: | |
| create = !arch->TripleVendorWasSpecified(); | |
| break; | |
| default: | |
| break; | |
| } | |
| if (create) { | |
| switch (triple.getOS()) { | |
| case llvm::Triple::Win32: | |
| break; | |
| case llvm::Triple::UnknownOS: | |
| create = arch->TripleOSWasSpecified(); | |
| break; | |
| default: | |
| create = false; | |
| break; | |
| } | |
| } | |
| } | |
| if (create) | |
| return PlatformSP(new PlatformWindows(is_host)); | |
| return PlatformSP(); | |
| } | |
| llvm::StringRef PlatformWindows::GetPluginDescriptionStatic(bool is_host) { | |
| return is_host ? "Local Windows user platform plug-in." | |
| : "Remote Windows user platform plug-in."; | |
| } | |
| void PlatformWindows::DebuggerInitialize(Debugger &debugger) { | |
| if (!PluginManager::GetSettingForPlatformPlugin(debugger, "windows")) { | |
| PluginManager::CreateSettingForPlatformPlugin( | |
| debugger, GetGlobalProperties().GetValueProperties(), | |
| "Properties for the Windows platform plugin.", | |
| /*is_global_property=*/true); | |
| } | |
| } | |
| void PlatformWindows::Initialize() { | |
| Platform::Initialize(); | |
| if (g_initialize_count++ == 0) { | |
| #if defined(_WIN32) | |
| // Force a host flag to true for the default platform object. | |
| PlatformSP default_platform_sp(new PlatformWindows(true)); | |
| default_platform_sp->SetSystemArchitecture(HostInfo::GetArchitecture()); | |
| Platform::SetHostPlatform(default_platform_sp); | |
| #endif | |
| PluginManager::RegisterPlugin( | |
| PlatformWindows::GetPluginNameStatic(false), | |
| PlatformWindows::GetPluginDescriptionStatic(false), | |
| PlatformWindows::CreateInstance, PlatformWindows::DebuggerInitialize); | |
| } | |
| } | |
| void PlatformWindows::Terminate() { | |
| if (g_initialize_count > 0) { | |
| if (--g_initialize_count == 0) { | |
| PluginManager::UnregisterPlugin(PlatformWindows::CreateInstance); | |
| } | |
| } | |
| Platform::Terminate(); | |
| } | |
| /// Default Constructor | |
| PlatformWindows::PlatformWindows(bool is_host) : RemoteAwarePlatform(is_host) { | |
| const auto &AddArch = [&](const ArchSpec &spec) { | |
| if (llvm::any_of(m_supported_architectures, [spec](const ArchSpec &rhs) { | |
| return spec.IsExactMatch(rhs); | |
| })) | |
| return; | |
| if (spec.IsValid()) | |
| m_supported_architectures.push_back(spec); | |
| }; | |
| AddArch(HostInfo::GetArchitecture(HostInfo::eArchKindDefault)); | |
| AddArch(HostInfo::GetArchitecture(HostInfo::eArchKind32)); | |
| AddArch(HostInfo::GetArchitecture(HostInfo::eArchKind64)); | |
| } | |
| Status PlatformWindows::ConnectRemote(Args &args) { | |
| if (IsHost()) | |
| return Status::FromErrorStringWithFormatv( | |
| "can't connect to the host platform '{0}', always connected", | |
| GetPluginName()); | |
| if (!m_remote_platform_sp) | |
| m_remote_platform_sp = | |
| platform_gdb_server::PlatformRemoteGDBServer::CreateInstance( | |
| /*force=*/true, nullptr); | |
| if (!m_remote_platform_sp) | |
| return Status::FromErrorString( | |
| "failed to create a 'remote-gdb-server' platform"); | |
| Status error = m_remote_platform_sp->ConnectRemote(args); | |
| if (error.Fail()) | |
| m_remote_platform_sp.reset(); | |
| return error; | |
| } | |
| uint32_t PlatformWindows::DoLoadImage(Process *process, | |
| const FileSpec &remote_file, | |
| const std::vector<std::string> *paths, | |
| Status &error, FileSpec *loaded_image) { | |
| DiagnosticManager diagnostics; | |
| if (loaded_image) | |
| loaded_image->Clear(); | |
| ThreadSP thread = process->GetThreadList().GetExpressionExecutionThread(); | |
| if (!thread) { | |
| error = Status::FromErrorString( | |
| "LoadLibrary error: no thread available to invoke LoadLibrary"); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| ExecutionContext context; | |
| thread->CalculateExecutionContext(context); | |
| llvm::Expected<UtilityFunction &> loader_or_err = | |
| process->GetLoadImageUtilityFunction( | |
| this, [&]() { return MakeLoadImageUtilityFunction(context); }); | |
| if (!loader_or_err) { | |
| error = Status::FromError(loader_or_err.takeError()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| FunctionCaller *invocation = loader_or_err->GetFunctionCaller(); | |
| if (!invocation) { | |
| error = Status::FromErrorString( | |
| "LoadLibrary error: could not get function caller"); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| /* Convert name */ | |
| llvm::SmallVector<llvm::UTF16, 261> name; | |
| if (!llvm::convertUTF8ToUTF16String(remote_file.GetPath(), name)) { | |
| error = Status::FromErrorString( | |
| "LoadLibrary error: could not convert path to UCS2"); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| name.emplace_back(L'\0'); | |
| /* Inject name paramter into inferior */ | |
| Status status; | |
| lldb::addr_t injected_name = | |
| process->AllocateMemory(name.size() * sizeof(llvm::UTF16), | |
| ePermissionsReadable | ePermissionsWritable, | |
| status); | |
| if (injected_name == LLDB_INVALID_ADDRESS) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: unable to allocate memory for name: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| llvm::scope_exit name_cleanup( | |
| [process, injected_name]() { process->DeallocateMemory(injected_name); }); | |
| process->WriteMemory(injected_name, name.data(), | |
| name.size() * sizeof(llvm::UTF16), status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: unable to write name: %s", status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| /* Inject paths parameter into inferior */ | |
| lldb::addr_t injected_paths{0x0}; | |
| std::optional<llvm::scope_exit<std::function<void()>>> paths_cleanup; | |
| if (paths) { | |
| llvm::SmallVector<llvm::UTF16, 261> search_paths; | |
| for (const auto &path : *paths) { | |
| if (path.empty()) | |
| continue; | |
| llvm::SmallVector<llvm::UTF16, 261> buffer; | |
| if (!llvm::convertUTF8ToUTF16String(path, buffer)) | |
| continue; | |
| search_paths.append(std::begin(buffer), std::end(buffer)); | |
| search_paths.emplace_back(L'\0'); | |
| } | |
| search_paths.emplace_back(L'\0'); | |
| injected_paths = | |
| process->AllocateMemory(search_paths.size() * sizeof(llvm::UTF16), | |
| ePermissionsReadable | ePermissionsWritable, | |
| status); | |
| if (injected_paths == LLDB_INVALID_ADDRESS) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: unable to allocate memory for paths: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| paths_cleanup.emplace([process, injected_paths]() { | |
| process->DeallocateMemory(injected_paths); | |
| }); | |
| process->WriteMemory(injected_paths, search_paths.data(), | |
| search_paths.size() * sizeof(llvm::UTF16), status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: unable to write paths: %s", status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| } | |
| /* Inject wszModulePath into inferior */ | |
| // Start with a MAX_PATH-sized buffer (enough for the vast majority of module | |
| // paths) and grow it on demand if GetModuleFileNameW reports truncation (see | |
| // the loop after the helper runs). | |
| unsigned injected_length = MAX_PATH; | |
| lldb::addr_t injected_module_path = process->AllocateMemory( | |
| (injected_length + 1) * sizeof(llvm::UTF16), | |
| ePermissionsReadable | ePermissionsWritable, status); | |
| if (injected_module_path == LLDB_INVALID_ADDRESS) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: unable to allocate memory for module location: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| llvm::scope_exit injected_module_path_cleanup( | |
| [process, injected_module_path]() { | |
| process->DeallocateMemory(injected_module_path); | |
| }); | |
| /* Inject __lldb_LoadLibraryResult into inferior */ | |
| const uint32_t word_size = process->GetAddressByteSize(); | |
| lldb::addr_t injected_result = | |
| process->AllocateMemory(3 * word_size, | |
| ePermissionsReadable | ePermissionsWritable, | |
| status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not allocate memory for result: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| llvm::scope_exit result_cleanup([process, injected_result]() { | |
| process->DeallocateMemory(injected_result); | |
| }); | |
| std::vector<lldb::addr_t> grown_path_buffers; | |
| llvm::scope_exit grown_path_cleanup([&]() { | |
| for (lldb::addr_t buffer : grown_path_buffers) | |
| process->DeallocateMemory(buffer); | |
| }); | |
| process->WritePointerToMemory(injected_result, 0, status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not initialize result: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| process->WritePointerToMemory(injected_result + word_size, | |
| injected_module_path, status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not initialize result: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| // XXX(compnerd) should we use the compiler to get the sizeof(unsigned)? | |
| process->WriteScalarToMemory(injected_result + 2 * word_size, | |
| Scalar{injected_length}, sizeof(unsigned), | |
| status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not initialize result: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| /* Setup Formal Parameters */ | |
| ValueList parameters = invocation->GetArgumentValues(); | |
| parameters.GetValueAtIndex(0)->GetScalar() = injected_name; | |
| parameters.GetValueAtIndex(1)->GetScalar() = injected_paths; | |
| parameters.GetValueAtIndex(2)->GetScalar() = injected_result; | |
| lldb::addr_t injected_parameters = LLDB_INVALID_ADDRESS; | |
| diagnostics.Clear(); | |
| if (!invocation->WriteFunctionArguments(context, injected_parameters, | |
| parameters, diagnostics)) { | |
| error = Status::FromError(diagnostics.GetAsError( | |
| eExpressionSetupError, | |
| "LoadLibrary error: unable to write function parameters:")); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| llvm::scope_exit parameter_cleanup( | |
| [invocation, &context, injected_parameters]() { | |
| invocation->DeallocateFunctionResults(context, injected_parameters); | |
| }); | |
| TypeSystemClangSP scratch_ts_sp = | |
| ScratchTypeSystemClang::GetForTarget(process->GetTarget()); | |
| if (!scratch_ts_sp) { | |
| error = Status::FromErrorString( | |
| "LoadLibrary error: unable to get (clang) type system"); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| /* Setup Return Type */ | |
| CompilerType VoidPtrTy = | |
| scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); | |
| Value value; | |
| value.SetCompilerType(VoidPtrTy); | |
| /* Invoke expression */ | |
| EvaluateExpressionOptions options; | |
| options.SetExecutionPolicy(eExecutionPolicyAlways); | |
| options.SetLanguage(eLanguageTypeC_plus_plus); | |
| options.SetIgnoreBreakpoints(true); | |
| options.SetUnwindOnError(true); | |
| // LoadLibraryEx{A,W}/FreeLibrary cannot raise exceptions which we can handle. | |
| // They may potentially throw SEH exceptions which we do not know how to | |
| // handle currently. | |
| options.SetTrapExceptions(false); | |
| options.SetTimeout(process->GetUtilityExpressionTimeout()); | |
| options.SetOneThreadTimeout(GetLoaderOneThreadTimeout(process)); | |
| options.SetIsForUtilityExpr(true); | |
| ExpressionResults result = | |
| invocation->ExecuteFunction(context, &injected_parameters, options, | |
| diagnostics, value); | |
| if (result != eExpressionCompleted) { | |
| error = Status::FromError(diagnostics.GetAsError( | |
| eExpressionSetupError, | |
| llvm::formatv("failed to execute LoadLibrary helper " | |
| "({0}):", | |
| toString(result)) | |
| .str())); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| /* Read result */ | |
| llvm::Expected<lldb::addr_t> token_or_err = | |
| process->ReadPointerFromMemory(injected_result); | |
| if (!token_or_err) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "LoadLibrary error: could not read the result: {0}", | |
| llvm::fmt_consume(token_or_err.takeError())); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| lldb::addr_t token = *token_or_err; | |
| if (!token) { | |
| // ErrorCode is a 4-byte `unsigned` field in __lldb_LoadLibraryResult. | |
| uint64_t error_code = process->ReadUnsignedIntegerFromMemory( | |
| injected_result + 2 * word_size + sizeof(unsigned), sizeof(unsigned), 0, | |
| status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not read error status: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| error = Status::FromErrorStringWithFormat("LoadLibrary Error: %" PRIu64, | |
| error_code); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| lldb::addr_t module_path_addr = injected_module_path; | |
| unsigned capacity = injected_length; | |
| uint32_t path_length = process->ReadUnsignedIntegerFromMemory( | |
| injected_result + 2 * word_size, sizeof(unsigned), 0, status); | |
| while (status.Success() && path_length >= capacity && | |
| capacity < PATHCCH_MAX_CCH) { | |
| capacity = std::min<unsigned>(capacity * 2, PATHCCH_MAX_CCH); | |
| lldb::addr_t buffer = process->AllocateMemory( | |
| (capacity + 1) * sizeof(llvm::UTF16), | |
| ePermissionsReadable | ePermissionsWritable, status); | |
| if (buffer == LLDB_INVALID_ADDRESS || status.Fail()) | |
| break; | |
| grown_path_buffers.push_back(buffer); | |
| process->WritePointerToMemory(injected_result + word_size, buffer, status); | |
| if (status.Fail()) | |
| break; | |
| process->WriteScalarToMemory(injected_result + 2 * word_size, | |
| Scalar{capacity}, sizeof(unsigned), status); | |
| if (status.Fail()) | |
| break; | |
| diagnostics.Clear(); | |
| if (invocation->ExecuteFunction(context, &injected_parameters, options, | |
| diagnostics, value) != eExpressionCompleted) | |
| break; | |
| module_path_addr = buffer; | |
| path_length = process->ReadUnsignedIntegerFromMemory( | |
| injected_result + 2 * word_size, sizeof(unsigned), 0, status); | |
| } | |
| llvm::SmallVector<llvm::UTF16, MAX_PATH> wide_path(path_length); | |
| if (path_length) | |
| process->ReadMemory(module_path_addr, wide_path.data(), | |
| path_length * sizeof(llvm::UTF16), status); | |
| if (status.Fail()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "LoadLibrary error: could not read module path: %s", | |
| status.AsCString()); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| std::string module_path; | |
| if (!llvm::convertUTF16ToUTF8String( | |
| llvm::ArrayRef<llvm::UTF16>(wide_path.data(), wide_path.size()), | |
| module_path)) { | |
| error = Status::FromErrorString( | |
| "LoadLibrary error: could not convert module path to UTF-8"); | |
| return LLDB_INVALID_IMAGE_TOKEN; | |
| } | |
| if (loaded_image) | |
| loaded_image->SetFile(module_path, llvm::sys::path::Style::native); | |
| return process->AddImageToken(token); | |
| } | |
| Status PlatformWindows::UnloadImage(Process *process, uint32_t image_token) { | |
| const addr_t address = process->GetImagePtrFromToken(image_token); | |
| if (address == LLDB_INVALID_ADDRESS) | |
| return Status::FromErrorString("invalid image token"); | |
| StreamString expression; | |
| expression.Printf("FreeLibrary((HMODULE)0x%" PRIx64 ")", address); | |
| ValueObjectSP value; | |
| Status result = | |
| EvaluateLoaderExpression(process, expression.GetData(), value); | |
| if (result.Fail()) | |
| return result; | |
| if (value->GetError().Fail()) | |
| return value->GetError().Clone(); | |
| Scalar scalar; | |
| if (value->ResolveValue(scalar)) { | |
| if (scalar.UInt(1)) | |
| return Status::FromErrorStringWithFormat("expression failed: \"%s\"", | |
| expression.GetData()); | |
| process->ResetImageToken(image_token); | |
| } | |
| return Status(); | |
| } | |
| Status PlatformWindows::DisconnectRemote() { | |
| Status error; | |
| if (IsHost()) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "can't disconnect from the host platform '{0}', always connected", | |
| GetPluginName()); | |
| } else { | |
| if (m_remote_platform_sp) | |
| error = m_remote_platform_sp->DisconnectRemote(); | |
| else | |
| error = | |
| Status::FromErrorString("the platform is not currently connected"); | |
| } | |
| return error; | |
| } | |
| ProcessSP PlatformWindows::DebugProcess(ProcessLaunchInfo &launch_info, | |
| Debugger &debugger, Target &target, | |
| Status &error) { | |
| // Windows has special considerations that must be followed when launching or | |
| // attaching to a process. The key requirement is that when launching or | |
| // attaching to a process, you must do it from the same the thread that will | |
| // go into a permanent loop which will then receive debug events from the | |
| // process. In particular, this means we can't use any of LLDB's generic | |
| // mechanisms to do it for us, because it doesn't have the special knowledge | |
| // required for setting up the background thread or passing the right flags. | |
| // | |
| // Another problem is that LLDB's standard model for debugging a process | |
| // is to first launch it, have it stop at the entry point, and then attach to | |
| // it. In Windows this doesn't quite work, you have to specify as an | |
| // argument to CreateProcess() that you're going to debug the process. So we | |
| // override DebugProcess here to handle this. Launch operations go directly | |
| // to the process plugin, and attach operations almost go directly to the | |
| // process plugin (but we hijack the events first). In essence, we | |
| // encapsulate all the logic of Launching and Attaching in the process | |
| // plugin, and PlatformWindows::DebugProcess is just a pass-through to get to | |
| // the process plugin. | |
| if (IsRemote()) { | |
| if (m_remote_platform_sp) | |
| return m_remote_platform_sp->DebugProcess(launch_info, debugger, target, | |
| error); | |
| else | |
| error = | |
| Status::FromErrorString("the platform is not currently connected"); | |
| } | |
| if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) { | |
| // This is a process attach. Don't need to launch anything. | |
| ProcessAttachInfo attach_info(launch_info); | |
| return Attach(attach_info, debugger, &target, error); | |
| } | |
| Environment &env = launch_info.GetEnvironment(); | |
| if (GetGlobalProperties().DisableDebugHeap() && | |
| !env.contains("_NO_DEBUG_HEAP")) { | |
| env.try_emplace("_NO_DEBUG_HEAP", "1"); | |
| } | |
| ProcessSP process_sp = | |
| target.CreateProcess(launch_info.GetListener(), | |
| launch_info.GetProcessPluginName(), nullptr, false); | |
| if (!process_sp) | |
| return nullptr; | |
| process_sp->HijackProcessEvents(launch_info.GetHijackListener()); | |
| process_sp->SetShadowListener(launch_info.GetShadowListener()); | |
| // We need to launch and attach to the process. | |
| launch_info.GetFlags().Set(eLaunchFlagDebug); | |
| error = process_sp->Launch(launch_info); | |
| #ifdef _WIN32 | |
| if (error.Success()) { | |
| process_sp->SetPseudoConsoleHandle(); | |
| } else { | |
| Log *log = GetLog(LLDBLog::Platform); | |
| LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__, | |
| error.AsCString()); | |
| } | |
| #endif | |
| return process_sp; | |
| } | |
| lldb::ProcessSP PlatformWindows::Attach(ProcessAttachInfo &attach_info, | |
| Debugger &debugger, Target *target, | |
| Status &error) { | |
| error.Clear(); | |
| lldb::ProcessSP process_sp; | |
| if (!IsHost()) { | |
| if (m_remote_platform_sp) | |
| process_sp = | |
| m_remote_platform_sp->Attach(attach_info, debugger, target, error); | |
| else | |
| error = | |
| Status::FromErrorString("the platform is not currently connected"); | |
| return process_sp; | |
| } | |
| if (target == nullptr) { | |
| TargetSP new_target_sp; | |
| error = debugger.GetTargetList().CreateTarget( | |
| debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); | |
| target = new_target_sp.get(); | |
| } | |
| if (!target || error.Fail()) | |
| return process_sp; | |
| process_sp = | |
| target->CreateProcess(attach_info.GetListenerForProcess(debugger), | |
| attach_info.GetProcessPluginName(), nullptr, false); | |
| if (process_sp) { | |
| process_sp->HijackProcessEvents(attach_info.GetHijackListener()); | |
| process_sp->SetShadowListener(attach_info.GetShadowListener()); | |
| error = process_sp->Attach(attach_info); | |
| } | |
| return process_sp; | |
| } | |
| void PlatformWindows::GetStatus(Stream &strm) { | |
| Platform::GetStatus(strm); | |
| #ifdef _WIN32 | |
| llvm::VersionTuple version = HostInfo::GetOSVersion(); | |
| strm << " Host: Windows " << version.getAsString() << '\n'; | |
| #endif | |
| } | |
| bool PlatformWindows::CanDebugProcess() { return true; } | |
| std::string PlatformWindows::GetFullNameForDylib(llvm::StringRef basename) { | |
| if (basename.empty()) | |
| return basename.str(); | |
| return llvm::formatv("{0}.dll", basename).str(); | |
| } | |
| size_t | |
| PlatformWindows::GetSoftwareBreakpointTrapOpcode(Target &target, | |
| BreakpointSite *bp_site) { | |
| ArchSpec arch = target.GetArchitecture(); | |
| assert(arch.IsValid()); | |
| const uint8_t *trap_opcode = nullptr; | |
| size_t trap_opcode_size = 0; | |
| switch (arch.GetMachine()) { | |
| case llvm::Triple::aarch64: { | |
| static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e, 0xd4}; // brk #0xf000 | |
| trap_opcode = g_aarch64_opcode; | |
| trap_opcode_size = sizeof(g_aarch64_opcode); | |
| if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size)) | |
| return trap_opcode_size; | |
| return 0; | |
| } break; | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: { | |
| static const uint8_t g_thumb_opcode[] = {0xfe, 0xde}; // udf #0xfe | |
| trap_opcode = g_thumb_opcode; | |
| trap_opcode_size = sizeof(g_thumb_opcode); | |
| if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size)) | |
| return trap_opcode_size; | |
| return 0; | |
| } break; | |
| default: | |
| return Platform::GetSoftwareBreakpointTrapOpcode(target, bp_site); | |
| } | |
| } | |
| llvm::Expected<std::unique_ptr<UtilityFunction>> | |
| PlatformWindows::MakeLoadImageUtilityFunction(ExecutionContext &context) { | |
| // FIXME(compnerd) `-fdeclspec` is not passed to the clang instance? | |
| static constexpr const char kLoaderDecls[] = R"( | |
| extern "C" { | |
| // errhandlingapi.h | |
| // `LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS` | |
| // | |
| // Directories in the standard search path are not searched. This value cannot | |
| // be combined with `LOAD_WITH_ALTERED_SEARCH_PATH`. | |
| // | |
| // This value represents the recommended maximum number of directories an | |
| // application should include in its DLL search path. | |
| #define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000 | |
| // If this value is used, and lpFileName specifies an absolute path, the system | |
| // uses the alternate file search strategy to find associated executable | |
| // modules. | |
| #define LOAD_WITH_ALTERED_SEARCH_PATH 0x00000008 | |
| // WINBASEAPI DWORD WINAPI GetLastError(VOID); | |
| /* __declspec(dllimport) */ uint32_t __stdcall GetLastError(); | |
| // libloaderapi.h | |
| // WINBASEAPI DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory(LPCWSTR); | |
| /* __declspec(dllimport) */ void * __stdcall AddDllDirectory(const wchar_t *); | |
| // WINBASEAPI BOOL WINAPI FreeModule(HMODULE); | |
| /* __declspec(dllimport) */ int __stdcall FreeModule(void *hLibModule); | |
| // WINBASEAPI DWORD WINAPI GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize); | |
| /* __declspec(dllimport) */ uint32_t GetModuleFileNameW(void *, wchar_t *, uint32_t); | |
| // WINBASEAPI HMODULE WINAPI LoadLibraryExW(LPCWSTR, HANDLE, DWORD); | |
| /* __declspec(dllimport) */ void * __stdcall LoadLibraryExW(const wchar_t *, void *, uint32_t); | |
| // corecrt_wstring.h | |
| // _ACRTIMP size_t __cdecl wcslen(wchar_t const *_String); | |
| /* __declspec(dllimport) */ size_t __cdecl wcslen(const wchar_t *); | |
| // lldb specific code | |
| struct __lldb_LoadLibraryResult { | |
| void *ImageBase; | |
| wchar_t *ModulePath; | |
| unsigned Length; | |
| unsigned ErrorCode; | |
| }; | |
| _Static_assert(sizeof(struct __lldb_LoadLibraryResult) <= 3 * sizeof(void *), | |
| "__lldb_LoadLibraryResult size mismatch"); | |
| void * __lldb_LoadLibraryHelper(const wchar_t *name, const wchar_t *paths, | |
| __lldb_LoadLibraryResult *result) { | |
| // When the caller presets ImageBase the module is already loaded and we are | |
| // only re-querying its path with a larger buffer. Skip LoadLibrary in that | |
| // case so we do not take an extra reference on the module. | |
| if (result->ImageBase == nullptr) { | |
| for (const wchar_t *path = paths; path && *path; ) { | |
| (void)AddDllDirectory(path); | |
| path += wcslen(path) + 1; | |
| } | |
| result->ImageBase = LoadLibraryExW(name, nullptr, | |
| LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); | |
| // Fallback: if the AddDllDirectory + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS path | |
| // failed to find the library, iterate the search paths ourselves and | |
| // load by absolute path using LOAD_WITH_ALTERED_SEARCH_PATH, which makes | |
| // Windows use the loaded DLL's own directory to resolve its sibling imports. | |
| if (result->ImageBase == nullptr) { | |
| wchar_t full[4096]; | |
| for (const wchar_t *path = paths; path && *path; path += wcslen(path) + 1) { | |
| size_t plen = wcslen(path); | |
| size_t nlen = wcslen(name); | |
| // Need room for: path + '\\' + name + '\0' | |
| if (plen + 1 + nlen + 1 > 4096) | |
| continue; | |
| wchar_t *p = full; | |
| for (size_t i = 0; i < plen; ++i) | |
| *p++ = path[i]; | |
| *p++ = L'\\'; | |
| for (size_t i = 0; i <= nlen; ++i) // Copy name including trailing '\0'. | |
| *p++ = name[i]; | |
| result->ImageBase = LoadLibraryExW(full, nullptr, | |
| LOAD_WITH_ALTERED_SEARCH_PATH); | |
| if (result->ImageBase != nullptr) | |
| break; | |
| } | |
| } | |
| } | |
| if (result->ImageBase == nullptr) | |
| result->ErrorCode = GetLastError(); | |
| else | |
| result->Length = GetModuleFileNameW(result->ImageBase, result->ModulePath, | |
| result->Length); | |
| return result->ImageBase; | |
| } | |
| } | |
| )"; | |
| static constexpr const char kName[] = "__lldb_LoadLibraryHelper"; | |
| ProcessSP process = context.GetProcessSP(); | |
| Target &target = process->GetTarget(); | |
| auto function = target.CreateUtilityFunction(std::string{kLoaderDecls}, kName, | |
| eLanguageTypeC_plus_plus, | |
| context); | |
| if (!function) { | |
| std::string error = llvm::toString(function.takeError()); | |
| return llvm::createStringError( | |
| "LoadLibrary error: could not create utility function: %s", | |
| error.c_str()); | |
| } | |
| TypeSystemClangSP scratch_ts_sp = | |
| ScratchTypeSystemClang::GetForTarget(target); | |
| if (!scratch_ts_sp) | |
| return llvm::createStringError("LoadLibrary error: no scratch type system"); | |
| CompilerType VoidPtrTy = | |
| scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); | |
| CompilerType WCharPtrTy = | |
| scratch_ts_sp->GetBasicType(eBasicTypeWChar).GetPointerType(); | |
| ValueList parameters; | |
| Value value; | |
| value.SetValueType(Value::ValueType::Scalar); | |
| value.SetCompilerType(WCharPtrTy); | |
| parameters.PushValue(value); // name | |
| parameters.PushValue(value); // paths | |
| value.SetCompilerType(VoidPtrTy); | |
| parameters.PushValue(value); // result | |
| Status error; | |
| std::unique_ptr<UtilityFunction> utility{std::move(*function)}; | |
| utility->MakeFunctionCaller(VoidPtrTy, parameters, context.GetThreadSP(), | |
| error); | |
| if (error.Fail()) | |
| return llvm::createStringError( | |
| "LoadLibrary error: could not create function caller: %s", | |
| error.AsCString()); | |
| if (!utility->GetFunctionCaller()) | |
| return llvm::createStringError( | |
| "LoadLibrary error: could not get function caller"); | |
| return utility; | |
| } | |
| Status PlatformWindows::EvaluateLoaderExpression(Process *process, | |
| const char *expression, | |
| ValueObjectSP &value) { | |
| // FIXME(compnerd) `-fdeclspec` is not passed to the clang instance? | |
| static constexpr const char kLoaderDecls[] = R"( | |
| extern "C" { | |
| // libloaderapi.h | |
| // WINBASEAPI DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory(LPCWSTR); | |
| /* __declspec(dllimport) */ void * __stdcall AddDllDirectory(const wchar_t *); | |
| // WINBASEAPI BOOL WINAPI FreeModule(HMODULE); | |
| /* __declspec(dllimport) */ int __stdcall FreeModule(void *); | |
| // WINBASEAPI DWORD WINAPI GetModuleFileNameW(HMODULE, LPWSTR, DWORD); | |
| /* __declspec(dllimport) */ uint32_t GetModuleFileNameW(void *, wchar_t *, uint32_t); | |
| // WINBASEAPI HMODULE WINAPI LoadLibraryExW(LPCWSTR, HANDLE, DWORD); | |
| /* __declspec(dllimport) */ void * __stdcall LoadLibraryExW(const wchar_t *, void *, uint32_t); | |
| } | |
| )"; | |
| if (DynamicLoader *loader = process->GetDynamicLoader()) { | |
| Status result = loader->CanLoadImage(); | |
| if (result.Fail()) | |
| return result; | |
| } | |
| ThreadSP thread = process->GetThreadList().GetExpressionExecutionThread(); | |
| if (!thread) | |
| return Status::FromErrorString("selected thread is invalid"); | |
| StackFrameSP frame = thread->GetStackFrameAtIndex(0); | |
| if (!frame) | |
| return Status::FromErrorString("frame 0 is invalid"); | |
| ExecutionContext context; | |
| frame->CalculateExecutionContext(context); | |
| EvaluateExpressionOptions options; | |
| options.SetUnwindOnError(true); | |
| options.SetIgnoreBreakpoints(true); | |
| options.SetExecutionPolicy(eExecutionPolicyAlways); | |
| options.SetLanguage(eLanguageTypeC_plus_plus); | |
| // LoadLibraryEx{A,W}/FreeLibrary cannot raise exceptions which we can handle. | |
| // They may potentially throw SEH exceptions which we do not know how to | |
| // handle currently. | |
| options.SetTrapExceptions(false); | |
| options.SetTimeout(process->GetUtilityExpressionTimeout()); | |
| options.SetOneThreadTimeout(GetLoaderOneThreadTimeout(process)); | |
| ExpressionResults result = UserExpression::Evaluate( | |
| context, options, expression, kLoaderDecls, value); | |
| if (result != eExpressionCompleted) | |
| return value | |
| ? value->GetError().Clone() | |
| : Status::FromErrorStringWithFormatv( | |
| "failed to execute loader helper ({0})", toString(result)); | |
| if (value && value->GetError().Fail()) | |
| return value->GetError().Clone(); | |
| return Status(); | |
| } |