[Lldb-commits] [lldb] [lldb] Implement WebAssembly debugging (PR #77949)
Paolo Severini via lldb-commits
lldb-commits at lists.llvm.org
Mon Jan 22 05:58:34 PST 2024
https://github.com/paolosevMSFT updated https://github.com/llvm/llvm-project/pull/77949
>From 30d932bb0988e1c78c3e023be2270259df5e6664 Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Fri, 12 Jan 2024 09:10:59 -0800
Subject: [PATCH 1/3] Add support for source-level debugging of WebAssembly
code
---
lldb/include/lldb/Target/Process.h | 40 +++
lldb/source/Core/Value.cpp | 2 +-
lldb/source/Expression/DWARFExpression.cpp | 41 +++
.../source/Interpreter/CommandInterpreter.cpp | 18 ++
lldb/source/Plugins/Process/CMakeLists.txt | 1 +
.../Process/gdb-remote/ProcessGDBRemote.cpp | 7 +-
.../Process/gdb-remote/ProcessGDBRemote.h | 2 +
.../Plugins/Process/wasm/CMakeLists.txt | 12 +
.../Plugins/Process/wasm/ProcessWasm.cpp | 291 ++++++++++++++++++
.../source/Plugins/Process/wasm/ProcessWasm.h | 129 ++++++++
.../Plugins/Process/wasm/ThreadWasm.cpp | 55 ++++
lldb/source/Plugins/Process/wasm/ThreadWasm.h | 44 +++
.../Plugins/Process/wasm/UnwindWasm.cpp | 76 +++++
lldb/source/Plugins/Process/wasm/UnwindWasm.h | 55 ++++
.../Process/wasm/wasmRegisterContext.cpp | 108 +++++++
.../Process/wasm/wasmRegisterContext.h | 75 +++++
16 files changed, 954 insertions(+), 2 deletions(-)
create mode 100644 lldb/source/Plugins/Process/wasm/CMakeLists.txt
create mode 100644 lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
create mode 100644 lldb/source/Plugins/Process/wasm/ProcessWasm.h
create mode 100644 lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
create mode 100644 lldb/source/Plugins/Process/wasm/ThreadWasm.h
create mode 100644 lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
create mode 100644 lldb/source/Plugins/Process/wasm/UnwindWasm.h
create mode 100644 lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
create mode 100644 lldb/source/Plugins/Process/wasm/wasmRegisterContext.h
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 24c599e044c78f..587ae085b479b7 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1548,6 +1548,46 @@ class Process : public std::enable_shared_from_this<Process>,
virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error);
+ /// Read of memory from a process.
+ ///
+ /// This function will read memory from the current process's address space
+ /// and remove any traps that may have been inserted into the memory.
+ ///
+ /// This overloads accepts an ExecutionContext as additional argument. By
+ /// default, it calls the previous overload without the ExecutionContext
+ /// argument, but it can be overridden by Process subclasses.
+ ///
+ /// \param[in] vm_addr
+ /// A virtual load address that indicates where to start reading
+ /// memory from.
+ ///
+ /// \param[out] buf
+ /// A byte buffer that is at least \a size bytes long that
+ /// will receive the memory bytes.
+ ///
+ /// \param[in] size
+ /// The number of bytes to read.
+ ///
+ /// \param[in] exe_ctx
+ /// The current execution context, if available.
+ ///
+ /// \param[out] error
+ /// An error that indicates the success or failure of this
+ /// operation. If error indicates success (error.Success()),
+ /// then the value returned can be trusted, otherwise zero
+ /// will be returned.
+ ///
+ /// \return
+ /// The number of bytes that were actually read into \a buf. If
+ /// the returned number is greater than zero, yet less than \a
+ /// size, then this function will get called again with \a
+ /// vm_addr, \a buf, and \a size updated appropriately. Zero is
+ /// returned in the case of an error.
+ virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ ExecutionContext *exe_ctx, Status &error) {
+ return ReadMemory(vm_addr, buf, size, error);
+ }
+
/// Read of memory from a process.
///
/// This function has the same semantics of ReadMemory except that it
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 995cc934c82044..47a5fdee773886 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -552,7 +552,7 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
if (process) {
const size_t bytes_read =
- process->ReadMemory(address, dst, byte_size, error);
+ process->ReadMemory(address, dst, byte_size, exe_ctx, error);
if (bytes_read != byte_size)
error.SetErrorStringWithFormat(
"read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index fe4928d4f43a43..ca24611724dc7c 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -346,6 +346,16 @@ static offset_t GetOpcodeDataSize(const DataExtractor &data,
return (offset - data_offset) + subexpr_len;
}
+ case DW_OP_WASM_location: {
+ uint8_t wasm_op = data.GetU8(&offset);
+ if (wasm_op == 3) {
+ data.GetU32(&offset);
+ } else {
+ data.GetULEB128(&offset);
+ }
+ return offset - data_offset;
+ }
+
default:
if (!dwarf_cu) {
return LLDB_INVALID_OFFSET;
@@ -2595,6 +2605,37 @@ bool DWARFExpression::Evaluate(
break;
}
+ case DW_OP_WASM_location: {
+ uint8_t wasm_op = opcodes.GetU8(&offset);
+ uint32_t index;
+
+ /* LLDB doesn't have an address space to represents WebAssembly locals,
+ * globals and operand stacks.
+ * We encode these elements into virtual registers:
+ * | tag: 2 bits | index: 30 bits |
+ * where tag is:
+ * 0: Not a WebAssembly location
+ * 1: Local
+ * 2: Global
+ * 3: Operand stack value
+ */
+ if (wasm_op == 3) {
+ index = opcodes.GetU32(&offset);
+ wasm_op = 1;
+ } else {
+ index = opcodes.GetULEB128(&offset);
+ }
+
+ reg_num = (((wasm_op + 1) & 0x03) << 30) | (index & 0x3fffffff);
+
+ if (ReadRegisterValueAsScalar(reg_ctx, reg_kind, reg_num, error_ptr, tmp))
+ stack.push_back(tmp);
+ else
+ return false;
+
+ break;
+ }
+
default:
if (dwarf_cu) {
if (dwarf_cu->GetSymbolFileDWARF().ParseVendorDWARFOpcode(
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 00651df48b6224..bcacc7aabb66ca 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -794,6 +794,24 @@ void CommandInterpreter::LoadCommandDictionary() {
}
}
+ std::unique_ptr<CommandObjectRegexCommand> connect_wasm_cmd_up(
+ new CommandObjectRegexCommand(
+ *this, "wasm",
+ "Connect to a WebAssembly process via remote GDB server. "
+ "If no host is specifed, localhost is assumed.",
+ "wasm [<hostname>:]<portnum>", 0, false));
+ if (connect_wasm_cmd_up) {
+ if (connect_wasm_cmd_up->AddRegexCommand(
+ "^([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)$",
+ "process connect --plugin wasm connect://%1:%2") &&
+ connect_wasm_cmd_up->AddRegexCommand(
+ "^([[:digit:]]+)$",
+ "process connect --plugin wasm connect://localhost:%1")) {
+ CommandObjectSP command_sp(connect_wasm_cmd_up.release());
+ m_command_dict[std::string(command_sp->GetCommandName())] = command_sp;
+ }
+ }
+
std::unique_ptr<CommandObjectRegexCommand> connect_kdp_remote_cmd_up(
new CommandObjectRegexCommand(
*this, "kdp-remote",
diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt
index a51d0f7afd1759..be109a303e8669 100644
--- a/lldb/source/Plugins/Process/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/CMakeLists.txt
@@ -19,3 +19,4 @@ add_subdirectory(elf-core)
add_subdirectory(mach-core)
add_subdirectory(minidump)
add_subdirectory(FreeBSDKernel)
+add_subdirectory(wasm)
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 316be471df9295..674bbc9ff4fd0b 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1628,6 +1628,11 @@ void ProcessGDBRemote::ParseExpeditedRegisters(
}
}
+std::shared_ptr<ThreadGDBRemote>
+ProcessGDBRemote::CreateThread(lldb::tid_t tid) {
+ return std::make_shared<ThreadGDBRemote>(*this, tid);
+}
+
ThreadSP ProcessGDBRemote::SetThreadStopInfo(
lldb::tid_t tid, ExpeditedRegisterMap &expedited_register_map,
uint8_t signo, const std::string &thread_name, const std::string &reason,
@@ -1652,7 +1657,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);
}
}
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index c1ea1cc7905587..0463e39b7a63a4 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -355,6 +355,8 @@ class ProcessGDBRemote : public Process,
MonitorDebugserverProcess(std::weak_ptr<ProcessGDBRemote> process_wp,
lldb::pid_t pid, int signo, int exit_status);
+ virtual std::shared_ptr<ThreadGDBRemote> CreateThread(lldb::tid_t tid);
+
lldb::StateType SetThreadStopInfo(StringExtractor &stop_packet);
bool
diff --git a/lldb/source/Plugins/Process/wasm/CMakeLists.txt b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
new file mode 100644
index 00000000000000..ef2bfc634962d5
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_lldb_library(lldbPluginProcessWasm PLUGIN
+ ProcessWasm.cpp
+ ThreadWasm.cpp
+ UnwindWasm.cpp
+ wasmRegisterContext.cpp
+
+ LINK_LIBS
+ lldbCore
+ ${LLDB_PLUGINS}
+ LINK_COMPONENTS
+ Support
+ )
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
new file mode 100644
index 00000000000000..6e633e69b9767a
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -0,0 +1,291 @@
+//===-- ProcessWasm.cpp ---------------------------------------------------===//
+//
+// 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/Utility/DataBufferHeap.h"
+
+#include "lldb/Target/UnixSignals.h"
+#include "llvm/ADT/ArrayRef.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+using namespace lldb_private::wasm;
+
+LLDB_PLUGIN_DEFINE(ProcessWasm)
+
+// ProcessGDBRemote constructor
+ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
+ : ProcessGDBRemote(target_sp, listener_sp) {
+ /* always use linux signals for wasm process */
+ m_unix_signals_sp =
+ UnixSignals::Create(ArchSpec{"wasm32-unknown-unknown-wasm"});
+}
+
+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() {
+ static ConstString g_name("wasm");
+ return g_name;
+}
+
+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) {
+ lldb::ProcessSP process_sp;
+ if (crash_file_path == nullptr)
+ process_sp = std::make_shared<ProcessWasm>(target_sp, listener_sp);
+ return process_sp;
+}
+
+bool ProcessWasm::CanDebug(lldb::TargetSP target_sp,
+ bool plugin_specified_by_name) {
+ if (plugin_specified_by_name)
+ return true;
+
+ Module *exe_module = target_sp->GetExecutableModulePointer();
+ if (exe_module) {
+ 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:
+ default:
+ error.SetErrorStringWithFormat(
+ "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
+ return 0;
+ }
+}
+
+size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ ExecutionContext *exe_ctx, Status &error) {
+ wasm_addr_t wasm_addr(vm_addr);
+
+ switch (wasm_addr.GetType()) {
+ case WasmAddressType::Memory: {
+ // If we don't have a valid module_id, this is actually a read from the
+ // Wasm memory space. We can calculate the module_id from the execution
+ // context.
+ if (wasm_addr.module_id == 0 && exe_ctx != nullptr) {
+ StackFrame *frame = exe_ctx->GetFramePtr();
+ assert(frame->CalculateTarget()->GetArchitecture().GetMachine() ==
+ llvm::Triple::wasm32);
+ wasm_addr.module_id = wasm_addr_t(frame->GetStackID().GetPC()).module_id;
+ wasm_addr.type = WasmAddressType::Memory;
+ }
+ if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size))
+ return size;
+ error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
+ vm_addr);
+ return 0;
+ }
+ case WasmAddressType::Object:
+ return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+ case WasmAddressType::Invalid:
+ default:
+ error.SetErrorStringWithFormat(
+ "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
+ return 0;
+ }
+}
+
+size_t ProcessWasm::WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr,
+ void *buf, size_t buffer_size) {
+ char packet[64];
+ int packet_len =
+ ::snprintf(packet, sizeof(packet), "qWasmMem:%d;%" PRIx64 ";%" PRIx64,
+ wasm_module_id, static_cast<uint64_t>(addr),
+ static_cast<uint64_t>(buffer_size));
+ assert(packet_len + 1 < (int)sizeof(packet));
+ UNUSED_IF_ASSERT_DISABLED(packet_len);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response,
+ GetInterruptTimeout()) ==
+ GDBRemoteCommunication::PacketResult::Success) {
+ if (response.IsNormalResponse()) {
+ return response.GetHexBytes(llvm::MutableArrayRef<uint8_t>(
+ static_cast<uint8_t *>(buf), buffer_size),
+ '\xdd');
+ }
+ }
+ return 0;
+}
+
+size_t ProcessWasm::WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr,
+ void *buf, size_t buffer_size) {
+ char packet[64];
+ int packet_len =
+ ::snprintf(packet, sizeof(packet), "qWasmData:%d;%" PRIx64 ";%" PRIx64,
+ wasm_module_id, static_cast<uint64_t>(addr),
+ static_cast<uint64_t>(buffer_size));
+ assert(packet_len + 1 < (int)sizeof(packet));
+ UNUSED_IF_ASSERT_DISABLED(packet_len);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet, response,
+ GetInterruptTimeout()) ==
+ GDBRemoteCommunication::PacketResult::Success) {
+ if (response.IsNormalResponse()) {
+ return response.GetHexBytes(llvm::MutableArrayRef<uint8_t>(
+ static_cast<uint8_t *>(buf), buffer_size),
+ '\xdd');
+ }
+ }
+ return 0;
+}
+
+bool ProcessWasm::GetWasmLocal(int frame_index, int index, void *buf,
+ size_t buffer_size, size_t &size) {
+ StreamString packet;
+ packet.Printf("qWasmLocal:");
+ packet.Printf("%d;%d", frame_index, index);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+ GDBRemoteCommunication::PacketResult::Success) {
+ return false;
+ }
+
+ if (!response.IsNormalResponse()) {
+ return false;
+ }
+
+ WritableDataBufferSP buffer_sp(
+ new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+ response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+ size = buffer_sp->GetByteSize();
+ if (size <= buffer_size) {
+ memcpy(buf, buffer_sp->GetBytes(), size);
+ return true;
+ }
+
+ return false;
+}
+
+bool ProcessWasm::GetWasmGlobal(int frame_index, int index, void *buf,
+ size_t buffer_size, size_t &size) {
+ StreamString packet;
+ packet.PutCString("qWasmGlobal:");
+ packet.Printf("%d;%d", frame_index, index);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+ GDBRemoteCommunication::PacketResult::Success) {
+ return false;
+ }
+
+ if (!response.IsNormalResponse()) {
+ return false;
+ }
+
+ WritableDataBufferSP buffer_sp(
+ new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+ response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+ size = buffer_sp->GetByteSize();
+ if (size <= buffer_size) {
+ memcpy(buf, buffer_sp->GetBytes(), size);
+ return true;
+ }
+
+ return false;
+}
+
+bool ProcessWasm::GetWasmStackValue(int frame_index, int index, void *buf,
+ size_t buffer_size, size_t &size) {
+ StreamString packet;
+ packet.PutCString("qWasmStackValue:");
+ packet.Printf("%d;%d", frame_index, index);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+ GDBRemoteCommunication::PacketResult::Success) {
+ return false;
+ }
+
+ if (!response.IsNormalResponse()) {
+ return false;
+ }
+
+ WritableDataBufferSP buffer_sp(
+ new DataBufferHeap(response.GetStringRef().size() / 2, 0));
+ response.GetHexBytes(buffer_sp->GetData(), '\xcc');
+ size = buffer_sp->GetByteSize();
+ if (size <= buffer_size) {
+ memcpy(buf, buffer_sp->GetBytes(), size);
+ return true;
+ }
+
+ return false;
+}
+
+bool ProcessWasm::GetWasmCallStack(lldb::tid_t tid,
+ std::vector<lldb::addr_t> &call_stack_pcs) {
+ call_stack_pcs.clear();
+ StreamString packet;
+ packet.Printf("qWasmCallStack:");
+ packet.Printf("%llx", tid);
+ StringExtractorGDBRemote response;
+ if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
+ GDBRemoteCommunication::PacketResult::Success) {
+ return false;
+ }
+
+ if (!response.IsNormalResponse()) {
+ return false;
+ }
+
+ addr_t buf[1024 / sizeof(addr_t)];
+ size_t bytes = response.GetHexBytes(
+ llvm::MutableArrayRef<uint8_t>((uint8_t *)buf, sizeof(buf)), '\xdd');
+ if (bytes == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < bytes / sizeof(addr_t); i++) {
+ call_stack_pcs.push_back(buf[i]);
+ }
+ return true;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
new file mode 100644
index 00000000000000..fc9736ec0684d2
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -0,0 +1,129 @@
+//===-- ProcessWasm.h -------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
+
+#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
+#include "lldb/Target/RegisterContext.h"
+
+namespace lldb_private {
+namespace wasm {
+
+/// Each WebAssembly module has separated address spaces for Code and Memory.
+/// A WebAssembly module also has a Data section which, when the module is
+/// loaded, gets mapped into a region in the module Memory.
+/// For the purpose of debugging, we can represent all these separated 32-bit
+/// address spaces with a single virtual 64-bit address space.
+///
+/// Struct wasm_addr_t provides this encoding using bitfields
+///
+enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0x03 };
+
+struct wasm_addr_t {
+ uint64_t offset : 32;
+ uint64_t module_id : 30;
+ uint64_t type : 2;
+
+ wasm_addr_t(lldb::addr_t addr)
+ : type(addr >> 62), module_id((addr & 0x00ffffff00000000) >> 32),
+ offset(addr & 0x00000000ffffffff) {}
+
+ wasm_addr_t(WasmAddressType type_, uint32_t module_id_, uint32_t offset_)
+ : type(type_), module_id(module_id_), offset(offset_) {}
+
+ WasmAddressType GetType() { return static_cast<WasmAddressType>(type); }
+ operator lldb::addr_t() { return *(uint64_t *)this; }
+};
+
+/// ProcessWasm provides the access to the Wasm program state
+/// retrieved from the Wasm engine.
+class ProcessWasm : public process_gdb_remote::ProcessGDBRemote {
+public:
+ ProcessWasm(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
+ ~ProcessWasm() override = default;
+
+ static lldb::ProcessSP CreateInstance(lldb::TargetSP target_sp,
+ lldb::ListenerSP listener_sp,
+ const FileSpec *crash_file_path,
+ bool can_connect);
+
+ static void Initialize();
+ static void DebuggerInitialize(Debugger &debugger);
+ static void Terminate();
+ static llvm::StringRef GetPluginNameStatic();
+ static llvm::StringRef GetPluginDescriptionStatic();
+
+ /// PluginInterface protocol.
+ /// \{
+ llvm::StringRef GetPluginName() override;
+ /// \}
+
+ /// Process protocol.
+ /// \{
+ size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ Status &error) override;
+
+ size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ ExecutionContext *exe_ctx, Status &error) override;
+ /// \}
+
+ /// Query the value of a WebAssembly local variable from the WebAssembly
+ /// remote process.
+ bool GetWasmLocal(int frame_index, int index, void *buf, size_t buffer_size,
+ size_t &size);
+
+ /// Query the value of a WebAssembly global variable from the WebAssembly
+ /// remote process.
+ bool GetWasmGlobal(int frame_index, int index, void *buf, size_t buffer_size,
+ size_t &size);
+
+ /// Query the value of an item in the WebAssembly operand stack from the
+ /// WebAssembly remote process.
+ bool GetWasmStackValue(int frame_index, int index, void *buf,
+ size_t buffer_size, size_t &size);
+
+ /// Read from the WebAssembly Memory space.
+ size_t WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr, void *buf,
+ size_t buffer_size);
+
+ /// Read from the WebAssembly Data space.
+ size_t WasmReadData(uint32_t wasm_module_id, lldb::addr_t addr, void *buf,
+ size_t buffer_size);
+
+ /// Retrieve the current call stack from the WebAssembly remote process.
+ bool GetWasmCallStack(lldb::tid_t tid,
+ std::vector<lldb::addr_t> &call_stack_pcs);
+
+ // Check if a given Process
+ bool CanDebug(lldb::TargetSP target_sp,
+ bool plugin_specified_by_name) override;
+
+protected:
+ /// ProcessGDBRemote protocol.
+ /// \{
+ std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
+ CreateThread(lldb::tid_t tid);
+ /// \}
+
+private:
+ friend class UnwindWasm;
+ friend class ThreadWasm;
+
+ process_gdb_remote::GDBRemoteDynamicRegisterInfoSP &GetRegisterInfo() {
+ return m_register_info_sp;
+ }
+
+ ProcessWasm(const ProcessWasm &);
+ const ProcessWasm &operator=(const ProcessWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
new file mode 100644
index 00000000000000..9d3ee48912e405
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
@@ -0,0 +1,55 @@
+//===-- ThreadWasm.cpp ----------------------------------------------------===//
+//
+// 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 "ThreadWasm.h"
+
+#include "ProcessWasm.h"
+#include "UnwindWasm.h"
+#include "lldb/Target/Target.h"
+#include "wasmRegisterContext.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::wasm;
+
+Unwind &ThreadWasm::GetUnwinder() {
+ if (!m_unwinder_up) {
+ assert(CalculateTarget()->GetArchitecture().GetMachine() ==
+ llvm::Triple::wasm32);
+ m_unwinder_up.reset(new wasm::UnwindWasm(*this));
+ }
+ return *m_unwinder_up;
+}
+
+bool ThreadWasm::GetWasmCallStack(std::vector<lldb::addr_t> &call_stack_pcs) {
+ ProcessSP process_sp(GetProcess());
+ if (process_sp) {
+ ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+ return wasm_process->GetWasmCallStack(GetID(), call_stack_pcs);
+ }
+ return false;
+}
+
+lldb::RegisterContextSP
+ThreadWasm::CreateRegisterContextForFrame(StackFrame *frame) {
+ lldb::RegisterContextSP reg_ctx_sp;
+ uint32_t concrete_frame_idx = 0;
+ ProcessSP process_sp(GetProcess());
+ ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+
+ if (frame)
+ concrete_frame_idx = frame->GetConcreteFrameIndex();
+
+ if (concrete_frame_idx == 0) {
+ reg_ctx_sp = std::make_shared<WasmRegisterContext>(
+ *this, concrete_frame_idx, wasm_process->GetRegisterInfo());
+ } else {
+ reg_ctx_sp = GetUnwinder().CreateRegisterContextForFrame(frame);
+ }
+ return reg_ctx_sp;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.h b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
new file mode 100644
index 00000000000000..c1aa2cd8b98825
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
@@ -0,0 +1,44 @@
+//===-- ThreadWasm.h --------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
+
+#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h"
+
+namespace lldb_private {
+namespace wasm {
+
+/// ProcessWasm provides the access to the Wasm program state
+/// retrieved from the Wasm engine.
+class ThreadWasm : public process_gdb_remote::ThreadGDBRemote {
+public:
+ ThreadWasm(Process &process, lldb::tid_t tid)
+ : process_gdb_remote::ThreadGDBRemote(process, tid) {}
+ ~ThreadWasm() override = default;
+
+ /// Retrieve the current call stack from the WebAssembly remote process.
+ bool GetWasmCallStack(std::vector<lldb::addr_t> &call_stack_pcs);
+
+ lldb::RegisterContextSP
+ CreateRegisterContextForFrame(StackFrame *frame) override;
+
+protected:
+ /// Thread protocol.
+ /// \{
+ Unwind &GetUnwinder() override;
+ /// \}
+
+ ThreadWasm(const ThreadWasm &);
+ const ThreadWasm &operator=(const ThreadWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_THREADWASM_H
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
new file mode 100644
index 00000000000000..9e8f6a9ce7d9ff
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
@@ -0,0 +1,76 @@
+//===-- UnwindWasm.cpp ----------------------------------------------------===//
+//
+// 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 "UnwindWasm.h"
+#include "Plugins/Process/gdb-remote/ThreadGDBRemote.h"
+#include "Plugins/Process/wasm/ProcessWasm.h"
+#include "Plugins/Process/wasm/ThreadWasm.h"
+#include "lldb/lldb-forward.h"
+#include "wasmRegisterContext.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace process_gdb_remote;
+using namespace wasm;
+
+class WasmGDBRemoteRegisterContext : public GDBRemoteRegisterContext {
+public:
+ WasmGDBRemoteRegisterContext(ThreadGDBRemote &thread,
+ uint32_t concrete_frame_idx,
+ GDBRemoteDynamicRegisterInfoSP ®_info_sp,
+ uint64_t pc)
+ : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
+ false) {
+ PrivateSetRegisterValue(0, pc);
+ }
+};
+
+lldb::RegisterContextSP
+UnwindWasm::DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) {
+ if (m_frames.size() <= frame->GetFrameIndex()) {
+ return lldb::RegisterContextSP();
+ }
+
+ ThreadSP thread = frame->GetThread();
+ ProcessSP process_sp = thread->GetProcess();
+ ThreadWasm *wasm_thread = static_cast<ThreadWasm *>(thread.get());
+ ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+ std::shared_ptr<WasmRegisterContext> reg_ctx_sp =
+ std::make_shared<WasmRegisterContext>(*wasm_thread,
+ frame->GetConcreteFrameIndex(),
+ wasm_process->GetRegisterInfo());
+ return reg_ctx_sp;
+}
+
+uint32_t UnwindWasm::DoGetFrameCount() {
+ if (!m_unwind_complete) {
+ m_unwind_complete = true;
+ m_frames.clear();
+
+ ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(GetThread());
+ if (!wasm_thread.GetWasmCallStack(m_frames))
+ m_frames.clear();
+ }
+ return m_frames.size();
+}
+
+bool UnwindWasm::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
+ lldb::addr_t &pc,
+ bool &behaves_like_zeroth_frame) {
+ if (m_frames.size() == 0) {
+ DoGetFrameCount();
+ }
+
+ if (frame_idx < m_frames.size()) {
+ behaves_like_zeroth_frame = (frame_idx == 0);
+ cfa = 0;
+ pc = m_frames[frame_idx];
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.h b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
new file mode 100644
index 00000000000000..9bd1dac9a98ae3
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
@@ -0,0 +1,55 @@
+//===-- UnwindWasm.h --------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_UnwindWasm_h_
+#define lldb_UnwindWasm_h_
+
+#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/Unwind.h"
+#include <vector>
+
+namespace lldb_private {
+namespace wasm {
+
+/// UnwindWasm manages stack unwinding for a WebAssembly process.
+class UnwindWasm : public lldb_private::Unwind {
+public:
+ UnwindWasm(lldb_private::Thread &thread)
+ : Unwind(thread), m_frames(), m_unwind_complete(false) {}
+ ~UnwindWasm() override = default;
+
+protected:
+ /// Unwind protocol.
+ /// \{
+ void DoClear() override {
+ m_frames.clear();
+ m_unwind_complete = false;
+ }
+
+ uint32_t DoGetFrameCount() override;
+
+ bool DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
+ lldb::addr_t &pc,
+ bool &behaves_like_zeroth_frame) override;
+
+ lldb::RegisterContextSP
+ DoCreateRegisterContextForFrame(lldb_private::StackFrame *frame) override;
+ /// \}
+
+private:
+ std::vector<lldb::addr_t> m_frames;
+ bool m_unwind_complete;
+
+ UnwindWasm(const UnwindWasm &);
+ const UnwindWasm &operator=(const UnwindWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // lldb_UnwindWasm_h_
diff --git a/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
new file mode 100644
index 00000000000000..470a0fb6a43cb3
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.cpp
@@ -0,0 +1,108 @@
+//===---- wasmRegisterContext.cpp -----------------------------------------===//
+//
+// 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 "wasmRegisterContext.h"
+#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
+#include "ProcessWasm.h"
+#include "ThreadWasm.h"
+#include "lldb/Utility/RegisterValue.h"
+#include <memory>
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_gdb_remote;
+using namespace lldb_private::wasm;
+
+WasmRegisterContext::WasmRegisterContext(
+ wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
+ GDBRemoteDynamicRegisterInfoSP reg_info_sp)
+ : GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
+ false) {}
+
+WasmRegisterContext::~WasmRegisterContext() = default;
+
+uint32_t WasmRegisterContext::ConvertRegisterKindToRegisterNumber(
+ lldb::RegisterKind kind, uint32_t num) {
+ return num;
+}
+
+size_t WasmRegisterContext::GetRegisterCount() { return 0; }
+
+const RegisterInfo *WasmRegisterContext::GetRegisterInfoAtIndex(size_t reg) {
+ uint32_t tag = (reg >> 30) & 0x03;
+ if (tag == 0) {
+ return m_reg_info_sp->GetRegisterInfoAtIndex(reg);
+ }
+
+ auto it = m_register_map.find(reg);
+ if (it == m_register_map.end()) {
+ WasmVirtualRegisterKinds kind =
+ static_cast<WasmVirtualRegisterKinds>(tag - 1);
+ std::tie(it, std::ignore) = m_register_map.insert(
+ {reg,
+ std::make_unique<WasmVirtualRegisterInfo>(kind, reg & 0x3fffffff)});
+ }
+ return it->second.get();
+}
+
+size_t WasmRegisterContext::GetRegisterSetCount() { return 0; }
+
+const RegisterSet *WasmRegisterContext::GetRegisterSet(size_t reg_set) {
+ return nullptr;
+}
+
+bool WasmRegisterContext::ReadRegister(const RegisterInfo *reg_info,
+ RegisterValue &value) {
+ if (reg_info->name) {
+ return GDBRemoteRegisterContext::ReadRegister(reg_info, value);
+ }
+
+ ThreadWasm *thread = static_cast<ThreadWasm *>(&GetThread());
+ ProcessWasm *process = static_cast<ProcessWasm *>(thread->GetProcess().get());
+ if (!thread)
+ return false;
+
+ uint32_t frame_index = m_concrete_frame_idx;
+ WasmVirtualRegisterInfo *wasm_reg_info =
+ static_cast<WasmVirtualRegisterInfo *>(
+ const_cast<RegisterInfo *>(reg_info));
+ uint8_t buf[16];
+ size_t size = 0;
+ switch (wasm_reg_info->kind) {
+ case eLocal:
+ process->GetWasmLocal(frame_index, wasm_reg_info->index, buf, sizeof(buf),
+ size);
+ break;
+ case eGlobal:
+ process->GetWasmGlobal(frame_index, wasm_reg_info->index, buf, sizeof(buf),
+ size);
+ break;
+ case eOperandStack:
+ process->GetWasmStackValue(frame_index, wasm_reg_info->index, buf,
+ sizeof(buf), size);
+ break;
+ default:
+ return false;
+ }
+
+ DataExtractor reg_data(buf, size, process->GetByteOrder(),
+ process->GetAddressByteSize());
+ const bool partial_data_ok = false;
+ wasm_reg_info->byte_size = size;
+ wasm_reg_info->encoding = lldb::eEncodingUint;
+ Status error(value.SetValueFromData(*reg_info, reg_data,
+ reg_info->byte_offset, partial_data_ok));
+ return error.Success();
+}
+
+void WasmRegisterContext::InvalidateAllRegisters() {}
+
+bool WasmRegisterContext::WriteRegister(const RegisterInfo *reg_info,
+ const RegisterValue &value) {
+ return false;
+}
diff --git a/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h
new file mode 100644
index 00000000000000..f60cc8e6a099ab
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/wasmRegisterContext.h
@@ -0,0 +1,75 @@
+//===----- wasmRegisterContext.h --------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H
+
+#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
+#include "ThreadWasm.h"
+#include "lldb/lldb-private-types.h"
+#include <unordered_map>
+
+namespace lldb_private {
+namespace wasm {
+
+class WasmRegisterContext;
+
+typedef std::shared_ptr<WasmRegisterContext> WasmRegisterContextSP;
+
+enum WasmVirtualRegisterKinds {
+ eLocal = 0, ///< wasm local
+ eGlobal, ///< wasm global
+ eOperandStack, ///< wasm operand stack
+ kNumWasmVirtualRegisterKinds
+};
+
+struct WasmVirtualRegisterInfo : public RegisterInfo {
+ WasmVirtualRegisterKinds kind;
+ uint32_t index;
+
+ WasmVirtualRegisterInfo(WasmVirtualRegisterKinds kind, uint32_t index)
+ : RegisterInfo(), kind(kind), index(index) {}
+};
+
+class WasmRegisterContext
+ : public process_gdb_remote::GDBRemoteRegisterContext {
+public:
+ WasmRegisterContext(
+ wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
+ process_gdb_remote::GDBRemoteDynamicRegisterInfoSP reg_info_sp);
+
+ ~WasmRegisterContext() override;
+
+ uint32_t ConvertRegisterKindToRegisterNumber(lldb::RegisterKind kind,
+ uint32_t num) override;
+
+ void InvalidateAllRegisters() override;
+
+ size_t GetRegisterCount() override;
+
+ const RegisterInfo *GetRegisterInfoAtIndex(size_t reg) override;
+
+ size_t GetRegisterSetCount() override;
+
+ const RegisterSet *GetRegisterSet(size_t reg_set) override;
+
+ bool ReadRegister(const RegisterInfo *reg_info,
+ RegisterValue &value) override;
+
+ bool WriteRegister(const RegisterInfo *reg_info,
+ const RegisterValue &value) override;
+
+private:
+ std::unordered_map<size_t, std::unique_ptr<WasmVirtualRegisterInfo>>
+ m_register_map;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_WASM_WASMREGISTERCONTEXT_H
>From cbd046618749f229fce7221e012bf51f4789301b Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Mon, 22 Jan 2024 05:17:23 -0800
Subject: [PATCH 2/3] Add unit tests
---
lldb/include/lldb/Target/Process.h | 54 +---
lldb/source/Core/Value.cpp | 5 +-
lldb/source/Expression/DWARFExpression.cpp | 2 +-
lldb/source/Expression/Materializer.cpp | 4 +-
.../Plugins/Process/wasm/ProcessWasm.cpp | 62 ++---
.../source/Plugins/Process/wasm/ProcessWasm.h | 13 +-
.../gdb_remote_client/TestWasm.py | 239 ++++++++++++++----
7 files changed, 242 insertions(+), 137 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 587ae085b479b7..b7779985db4fc3 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1449,6 +1449,14 @@ class Process : public std::enable_shared_from_this<Process>,
/// platforms where there is a difference (only Arm Thumb at this time).
lldb::addr_t FixAnyAddress(lldb::addr_t pc);
+ /// Some targets might use bits in a code address to represent additional
+ /// information; for example WebAssembly targets have a different memory space
+ /// per module and have a different address space per memory and code.
+ virtual lldb::addr_t FixMemoryAddress(lldb::addr_t address,
+ StackFrame *stack_frame) const {
+ return address;
+ }
+
/// Get the Modification ID of the process.
///
/// \return
@@ -1548,46 +1556,6 @@ class Process : public std::enable_shared_from_this<Process>,
virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error);
- /// Read of memory from a process.
- ///
- /// This function will read memory from the current process's address space
- /// and remove any traps that may have been inserted into the memory.
- ///
- /// This overloads accepts an ExecutionContext as additional argument. By
- /// default, it calls the previous overload without the ExecutionContext
- /// argument, but it can be overridden by Process subclasses.
- ///
- /// \param[in] vm_addr
- /// A virtual load address that indicates where to start reading
- /// memory from.
- ///
- /// \param[out] buf
- /// A byte buffer that is at least \a size bytes long that
- /// will receive the memory bytes.
- ///
- /// \param[in] size
- /// The number of bytes to read.
- ///
- /// \param[in] exe_ctx
- /// The current execution context, if available.
- ///
- /// \param[out] error
- /// An error that indicates the success or failure of this
- /// operation. If error indicates success (error.Success()),
- /// then the value returned can be trusted, otherwise zero
- /// will be returned.
- ///
- /// \return
- /// The number of bytes that were actually read into \a buf. If
- /// the returned number is greater than zero, yet less than \a
- /// size, then this function will get called again with \a
- /// vm_addr, \a buf, and \a size updated appropriately. Zero is
- /// returned in the case of an error.
- virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
- ExecutionContext *exe_ctx, Status &error) {
- return ReadMemory(vm_addr, buf, size, error);
- }
-
/// Read of memory from a process.
///
/// This function has the same semantics of ReadMemory except that it
@@ -1965,9 +1933,9 @@ class Process : public std::enable_shared_from_this<Process>,
/// the instruction has completed executing.
bool GetWatchpointReportedAfter();
- lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
- lldb::addr_t header_addr,
- size_t size_to_read = 512);
+ virtual lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
+ lldb::addr_t header_addr,
+ size_t size_to_read = 512);
/// Attempt to get the attributes for a region of memory in the process.
///
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 47a5fdee773886..7766d6543edb0c 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -551,8 +551,9 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
Process *process = exe_ctx->GetProcessPtr();
if (process) {
- const size_t bytes_read =
- process->ReadMemory(address, dst, byte_size, exe_ctx, error);
+ const size_t bytes_read = process->ReadMemory(
+ process->FixMemoryAddress(address, exe_ctx->GetFramePtr()), dst,
+ byte_size, error);
if (bytes_read != byte_size)
error.SetErrorStringWithFormat(
"read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index ca24611724dc7c..95033db5ed8f5a 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -2621,7 +2621,7 @@ bool DWARFExpression::Evaluate(
*/
if (wasm_op == 3) {
index = opcodes.GetU32(&offset);
- wasm_op = 1;
+ wasm_op = 2; // Global
} else {
index = opcodes.GetULEB128(&offset);
}
diff --git a/lldb/source/Expression/Materializer.cpp b/lldb/source/Expression/Materializer.cpp
index 6e344dfd57c4b5..8c4cd4dfc2db5f 100644
--- a/lldb/source/Expression/Materializer.cpp
+++ b/lldb/source/Expression/Materializer.cpp
@@ -1077,7 +1077,9 @@ class EntityResultVariable : public Materializer::Entity {
const size_t pvar_byte_size = ret->GetByteSize().value_or(0);
uint8_t *pvar_data = ret->GetValueBytes();
- map.ReadMemory(pvar_data, address, pvar_byte_size, read_error);
+ map.ReadMemory(pvar_data,
+ process_sp->FixMemoryAddress(address, frame_sp.get()),
+ pvar_byte_size, read_error);
if (!read_error.Success()) {
err.SetErrorString(
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index 6e633e69b9767a..19b8babeb59351 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -94,8 +94,19 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
switch (wasm_addr.GetType()) {
case WasmAddressType::Memory:
- case WasmAddressType::Object:
- return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+ if (wasm_addr.module_id != 0) {
+ if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size)) {
+ return size;
+ }
+ error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
+ vm_addr);
+ return 0;
+ } else {
+ return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
+ }
+ case WasmAddressType::Code:
+ wasm_addr.type = 0;
+ return ProcessGDBRemote::ReadMemory(wasm_addr, buf, size, error);
case WasmAddressType::Invalid:
default:
error.SetErrorStringWithFormat(
@@ -104,36 +115,27 @@ size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
}
}
-size_t ProcessWasm::ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
- ExecutionContext *exe_ctx, Status &error) {
- wasm_addr_t wasm_addr(vm_addr);
+lldb::ModuleSP ProcessWasm::ReadModuleFromMemory(const FileSpec &file_spec,
+ lldb::addr_t header_addr,
+ size_t size_to_read) {
+ wasm_addr_t wasm_addr(header_addr);
+ wasm_addr.type = WasmAddressType::Code;
+ return Process::ReadModuleFromMemory(file_spec, wasm_addr, size_to_read);
+}
- switch (wasm_addr.GetType()) {
- case WasmAddressType::Memory: {
- // If we don't have a valid module_id, this is actually a read from the
- // Wasm memory space. We can calculate the module_id from the execution
- // context.
- if (wasm_addr.module_id == 0 && exe_ctx != nullptr) {
- StackFrame *frame = exe_ctx->GetFramePtr();
- assert(frame->CalculateTarget()->GetArchitecture().GetMachine() ==
- llvm::Triple::wasm32);
- wasm_addr.module_id = wasm_addr_t(frame->GetStackID().GetPC()).module_id;
- wasm_addr.type = WasmAddressType::Memory;
- }
- if (WasmReadMemory(wasm_addr.module_id, wasm_addr.offset, buf, size))
- return size;
- error.SetErrorStringWithFormat("Wasm memory read failed for 0x%" PRIx64,
- vm_addr);
- return 0;
- }
- case WasmAddressType::Object:
- return ProcessGDBRemote::ReadMemory(vm_addr, buf, size, error);
- case WasmAddressType::Invalid:
- default:
- error.SetErrorStringWithFormat(
- "Wasm read failed for invalid address 0x%" PRIx64, vm_addr);
- return 0;
+lldb::addr_t ProcessWasm::FixMemoryAddress(lldb::addr_t address,
+ StackFrame *stack_frame) const {
+ if (stack_frame) {
+ assert(stack_frame->CalculateTarget()->GetArchitecture().GetMachine() ==
+ llvm::Triple::wasm32);
+ // Extract Wasm module ID from the program counter.
+ wasm_addr_t wasm_addr(address);
+ wasm_addr.module_id =
+ wasm_addr_t(stack_frame->GetStackID().GetPC()).module_id;
+ wasm_addr.type = WasmAddressType::Memory;
+ return wasm_addr;
}
+ return address;
}
size_t ProcessWasm::WasmReadMemory(uint32_t wasm_module_id, lldb::addr_t addr,
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index fc9736ec0684d2..b1051b0840c2b2 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -23,7 +23,7 @@ namespace wasm {
///
/// Struct wasm_addr_t provides this encoding using bitfields
///
-enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0x03 };
+enum WasmAddressType { Memory = 0x00, Code = 0x01, Invalid = 0x03 };
struct wasm_addr_t {
uint64_t offset : 32;
@@ -64,14 +64,15 @@ class ProcessWasm : public process_gdb_remote::ProcessGDBRemote {
llvm::StringRef GetPluginName() override;
/// \}
- /// Process protocol.
- /// \{
size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error) override;
- size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
- ExecutionContext *exe_ctx, Status &error) override;
- /// \}
+ lldb::ModuleSP ReadModuleFromMemory(const FileSpec &file_spec,
+ lldb::addr_t header_addr,
+ size_t size_to_read = 512) override;
+
+ lldb::addr_t FixMemoryAddress(lldb::addr_t address,
+ StackFrame *stack_frame) const override;
/// Query the value of a WebAssembly local variable from the WebAssembly
/// remote process.
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index 61f315bb65ca53..f2ac2abdeef0c0 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -6,8 +6,8 @@
from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
LLDB_INVALID_ADDRESS = lldb.LLDB_INVALID_ADDRESS
-load_address = 0x400000000
-
+MODULE_ID = 4
+LOAD_ADDRESS = MODULE_ID << 32
def format_register_value(val):
"""
@@ -23,18 +23,33 @@ def format_register_value(val):
shift += 8
return result
+def make_code_address(module_id, offset):
+ return 0x4000000000000000 | (module_id << 32) | offset
class MyResponder(MockGDBServerResponder):
- current_pc = load_address + 0x0A
+ current_pc = LOAD_ADDRESS + 0x0A
- def __init__(self, obj_path, module_name=""):
+ def __init__(self, obj_path, module_name, wasm_call_stacks=[], memory_view=[]):
self._obj_path = obj_path
self._module_name = module_name or obj_path
+ self._bp_address = 0
+ self._wasm_call_stacks = wasm_call_stacks
+ self._call_stack_request_count = 0
+ self._memory_view = memory_view
MockGDBServerResponder.__init__(self)
+ def SetCurrentPC(self, address):
+ self.current_pc = LOAD_ADDRESS + address
+
def respond(self, packet):
if packet[0:13] == "qRegisterInfo":
return self.qRegisterInfo(packet[13:])
+ if packet.startswith("qWasmCallStack"):
+ return self.qWasmCallStack();
+ if packet.startswith("qWasmLocal"):
+ return self.qWasmLocal(packet);
+ if packet.startswith("qWasmMem"):
+ return self.qWasmMem(packet);
return MockGDBServerResponder.respond(self, packet)
def qSupported(self, client_supported):
@@ -50,7 +65,7 @@ def qfThreadInfo(self):
return "OK"
def qRegisterInfo(self, index):
- if index == 0:
+ if index == "0":
return "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"
return "E45"
@@ -61,36 +76,131 @@ def qProcessInfo(self):
)
def haltReason(self):
- return "T05thread:1;"
+ return "T05thread-pcs:300000083;thread:1;library:;"
def readRegister(self, register):
+ assert(register == 0)
return format_register_value(self.current_pc)
def qXferRead(self, obj, annex, offset, length):
if obj == "libraries":
xml = (
'<library-list><library name="%s"><section address="%d"/></library></library-list>'
- % (self._module_name, load_address)
+ % (self._module_name, make_code_address(MODULE_ID, 0))
)
return xml, False
else:
return None, False
def readMemory(self, addr, length):
- if addr < load_address:
- return "E02"
result = ""
with open(self._obj_path, mode="rb") as file:
+ if addr < LOAD_ADDRESS:
+ return "E02"
file_content = bytearray(file.read())
- addr_from = addr - load_address
+ if addr >= LOAD_ADDRESS + len(file_content):
+ return "E03"
+ addr_from = addr - LOAD_ADDRESS
addr_to = addr_from + min(length, len(file_content) - addr_from)
for i in range(addr_from, addr_to):
result += format(file_content[i], "02x")
file.close()
return result
+ def setBreakpoint(self, packet):
+ bp_data = packet[1:].split(",")
+ self._bp_address = bp_data[1]
+ return "OK"
+
+ def qfThreadInfo(self):
+ return "m1"
+
+ def cont(self):
+ # Continue execution. Simulates running the Wasm engine until a breakpoint is hit.
+ return "T05thread-pcs:" + format(int(self._bp_address, 16) & 0x3fffffffffffffff, 'x') + ";thread:1"
+
+ def qWasmCallStack(self):
+ if len(self._wasm_call_stacks) == 0:
+ return ""
+ result = self._wasm_call_stacks[self._call_stack_request_count].format();
+ self._call_stack_request_count = self._call_stack_request_count + 1
+ return result
+
+ def qWasmLocal(self, packet):
+ # Format: qWasmLocal:frame_index;index
+ data = packet.split(":")
+ data = data[1].split(";")
+ frame_index = data[0]
+ local_index = data[1]
+ if frame_index == '0' and local_index == '4':
+ return "b0ff0000"
+ if frame_index == '1' and local_index == '5':
+ return "c0ff0000"
+ return "E03";
+
+ def qWasmMem(self, packet):
+ # Format: qWasmMem:module_id;addr;len
+ data = packet.split(":")
+ data = data[1].split(";")
+ module_id = data[0]
+ addr = int(data[1], 16)
+ length = int(data[2])
+ if module_id != '4':
+ return "E03"
+ if addr >= 0xffb8 and addr < 0x10000:
+ chunk = self._memory_view[addr:addr+length]
+ return chunk.hex()
+ return "E03";
+
+class WasmStackFrame:
+ pass
+
+ def __init__(self, module_id, address):
+ self._module_id = module_id
+ self._address = address
+
+ def format(self):
+ return format_register_value(make_code_address(self._module_id, self._address))
+
+class WasmCallStack:
+ pass
+
+ def __init__(self, wasm_stack_frames):
+ self._wasm_stack_frames = wasm_stack_frames
+
+ def format(self):
+ result = ""
+ for frame in self._wasm_stack_frames:
+ result += frame.format()
+ return result
+
+class WasmMemorySpan:
+ pass
+
+ def __init__(self, offset, bytes):
+ self._offset = offset
+ self._bytes = bytes
class TestWasm(GDBRemoteTestBase):
+ def connect_to_wasm_engine(self, target):
+ """
+ Create a process by connecting to the mock GDB server running in a mock WebAssembly engine.
+ Includes assertions that the process was successfully created.
+ """
+ listener = self.dbg.GetListener()
+ error = lldb.SBError()
+ process = target.ConnectRemote(
+ listener, self.server.get_connect_url(), "wasm", error
+ )
+ self.assertTrue(error.Success(), error.description)
+ self.assertTrue(process, PROCESS_IS_VALID)
+ return process
+
+ def store_bytes(self, offset, bytes_obj):
+ chunk = self.memory_view[offset:offset+len(bytes_obj)]
+ for i in range(len(bytes_obj)):
+ chunk[i] = bytes_obj[i]
+
@skipIfAsan
@skipIfXmlSupportMissing
def test_load_module_with_embedded_symbols_from_remote(self):
@@ -104,7 +214,7 @@ def test_load_module_with_embedded_symbols_from_remote(self):
self.server.responder = MyResponder(obj_path, "test_wasm")
target = self.dbg.CreateTarget("")
- process = self.connect(target)
+ process = self.connect_to_wasm_engine(target)
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
@@ -119,35 +229,35 @@ def test_load_module_with_embedded_symbols_from_remote(self):
code_section = module.GetSectionAtIndex(0)
self.assertEquals("code", code_section.GetName())
self.assertEquals(
- load_address | code_section.GetFileOffset(),
+ make_code_address(MODULE_ID, code_section.GetFileOffset()),
code_section.GetLoadAddress(target),
)
debug_info_section = module.GetSectionAtIndex(1)
self.assertEquals(".debug_info", debug_info_section.GetName())
self.assertEquals(
- load_address | debug_info_section.GetFileOffset(),
+ make_code_address(MODULE_ID, debug_info_section.GetFileOffset()),
debug_info_section.GetLoadAddress(target),
)
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEquals(
- load_address | debug_abbrev_section.GetFileOffset(),
+ make_code_address(MODULE_ID, debug_abbrev_section.GetFileOffset()),
debug_abbrev_section.GetLoadAddress(target),
)
debug_line_section = module.GetSectionAtIndex(3)
self.assertEquals(".debug_line", debug_line_section.GetName())
self.assertEquals(
- load_address | debug_line_section.GetFileOffset(),
+ make_code_address(MODULE_ID, debug_line_section.GetFileOffset()),
debug_line_section.GetLoadAddress(target),
)
debug_str_section = module.GetSectionAtIndex(4)
self.assertEquals(".debug_str", debug_str_section.GetName())
self.assertEquals(
- load_address | debug_line_section.GetFileOffset(),
+ make_code_address(MODULE_ID, debug_line_section.GetFileOffset()),
debug_line_section.GetLoadAddress(target),
)
@@ -174,7 +284,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
)
target = self.dbg.CreateTarget("")
- process = self.connect(target)
+ process = self.connect_to_wasm_engine(target)
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
@@ -189,7 +299,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
code_section = module.GetSectionAtIndex(0)
self.assertEquals("code", code_section.GetName())
self.assertEquals(
- load_address | code_section.GetFileOffset(),
+ make_code_address(MODULE_ID, code_section.GetFileOffset()),
code_section.GetLoadAddress(target),
)
@@ -216,59 +326,80 @@ def test_load_module_with_stripped_symbols_from_remote(self):
self.assertEquals(
LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
)
-
+
@skipIfAsan
@skipIfXmlSupportMissing
- def test_load_module_from_file(self):
- """Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module from a file"""
+ def test_simple_wasm_debugging_session(self):
+ """Test connecting to a WebAssembly engine via GDB-remote, loading a Wasm module with embedded DWARF symbols, setting a breakpoint and checking the debuggee state"""
- yaml_path = "test_wasm_embedded_debug_sections.yaml"
+ yaml_path = "calc-cpp-o0.yaml"
yaml_base, ext = os.path.splitext(yaml_path)
- obj_path = self.getBuildArtifact(yaml_base)
+ obj_path = self.getBuildArtifact(yaml_base) + ".wasm"
self.yaml2obj(yaml_path, obj_path)
- self.server.responder = MyResponder(obj_path)
+ self.memory = bytearray(65536)
+ self.memory_view = memoryview(self.memory)
+ self.store_bytes(0xffb8, bytes.fromhex("d8ff0000"))
+ self.store_bytes(0xffbc, bytes.fromhex("e0ff0000"))
+ self.store_bytes(0xffe0, bytes.fromhex("0000000000000000"))
+ self.store_bytes(0xffe8, bytes.fromhex("0000000000004540"))
+ self.store_bytes(0xfff0, bytes.fromhex("0000000000003640"))
+ self.store_bytes(0xfff8, bytes.fromhex("0000000000003440"))
+
+ call_stacks = [
+ WasmCallStack([WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)]),
+ WasmCallStack([WasmStackFrame(4, 0x000002ad), WasmStackFrame(4, 0x0000014a), WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)])
+ ]
+ self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks, self.memory_view)
target = self.dbg.CreateTarget("")
- process = self.connect(target)
+ breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9);
+
+ process = self.connect_to_wasm_engine(target)
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
- num_modules = target.GetNumModules()
- self.assertEquals(1, num_modules)
+ # Verify all breakpoint locations are enabled.
+ location = breakpoint.GetLocationAtIndex(0)
+ self.assertTrue(location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION)
- module = target.GetModuleAtIndex(0)
- num_sections = module.GetNumSections()
- self.assertEquals(5, num_sections)
+ # Continue execution.
+ self.runCmd("c")
- code_section = module.GetSectionAtIndex(0)
- self.assertEquals("code", code_section.GetName())
- self.assertEquals(
- load_address | code_section.GetFileOffset(),
- code_section.GetLoadAddress(target),
- )
+ # Verify 1st breakpoint location is hit.
+ from lldbsuite.test.lldbutil import get_stopped_thread
- debug_info_section = module.GetSectionAtIndex(1)
- self.assertEquals(".debug_info", debug_info_section.GetName())
- self.assertEquals(
- LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)
+ thread = get_stopped_thread(process, lldb.eStopReasonSignal)
+ self.assertTrue(
+ thread.IsValid(), "There should be a thread stopped due to breakpoint"
)
+ frame0 = thread.GetFrameAtIndex(0)
+ self.server.responder.SetCurrentPC(0x000002ad);
- debug_abbrev_section = module.GetSectionAtIndex(2)
- self.assertEquals(".debug_abbrev", debug_abbrev_section.GetName())
- self.assertEquals(
- LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)
- )
+ # Update Wasm server memory state
+ self.store_bytes(0xffe0, bytes.fromhex("0000000000003440"))
- debug_line_section = module.GetSectionAtIndex(3)
- self.assertEquals(".debug_line", debug_line_section.GetName())
- self.assertEquals(
- LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
+ # We should be in function 'bar'.
+ self.assertTrue(frame0.IsValid())
+ function_name = frame0.GetFunctionName()
+ self.assertIn(
+ "Calc::add(Number const&)", function_name, "Unexpected function name {}".format(function_name)
)
- debug_str_section = module.GetSectionAtIndex(4)
- self.assertEquals(".debug_str", debug_str_section.GetName())
- self.assertEquals(
- LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
- )
+ # We should be able to evaluate the expression "*this".
+ value = frame0.EvaluateExpression("*this")
+ self.assertEqual(value.GetTypeName(), "Calc")
+ field = value.GetChildAtIndex(0)
+ self.assertEqual(field.GetName(), "result_")
+ self.assertEqual(field.GetTypeName(), "double")
+ self.assertEqual(field.GetValue(), "20")
+
+ # Examine next stack frame.
+ self.runCmd("up")
+ frame1 = thread.GetSelectedFrame()
+
+ # We should be able to evaluate the expression "v2".
+ value = frame1.EvaluateExpression("v2")
+ self.assertEqual(value.GetTypeName(), "double")
+ self.assertEqual(value.GetValue(), "22")
>From db0bc4829b02aedf7a988824b38b56a1564a8125 Mon Sep 17 00:00:00 2001
From: Paolo Severini <paolosev at microsoft.com>
Date: Mon, 22 Jan 2024 05:57:53 -0800
Subject: [PATCH 3/3] Add test files and fix formatting
---
.../gdb_remote_client/TestWasm.py | 76 ++++--
.../gdb_remote_client/calc-cpp-o0.yaml | 251 ++++++++++++++++++
.../gdb_remote_client/calc.cpp | 27 ++
3 files changed, 326 insertions(+), 28 deletions(-)
create mode 100644 lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
create mode 100644 lldb/test/API/functionalities/gdb_remote_client/calc.cpp
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index f2ac2abdeef0c0..5cde7f58eb920d 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -45,11 +45,11 @@ def respond(self, packet):
if packet[0:13] == "qRegisterInfo":
return self.qRegisterInfo(packet[13:])
if packet.startswith("qWasmCallStack"):
- return self.qWasmCallStack();
+ return self.qWasmCallStack()
if packet.startswith("qWasmLocal"):
- return self.qWasmLocal(packet);
+ return self.qWasmLocal(packet)
if packet.startswith("qWasmMem"):
- return self.qWasmMem(packet);
+ return self.qWasmMem(packet)
return MockGDBServerResponder.respond(self, packet)
def qSupported(self, client_supported):
@@ -79,7 +79,7 @@ def haltReason(self):
return "T05thread-pcs:300000083;thread:1;library:;"
def readRegister(self, register):
- assert(register == 0)
+ assert register == 0
return format_register_value(self.current_pc)
def qXferRead(self, obj, annex, offset, length):
@@ -117,12 +117,16 @@ def qfThreadInfo(self):
def cont(self):
# Continue execution. Simulates running the Wasm engine until a breakpoint is hit.
- return "T05thread-pcs:" + format(int(self._bp_address, 16) & 0x3fffffffffffffff, 'x') + ";thread:1"
+ return (
+ "T05thread-pcs:"
+ + format(int(self._bp_address, 16) & 0x3FFFFFFFFFFFFFFF, "x")
+ + ";thread:1"
+ )
def qWasmCallStack(self):
if len(self._wasm_call_stacks) == 0:
return ""
- result = self._wasm_call_stacks[self._call_stack_request_count].format();
+ result = self._wasm_call_stacks[self._call_stack_request_count].format()
self._call_stack_request_count = self._call_stack_request_count + 1
return result
@@ -132,11 +136,11 @@ def qWasmLocal(self, packet):
data = data[1].split(";")
frame_index = data[0]
local_index = data[1]
- if frame_index == '0' and local_index == '4':
+ if frame_index == "0" and local_index == "4":
return "b0ff0000"
- if frame_index == '1' and local_index == '5':
+ if frame_index == "1" and local_index == "5":
return "c0ff0000"
- return "E03";
+ return "E03"
def qWasmMem(self, packet):
# Format: qWasmMem:module_id;addr;len
@@ -145,12 +149,13 @@ def qWasmMem(self, packet):
module_id = data[0]
addr = int(data[1], 16)
length = int(data[2])
- if module_id != '4':
+ if module_id != "4":
return "E03"
- if addr >= 0xffb8 and addr < 0x10000:
- chunk = self._memory_view[addr:addr+length]
+ if addr >= 0xFFB8 and addr < 0x10000:
+ chunk = self._memory_view[addr : addr + length]
return chunk.hex()
- return "E03";
+ return "E03"
+
class WasmStackFrame:
pass
@@ -162,6 +167,7 @@ def __init__(self, module_id, address):
def format(self):
return format_register_value(make_code_address(self._module_id, self._address))
+
class WasmCallStack:
pass
@@ -174,6 +180,7 @@ def format(self):
result += frame.format()
return result
+
class WasmMemorySpan:
pass
@@ -197,7 +204,7 @@ def connect_to_wasm_engine(self, target):
return process
def store_bytes(self, offset, bytes_obj):
- chunk = self.memory_view[offset:offset+len(bytes_obj)]
+ chunk = self.memory_view[offset : offset + len(bytes_obj)]
for i in range(len(bytes_obj)):
chunk[i] = bytes_obj[i]
@@ -326,7 +333,7 @@ def test_load_module_with_stripped_symbols_from_remote(self):
self.assertEquals(
LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
)
-
+
@skipIfAsan
@skipIfXmlSupportMissing
def test_simple_wasm_debugging_session(self):
@@ -339,21 +346,32 @@ def test_simple_wasm_debugging_session(self):
self.memory = bytearray(65536)
self.memory_view = memoryview(self.memory)
- self.store_bytes(0xffb8, bytes.fromhex("d8ff0000"))
- self.store_bytes(0xffbc, bytes.fromhex("e0ff0000"))
- self.store_bytes(0xffe0, bytes.fromhex("0000000000000000"))
- self.store_bytes(0xffe8, bytes.fromhex("0000000000004540"))
- self.store_bytes(0xfff0, bytes.fromhex("0000000000003640"))
- self.store_bytes(0xfff8, bytes.fromhex("0000000000003440"))
+ self.store_bytes(0xFFB8, bytes.fromhex("d8ff0000"))
+ self.store_bytes(0xFFBC, bytes.fromhex("e0ff0000"))
+ self.store_bytes(0xFFE0, bytes.fromhex("0000000000000000"))
+ self.store_bytes(0xFFE8, bytes.fromhex("0000000000004540"))
+ self.store_bytes(0xFFF0, bytes.fromhex("0000000000003640"))
+ self.store_bytes(0xFFF8, bytes.fromhex("0000000000003440"))
call_stacks = [
- WasmCallStack([WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)]),
- WasmCallStack([WasmStackFrame(4, 0x000002ad), WasmStackFrame(4, 0x0000014a), WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000f)])
+ WasmCallStack(
+ [WasmStackFrame(3, 0x00000083), WasmStackFrame(3, 0x0000000F)]
+ ),
+ WasmCallStack(
+ [
+ WasmStackFrame(4, 0x000002AD),
+ WasmStackFrame(4, 0x0000014A),
+ WasmStackFrame(3, 0x00000083),
+ WasmStackFrame(3, 0x0000000F),
+ ]
+ ),
]
- self.server.responder = MyResponder(obj_path, "test_wasm", call_stacks, self.memory_view)
+ self.server.responder = MyResponder(
+ obj_path, "test_wasm", call_stacks, self.memory_view
+ )
target = self.dbg.CreateTarget("")
- breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9);
+ breakpoint = target.BreakpointCreateByLocation("calc.cpp", 9)
process = self.connect_to_wasm_engine(target)
lldbutil.expect_state_changes(
@@ -375,16 +393,18 @@ def test_simple_wasm_debugging_session(self):
thread.IsValid(), "There should be a thread stopped due to breakpoint"
)
frame0 = thread.GetFrameAtIndex(0)
- self.server.responder.SetCurrentPC(0x000002ad);
+ self.server.responder.SetCurrentPC(0x000002AD)
# Update Wasm server memory state
- self.store_bytes(0xffe0, bytes.fromhex("0000000000003440"))
+ self.store_bytes(0xFFE0, bytes.fromhex("0000000000003440"))
# We should be in function 'bar'.
self.assertTrue(frame0.IsValid())
function_name = frame0.GetFunctionName()
self.assertIn(
- "Calc::add(Number const&)", function_name, "Unexpected function name {}".format(function_name)
+ "Calc::add(Number const&)",
+ function_name,
+ "Unexpected function name {}".format(function_name),
)
# We should be able to evaluate the expression "*this".
diff --git a/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml b/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
new file mode 100644
index 00000000000000..ec7b99f19106fe
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/calc-cpp-o0.yaml
@@ -0,0 +1,251 @@
+--- !WASM
+FileHeader:
+ Version: 0x1
+Sections:
+ - Type: TYPE
+ Signatures:
+ - Index: 0
+ ParamTypes:
+ - F64
+ - F64
+ - F64
+ ReturnTypes:
+ - I32
+ - Index: 1
+ ParamTypes:
+ - I32
+ ReturnTypes:
+ - I32
+ - Index: 2
+ ParamTypes:
+ - I32
+ - F64
+ ReturnTypes:
+ - I32
+ - Index: 3
+ ParamTypes:
+ - I32
+ - I32
+ ReturnTypes: []
+ - Index: 4
+ ParamTypes:
+ - I32
+ ReturnTypes:
+ - F64
+ - Type: FUNCTION
+ FunctionTypes: [ 0, 1, 2, 3, 4 ]
+ - Type: TABLE
+ Tables:
+ - Index: 0
+ ElemType: FUNCREF
+ Limits:
+ Flags: [ HAS_MAX ]
+ Minimum: 0x1
+ Maximum: 0x1
+ - Type: MEMORY
+ Memories:
+ - Flags: [ HAS_MAX ]
+ Minimum: 0x100
+ Maximum: 0x100
+ - Type: GLOBAL
+ Globals:
+ - Index: 0
+ Type: I32
+ Mutable: true
+ InitExpr:
+ Opcode: I32_CONST
+ Value: 65536
+ - Type: EXPORT
+ Exports:
+ - Name: memory
+ Kind: MEMORY
+ Index: 0
+ - Name: test_sum
+ Kind: FUNCTION
+ Index: 0
+ - Name: __indirect_function_table
+ Kind: TABLE
+ Index: 0
+ - Type: CODE
+ Functions:
+ - Index: 0
+ Locals:
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ Body: 2300210341C0002104200320046B21052005240020052000390338200520013903302005200239032841202106200520066A210720072108200810011A20052B033821094118210A2005200A6A210B200B210C200C200910021A4120210D2005200D6A210E200E210F41182110200520106A211120112112200F2012100320052B0330211341102114200520146A2115201521162016201310021A41202117200520176A2118201821194110211A2005201A6A211B201B211C2019201C10034120211D2005201D6A211E201E211F201F100421202005202039030820052B0328212120052B0308212220212022612123410121242023202471212541C0002126200520266A21272027240020250F0B
+ - Index: 1
+ Locals:
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ Body: 2300210141102102200120026B21032003200036020C200328020C2104410021052005B721062004200639030020040F0B
+ - Index: 2
+ Locals:
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ Body: 2300210241102103200220036B21042004200036020C20042001390300200428020C210520042B030021062005200639030020050F0B
+ - Index: 3
+ Locals:
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: F64
+ Count: 1
+ Body: 2300210241102103200220036B21042004200036020C20042001360208200428020C21052004280208210620062B0300210720052B0300210820082007A02109200520093903000F0B
+ - Index: 4
+ Locals:
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: F64
+ Count: 1
+ - Type: I32
+ Count: 1
+ - Type: I32
+ Count: 1
+ Body: 2300210141102102200120026B210320032400200320003602042003280204210420042B0300210541082106200320066A2107200721082008200510021A20032B030821094110210A2003200A6A210B200B240020090F0B
+ - Type: CUSTOM
+ Name: name
+ FunctionNames:
+ - Index: 0
+ Name: test_sum
+ - Index: 1
+ Name: 'Calc::Calc()'
+ - Index: 2
+ Name: 'Number::Number(double)'
+ - Index: 3
+ Name: 'Calc::add(Number const&)'
+ - Index: 4
+ Name: 'Calc::result() const'
+ GlobalNames:
+ - Index: 0
+ Name: __stack_pointer
+ - Type: CUSTOM
+ Name: .debug_abbrev
+ Payload: 011101250E1305030E10171B0E110155170000021301360B030E0B0B3A0B3B0B0000030D00030E49133A0B3B0B380B0000042E01030E3A0B3B0B3C193F19000005050049133419000006050049130000072400030E3E0B0B0B0000080F0049130000090D00030E49133A0B3B0B380B320B00000A2E016E0E030E3A0B3B0B3C193F1900000B2E016E0E030E3A0B3B0B49133C193F1900000C2E01030E3C1934193F1900000D1000491300000E2600491300000F2E01110112064018030E3A0B3B0B49133F1900001005000218030E3A0B3B0B491300001134000218030E3A0B3B0B49130000122E0111011206401864133A0B3B0B6E0E471300001305000218030E491334190000142E01110112064018641347130000152E0111011206401864136E0E4713000000
+ - Type: CUSTOM
+ Name: .debug_info
+ Payload: E40100000400000000000401F60000002100970000000000000020000000000000000000000002059000000008010103E90000004E000000010300049000000001020555000000064E000000000007B4000000040808260000000205DC00000008010609E10000004E000000011000030A80000000C4000000010705A700000006AC000000000B000000006D000000010B2600000005B6000000000CDC00000005A70000000000085A0000000DB10000000E2600000008BB0000000E5A0000000F030000005A01000004ED00059FA00000000114D101000010029138F300000001144E00000010029130F000000001144E00000010029128BB00000001144E00000011029120D700000001155A000000110291086D00000001182600000000125E0100003E00000004ED00039F3B0100000106130000009B0000001302910C74000000D80100000014DF0100005A00000004ED00049F5E010000700000001302910C74000000D801000010029108790000000107AC00000000159D0100004100000004ED00049F93010000C80000003B0000001302910C74000000DD01000010029100AE00000001024E00000000143A0200006F00000004ED00039FC4010000860000001302910474000000E20100000007A90000000201085A000000082600000008BB00000000
+ - Type: CUSTOM
+ Name: .debug_ranges
+ Payload: 030000005D0100005E0100009C010000DF010000390200009D010000DE0100003A020000A90200000000000000000000
+ - Type: CUSTOM
+ Name: .debug_str
+ Payload: 5F5A4E4B3443616C6336726573756C744576005F5A4E3443616C634332457600513A5C70616F6C6F7365764D5346545C6C6C766D2D70726F6A6563745C6C6C64625C746573745C4150495C66756E6374696F6E616C69746965735C6764625F72656D6F74655F636C69656E7400726573756C740074686973006E756D626572005F5A4E3443616C633361646445524B364E756D6265720063616C632E63707000746573745F73756D00626F6F6C0076616C756500646F75626C6500657870656374656400616464005F5A4E364E756D626572433245640063616C630043616C6300726573756C745F0076616C75655F00763200763100636C616E672076657273696F6E2031382E302E30202868747470733A2F2F6769746875622E636F6D2F6C6C766D2F6C6C766D2D70726F6A65637420373535303166353336323464653932616166636532663164613639386232343961373239336463372900
+ - Type: CUSTOM
+ Name: .debug_line
+ Payload: A701000004002B000000010101FB0E0D0001010101000000010000010063616C632E637070000000003C737464696E3E00000000000005020300000003130100050277000000030105080A010005028B000000030105130100050292000000050C0601000502A8000000050801000502CC000000030105130601000502D3000000050C0601000502E90000000508010005020D010000030105180601000502290100000301050A0100050230010000051D0601000502370100000513010005023E0100000503010005025D0100000001010005025E01000003050100050288010000030A050A0A010005029801000003760508010005029C0100000001010005029D010000030101000502CC01000005210A01000502D3010000051A0601000502DA010000052901000502DE010000000101000502DF01000003060100050214020000030105100A010005021B0200000517060100050222020000050D0100050237020000030105030601000502390200000001010005023A020000030A0100050272020000030105130A0100050279020000050C06010005028F020000050501000502A9020000000101
+...
diff --git a/lldb/test/API/functionalities/gdb_remote_client/calc.cpp b/lldb/test/API/functionalities/gdb_remote_client/calc.cpp
new file mode 100644
index 00000000000000..7e4c53c42cb500
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/calc.cpp
@@ -0,0 +1,27 @@
+struct Number {
+ Number(double value) : value_(value) {}
+ double value_;
+};
+
+struct Calc {
+ void add(const Number& number) {
+ result_ += number.value_;
+ }
+
+ Number result() const {
+ return Number(result_);
+ }
+
+ private:
+ double result_ = 0;
+};
+
+extern "C" {
+bool test_sum(double v1, double v2, double expected) {
+ Calc calc;
+ calc.add(Number(v1));
+ calc.add(Number(v2));
+ Number result = calc.result();
+ return expected == result.value_;
+}
+}
\ No newline at end of file
More information about the lldb-commits
mailing list