Author: Jonas Devlieghere Date: 2025-12-11T11:12:26-08:00 New Revision: 8d59cca1ab9cf4e39e43bf695e415de9ccd41115
URL: https://github.com/llvm/llvm-project/commit/8d59cca1ab9cf4e39e43bf695e415de9ccd41115 DIFF: https://github.com/llvm/llvm-project/commit/8d59cca1ab9cf4e39e43bf695e415de9ccd41115.diff LOG: [lldb] Add WebAssembly platform (#171507) This PR adds a platform for WebAssembly. Heavily inspired by Pavel's QemuUser, the platform lets you configure a WebAssembly runtime to run a Wasm binary. For example, the following configuration can be used to launch binaries under the WebAssembly Micro Runtime (WARM): ``` settings set -- platform.plugin.wasm.runtime-args --heap-size=1048576 settings set -- platform.plugin.wasm.port-arg -g=127.0.0.1: settings set -- platform.plugin.wasm.runtime-path /path/to/iwasm-2.4.0 ``` With the settings above, you can now launch a binary directly under WAMR: ``` ❯ lldb simple.wasm (lldb) target create "/Users/jonas/wasm-micro-runtime/product-mini/platforms/darwin/build/simple.wasm" Current executable set to '/Users/jonas/wasm-micro-runtime/product-mini/platforms/darwin/build/simple.wasm' (wasm32). (lldb) b main Breakpoint 1: 2 locations. (lldb) r Process 1 launched: '/Users/jonas/wasm-micro-runtime/product-mini/platforms/darwin/build/simple.wasm' (wasm32) 2 locations added to breakpoint 1 [22:28:05:124 - 16FE27000]: control thread of debug object 0x1005e9020 start [22:28:05:124 - 16FE27000]: Debug server listening on 127.0.0.1:49170 the module name is /Users/jonas/wasm-micro-runtime/product-mini/platforms/darwin/build/simple.wasm Process 1 stopped * thread #1, name = 'nobody', stop reason = breakpoint 1.3 frame #0: 0x40000000000001d3 simple.wasm`main at simple.c:8:7 5 } 6 7 int main() { -> 8 int i = 1; 9 int j = 2; 10 return add(i, j); 11 } (lldb) ``` Added: lldb/source/Plugins/Platform/WebAssembly/CMakeLists.txt lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.cpp lldb/source/Plugins/Platform/WebAssembly/PlatformWasm.h lldb/source/Plugins/Platform/WebAssembly/PlatformWasmProperties.td Modified: lldb/source/Plugins/Platform/CMakeLists.txt llvm/docs/ReleaseNotes.md Removed: ################################################################################ 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<OptionValueProperties>( + PlatformWasm::GetPluginNameStatic()); + m_collection_sp->Initialize(g_platformwasm_properties); + } + + 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() { + 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() : "<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(); +} + +std::vector<ArchSpec> +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<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}", 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<ArchSpec> + 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 4496b54beffb6..ee285fe56b51a 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -249,13 +249,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. _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
