[lldb] Implement RegisterContextWasm (#151056)

This PR implements a register context for Wasm, which uses virtual
registers to resolve Wasm local, globals and stack values. The registers
are used to implement support for `DW_OP_WASM_location` in the DWARF
expression evaluator (#151010). This also adds a more comprehensive
test, showing that we can use this to show local variables.
This commit is contained in:
Jonas Devlieghere
2025-07-30 19:51:09 -07:00
committed by GitHub
parent ef962752d9
commit f62370290a
11 changed files with 702 additions and 82 deletions

View File

@@ -1998,22 +1998,6 @@ threads (live system debug) / cores (JTAG) in your program have
stopped and allows LLDB to display and control your program
correctly.
## qWasmCallStack
Get the Wasm call stack for the given thread id. This returns a hex-encoded
list of PC values, one for each frame of the call stack. To match the Wasm
specification, the addresses are encoded in little endian byte order, even if
the endian of the Wasm runtime's host is not little endian.
```
send packet: $qWasmCallStack:202dbe040#08
read packet: $9c01000000000040e501000000000040fe01000000000040#
```
**Priority to Implement:** Only required for Wasm support. This packed is
supported by the [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime)
and [V8](https://v8.dev) Wasm runtimes.
## qWatchpointSupportInfo
Get the number of hardware watchpoints available on the remote target.
@@ -2479,3 +2463,70 @@ omitting them will work fine; these numbers are always base 16.
The length of the payload is not provided. A reliable, 8-bit clean,
transport layer is assumed.
## Wasm Packets
The packet below are supported by the
[WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) and
[V8](https://v8.dev) Wasm runtimes.
### qWasmCallStack
Get the Wasm call stack for the given thread id. This returns a hex-encoded
list of PC values, one for each frame of the call stack. To match the Wasm
specification, the addresses are encoded in little endian byte order, even if
the endian of the Wasm runtime's host is not little endian.
```
send packet: $qWasmCallStack:202dbe040#08
read packet: $9c01000000000040e501000000000040fe01000000000040#
```
**Priority to Implement:** Only required for Wasm support. Necessary to show
stack traces.
### qWasmGlobal
Get the value of a Wasm global variable for the given frame index at the given
variable index. The indexes are encoded as base 10. The result is a hex-encoded
address from where to read the value.
```
send packet: $qWasmGlobal:0;2#cb
read packet: $e0030100#b9
```
**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.
### qWasmLocal
Get the value of a Wasm function argument or local variable for the given frame
index at the given variable index. The indexes are encoded as base 10. The
result is a hex-encoded address from where to read the value.
```
send packet: $qWasmLocal:0;2#cb
read packet: $e0030100#b9
```
**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.
### qWasmStackValue
Get the value of a Wasm local variable from the Wasm operand stack, for the
given frame index at the given variable index. The indexes are encoded as base
10. The result is a hex-encoded address from where to read value.
```
send packet: $qWasmStackValue:0;2#cb
read packet: $e0030100#b9
```
**Priority to Implement:** Only required for Wasm support. Necessary to show
variables.

View File

@@ -1,5 +1,6 @@
add_lldb_library(lldbPluginProcessWasm PLUGIN
ProcessWasm.cpp
RegisterContextWasm.cpp
ThreadWasm.cpp
UnwindWasm.cpp

View File

@@ -131,3 +131,36 @@ ProcessWasm::GetWasmCallStack(lldb::tid_t tid) {
return call_stack_pcs;
}
llvm::Expected<lldb::DataBufferSP>
ProcessWasm::GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index,
int index) {
StreamString packet;
switch (kind) {
case eWasmTagLocal:
packet.Printf("qWasmLocal:");
break;
case eWasmTagGlobal:
packet.Printf("qWasmGlobal:");
break;
case eWasmTagOperandStack:
packet.PutCString("qWasmStackValue:");
break;
case eWasmTagNotAWasmLocation:
return llvm::createStringError("not a Wasm location");
}
packet.Printf("%d;%d", frame_index, index);
StringExtractorGDBRemote response;
if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) !=
GDBRemoteCommunication::PacketResult::Success)
return llvm::createStringError("failed to send Wasm variable");
if (!response.IsNormalResponse())
return llvm::createStringError("failed to get response for Wasm variable");
WritableDataBufferSP buffer_sp(
new DataBufferHeap(response.GetStringRef().size() / 2, 0));
response.GetHexBytes(buffer_sp->GetData(), '\xcc');
return buffer_sp;
}

View File

@@ -10,6 +10,7 @@
#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_PROCESSWASM_H
#include "Plugins/Process/gdb-remote/ProcessGDBRemote.h"
#include "Utility/WasmVirtualRegisters.h"
namespace lldb_private {
namespace wasm {
@@ -71,12 +72,19 @@ public:
/// Retrieve the current call stack from the WebAssembly remote process.
llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack(lldb::tid_t tid);
/// Query the value of a WebAssembly variable from the WebAssembly
/// remote process.
llvm::Expected<lldb::DataBufferSP>
GetWasmVariable(WasmVirtualRegisterKinds kind, int frame_index, int index);
protected:
std::shared_ptr<process_gdb_remote::ThreadGDBRemote>
CreateThread(lldb::tid_t tid) override;
private:
friend class UnwindWasm;
friend class ThreadWasm;
process_gdb_remote::GDBRemoteDynamicRegisterInfoSP &GetRegisterInfo() {
return m_register_info_sp;
}

View File

@@ -0,0 +1,109 @@
//===----------------------------------------------------------------------===//
//
// 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 "RegisterContextWasm.h"
#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
#include "ProcessWasm.h"
#include "ThreadWasm.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RegisterValue.h"
#include "llvm/Support/Error.h"
#include <memory>
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
using namespace lldb_private::wasm;
RegisterContextWasm::RegisterContextWasm(
wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
GDBRemoteDynamicRegisterInfoSP reg_info_sp)
: GDBRemoteRegisterContext(thread, concrete_frame_idx, reg_info_sp, false,
false) {}
RegisterContextWasm::~RegisterContextWasm() = default;
uint32_t RegisterContextWasm::ConvertRegisterKindToRegisterNumber(
lldb::RegisterKind kind, uint32_t num) {
return num;
}
size_t RegisterContextWasm::GetRegisterCount() {
// Wasm has no registers.
return 0;
}
const RegisterInfo *RegisterContextWasm::GetRegisterInfoAtIndex(size_t reg) {
uint32_t tag = GetWasmVirtualRegisterTag(reg);
if (tag == eWasmTagNotAWasmLocation)
return m_reg_info_sp->GetRegisterInfoAtIndex(
GetWasmVirtualRegisterIndex(reg));
auto it = m_register_map.find(reg);
if (it == m_register_map.end()) {
WasmVirtualRegisterKinds kind = static_cast<WasmVirtualRegisterKinds>(tag);
std::tie(it, std::ignore) = m_register_map.insert(
{reg, std::make_unique<WasmVirtualRegisterInfo>(
kind, GetWasmVirtualRegisterIndex(reg))});
}
return it->second.get();
}
size_t RegisterContextWasm::GetRegisterSetCount() { return 0; }
const RegisterSet *RegisterContextWasm::GetRegisterSet(size_t reg_set) {
// Wasm has no registers.
return nullptr;
}
bool RegisterContextWasm::ReadRegister(const RegisterInfo *reg_info,
RegisterValue &value) {
// The only real registers is the PC.
if (reg_info->name)
return GDBRemoteRegisterContext::ReadRegister(reg_info, value);
// Read the virtual registers.
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));
llvm::Expected<DataBufferSP> maybe_buffer = process->GetWasmVariable(
wasm_reg_info->kind, frame_index, wasm_reg_info->index);
if (!maybe_buffer) {
LLDB_LOG_ERROR(GetLog(LLDBLog::Process), maybe_buffer.takeError(),
"Failed to read Wasm local: {0}");
return false;
}
DataBufferSP buffer_sp = *maybe_buffer;
DataExtractor reg_data(buffer_sp, process->GetByteOrder(),
process->GetAddressByteSize());
wasm_reg_info->byte_size = buffer_sp->GetByteSize();
wasm_reg_info->encoding = lldb::eEncodingUint;
Status error = value.SetValueFromData(
*reg_info, reg_data, reg_info->byte_offset, /*partial_data_ok=*/false);
return error.Success();
}
void RegisterContextWasm::InvalidateAllRegisters() {}
bool RegisterContextWasm::WriteRegister(const RegisterInfo *reg_info,
const RegisterValue &value) {
// The only real registers is the PC.
if (reg_info->name)
return GDBRemoteRegisterContext::WriteRegister(reg_info, value);
return false;
}

View File

@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// 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_REGISTERCONTEXTWASM_H
#define LLDB_SOURCE_PLUGINS_PROCESS_WASM_REGISTERCONTEXTWASM_H
#include "Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h"
#include "ThreadWasm.h"
#include "Utility/WasmVirtualRegisters.h"
#include "lldb/lldb-private-types.h"
#include <unordered_map>
namespace lldb_private {
namespace wasm {
class RegisterContextWasm;
typedef std::shared_ptr<RegisterContextWasm> RegisterContextWasmSP;
struct WasmVirtualRegisterInfo : public RegisterInfo {
WasmVirtualRegisterKinds kind;
uint32_t index;
WasmVirtualRegisterInfo(WasmVirtualRegisterKinds kind, uint32_t index)
: RegisterInfo(), kind(kind), index(index) {}
};
class RegisterContextWasm
: public process_gdb_remote::GDBRemoteRegisterContext {
public:
RegisterContextWasm(
wasm::ThreadWasm &thread, uint32_t concrete_frame_idx,
process_gdb_remote::GDBRemoteDynamicRegisterInfoSP reg_info_sp);
~RegisterContextWasm() 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

View File

@@ -9,6 +9,7 @@
#include "ThreadWasm.h"
#include "ProcessWasm.h"
#include "RegisterContextWasm.h"
#include "UnwindWasm.h"
#include "lldb/Target/Target.h"
@@ -32,3 +33,19 @@ llvm::Expected<std::vector<lldb::addr_t>> ThreadWasm::GetWasmCallStack() {
}
return llvm::createStringError("no process");
}
lldb::RegisterContextSP
ThreadWasm::CreateRegisterContextForFrame(StackFrame *frame) {
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)
return std::make_shared<RegisterContextWasm>(
*this, concrete_frame_idx, wasm_process->GetRegisterInfo());
return GetUnwinder().CreateRegisterContextForFrame(frame);
}

View File

@@ -25,6 +25,9 @@ public:
/// Retrieve the current call stack from the WebAssembly remote process.
llvm::Expected<std::vector<lldb::addr_t>> GetWasmCallStack();
lldb::RegisterContextSP
CreateRegisterContextForFrame(StackFrame *frame) override;
protected:
Unwind &GetUnwinder() override;

View File

@@ -1,12 +1,15 @@
import lldb
import os
import binascii
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test.gdbclientutils import *
from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
LLDB_INVALID_ADDRESS = lldb.LLDB_INVALID_ADDRESS
load_address = 0x400000000
MODULE_ID = 4
LOAD_ADDRESS = MODULE_ID << 32
WASM_LOCAL_ADDR = 0x103E0
def format_register_value(val):
"""
@@ -23,12 +26,59 @@ def format_register_value(val):
return result
class MyResponder(MockGDBServerResponder):
current_pc = load_address + 0x0A
class WasmStackFrame:
def __init__(self, address):
self._address = address
def __init__(self, obj_path, module_name=""):
def __str__(self):
return format_register_value(LOAD_ADDRESS | self._address)
class WasmCallStack:
def __init__(self, wasm_stack_frames):
self._wasm_stack_frames = wasm_stack_frames
def __str__(self):
result = ""
for frame in self._wasm_stack_frames:
result += str(frame)
return result
class FakeMemory:
def __init__(self, start_addr, end_addr):
self._base_addr = start_addr
self._memory = bytearray(end_addr - start_addr)
self._memoryview = memoryview(self._memory)
def store_bytes(self, addr, bytes_obj):
assert addr > self._base_addr
assert addr < self._base_addr + len(self._memoryview)
offset = addr - self._base_addr
chunk = self._memoryview[offset : offset + len(bytes_obj)]
for i in range(len(bytes_obj)):
chunk[i] = bytes_obj[i]
def get_bytes(self, addr, length):
assert addr > self._base_addr
assert addr < self._base_addr + len(self._memoryview)
offset = addr - self._base_addr
return self._memoryview[offset : offset + length]
def contains(self, addr):
return addr - self._base_addr < len(self._memoryview)
class MyResponder(MockGDBServerResponder):
current_pc = LOAD_ADDRESS | 0x01AD
def __init__(self, obj_path, module_name="", wasm_call_stacks=[], memory=None):
self._obj_path = obj_path
self._module_name = module_name or obj_path
self._wasm_call_stacks = wasm_call_stacks
self._call_stack_request_count = 0
self._memory = memory
MockGDBServerResponder.__init__(self)
def respond(self, packet):
@@ -36,6 +86,8 @@ class MyResponder(MockGDBServerResponder):
return self.qRegisterInfo(packet[13:])
if packet.startswith("qWasmCallStack"):
return self.qWasmCallStack()
if packet.startswith("qWasmLocal"):
return self.qWasmLocal(packet)
return MockGDBServerResponder.respond(self, packet)
def qSupported(self, client_supported):
@@ -71,28 +123,61 @@ class MyResponder(MockGDBServerResponder):
if obj == "libraries":
xml = (
'<library-list><library name="%s"><section address="%d"/></library></library-list>'
% (self._module_name, load_address)
% (self._module_name, LOAD_ADDRESS)
)
return xml, False
else:
return None, False
def readMemory(self, addr, length):
if addr < load_address:
if self._memory and self._memory.contains(addr):
chunk = self._memory.get_bytes(addr, length)
return chunk.hex()
if addr < LOAD_ADDRESS:
return "E02"
result = ""
with open(self._obj_path, mode="rb") as file:
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):
# Return two 64-bit addresses: 0x40000000000001B3, 0x40000000000001FE
return "b301000000000040fe01000000000040"
if len(self._wasm_call_stacks) == 0:
return ""
result = str(self._wasm_call_stacks[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, local_index = data
if frame_index == "0" and local_index == "2":
return format_register_value(WASM_LOCAL_ADDR)
return "E03"
class TestWasm(GDBRemoteTestBase):
@@ -124,35 +209,35 @@ class TestWasm(GDBRemoteTestBase):
code_section = module.GetSectionAtIndex(0)
self.assertEqual("code", code_section.GetName())
self.assertEqual(
load_address | code_section.GetFileOffset(),
LOAD_ADDRESS | code_section.GetFileOffset(),
code_section.GetLoadAddress(target),
)
debug_info_section = module.GetSectionAtIndex(1)
self.assertEqual(".debug_info", debug_info_section.GetName())
self.assertEqual(
load_address | debug_info_section.GetFileOffset(),
LOAD_ADDRESS | debug_info_section.GetFileOffset(),
debug_info_section.GetLoadAddress(target),
)
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEqual(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEqual(
load_address | debug_abbrev_section.GetFileOffset(),
LOAD_ADDRESS | debug_abbrev_section.GetFileOffset(),
debug_abbrev_section.GetLoadAddress(target),
)
debug_line_section = module.GetSectionAtIndex(3)
self.assertEqual(".debug_line", debug_line_section.GetName())
self.assertEqual(
load_address | debug_line_section.GetFileOffset(),
LOAD_ADDRESS | debug_line_section.GetFileOffset(),
debug_line_section.GetLoadAddress(target),
)
debug_str_section = module.GetSectionAtIndex(4)
self.assertEqual(".debug_str", debug_str_section.GetName())
self.assertEqual(
load_address | debug_line_section.GetFileOffset(),
LOAD_ADDRESS | debug_line_section.GetFileOffset(),
debug_line_section.GetLoadAddress(target),
)
@@ -194,97 +279,103 @@ class TestWasm(GDBRemoteTestBase):
code_section = module.GetSectionAtIndex(0)
self.assertEqual("code", code_section.GetName())
self.assertEqual(
load_address | code_section.GetFileOffset(),
LOAD_ADDRESS | code_section.GetFileOffset(),
code_section.GetLoadAddress(target),
)
debug_info_section = module.GetSectionAtIndex(1)
self.assertEqual(".debug_info", debug_info_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)
lldb.LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)
)
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEqual(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)
lldb.LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)
)
debug_line_section = module.GetSectionAtIndex(3)
self.assertEqual(".debug_line", debug_line_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
lldb.LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
)
debug_str_section = module.GetSectionAtIndex(4)
self.assertEqual(".debug_str", debug_str_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
lldb.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_base, ext = os.path.splitext(yaml_path)
# simple.yaml was created by compiling simple.c to wasm and using
# obj2yaml on the output.
#
# $ clang -target wasm32 -nostdlib -Wl,--no-entry -Wl,--export-all -O0 -g -o simple.wasm simple.c
# $ obj2yaml simple.wasm -o simple.yaml
yaml_path = "simple.yaml"
yaml_base, _ = os.path.splitext(yaml_path)
obj_path = self.getBuildArtifact(yaml_base)
self.yaml2obj(yaml_path, obj_path)
self.server.responder = MyResponder(obj_path)
# Create a fake call stack.
call_stacks = [
WasmCallStack(
[WasmStackFrame(0x019C), WasmStackFrame(0x01E5), WasmStackFrame(0x01FE)]
),
]
# Create fake memory for our wasm locals.
self.memory = FakeMemory(0x10000, 0x20000)
self.memory.store_bytes(
WASM_LOCAL_ADDR,
bytes.fromhex(
"0000000000000000020000000100000000000000020000000100000000000000"
),
)
self.server.responder = MyResponder(
obj_path, "test_wasm", call_stacks, self.memory
)
target = self.dbg.CreateTarget("")
breakpoint = target.BreakpointCreateByName("add")
process = self.connect(target, "wasm")
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
location = breakpoint.GetLocationAtIndex(0)
self.assertTrue(location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION)
num_modules = target.GetNumModules()
self.assertEqual(1, num_modules)
module = target.GetModuleAtIndex(0)
num_sections = module.GetNumSections()
self.assertEqual(5, num_sections)
code_section = module.GetSectionAtIndex(0)
self.assertEqual("code", code_section.GetName())
self.assertEqual(
load_address | code_section.GetFileOffset(),
code_section.GetLoadAddress(target),
)
debug_info_section = module.GetSectionAtIndex(1)
self.assertEqual(".debug_info", debug_info_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)
)
debug_abbrev_section = module.GetSectionAtIndex(2)
self.assertEqual(".debug_abbrev", debug_abbrev_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)
)
debug_line_section = module.GetSectionAtIndex(3)
self.assertEqual(".debug_line", debug_line_section.GetName())
self.assertEqual(
LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
)
debug_str_section = module.GetSectionAtIndex(4)
self.assertEqual(".debug_str", debug_str_section.GetName())
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)
# Check that our frames match our fake call stack.
frame0 = thread.GetFrameAtIndex(0)
self.assertTrue(frame0.IsValid())
self.assertEqual(frame0.GetPC(), LOAD_ADDRESS | 0x019C)
self.assertIn("add", frame0.GetFunctionName())
frame = thread.GetFrameAtIndex(1)
self.assertTrue(frame.IsValid())
self.assertEqual(frame.GetPC(), 0x40000000000001FE)
frame1 = thread.GetFrameAtIndex(1)
self.assertTrue(frame1.IsValid())
self.assertEqual(frame1.GetPC(), LOAD_ADDRESS | 0x01E5)
self.assertIn("main", frame1.GetFunctionName())
# Check that we can resolve local variables.
a = frame0.FindVariable("a")
self.assertTrue(a.IsValid())
self.assertEqual(a.GetValueAsUnsigned(), 1)
b = frame0.FindVariable("b")
self.assertTrue(b.IsValid())
self.assertEqual(b.GetValueAsUnsigned(), 2)

View File

@@ -0,0 +1,10 @@
int add(int a, int b) {
// Break here
return a + b;
}
int main() {
int i = 1;
int j = 2;
return add(i, j);
}

View File

@@ -0,0 +1,228 @@
--- !WASM
FileHeader:
Version: 0x1
Sections:
- Type: TYPE
Signatures:
- Index: 0
ParamTypes: []
ReturnTypes: []
- Index: 1
ParamTypes:
- I32
- I32
ReturnTypes:
- I32
- Index: 2
ParamTypes: []
ReturnTypes:
- I32
- Type: FUNCTION
FunctionTypes: [ 0, 1, 2, 1 ]
- Type: TABLE
Tables:
- Index: 0
ElemType: FUNCREF
Limits:
Flags: [ HAS_MAX ]
Minimum: 0x1
Maximum: 0x1
- Type: MEMORY
Memories:
- Minimum: 0x2
- Type: GLOBAL
Globals:
- Index: 0
Type: I32
Mutable: true
InitExpr:
Opcode: I32_CONST
Value: 66560
- Index: 1
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 1024
- Index: 2
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 1024
- Index: 3
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 1024
- Index: 4
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 66560
- Index: 5
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 1024
- Index: 6
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 66560
- Index: 7
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 131072
- Index: 8
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 0
- Index: 9
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 1
- Index: 10
Type: I32
Mutable: false
InitExpr:
Opcode: I32_CONST
Value: 65536
- Type: EXPORT
Exports:
- Name: memory
Kind: MEMORY
Index: 0
- Name: __wasm_call_ctors
Kind: FUNCTION
Index: 0
- Name: add
Kind: FUNCTION
Index: 1
- Name: __original_main
Kind: FUNCTION
Index: 2
- Name: main
Kind: FUNCTION
Index: 3
- Name: __main_void
Kind: FUNCTION
Index: 2
- Name: __indirect_function_table
Kind: TABLE
Index: 0
- Name: __dso_handle
Kind: GLOBAL
Index: 1
- Name: __data_end
Kind: GLOBAL
Index: 2
- Name: __stack_low
Kind: GLOBAL
Index: 3
- Name: __stack_high
Kind: GLOBAL
Index: 4
- Name: __global_base
Kind: GLOBAL
Index: 5
- Name: __heap_base
Kind: GLOBAL
Index: 6
- Name: __heap_end
Kind: GLOBAL
Index: 7
- Name: __memory_base
Kind: GLOBAL
Index: 8
- Name: __table_base
Kind: GLOBAL
Index: 9
- Name: __wasm_first_page_end
Kind: GLOBAL
Index: 10
- Type: CODE
Functions:
- Index: 0
Locals: []
Body: 0B
- Index: 1
Locals:
- Type: I32
Count: 1
Body: 23808080800041106B21022002200036020C20022001360208200228020C20022802086A0F0B
- Index: 2
Locals:
- Type: I32
Count: 2
Body: 23808080800041106B210020002480808080002000410036020C2000410136020820004102360204200028020820002802041081808080002101200041106A24808080800020010F0B
- Index: 3
Locals: []
Body: 1082808080000F0B
- Type: CUSTOM
Name: .debug_abbrev
Payload: 011101250E1305030E10171B0E110155170000022E01110112064018030E3A0B3B0B271949133F1900000305000218030E3A0B3B0B49130000042E01110112064018030E3A0B3B0B49133F1900000534000218030E3A0B3B0B49130000062400030E3E0B0B0B000000
- Type: CUSTOM
Name: .debug_info
Payload: 940000000400000000000401620000001D0055000000000000000D000000000000000000000002050000002900000004ED00029F510000000101900000000302910C60000000010190000000030291085E00000001019000000000042F0000004C00000004ED00009F04000000010690000000050291080B0000000107900000000502910409000000010890000000000600000000050400
- Type: CUSTOM
Name: .debug_ranges
Payload: 050000002E0000002F0000007B0000000000000000000000
- Type: CUSTOM
Name: .debug_str
Payload: 696E74006D61696E006A0069002F55736572732F6A6F6E61732F7761736D2D6D6963726F2D72756E74696D652F70726F647563742D6D696E692F706C6174666F726D732F64617277696E2F6275696C64006164640073696D706C652E630062006100636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A4A4465766C696567686572652F6C6C766D2D70726F6A6563742E67697420343161363839613132323834633834623632383933393461356338306264636534383733656466302900
- Type: CUSTOM
Name: .debug_line
Payload: 62000000040020000000010101FB0E0D0001010101000000010000010073696D706C652E6300000000000005020500000001050A0A08AE050E0658050C5805032002020001010005022F0000001705070A08BB75050E7505110658050A58050382020F000101
- Type: CUSTOM
Name: name
FunctionNames:
- Index: 0
Name: __wasm_call_ctors
- Index: 1
Name: add
- Index: 2
Name: __original_main
- Index: 3
Name: main
GlobalNames:
- Index: 0
Name: __stack_pointer
- Type: CUSTOM
Name: producers
Languages:
- Name: C11
Version: ''
Tools:
- Name: clang
Version: '22.0.0git'
- Type: CUSTOM
Name: target_features
Features:
- Prefix: USED
Name: bulk-memory
- Prefix: USED
Name: bulk-memory-opt
- Prefix: USED
Name: call-indirect-overlong
- Prefix: USED
Name: multivalue
- Prefix: USED
Name: mutable-globals
- Prefix: USED
Name: nontrapping-fptoint
- Prefix: USED
Name: reference-types
- Prefix: USED
Name: sign-ext
...