Skip to content

Commit 8d59cca

Browse files
authored
[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) ```
1 parent f6c04cd commit 8d59cca

File tree

6 files changed

+344
-2
lines changed

6 files changed

+344
-2
lines changed

lldb/source/Plugins/Platform/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ add_subdirectory(NetBSD)
1515
add_subdirectory(OpenBSD)
1616
add_subdirectory(POSIX)
1717
add_subdirectory(QemuUser)
18+
add_subdirectory(WebAssembly)
1819
add_subdirectory(Windows)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
lldb_tablegen(PlatformWasmProperties.inc -gen-lldb-property-defs
2+
SOURCE PlatformWasmProperties.td
3+
TARGET LLDBPluginPlatformWasmPropertiesGen)
4+
5+
lldb_tablegen(PlatformWasmPropertiesEnum.inc -gen-lldb-property-enum-defs
6+
SOURCE PlatformWasmProperties.td
7+
TARGET LLDBPluginPlatformWasmPropertiesEnumGen)
8+
9+
add_lldb_library(lldbPluginPlatformWasm PLUGIN
10+
PlatformWasm.cpp
11+
12+
LINK_LIBS
13+
lldbCore
14+
lldbHost
15+
lldbTarget
16+
lldbUtility
17+
LINK_COMPONENTS
18+
Support
19+
)
20+
21+
add_dependencies(lldbPluginPlatformWasm
22+
LLDBPluginPlatformWasmPropertiesGen
23+
LLDBPluginPlatformWasmPropertiesEnumGen)
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Plugins/Platform/WebAssembly/PlatformWasm.h"
10+
#include "Plugins/Process/wasm/ProcessWasm.h"
11+
#include "lldb/Core/PluginManager.h"
12+
#include "lldb/Host/FileSystem.h"
13+
#include "lldb/Host/ProcessLaunchInfo.h"
14+
#include "lldb/Host/common/TCPSocket.h"
15+
#include "lldb/Interpreter/OptionValueProperties.h"
16+
#include "lldb/Target/Process.h"
17+
#include "lldb/Target/Target.h"
18+
#include "lldb/Utility/LLDBLog.h"
19+
#include "lldb/Utility/Listener.h"
20+
#include "lldb/Utility/Log.h"
21+
#include "llvm/ADT/StringExtras.h"
22+
23+
using namespace lldb;
24+
using namespace lldb_private;
25+
26+
LLDB_PLUGIN_DEFINE(PlatformWasm)
27+
28+
namespace {
29+
#define LLDB_PROPERTIES_platformwasm
30+
#include "PlatformWasmProperties.inc"
31+
32+
enum {
33+
#define LLDB_PROPERTIES_platformwasm
34+
#include "PlatformWasmPropertiesEnum.inc"
35+
};
36+
37+
class PluginProperties : public Properties {
38+
public:
39+
PluginProperties() {
40+
m_collection_sp = std::make_shared<OptionValueProperties>(
41+
PlatformWasm::GetPluginNameStatic());
42+
m_collection_sp->Initialize(g_platformwasm_properties);
43+
}
44+
45+
FileSpec GetRuntimePath() const {
46+
return GetPropertyAtIndexAs<FileSpec>(ePropertyRuntimePath, {});
47+
}
48+
49+
Args GetRuntimeArgs() const {
50+
Args result;
51+
m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyRuntimeArgs, result);
52+
return result;
53+
}
54+
55+
llvm::StringRef GetPortArg() const {
56+
return GetPropertyAtIndexAs<llvm::StringRef>(ePropertyPortArg, {});
57+
}
58+
};
59+
60+
} // namespace
61+
62+
static PluginProperties &GetGlobalProperties() {
63+
static PluginProperties g_settings;
64+
return g_settings;
65+
}
66+
67+
llvm::StringRef PlatformWasm::GetPluginDescriptionStatic() {
68+
return "Platform for debugging Wasm";
69+
}
70+
71+
void PlatformWasm::Initialize() {
72+
PluginManager::RegisterPlugin(
73+
GetPluginNameStatic(), GetPluginDescriptionStatic(),
74+
PlatformWasm::CreateInstance, PlatformWasm::DebuggerInitialize);
75+
}
76+
77+
void PlatformWasm::Terminate() {
78+
PluginManager::UnregisterPlugin(PlatformWasm::CreateInstance);
79+
}
80+
81+
void PlatformWasm::DebuggerInitialize(Debugger &debugger) {
82+
if (!PluginManager::GetSettingForPlatformPlugin(debugger,
83+
GetPluginNameStatic())) {
84+
PluginManager::CreateSettingForPlatformPlugin(
85+
debugger, GetGlobalProperties().GetValueProperties(),
86+
"Properties for the wasm platform plugin.",
87+
/*is_global_property=*/true);
88+
}
89+
}
90+
91+
PlatformSP PlatformWasm::CreateInstance(bool force, const ArchSpec *arch) {
92+
Log *log = GetLog(LLDBLog::Platform);
93+
LLDB_LOG(log, "force = {0}, arch = ({1}, {2})", force,
94+
arch ? arch->GetArchitectureName() : "<null>",
95+
arch ? arch->GetTriple().getTriple() : "<null>");
96+
97+
bool create = force;
98+
if (!create && arch && arch->IsValid()) {
99+
const llvm::Triple &triple = arch->GetTriple();
100+
switch (triple.getArch()) {
101+
case llvm::Triple::wasm32:
102+
case llvm::Triple::wasm64:
103+
create = true;
104+
break;
105+
default:
106+
break;
107+
}
108+
}
109+
110+
LLDB_LOG(log, "create = {0}", create);
111+
return create ? PlatformSP(new PlatformWasm()) : PlatformSP();
112+
}
113+
114+
std::vector<ArchSpec>
115+
PlatformWasm::GetSupportedArchitectures(const ArchSpec &process_host_arch) {
116+
return {ArchSpec("wasm32-unknown-unknown-wasm"),
117+
ArchSpec("wasm64-unknown-unknown-wasm")};
118+
}
119+
120+
static auto get_arg_range(const Args &args) {
121+
return llvm::make_range(args.GetArgumentArrayRef().begin(),
122+
args.GetArgumentArrayRef().end());
123+
}
124+
125+
lldb::ProcessSP PlatformWasm::DebugProcess(ProcessLaunchInfo &launch_info,
126+
Debugger &debugger, Target &target,
127+
Status &error) {
128+
Log *log = GetLog(LLDBLog::Platform);
129+
130+
const PluginProperties &properties = GetGlobalProperties();
131+
132+
FileSpec runtime = properties.GetRuntimePath();
133+
FileSystem::Instance().ResolveExecutableLocation(runtime);
134+
135+
if (!FileSystem::Instance().Exists(runtime)) {
136+
error = Status::FromErrorStringWithFormatv(
137+
"WebAssembly runtime does not exist: {0}", runtime.GetPath());
138+
return nullptr;
139+
}
140+
141+
uint16_t port = 0;
142+
{
143+
// Get the next available port by binding a socket to port 0.
144+
TCPSocket listen_socket(true);
145+
error = listen_socket.Listen("localhost:0", /*backlog=*/5);
146+
if (error.Fail())
147+
return nullptr;
148+
port = listen_socket.GetLocalPortNumber();
149+
}
150+
151+
if (error.Fail())
152+
return nullptr;
153+
154+
Args args({runtime.GetPath(),
155+
llvm::formatv("{0}{1}", properties.GetPortArg(), port).str()});
156+
args.AppendArguments(properties.GetRuntimeArgs());
157+
args.AppendArguments(launch_info.GetArguments());
158+
159+
launch_info.SetArguments(args, true);
160+
launch_info.SetLaunchInSeparateProcessGroup(true);
161+
launch_info.GetFlags().Clear(eLaunchFlagDebug);
162+
163+
auto exit_code = std::make_shared<std::optional<int>>();
164+
launch_info.SetMonitorProcessCallback(
165+
[=](lldb::pid_t pid, int signal, int status) {
166+
LLDB_LOG(
167+
log,
168+
"WebAssembly runtime exited: pid = {0}, signal = {1}, status = {2}",
169+
pid, signal, status);
170+
exit_code->emplace(status);
171+
});
172+
173+
// This is automatically done for host platform in
174+
// Target::FinalizeFileActions, but we're not a host platform.
175+
llvm::Error Err = launch_info.SetUpPtyRedirection();
176+
LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}");
177+
178+
LLDB_LOG(log, "{0}", get_arg_range(launch_info.GetArguments()));
179+
error = Host::LaunchProcess(launch_info);
180+
if (error.Fail())
181+
return nullptr;
182+
183+
ProcessSP process_sp = target.CreateProcess(
184+
launch_info.GetListener(), wasm::ProcessWasm::GetPluginNameStatic(),
185+
nullptr, true);
186+
if (!process_sp) {
187+
error = Status::FromErrorString("failed to create WebAssembly process");
188+
return nullptr;
189+
}
190+
191+
process_sp->HijackProcessEvents(launch_info.GetHijackListener());
192+
193+
error = process_sp->ConnectRemote(
194+
llvm::formatv("connect://localhost:{0}", port).str());
195+
if (error.Fail()) {
196+
// If we know the runtime has exited, that's a better error message than
197+
// failing to connect.
198+
if (*exit_code)
199+
error = Status::FromError(llvm::joinErrors(
200+
llvm::createStringError(llvm::formatv(
201+
"WebAssembly runtime exited with exit code {0}", **exit_code)),
202+
error.takeError()));
203+
204+
return nullptr;
205+
}
206+
207+
if (launch_info.GetPTY().GetPrimaryFileDescriptor() !=
208+
PseudoTerminal::invalid_fd)
209+
process_sp->SetSTDIOFileDescriptor(
210+
launch_info.GetPTY().ReleasePrimaryFileDescriptor());
211+
212+
return process_sp;
213+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H
10+
#define LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H
11+
12+
#include "lldb/Host/Host.h"
13+
#include "lldb/Host/HostInfo.h"
14+
#include "lldb/Target/Platform.h"
15+
16+
namespace lldb_private {
17+
18+
class PlatformWasm : public Platform {
19+
public:
20+
static void Initialize();
21+
static void Terminate();
22+
23+
static llvm::StringRef GetPluginNameStatic() { return "wasm"; }
24+
static llvm::StringRef GetPluginDescriptionStatic();
25+
26+
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
27+
llvm::StringRef GetDescription() override {
28+
return GetPluginDescriptionStatic();
29+
}
30+
31+
UserIDResolver &GetUserIDResolver() override {
32+
return HostInfo::GetUserIDResolver();
33+
}
34+
35+
std::vector<ArchSpec>
36+
GetSupportedArchitectures(const ArchSpec &process_host_arch) override;
37+
38+
lldb::ProcessSP DebugProcess(ProcessLaunchInfo &launch_info,
39+
Debugger &debugger, Target &target,
40+
Status &error) override;
41+
42+
lldb::ProcessSP Attach(ProcessAttachInfo &attach_info, Debugger &debugger,
43+
Target *target, Status &status) override {
44+
status = Status::FromErrorString("Not supported");
45+
return nullptr;
46+
}
47+
48+
uint32_t FindProcesses(const ProcessInstanceInfoMatch &match_info,
49+
ProcessInstanceInfoList &proc_infos) override {
50+
return 0;
51+
}
52+
53+
bool GetProcessInfo(lldb::pid_t pid,
54+
ProcessInstanceInfo &proc_info) override {
55+
return false;
56+
}
57+
58+
bool IsConnected() const override { return true; }
59+
60+
void CalculateTrapHandlerSymbolNames() override {}
61+
62+
MmapArgList GetMmapArgumentList(const ArchSpec &arch, lldb::addr_t addr,
63+
lldb::addr_t length, unsigned prot,
64+
unsigned flags, lldb::addr_t fd,
65+
lldb::addr_t offset) override {
66+
return Platform::GetHostPlatform()->GetMmapArgumentList(
67+
arch, addr, length, prot, flags, fd, offset);
68+
}
69+
70+
private:
71+
static lldb::PlatformSP CreateInstance(bool force, const ArchSpec *arch);
72+
static void DebuggerInitialize(Debugger &debugger);
73+
74+
PlatformWasm() : Platform(/*is_host=*/true) {}
75+
};
76+
77+
} // namespace lldb_private
78+
79+
#endif // LLDB_SOURCE_PLUGINS_PLATFORM_WASM_PLATFORMWASM_H
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
include "../../../../include/lldb/Core/PropertiesBase.td"
2+
3+
let Definition = "platformwasm" in {
4+
def RuntimePath : Property<"runtime-path", "FileSpec">,
5+
Global,
6+
DefaultStringValue<"">,
7+
Desc<"Path to the WebAssembly runtime binary. If the path "
8+
"does not contain a directory separator, the filename "
9+
"is looked up in the PATH environment variable.">;
10+
def PortArg : Property<"port-arg", "String">,
11+
Global,
12+
DefaultStringValue<"">,
13+
Desc<"Argument to the WebAssembly runtime to specify the "
14+
"GDB remote port. The port number chosen by LLDB will be "
15+
"concatenated to this argument. For example: "
16+
"`-g=127.0.0.1:` or `--debugger-port `.">;
17+
def RuntimeArgs : Property<"runtime-args", "Args">,
18+
Global,
19+
DefaultStringValue<"">,
20+
Desc<"Extra arguments to pass to the WebAssembly runtime. "
21+
"For the argument that specifies the GDB remote port, "
22+
"use port-arg instead.">;
23+
}

llvm/docs/ReleaseNotes.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,16 @@ Changes to LLDB
249249

250250
* LLDB can now set breakpoints, show backtraces, and display variables when
251251
debugging Wasm with supported runtimes (WAMR and V8).
252-
* LLDB no longer stops processes by default when receiving SIGWINCH signals
252+
* LLDB now has a Wasm platform, which can be configured to run WebAssembly
253+
binaries directly under a Wasm runtime. Configurable through the
254+
platform.plugin.wasm settings.
255+
* LLDB no longer stops processes by default when receiving SIGWINCH signals
253256
(window resize events) on Linux. This is the default on other Unix platforms.
254257
You can re-enable it using `process handle --notify=true --stop=true SIGWINCH`.
255258
* The `show-progress` setting, which became a NOOP with the introduction of the
256259
statusline, now defaults to off and controls using OSC escape codes to show a
257260
native progress bar in supporting terminals like Ghostty and ConEmu.
258-
* The default PDB reader on Windows was changed from DIA to native, which uses
261+
* The default PDB reader on Windows was changed from DIA to native, which uses
259262
LLVM's PDB and CodeView support. You can switch back to the DIA reader with
260263
`settings set plugin.symbol-file.pdb.reader dia`. Note that support for the
261264
DIA reader will be removed in a future version of LLDB.

0 commit comments

Comments
 (0)