https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/150143

>From 1d0ad45e68339fa1da2f7e3fa34826498c21594c Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Tue, 22 Jul 2025 16:37:03 -0700
Subject: [PATCH 1/2] [lldb] Add WebAssembly Process Plugin

Extend support in LLDB for WebAssembly. This PR adds a new Process
plugin (ProcessWasm)that extends ProcessGDBRemote for WebAssembly
targets. It adds support for WebAssembly's memory model with separate
address spaces, and the ability to fetch the call stack from the
WebAssembly runtime.

I have tested this change with the WebAssembly Micro Runtime (WAMR,
https://github.com/bytecodealliance/wasm-micro-runtime) which implements
a GDB debug stub and supports the qWasmCallStack packet.

```
(lldb) process connect --plugin wasm connect://localhost:4567
Process 1 stopped
* thread #1, name = 'nobody', stop reason = trace
    frame #0: 0x40000000000001ad
wasm32_args.wasm`main:
->  0x40000000000001ad <+3>:  global.get 0
    0x40000000000001b3 <+9>:  i32.const 16
    0x40000000000001b5 <+11>: i32.sub
    0x40000000000001b6 <+12>: local.set 0
(lldb) b add
Breakpoint 1: where = wasm32_args.wasm`add + 28 at test.c:4:12, address = 
0x400000000000019c
(lldb) c
Process 1 resuming
Process 1 stopped
* thread #1, name = 'nobody', stop reason = breakpoint 1.1
    frame #0: 0x400000000000019c wasm32_args.wasm`add(a=<unavailable>, 
b=<unavailable>) at test.c:4:12
   1    int
   2    add(int a, int b)
   3    {
-> 4        return a + b;
   5    }
   6
   7    int
(lldb) bt
* thread #1, name = 'nobody', stop reason = breakpoint 1.1
  * frame #0: 0x400000000000019c wasm32_args.wasm`add(a=<unavailable>, 
b=<unavailable>) at test.c:4:12
    frame #1: 0x40000000000001e5 wasm32_args.wasm`main at test.c:12:12
    frame #2: 0x40000000000001fe wasm32_args.wasm
```

This PR is based on an unmerged patch from Paolo Severini:
https://reviews.llvm.org/D78801. I intentionally stuck to the
foundations to keep this PR small. I have more PRs in the pipeline to
support the other features/packets.

My motivation for supporting Wasm is to support Swift compiled to
WebAssembly: 
https://www.swift.org/documentation/articles/wasm-getting-started.html
---
 .../Python/lldbsuite/test/lldbgdbclient.py    |   4 +-
 lldb/source/Plugins/Process/CMakeLists.txt    |   1 +
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |   9 +-
 .../Process/gdb-remote/ProcessGDBRemote.h     |   2 +
 .../Plugins/Process/wasm/CMakeLists.txt       |  10 ++
 .../Plugins/Process/wasm/ProcessWasm.cpp      | 124 ++++++++++++++++++
 .../source/Plugins/Process/wasm/ProcessWasm.h |  88 +++++++++++++
 .../Plugins/Process/wasm/ThreadWasm.cpp       |  34 +++++
 lldb/source/Plugins/Process/wasm/ThreadWasm.h |  38 ++++++
 .../Plugins/Process/wasm/UnwindWasm.cpp       |  81 ++++++++++++
 lldb/source/Plugins/Process/wasm/UnwindWasm.h |  51 +++++++
 lldb/source/Target/Platform.cpp               |   6 +
 .../gdb_remote_client/TestWasm.py             |  26 +++-
 13 files changed, 465 insertions(+), 9 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

diff --git a/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py 
b/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
index 459460b84fbae..599f7878e6edb 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbgdbclient.py
@@ -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.
 
@@ -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)
diff --git a/lldb/source/Plugins/Process/CMakeLists.txt 
b/lldb/source/Plugins/Process/CMakeLists.txt
index bd9b1b86dbf13..3413360e975fb 100644
--- a/lldb/source/Plugins/Process/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/CMakeLists.txt
@@ -29,3 +29,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 a2c34ddfc252e..14dfdec6a6f62 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -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 =
@@ -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 {
@@ -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);
     }
   }
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h 
b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 7ae33837fd067..7c3dfb179a4b3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -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.
diff --git a/lldb/source/Plugins/Process/wasm/CMakeLists.txt 
b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
new file mode 100644
index 0000000000000..ff8a3c792ad53
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_lldb_library(lldbPluginProcessWasm PLUGIN
+  ProcessWasm.cpp
+  ThreadWasm.cpp
+  UnwindWasm.cpp
+
+  LINK_LIBS
+    lldbCore
+  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 0000000000000..4a9240c58e899
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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) {
+  /* always use linux signals for wasm process */
+  m_unix_signals_sp = UnixSignals::Create(ArchSpec{"wasm32-Ant-wasi-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() { 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");
+
+  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 llvm::createStringError("invalid response for qWasmCallStack");
+
+  std::vector<lldb::addr_t> call_stack_pcs;
+  for (size_t i = 0; i < bytes / sizeof(addr_t); i++)
+    call_stack_pcs.push_back(buf[i]);
+
+  return call_stack_pcs;
+}
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
new file mode 100644
index 0000000000000..dcfd05d4e4546
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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"
+
+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)
+      : offset(addr & 0x00000000ffffffff),
+        module_id((addr & 0x00ffffff00000000) >> 32), type(addr >> 62) {}
+
+  wasm_addr_t(WasmAddressType type, uint32_t module_id, uint32_t offset)
+      : offset(offset), module_id(module_id), type(type) {}
+
+  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();
+
+  llvm::StringRef GetPluginName() override;
+
+  size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+                    Status &error) override;
+
+  bool CanDebug(lldb::TargetSP target_sp,
+                bool plugin_specified_by_name) override;
+
+  /// Retrieve the current call stack from the WebAssembly remote process.
+  llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack(lldb::tid_t tid);
+
+protected:
+  std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
+  CreateThread(lldb::tid_t tid) override;
+
+private:
+  friend class UnwindWasm;
+  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
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
new file mode 100644
index 0000000000000..a6553ffffedaa
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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"
+
+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;
+}
+
+llvm::Expected<std::vector<lldb::addr_t>> ThreadWasm::GetWasmCallStack() {
+  if (ProcessSP process_sp = GetProcess()) {
+    ProcessWasm *wasm_process = static_cast<ProcessWasm *>(process_sp.get());
+    return wasm_process->GetWasmCallStack(GetID());
+  }
+  return llvm::createStringError("no process");
+}
diff --git a/lldb/source/Plugins/Process/wasm/ThreadWasm.h 
b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
new file mode 100644
index 0000000000000..1c90f58767bc8
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/ThreadWasm.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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.
+  llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack();
+
+protected:
+  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 0000000000000..951a0ef479f3e
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.cpp
@@ -0,0 +1,81 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "ProcessWasm.h"
+#include "ThreadWasm.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.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 &reg_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();
+  ThreadGDBRemote *gdb_thread = static_cast<ThreadGDBRemote *>(thread.get());
+  ProcessWasm *wasm_process =
+      static_cast<ProcessWasm *>(thread->GetProcess().get());
+
+  return std::make_shared<WasmGDBRemoteRegisterContext>(
+      *gdb_thread, frame->GetConcreteFrameIndex(),
+      wasm_process->GetRegisterInfo(), m_frames[frame->GetFrameIndex()]);
+}
+
+uint32_t UnwindWasm::DoGetFrameCount() {
+  if (!m_unwind_complete) {
+    m_unwind_complete = true;
+    m_frames.clear();
+
+    ThreadWasm &wasm_thread = static_cast<ThreadWasm &>(GetThread());
+    llvm::Expected<std::vector<lldb::addr_t>> call_stack_pcs =
+        wasm_thread.GetWasmCallStack();
+    if (!call_stack_pcs) {
+      LLDB_LOG_ERROR(GetLog(LLDBLog::Unwind), call_stack_pcs.takeError(),
+                     "Failed to get Wasm callstack: {0}");
+      m_frames.clear();
+      return 0;
+    }
+    m_frames = *call_stack_pcs;
+  }
+  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;
+}
diff --git a/lldb/source/Plugins/Process/wasm/UnwindWasm.h 
b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
new file mode 100644
index 0000000000000..ff5e06d23d960
--- /dev/null
+++ b/lldb/source/Plugins/Process/wasm/UnwindWasm.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_UNWINDWASM_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_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) {}
+  ~UnwindWasm() override = default;
+
+protected:
+  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 = false;
+
+  UnwindWasm(const UnwindWasm &);
+  const UnwindWasm &operator=(const UnwindWasm &) = delete;
+};
+
+} // namespace wasm
+} // namespace lldb_private
+
+#endif
diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index 8000cd07565ae..f9bf5d1a9f160 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -2076,6 +2076,12 @@ size_t Platform::GetSoftwareBreakpointTrapOpcode(Target 
&target,
     trap_opcode_size = sizeof(g_loongarch_opcode);
   } break;
 
+  case llvm::Triple::wasm32: {
+    static const uint8_t g_wasm_opcode[] = {0x00}; // unreachable
+    trap_opcode = g_wasm_opcode;
+    trap_opcode_size = sizeof(g_wasm_opcode);
+  } break;
+
   default:
     return 0;
   }
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py 
b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
index 733f40b5e1429..231510d23c937 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestWasm.py
@@ -35,6 +35,8 @@ def __init__(self, obj_path, module_name=""):
     def respond(self, packet):
         if packet[0:13] == "qRegisterInfo":
             return self.qRegisterInfo(packet[13:])
+        if packet[0:14] == "qWasmCallStack":
+            return self.qWasmCallStack(packet[14:])
         return MockGDBServerResponder.respond(self, packet)
 
     def qSupported(self, client_supported):
@@ -47,7 +49,7 @@ def QEnableErrorStrings(self):
         return ""
 
     def qfThreadInfo(self):
-        return "OK"
+        return "m1,"
 
     def qRegisterInfo(self, index):
         if index == 0:
@@ -61,7 +63,7 @@ def qProcessInfo(self):
         )
 
     def haltReason(self):
-        return "T05thread:1;"
+        return "T02thread:1;"
 
     def readRegister(self, register):
         return format_register_value(self.current_pc)
@@ -89,6 +91,9 @@ def readMemory(self, addr, length):
             file.close()
         return result
 
+    def qWasmCallStack(self, data):
+        return "b301000000000040fe01000000000040"
+
 
 class TestWasm(GDBRemoteTestBase):
     @skipIfAsan
@@ -104,7 +109,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(target, "wasm")
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
@@ -174,7 +179,7 @@ def 
test_load_module_with_stripped_symbols_from_remote(self):
         )
 
         target = self.dbg.CreateTarget("")
-        process = self.connect(target)
+        process = self.connect(target, "wasm")
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
@@ -230,7 +235,7 @@ def test_load_module_from_file(self):
         self.server.responder = MyResponder(obj_path)
 
         target = self.dbg.CreateTarget("")
-        process = self.connect(target)
+        process = self.connect(target, "wasm")
         lldbutil.expect_state_changes(
             self, self.dbg.GetListener(), process, [lldb.eStateStopped]
         )
@@ -272,3 +277,14 @@ def test_load_module_from_file(self):
         self.assertEqual(
             LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
         )
+
+        thread = process.GetThreadAtIndex(0)
+        self.assertTrue(thread.IsValid())
+
+        frame = thread.GetFrameAtIndex(0)
+        self.assertTrue(frame.IsValid())
+        self.assertEqual(frame.GetPC(), 0x40000000000001B3)
+
+        frame = thread.GetFrameAtIndex(1)
+        self.assertTrue(frame.IsValid())
+        self.assertEqual(frame.GetPC(), 0x40000000000001FE)

>From 89457406c0184f0f543ae7cbdef752d9f07b1f7f Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Wed, 23 Jul 2025 21:08:01 -0700
Subject: [PATCH 2/2] Address Adrian's feedback

---
 .../source/Plugins/Process/wasm/ProcessWasm.cpp | 17 ++++++++++-------
 lldb/source/Plugins/Process/wasm/ProcessWasm.h  |  9 +++++----
 2 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
index 4a9240c58e899..0c8159c7cfda8 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.cpp
@@ -24,7 +24,7 @@ LLDB_PLUGIN_DEFINE(ProcessWasm)
 
 ProcessWasm::ProcessWasm(lldb::TargetSP target_sp, ListenerSP listener_sp)
     : ProcessGDBRemote(target_sp, listener_sp) {
-  /* always use linux signals for wasm process */
+  // Always use Linux signals for Wasm process.
   m_unix_signals_sp = UnixSignals::Create(ArchSpec{"wasm32-Ant-wasi-wasm"});
 }
 
@@ -102,6 +102,7 @@ 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)
@@ -110,15 +111,17 @@ ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
   if (!response.IsNormalResponse())
     return llvm::createStringError("failed to get response for 
qWasmCallStack");
 
-  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)
+  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");
 
   std::vector<lldb::addr_t> call_stack_pcs;
-  for (size_t i = 0; i < bytes / sizeof(addr_t); i++)
-    call_stack_pcs.push_back(buf[i]);
+  DataExtractor data(data_buffer_sp, GetByteOrder(), GetAddressByteSize());
+  lldb::offset_t offset = 0;
+  while (offset < bytes)
+    call_stack_pcs.push_back(data.GetU64(&offset));
 
   return call_stack_pcs;
 }
diff --git a/lldb/source/Plugins/Process/wasm/ProcessWasm.h 
b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
index dcfd05d4e4546..d0b0560aa04d3 100644
--- a/lldb/source/Plugins/Process/wasm/ProcessWasm.h
+++ b/lldb/source/Plugins/Process/wasm/ProcessWasm.h
@@ -17,11 +17,11 @@ 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 };
+
+/// For the purpose of debugging, we can represent all these separated 32-bit
+/// address spaces with a single virtual 64-bit address space. The
+/// wasm_addr_t provides this encoding using bitfields.
 struct wasm_addr_t {
   uint64_t offset : 32;
   uint64_t module_id : 30;
@@ -35,6 +35,7 @@ struct wasm_addr_t {
       : offset(offset), module_id(module_id), type(type) {}
 
   WasmAddressType GetType() { return static_cast<WasmAddressType>(type); }
+
   operator lldb::addr_t() { return *(uint64_t *)this; }
 };
 

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to