mirror of
https://github.com/intel/llvm.git
synced 2026-01-12 18:27:07 +08:00
[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:
committed by
GitHub
parent
ef962752d9
commit
f62370290a
@@ -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.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
add_lldb_library(lldbPluginProcessWasm PLUGIN
|
||||
ProcessWasm.cpp
|
||||
RegisterContextWasm.cpp
|
||||
ThreadWasm.cpp
|
||||
UnwindWasm.cpp
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
109
lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
Normal file
109
lldb/source/Plugins/Process/wasm/RegisterContextWasm.cpp
Normal 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;
|
||||
}
|
||||
69
lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
Normal file
69
lldb/source/Plugins/Process/wasm/RegisterContextWasm.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
lldb/test/API/functionalities/gdb_remote_client/simple.c
Normal file
10
lldb/test/API/functionalities/gdb_remote_client/simple.c
Normal 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);
|
||||
}
|
||||
228
lldb/test/API/functionalities/gdb_remote_client/simple.yaml
Normal file
228
lldb/test/API/functionalities/gdb_remote_client/simple.yaml
Normal 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
|
||||
...
|
||||
Reference in New Issue
Block a user