diff --git a/lldb/source/Plugins/Platform/CMakeLists.txt b/lldb/source/Plugins/Platform/CMakeLists.txt index f4753ab47ce11..cc1432aa4754b 100644 --- a/lldb/source/Plugins/Platform/CMakeLists.txt +++ b/lldb/source/Plugins/Platform/CMakeLists.txt @@ -15,4 +15,5 @@ add_subdirectory(NetBSD) add_subdirectory(OpenBSD) add_subdirectory(POSIX) add_subdirectory(QemuUser) +add_subdirectory(WebAssembly) add_subdirectory(Windows) diff --git a/lldb/source/Plugins/Platform/WebAssembly/CMakeLists.txt b/lldb/source/Plugins/Platform/WebAssembly/CMakeLists.txt new file mode 100644 index 0000000000000..7fb17b295fbb8 --- /dev/null +++ b/lldb/source/Plugins/Platform/WebAssembly/CMakeLists.txt @@ -0,0 +1,23 @@ +lldb_tablegen(PlatformWasmProperties.inc -gen-lldb-property-defs + SOURCE PlatformWasmProperties.td + TARGET LLDBPluginPlatformWasmPropertiesGen) + +lldb_tablegen(PlatformWasmPropertiesEnum.inc -gen-lldb-property-enum-defs + SOURCE PlatformWasmProperties.td + TARGET LLDBPluginPlatformWasmPropertiesEnumGen) + +add_lldb_library(lldbPluginPlatformWasm PLUGIN + PlatformWasm.cpp + + LINK_LIBS + lldbCore + lldbHost + lldbTarget + lldbUtility + LINK_COMPONENTS + Support + ) + +add_dependencies(lldbPluginPlatformWasm + LLDBPluginPlatformWasmPropertiesGen + LLDBPluginPlatformWasmPropertiesEnumGen) diff --git a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp new file mode 100644 index 0000000000000..f77ac7abbb678 --- /dev/null +++ b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp @@ -0,0 +1,213 @@ +//===----------------------------------------------------------------------===// +// +// 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/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" + +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( + PlatformWasm::GetPluginNameStatic()); + m_collection_sp->Initialize(g_platformwasm_properties); + } + + FileSpec GetRuntimePath() const { + return GetPropertyAtIndexAs(ePropertyRuntimePath, {}); + } + + Args GetRuntimeArgs() const { + Args result; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyRuntimeArgs, result); + return result; + } + + llvm::StringRef GetPortArg() const { + return GetPropertyAtIndexAs(ePropertyPortArg, {}); + } +}; + +} // namespace + +static PluginProperties &GetGlobalProperties() { + static PluginProperties g_settings; + return g_settings; +} + +llvm::StringRef PlatformWasm::GetPluginDescriptionStatic() { + return "Platform for debugging Wasm"; +} + +void PlatformWasm::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), GetPluginDescriptionStatic(), + PlatformWasm::CreateInstance, PlatformWasm::DebuggerInitialize); +} + +void PlatformWasm::Terminate() { + PluginManager::UnregisterPlugin(PlatformWasm::CreateInstance); +} + +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() : "", + arch ? arch->GetTriple().getTriple() : ""); + + 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(); +} + +std::vector +PlatformWasm::GetSupportedArchitectures(const ArchSpec &process_host_arch) { + return {ArchSpec("wasm32-unknown-unknown-wasm"), + ArchSpec("wasm64-unknown-unknown-wasm")}; +} + +static auto get_arg_range(const Args &args) { + return llvm::make_range(args.GetArgumentArrayRef().begin(), + args.GetArgumentArrayRef().end()); +} + +lldb::ProcessSP PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info, + Debugger &debugger, Target &target, + Status &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; + } + + uint16_t port = 0; + { + // Get the next available port by binding a socket to port 0. + TCPSocket listen_socket(true); + error = listen_socket.Listen("localhost:0", /*backlog=*/5); + if (error.Fail()) + return nullptr; + port = listen_socket.GetLocalPortNumber(); + } + + if (error.Fail()) + return nullptr; + + 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); + + auto exit_code = std::make_shared>(); + 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}", get_arg_range(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::createStringError(llvm::formatv( + "WebAssembly runtime exited with exit code {0}", **exit_code)), + error.takeError())); + + return nullptr; + } + + if (launch_info.GetPTY().GetPrimaryFileDescriptor() != + PseudoTerminal::invalid_fd) + process_sp->SetSTDIOFileDescriptor( + launch_info.GetPTY().ReleasePrimaryFileDescriptor()); + + return process_sp; +} diff --git a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.h b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.h new file mode 100644 index 0000000000000..cba4c7c549cb0 --- /dev/null +++ b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.h @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H +#define LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H + +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/Platform.h" + +namespace lldb_private { + +class PlatformWasm : public Platform { +public: + static void Initialize(); + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "wasm"; } + static llvm::StringRef GetPluginDescriptionStatic(); + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + llvm::StringRef GetDescription() override { + return GetPluginDescriptionStatic(); + } + + UserIDResolver &GetUserIDResolver() override { + return HostInfo::GetUserIDResolver(); + } + + std::vector + GetSupportedArchitectures(const ArchSpec &process_host_arch) override; + + lldb::ProcessSP DebugProcess(ProcessLaunchInfo &launch_info, + Debugger &debugger, Target &target, + Status &error) override; + + lldb::ProcessSP Attach(ProcessAttachInfo &attach_info, Debugger &debugger, + Target *target, Status &status) override { + status = Status::FromErrorString("Not supported"); + return nullptr; + } + + uint32_t FindProcesses(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &proc_infos) override { + return 0; + } + + bool GetProcessInfo(lldb::pid_t pid, + ProcessInstanceInfo &proc_info) override { + return false; + } + + bool IsConnected() const override { return true; } + + void CalculateTrapHandlerSymbolNames() override {} + + MmapArgList GetMmapArgumentList(const ArchSpec &arch, lldb::addr_t addr, + lldb::addr_t length, unsigned prot, + unsigned flags, lldb::addr_t fd, + lldb::addr_t offset) override { + return Platform::GetHostPlatform()->GetMmapArgumentList( + arch, addr, length, prot, flags, fd, offset); + } + +private: + static lldb::PlatformSP CreateInstance(bool force, const ArchSpec *arch); + static void DebuggerInitialize(Debugger &debugger); + + PlatformWasm() : Platform(/*is_host=*/true) {} +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H diff --git a/lldb/source/Plugins/Platform/WebAssembly/PlatformWasmProperties.td b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasmProperties.td new file mode 100644 index 0000000000000..5626372482f13 --- /dev/null +++ b/lldb/source/Plugins/Platform/WebAssembly/PlatformWasmProperties.td @@ -0,0 +1,23 @@ +include "../../../../include/lldb/Core/PropertiesBase.td" + +let Definition = "platformwasm" in { + def RuntimePath : Property<"runtime-path", "FileSpec">, + Global, + DefaultStringValue<"">, + Desc<"Path to the WebAssembly runtime binary. If the path " + "does not contain a directory separator, the filename " + "is looked up in the PATH environment variable.">; + def PortArg : Property<"port-arg", "String">, + Global, + DefaultStringValue<"">, + Desc<"Argument to the WebAssembly runtime to specify the " + "GDB remote port. The port number chosen by LLDB will be " + "concatenated to this argument. For example: " + "`-g=127.0.0.1:` or `--debugger-port `.">; + def RuntimeArgs : Property<"runtime-args", "Args">, + Global, + DefaultStringValue<"">, + Desc<"Extra arguments to pass to the WebAssembly runtime. " + "For the argument that specifies the GDB remote port, " + "use port-arg instead.">; +} diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md index 22d5b4183fac0..2354749228eac 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -243,13 +243,16 @@ Changes to LLDB * LLDB can now set breakpoints, show backtraces, and display variables when debugging Wasm with supported runtimes (WAMR and V8). -* LLDB no longer stops processes by default when receiving SIGWINCH signals +* LLDB now has a Wasm platform, which can be configured to run WebAssembly + binaries directly under a Wasm runtime. Configurable through the + platform.plugin.wasm settings. +* LLDB no longer stops processes by default when receiving SIGWINCH signals (window resize events) on Linux. This is the default on other Unix platforms. You can re-enable it using `process handle --notify=true --stop=true SIGWINCH`. * The `show-progress` setting, which became a NOOP with the introduction of the statusline, now defaults to off and controls using OSC escape codes to show a native progress bar in supporting terminals like Ghostty and ConEmu. -* The default PDB reader on Windows was changed from DIA to native, which uses +* The default PDB reader on Windows was changed from DIA to native, which uses LLVM's PDB and CodeView support. You can switch back to the DIA reader with `settings set plugin.symbol-file.pdb.reader dia`. Note that support for the DIA reader will be removed in a future version of LLDB.