Maarten Steevens d400080063
[lldb] Inherit Host::GetEnvironment() when launching a wasm runtime (#190476)
Some WebAssembly runtimes might use environment variables such as `HOME`
or `XDG_CONFIG_HOME` to store configuration files, additionally some VMs
might allow wasm modules to read environment variables from the host.
Currently, if a runtime is launched using the WebAssembly platform it
doesn't inherit the environment. As a result wasm runtimes and modules
are unable to read from environment variables and might even fail to
launch (if the config file is required). This PR aims to resolve this
issue, I have tested this with the WARDuino runtime and my WIP gdbstub.
2026-04-04 21:18:09 +00:00

243 lines
7.9 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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 "Plugins/Platform/WebAssembly/PlatformWasm.h"
#include "Plugins/Platform/WebAssembly/PlatformWasmRemoteGDBServer.h"
#include "Plugins/Platform/WebAssembly/PlatformWebInspectorWasm.h"
#include "Plugins/Process/wasm/ProcessWasm.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Host/common/TCPSocket.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Listener.h"
#include "lldb/Utility/Log.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ErrorExtras.h"
using namespace lldb;
using namespace lldb_private;
LLDB_PLUGIN_DEFINE(PlatformWasm)
namespace {
#define LLDB_PROPERTIES_platformwasm
#include "PlatformWasmProperties.inc"
enum {
#define LLDB_PROPERTIES_platformwasm
#include "PlatformWasmPropertiesEnum.inc"
};
class PluginProperties : public Properties {
public:
PluginProperties() {
m_collection_sp = std::make_shared<OptionValueProperties>(
PlatformWasm::GetPluginNameStatic());
m_collection_sp->Initialize(g_platformwasm_properties_def);
}
FileSpec GetRuntimePath() const {
return GetPropertyAtIndexAs<FileSpec>(ePropertyRuntimePath, {});
}
Args GetRuntimeArgs() const {
Args result;
m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyRuntimeArgs, result);
return result;
}
llvm::StringRef GetPortArg() const {
return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyPortArg, {});
}
};
} // namespace
static PluginProperties &GetGlobalProperties() {
static PluginProperties g_settings;
return g_settings;
}
llvm::StringRef PlatformWasm::GetPluginDescriptionStatic() {
return "Platform for debugging Wasm";
}
void PlatformWasm::Initialize() {
PlatformWebInspectorWasm::Initialize();
PluginManager::RegisterPlugin(
GetPluginNameStatic(), GetPluginDescriptionStatic(),
PlatformWasm::CreateInstance, PlatformWasm::DebuggerInitialize);
}
void PlatformWasm::Terminate() {
PluginManager::UnregisterPlugin(PlatformWasm::CreateInstance);
PlatformWebInspectorWasm::Terminate();
}
void PlatformWasm::DebuggerInitialize(Debugger &debugger) {
if (!PluginManager::GetSettingForPlatformPlugin(debugger,
GetPluginNameStatic())) {
PluginManager::CreateSettingForPlatformPlugin(
debugger, GetGlobalProperties().GetValueProperties(),
"Properties for the wasm platform plugin.",
/*is_global_property=*/true);
}
}
PlatformSP PlatformWasm::CreateInstance(bool force, const ArchSpec *arch) {
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOG(log, "force = {0}, arch = ({1}, {2})", force,
arch ? arch->GetArchitectureName() : "<null>",
arch ? arch->GetTriple().getTriple() : "<null>");
bool create = force;
if (!create && arch && arch->IsValid()) {
const llvm::Triple &triple = arch->GetTriple();
switch (triple.getArch()) {
case llvm::Triple::wasm32:
case llvm::Triple::wasm64:
create = true;
break;
default:
break;
}
}
LLDB_LOG(log, "create = {0}", create);
return create ? PlatformSP(new PlatformWasm()) : PlatformSP();
}
llvm::Expected<uint16_t> PlatformWasm::FindFreeTCPPort() {
TCPSocket sock(/*should_close=*/true);
Status status = sock.Listen("localhost:0", /*backlog=*/5);
if (status.Fail())
return status.takeError();
return sock.GetLocalPortNumber();
}
std::vector<ArchSpec>
PlatformWasm::GetSupportedArchitectures(const ArchSpec &process_host_arch) {
return {ArchSpec("wasm32-unknown-unknown-wasm"),
ArchSpec("wasm64-unknown-unknown-wasm")};
}
lldb::ProcessSP PlatformWasm::Attach(ProcessAttachInfo &attach_info,
Debugger &debugger, Target *target,
Status &status) {
if (m_remote_platform_sp)
return m_remote_platform_sp->Attach(attach_info, debugger, target, status);
status = Status::FromErrorString(
"attaching is only supported when connected to a remote Wasm platform");
return nullptr;
}
lldb::ProcessSP PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info,
Debugger &debugger, Target &target,
Status &error) {
if (m_remote_platform_sp)
return m_remote_platform_sp->DebugProcess(launch_info, debugger, target,
error);
Log *log = GetLog(LLDBLog::Platform);
const PluginProperties &properties = GetGlobalProperties();
FileSpec runtime = properties.GetRuntimePath();
FileSystem::Instance().ResolveExecutableLocation(runtime);
if (!FileSystem::Instance().Exists(runtime)) {
error = Status::FromErrorStringWithFormatv(
"WebAssembly runtime does not exist: {0}", runtime.GetPath());
return nullptr;
}
llvm::Expected<uint16_t> expected_port = FindFreeTCPPort();
if (!expected_port) {
error = Status::FromError(expected_port.takeError());
return nullptr;
}
uint16_t port = *expected_port;
Args args({runtime.GetPath(),
llvm::formatv("{0}{1}", properties.GetPortArg(), port).str()});
args.AppendArguments(properties.GetRuntimeArgs());
args.AppendArguments(launch_info.GetArguments());
launch_info.SetArguments(args, true);
launch_info.SetLaunchInSeparateProcessGroup(true);
launch_info.GetFlags().Clear(eLaunchFlagDebug);
launch_info.GetEnvironment() = Host::GetEnvironment();
auto exit_code = std::make_shared<std::optional<int>>();
launch_info.SetMonitorProcessCallback(
[=](lldb::pid_t pid, int signal, int status) {
LLDB_LOG(
log,
"WebAssembly runtime exited: pid = {0}, signal = {1}, status = {2}",
pid, signal, status);
exit_code->emplace(status);
});
// This is automatically done for host platform in
// Target::FinalizeFileActions, but we're not a host platform.
llvm::Error Err = launch_info.SetUpPtyRedirection();
LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}");
LLDB_LOG(log, "{0}", GetArgRange(launch_info.GetArguments()));
error = Host::LaunchProcess(launch_info);
if (error.Fail())
return nullptr;
ProcessSP process_sp = target.CreateProcess(
launch_info.GetListener(), wasm::ProcessWasm::GetPluginNameStatic(),
nullptr, true);
if (!process_sp) {
error = Status::FromErrorString("failed to create WebAssembly process");
return nullptr;
}
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
error = process_sp->ConnectRemote(
llvm::formatv("connect://localhost:{0}", port).str());
if (error.Fail()) {
// If we know the runtime has exited, that's a better error message than
// failing to connect.
if (*exit_code)
error = Status::FromError(llvm::joinErrors(
llvm::createStringErrorV(
"WebAssembly runtime exited with exit code {0}", **exit_code),
error.takeError()));
return nullptr;
}
#ifndef _WIN32
if (launch_info.GetPTY().GetPrimaryFileDescriptor() !=
PseudoTerminal::invalid_fd)
process_sp->SetSTDIOFileDescriptor(
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
#endif
return process_sp;
}
Status PlatformWasm::ConnectRemote(Args &args) {
if (IsHost())
return Status::FromErrorString(
"can't connect to the host platform, always connected");
if (!m_remote_platform_sp)
m_remote_platform_sp = PlatformSP(new PlatformWasmRemoteGDBServer());
return m_remote_platform_sp->ConnectRemote(args);
}