//===-- 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 <cstdio>
#include <optional>
#if defined(_WIN32)
#include "lldb/Host/windows/windows.h"
#include <winsock2.h>
#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/Status.h"

#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/ConvertUTF.h"

using namespace lldb;
using namespace lldb_private;

LLDB_PLUGIN_DEFINE(PlatformWindows)

static uint32_t g_initialize_count = 0;

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::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);
  }
}

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) {
  Status error;
  if (IsHost()) {
    error = Status::FromErrorStringWithFormatv(
        "can't connect to the host platform '{0}', always connected",
        GetPluginName());
  } else {
    if (!m_remote_platform_sp)
      m_remote_platform_sp =
          platform_gdb_server::PlatformRemoteGDBServer::CreateInstance(
              /*force=*/true, nullptr);

    if (m_remote_platform_sp) {
      if (error.Success()) {
        if (m_remote_platform_sp) {
          error = m_remote_platform_sp->ConnectRemote(args);
        } else {
          error = Status::FromErrorString(
              "\"platform connect\" takes a single argument: <connect-url>");
        }
      }
    } else
      error = Status::FromErrorString(
          "failed to create a 'remote-gdb-server' platform");

    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);

  Status status;
  UtilityFunction *loader =
      process->GetLoadImageUtilityFunction(this, [&]() -> std::unique_ptr<UtilityFunction> {
        return MakeLoadImageUtilityFunction(context, status);
      });
  if (loader == nullptr)
    return LLDB_INVALID_IMAGE_TOKEN;

  FunctionCaller *invocation = loader->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 */
  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;
  }

  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 = 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::detail::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 */
  // 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 = Status::FromErrorStringWithFormat(
        "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 = Status::FromErrorStringWithFormat(
        "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 = 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;
  }

  auto parameter_cleanup =
      llvm::make_scope_exit([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.SetIsForUtilityExpr(true);

  ExpressionResults result =
      invocation->ExecuteFunction(context, &injected_parameters, options,
                                  diagnostics, value);
  if (result != eExpressionCompleted) {
    error = Status::FromError(diagnostics.GetAsError(
        eExpressionSetupError,
        "LoadLibrary error: failed to execute LoadLibrary helper:"));
    return LLDB_INVALID_IMAGE_TOKEN;
  }

  /* Read result */
  lldb::addr_t token = process->ReadPointerFromMemory(injected_result, status);
  if (status.Fail()) {
    error = Status::FromErrorStringWithFormat(
        "LoadLibrary error: could not read the result: %s", status.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }

  if (!token) {
    // 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, 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;
  }

  std::string module_path;
  process->ReadCStringFromMemory(injected_module_path, module_path, status);
  if (status.Fail()) {
    error = Status::FromErrorStringWithFormat(
        "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_IMAGE_TOKEN)
    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);
  }

  ProcessSP process_sp =
      target.CreateProcess(launch_info.GetListener(),
                           launch_info.GetProcessPluginName(), nullptr, false);

  process_sp->HijackProcessEvents(launch_info.GetHijackListener());

  // We need to launch and attach to the process.
  launch_info.GetFlags().Set(eLaunchFlagDebug);
  if (process_sp)
    error = process_sp->Launch(launch_info);

  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;
    FileSpec emptyFileSpec;
    ArchSpec emptyArchSpec;

    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);

  process_sp->HijackProcessEvents(attach_info.GetHijackListener());
  if (process_sp)
    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; }

ConstString PlatformWindows::GetFullNameForDylib(ConstString basename) {
  if (basename.IsEmpty())
    return basename;

  StreamString stream;
  stream.Printf("%s.dll", basename.GetCString());
  return ConstString(stream.GetString());
}

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);
  }
}

std::unique_ptr<UtilityFunction>
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 && *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 = Status::FromErrorStringWithFormat(
        "LoadLibrary error: could not create utility function: %s",
        error.c_str());
    return nullptr;
  }

  TypeSystemClangSP scratch_ts_sp =
      ScratchTypeSystemClang::GetForTarget(target);
  if (!scratch_ts_sp)
    return nullptr;

  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()) {
    status = Status::FromErrorStringWithFormat(
        "LoadLibrary error: could not create function caller: %s",
        error.AsCString());
    return nullptr;
  }

  if (!utility->GetFunctionCaller()) {
    status = Status::FromErrorString(
        "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::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());

  ExpressionResults result = UserExpression::Evaluate(
      context, options, expression, kLoaderDecls, value);
  if (result != eExpressionCompleted)
    return value ? value->GetError().Clone() : Status("unknown error");

  if (value && value->GetError().Fail())
    return value->GetError().Clone();

  return Status();
}
