diff --git a/lldb/source/Plugins/Platform/Windows/CMakeLists.txt b/lldb/source/Plugins/Platform/Windows/CMakeLists.txt --- a/lldb/source/Plugins/Platform/Windows/CMakeLists.txt +++ b/lldb/source/Plugins/Platform/Windows/CMakeLists.txt @@ -6,4 +6,7 @@ lldbCore lldbHost lldbTarget + + LINK_COMPONENTS + Support ) diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.h b/lldb/source/Plugins/Platform/Windows/PlatformWindows.h --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.h +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.h @@ -44,6 +44,15 @@ lldb_private::Status DisconnectRemote() override; + uint32_t DoLoadImage(lldb_private::Process *process, + const lldb_private::FileSpec &remote_file, + const std::vector *paths, + lldb_private::Status &error, + lldb_private::FileSpec *loaded_path) override; + + lldb_private::Status UnloadImage(lldb_private::Process *process, + uint32_t image_token) override; + lldb::ProcessSP DebugProcess(lldb_private::ProcessLaunchInfo &launch_info, lldb_private::Debugger &debugger, lldb_private::Target &target, @@ -71,6 +80,15 @@ BreakpointSite *bp_site) override; std::vector m_supported_architectures; + +private: + std::unique_ptr + MakeLoadImageUtilityFunction(lldb_private::ExecutionContext &context, + lldb_private::Status &status); + + lldb_private::Status EvaluateLoaderExpression(lldb_private::Process *process, + const char *expression, + lldb::ValueObjectSP &value); }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp --- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp +++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp @@ -19,10 +19,19 @@ #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/Process.h" #include "lldb/Utility/Status.h" +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/ConvertUTF.h" + using namespace lldb; using namespace lldb_private; @@ -151,6 +160,281 @@ return error; } +uint32_t PlatformWindows::DoLoadImage(Process *process, + const FileSpec &remote_file, + const std::vector *paths, + Status &error, FileSpec *loaded_image) { + DiagnosticManager diagnostics; + + if (loaded_image) + loaded_image->Clear(); + + ThreadSP thread = process->GetThreadList().GetExpressionExecutionThread(); + if (!thread) { + error.SetErrorString("LoadLibrary error: no thread available to invoke LoadLibrary"); + return LLDB_INVALID_IMAGE_TOKEN; + } + + ExecutionContext context; + thread->CalculateExecutionContext(context); + + Status status; + UtilityFunction *loader = + process->GetLoadImageUtilityFunction(this, [&]() -> std::unique_ptr { + return MakeLoadImageUtilityFunction(context, status); + }); + if (loader == nullptr) + return LLDB_INVALID_IMAGE_TOKEN; + + FunctionCaller *invocation = loader->GetFunctionCaller(); + if (!invocation) { + error.SetErrorString("LoadLibrary error: could not get function caller"); + return LLDB_INVALID_IMAGE_TOKEN; + } + + /* Convert name */ + llvm::SmallVector name; + if (!llvm::convertUTF8ToUTF16String(remote_file.GetPath(), name)) { + error.SetErrorString("LoadLibrary error: could not convert path to UCS2"); + return LLDB_INVALID_IMAGE_TOKEN; + } + + /* Inject name paramter into inferior */ + lldb::addr_t injected_name = + process->AllocateMemory(name.size() * sizeof(llvm::UTF16), + ePermissionsReadable | ePermissionsWritable, + status); + if (injected_name == LLDB_INVALID_ADDRESS) { + error.SetErrorStringWithFormat("LoadLibrary error: unable to allocate memory for name: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + auto name_cleanup = llvm::make_scope_exit([process, injected_name]() { + process->DeallocateMemory(injected_name); + }); + + process->WriteMemory(injected_name, name.data(), + name.size() * sizeof(llvm::UTF16), status); + if (status.Fail()) { + error.SetErrorStringWithFormat("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}; + llvm::Optional>> paths_cleanup; + if (paths) { + llvm::SmallVector search_paths; + + for (const auto &path : *paths) { + if (path.empty()) + continue; + + llvm::SmallVector buffer; + if (!llvm::convertUTF8ToUTF16String(path, buffer)) + continue; + + search_paths.append(std::begin(buffer), std::end(buffer)); + } + 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.SetErrorStringWithFormat("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.SetErrorStringWithFormat("LoadLibrary error: unable to write paths: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + } + + /* Inject wszModulePath into inferior */ + // FIXME(compnerd) should do something better for the length? + // GetModuleFileNameA is likely limited to PATH_MAX rather than the NT path + // limit. + unsigned injected_length = 261; + + lldb::addr_t injected_module_path = + process->AllocateMemory(injected_length + 1, + ePermissionsReadable | ePermissionsWritable, + status); + if (injected_module_path == LLDB_INVALID_ADDRESS) { + error.SetErrorStringWithFormat("LoadLibrary error: unable to allocate memory for module location: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + auto injected_module_path_cleanup = + llvm::make_scope_exit([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.SetErrorStringWithFormat("LoadLibrary error: could not allocate memory for result: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + auto result_cleanup = llvm::make_scope_exit([process, injected_result]() { + process->DeallocateMemory(injected_result); + }); + + process->WritePointerToMemory(injected_result + word_size, + injected_module_path, status); + if (status.Fail()) { + error.SetErrorStringWithFormat("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.SetErrorStringWithFormat("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.SetErrorStringWithFormat("LoadLibrary error: unable to write function parameters: %s", + diagnostics.GetString().c_str()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + auto parameter_cleanup = llvm::make_scope_exit([invocation, &context, injected_parameters]() { + invocation->DeallocateFunctionResults(context, injected_parameters); + }); + + TypeSystemClang *ast = + ScratchTypeSystemClang::GetForTarget(process->GetTarget()); + if (!ast) { + error.SetErrorString("LoadLibrary error: unable to get (clang) type system"); + return LLDB_INVALID_IMAGE_TOKEN; + } + + /* Setup Return Type */ + CompilerType VoidPtrTy = ast->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.SetIsForUtilityExpr(true); + + ExpressionResults result = + invocation->ExecuteFunction(context, &injected_parameters, options, + diagnostics, value); + if (result != eExpressionCompleted) { + error.SetErrorStringWithFormat("LoadLibrary error: failed to execute LoadLibrary helper: %s", + diagnostics.GetString().c_str()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + /* Read result */ + lldb::addr_t token = process->ReadPointerFromMemory(injected_result, status); + if (status.Fail()) { + error.SetErrorStringWithFormat("LoadLibrary error: could not read the result: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + if (token == NULL) { + // XXX(compnerd) should we use the compiler to get the sizeof(unsigned)? + uint64_t error_code = + process->ReadUnsignedIntegerFromMemory(injected_result + 2 * word_size + sizeof(unsigned), + word_size, ERROR_SUCCESS, status); + if (status.Fail()) { + error.SetErrorStringWithFormat("LoadLibrary error: could not read error status: %s", + status.AsCString()); + return LLDB_INVALID_IMAGE_TOKEN; + } + + error.SetErrorStringWithFormat("LoadLibrary Error: %lu", error_code); + return LLDB_INVALID_IMAGE_TOKEN; + } + + std::string module_path; + process->ReadCStringFromMemory(injected_module_path, module_path, status); + if (status.Fail()) { + error.SetErrorStringWithFormat("LoadLibrary error: could not read module path: %s", + status.AsCString()); + 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("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(); + + Scalar scalar; + if (value->ResolveValue(scalar)) { + if (scalar.UInt(1)) + return Status("expression failed: \"%s\"", expression.GetData()); + process->ResetImageToken(image_token); + } + + return Status(); +} + Status PlatformWindows::DisconnectRemote() { Status error; @@ -307,3 +591,188 @@ return Platform::GetSoftwareBreakpointTrapOpcode(target, bp_site); } } + +std::unique_ptr +PlatformWindows::MakeLoadImageUtilityFunction(ExecutionContext &context, + Status &status) { + // 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 + +// 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 GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize); +/* __declspec(dllimport) */ uint32_t GetModuleFileNameA(void *, char *, 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; + char *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) { + for (const wchar_t *path = paths; path; ) { + (void)AddDllDirectory(path); + path += wcslen(path) + 1; + } + + result->ImageBase = LoadLibraryExW(name, nullptr, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + if (result->ImageBase == nullptr) + result->ErrorCode = GetLastError(); + else + result->Length = GetModuleFileNameA(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()); + status.SetErrorStringWithFormat("LoadLibrary error: could not create utility function: %s", + error.c_str()); + return nullptr; + } + + TypeSystemClang *ast = ScratchTypeSystemClang::GetForTarget(target); + if (!ast) + return nullptr; + + CompilerType VoidPtrTy = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); + CompilerType WCharPtrTy = ast->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 utility{std::move(*function)}; + utility->MakeFunctionCaller(VoidPtrTy, parameters, context.GetThreadSP(), + error); + if (error.Fail()) { + status.SetErrorStringWithFormat("LoadLibrary error: could not create function caller: %s", + error.AsCString()); + return nullptr; + } + + if (!utility->GetFunctionCaller()) { + status.SetErrorString("LoadLibrary error: could not get function caller"); + return nullptr; + } + + 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 GetModuleFileNameA(HMODULE, LPSTR, DWORD); +/* __declspec(dllimport) */ uint32_t GetModuleFileNameA(void *, char *, 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("selected thread is invalid"); + + StackFrameSP frame = thread->GetStackFrameAtIndex(0); + if (!frame) + return Status("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()); + + Status error; + ExpressionResults result = UserExpression::Evaluate( + context, options, expression, kLoaderDecls, value, error); + if (result != eExpressionCompleted) + return error; + + if (value->GetError().Fail()) + return value->GetError(); + + return Status(); +} diff --git a/lldb/test/Shell/Process/Windows/process_load.cpp b/lldb/test/Shell/Process/Windows/process_load.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/Process/Windows/process_load.cpp @@ -0,0 +1,12 @@ +// clang-format off + +// REQUIRES: system-windows +// RUN: %build --compiler=clang-cl -o %t.exe -- %s +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 %lldb -f %t.exe -o "b main" -o "process launch" -o "process load kernel32.dll" | FileCheck %s + +int main(int argc, char *argv[]) { + return 0; +} + +// CHECK: "Loading "kernel32.dll"...ok{{.*}} +// CHECK: Image 0 loaded.