Skip to content

Backport Wasm Debugging changes to the LLVM 21.x Release #151559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions lldb/docs/resources/lldbgdbremote.md
Original file line number Diff line number Diff line change
Expand Up @@ -2463,3 +2463,70 @@ omitting them will work fine; these numbers are always base 16.

The length of the payload is not provided. A reliable, 8-bit clean,
transport layer is assumed.

## Wasm Packets

The packet below are supported by the
[WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) and
[V8](https://v8.dev) Wasm runtimes.


### qWasmCallStack

Get the Wasm call stack for the given thread id. This returns a hex-encoded
list of PC values, one for each frame of the call stack. To match the Wasm
specification, the addresses are encoded in little endian byte order, even if
the endian of the Wasm runtime's host is not little endian.

```
send packet: $qWasmCallStack:202dbe040#08
read packet: $9c01000000000040e501000000000040fe01000000000040#
```

**Priority to Implement:** Only required for Wasm support. Necessary to show
stack traces.

### qWasmGlobal

Get the value of a Wasm global variable for the given frame index at the given
variable index. The indexes are encoded as base 10. The result is a hex-encoded
address from where to read the value.

```
send packet: $qWasmGlobal:0;2#cb
read packet: $e0030100#b9
```

**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.


### qWasmLocal

Get the value of a Wasm function argument or local variable for the given frame
index at the given variable index. The indexes are encoded as base 10. The
result is a hex-encoded address from where to read the value.


```
send packet: $qWasmLocal:0;2#cb
read packet: $e0030100#b9
```

**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.


### qWasmStackValue

Get the value of a Wasm local variable from the Wasm operand stack, for the
given frame index at the given variable index. The indexes are encoded as base
10. The result is a hex-encoded address from where to read value.

```
send packet: $qWasmStackValue:0;2#cb
read packet: $e0030100#b9
```

**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.
12 changes: 8 additions & 4 deletions lldb/include/lldb/Expression/DWARFExpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ class DWARFExpression {
GetVendorDWARFOpcodeSize(const DataExtractor &data,
const lldb::offset_t data_offset,
const uint8_t op) const = 0;
virtual bool ParseVendorDWARFOpcode(uint8_t op,
const DataExtractor &opcodes,
lldb::offset_t &offset,
Stack &stack) const = 0;
virtual bool
ParseVendorDWARFOpcode(uint8_t op, const DataExtractor &opcodes,
lldb::offset_t &offset, RegisterContext *reg_ctx,
lldb::RegisterKind reg_kind, Stack &stack) const = 0;

Delegate(const Delegate &) = delete;
Delegate &operator=(const Delegate &) = delete;
Expand Down Expand Up @@ -163,6 +163,10 @@ class DWARFExpression {

bool MatchesOperand(StackFrame &frame, const Instruction::Operand &op) const;

static llvm::Error ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
lldb::RegisterKind reg_kind,
uint32_t reg_num, Value &value);

private:
/// A data extractor capable of reading opcode bytes
DataExtractor m_data;
Expand Down
4 changes: 2 additions & 2 deletions lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def createTarget(self, yaml_path):
self.yaml2obj(yaml_path, obj_path)
return self.dbg.CreateTarget(obj_path)

def connect(self, target):
def connect(self, target, plugin="gdb-remote"):
"""
Create a process by connecting to the mock GDB server.

Expand All @@ -54,7 +54,7 @@ def connect(self, target):
listener = self.dbg.GetListener()
error = lldb.SBError()
process = target.ConnectRemote(
listener, self.server.get_connect_url(), "gdb-remote", error
listener, self.server.get_connect_url(), plugin, error
)
self.assertTrue(error.Success(), error.description)
self.assertTrue(process, PROCESS_IS_VALID)
Expand Down
10 changes: 6 additions & 4 deletions lldb/source/Expression/DWARFExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ void DWARFExpression::SetRegisterKind(RegisterKind reg_kind) {
m_reg_kind = reg_kind;
}

static llvm::Error ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
lldb::RegisterKind reg_kind,
uint32_t reg_num, Value &value) {
llvm::Error
DWARFExpression::ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
lldb::RegisterKind reg_kind,
uint32_t reg_num, Value &value) {
if (reg_ctx == nullptr)
return llvm::createStringError("no register context in frame");

Expand Down Expand Up @@ -2302,7 +2303,8 @@ llvm::Expected<Value> DWARFExpression::Evaluate(

default:
if (dwarf_cu) {
if (dwarf_cu->ParseVendorDWARFOpcode(op, opcodes, offset, stack)) {
if (dwarf_cu->ParseVendorDWARFOpcode(op, opcodes, offset, reg_ctx,
reg_kind, stack)) {
break;
}
}
Expand Down
6 changes: 5 additions & 1 deletion lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,13 @@ DataExtractor ObjectFileWasm::ReadImageData(offset_t offset, uint32_t size) {
DataBufferSP buffer_sp(data_up.release());
data.SetData(buffer_sp, 0, buffer_sp->GetByteSize());
}
} else if (offset < m_data.GetByteSize()) {
size =
std::min(static_cast<uint64_t>(size), m_data.GetByteSize() - offset);
return DataExtractor(m_data.GetDataStart() + offset, size, GetByteOrder(),
GetAddressByteSize());
}
}

data.SetByteOrder(GetByteOrder());
return data;
}
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/Process/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ add_subdirectory(elf-core)
add_subdirectory(mach-core)
add_subdirectory(minidump)
add_subdirectory(FreeBSDKernel)
add_subdirectory(wasm)
9 changes: 7 additions & 2 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ ProcessGDBRemote::~ProcessGDBRemote() {
KillDebugserverProcess();
}

std::shared_ptr<ThreadGDBRemote>
ProcessGDBRemote::CreateThread(lldb::tid_t tid) {
return std::make_shared<ThreadGDBRemote>(*this, tid);
}

bool ProcessGDBRemote::ParsePythonTargetDefinition(
const FileSpec &target_definition_fspec) {
ScriptInterpreter *interpreter =
Expand Down Expand Up @@ -1594,7 +1599,7 @@ bool ProcessGDBRemote::DoUpdateThreadList(ThreadList &old_thread_list,
ThreadSP thread_sp(
old_thread_list_copy.RemoveThreadByProtocolID(tid, false));
if (!thread_sp) {
thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
thread_sp = CreateThread(tid);
LLDB_LOGV(log, "Making new thread: {0} for thread ID: {1:x}.",
thread_sp.get(), thread_sp->GetID());
} else {
Expand Down Expand Up @@ -1726,7 +1731,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(

if (!thread_sp) {
// Create the thread if we need to
thread_sp = std::make_shared<ThreadGDBRemote>(*this, tid);
thread_sp = CreateThread(tid);
m_thread_list_real.AddThread(thread_sp);
}
}
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ class ProcessGDBRemote : public Process,

ProcessGDBRemote(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);

virtual std::shared_ptr<ThreadGDBRemote> CreateThread(lldb::tid_t tid);

bool SupportsMemoryTagging() override;

/// Broadcaster event bits definitions.
Expand Down
11 changes: 11 additions & 0 deletions lldb/source/Plugins/Process/wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_lldb_library(lldbPluginProcessWasm PLUGIN
ProcessWasm.cpp
RegisterContextWasm.cpp
ThreadWasm.cpp
UnwindWasm.cpp

LINK_LIBS
lldbCore
LINK_COMPONENTS
Support
)
166 changes: 166 additions & 0 deletions lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//===----------------------------------------------------------------------===//
//
// 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 "ProcessWasm.h"
#include "ThreadWasm.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Value.h"
#include "lldb/Utility/DataBufferHeap.h"

#include "lldb/Target/UnixSignals.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
using namespace lldb_private::wasm;

LLDB_PLUGIN_DEFINE(ProcessWasm)

ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
: ProcessGDBRemote(target_sp, listener_sp) {
assert(target_sp);
// Wasm doesn't have any Unix-like signals as a platform concept, but pretend
// like it does to appease LLDB.
m_unix_signals_sp = UnixSignals::Create(target_sp->GetArchitecture());
}

void ProcessWasm::Initialize() {
static llvm::once_flag g_once_flag;

llvm::call_once(g_once_flag, []() {
PluginManager::RegisterPlugin(GetPluginNameStatic(),
GetPluginDescriptionStatic(), CreateInstance,
DebuggerInitialize);
});
}

void ProcessWasm::DebuggerInitialize(Debugger &debugger) {
ProcessGDBRemote::DebuggerInitialize(debugger);
}

llvm::StringRef ProcessWasm::GetPluginName() { return GetPluginNameStatic(); }

llvm::StringRef ProcessWasm::GetPluginNameStatic() { return "wasm"; }

llvm::StringRef ProcessWasm::GetPluginDescriptionStatic() {
return "GDB Remote protocol based WebAssembly debugging plug-in.";
}

void ProcessWasm::Terminate() {
PluginManager::UnregisterPlugin(ProcessWasm::CreateInstance);
}

lldb::ProcessSP ProcessWasm::CreateInstance(lldb::TargetSP target_sp,
ListenerSP listener_sp,
const FileSpec *crash_file_path,
bool can_connect) {
if (crash_file_path == nullptr)
return std::make_shared<ProcessWasm>(target_sp, listener_sp);
return {};
}

bool ProcessWasm::CanDebug(lldb::TargetSP target_sp,
bool plugin_specified_by_name) {
if (plugin_specified_by_name)
return true;

if (Module *exe_module = target_sp->GetExecutableModulePointer()) {
if (ObjectFile *exe_objfile = exe_module->GetObjectFile())
return exe_objfile->GetArchitecture().GetMachine() ==
llvm::Triple::wasm32;
}

// However, if there is no wasm module, we return false, otherwise,
// we might use ProcessWasm to attach gdb remote.
return false;
}

std::shared_ptr<ThreadGDBRemote> ProcessWasm::CreateThread(lldb::tid_t tid) {
return std::make_shared<ThreadWasm>(*this, tid);
}

size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) {
wasm_addr_t wasm_addr(vm_addr);

switch (wasm_addr.GetType()) {
case WasmAddressType::Memory:
case WasmAddressType::Object:
return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
case WasmAddressType::Invalid:
error.FromErrorStringWithFormat(
"Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
return 0;
}
}

llvm::Expected<std::vector<lldb::addr_t>>
ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
StreamString packet;
packet.Printf("qWasmCallStack:");
packet.Printf("%llx", tid);

StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send qWasmCallStack");

if (!response.IsNormalResponse())
return llvm::createStringError("failed to get response for qWasmCallStack");

WritableDataBufferSP data_buffer_sp =
std::make_shared<DataBufferHeap>(response.GetStringRef().size() / 2, 0);
const size_t bytes = response.GetHexBytes(data_buffer_sp->GetData(), '\xcc');
if (bytes == 0 || bytes % sizeof(uint64_t) != 0)
return llvm::createStringError("invalid response for qWasmCallStack");

// To match the Wasm specification, the addresses are encoded in little endian
// byte order.
DataExtractor data(data_buffer_sp, lldb::eByteOrderLittle,
GetAddressByteSize());
lldb::offset_t offset = 0;
std::vector<lldb::addr_t> call_stack_pcs;
while (offset < bytes)
call_stack_pcs.push_back(data.GetU64(&offset));

return call_stack_pcs;
}

llvm::Expected<lldb::DataBufferSP>
ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index,
int index) {
StreamString packet;
switch (kind) {
case eWasmTagLocal:
packet.Printf("qWasmLocal:");
break;
case eWasmTagGlobal:
packet.Printf("qWasmGlobal:");
break;
case eWasmTagOperandStack:
packet.PutCString("qWasmStackValue:");
break;
case eWasmTagNotAWasmLocation:
return llvm::createStringError("not a Wasm location");
}
packet.Printf("%d;%d", frame_index, index);

StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send Wasm variable");

if (!response.IsNormalResponse())
return llvm::createStringError("failed to get response for Wasm variable");

WritableDataBufferSP buffer_sp(
new DataBufferHeap(response.GetStringRef().size() / 2, 0));
response.GetHexBytes(buffer_sp->GetData(), '\xcc');
return buffer_sp;
}
Loading
Loading