blob: 73c81efb793478198370225ead4ba39a79452b71 [file] [log] [blame]
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
MODULE_ID = 4
LOAD_ADDRESS = MODULE_ID << 32
WASM_LOCAL_ADDR = 0x103E0
def format_register_value(val):
"""
Encode each byte by two hex digits in little-endian order.
"""
result = ""
mask = 0xFF
shift = 0
for i in range(0, 8):
x = (val & mask) >> shift
result += format(x, "02x")
mask <<= 8
shift += 8
return result
class WasmStackFrame:
def __init__(self, address):
self._address = address
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):
if packet[0:13] == "qRegisterInfo":
return self.qRegisterInfo(packet[13:])
if packet.startswith("qWasmCallStack"):
return self.qWasmCallStack()
if packet.startswith("qWasmLocal"):
return self.qWasmLocal(packet)
return MockGDBServerResponder.respond(self, packet)
def qSupported(self, client_supported):
return "qXfer:libraries:read+;PacketSize=1000;vContSupported-"
def qHostInfo(self):
return ""
def QEnableErrorStrings(self):
return ""
def qfThreadInfo(self):
return "m1,"
def qRegisterInfo(self, index):
if index == 0:
return "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"
return "E45"
def qProcessInfo(self):
return "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:%s;triple:%s;ptrsize:4" % (
hex_encode_bytes("lldb"),
hex_encode_bytes("wasm32-unknown-unknown-wasm"),
)
def haltReason(self):
return "T02thread:1;"
def readRegister(self, register):
return format_register_value(self.current_pc)
def qXferRead(self, obj, annex, offset, length):
if obj == "libraries":
xml = (
'<library-list><library name="%s"><section address="%d"/></library></library-list>'
% (self._module_name, LOAD_ADDRESS)
)
return xml, False
else:
return None, False
def readMemory(self, addr, length):
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())
if addr >= LOAD_ADDRESS + len(file_content):
return "E03"
addr_from = addr - LOAD_ADDRESS
addr_to = addr_from + min(length, len(file_content) - addr_from)
for i in range(addr_from, addr_to):
result += format(file_content[i], "02x")
file.close()
return result
def setBreakpoint(self, packet):
bp_data = packet[1:].split(",")
self._bp_address = bp_data[1]
return "OK"
def qfThreadInfo(self):
return "m1"
def cont(self):
# Continue execution. Simulates running the Wasm engine until a breakpoint is hit.
return (
"T05thread-pcs:"
+ format(int(self._bp_address, 16) & 0x3FFFFFFFFFFFFFFF, "x")
+ ";thread:1"
)
def qWasmCallStack(self):
if len(self._wasm_call_stacks) == 0:
return ""
result = 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):
@skipIfAsan
@skipIfXmlSupportMissing
def test_load_module_with_embedded_symbols_from_remote(self):
"""Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with embedded DWARF symbols"""
yaml_path = "test_wasm_embedded_debug_sections.yaml"
yaml_base, ext = os.path.splitext(yaml_path)
obj_path = self.getBuildArtifact(yaml_base)
self.yaml2obj(yaml_path, obj_path)
self.server.responder = MyResponder(obj_path, "test_wasm")
target = self.dbg.CreateTarget("")
process = self.connect(target, "wasm")
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
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(
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(),
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(),
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(),
debug_line_section.GetLoadAddress(target),
)
@skipIfAsan
@skipIfXmlSupportMissing
def test_load_module_with_stripped_symbols_from_remote(self):
"""Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with symbols stripped into a separate Wasm file"""
sym_yaml_path = "test_sym.yaml"
sym_yaml_base, ext = os.path.splitext(sym_yaml_path)
sym_obj_path = self.getBuildArtifact(sym_yaml_base) + ".wasm"
self.yaml2obj(sym_yaml_path, sym_obj_path)
yaml_path = "test_wasm_external_debug_sections.yaml"
yaml_base, ext = os.path.splitext(yaml_path)
obj_path = self.getBuildArtifact(yaml_base) + ".wasm"
self.yaml2obj(yaml_path, obj_path)
self.server.responder = MyResponder(obj_path, "test_wasm")
folder, _ = os.path.split(obj_path)
self.runCmd(
"settings set target.debug-file-search-paths " + os.path.abspath(folder)
)
target = self.dbg.CreateTarget("")
process = self.connect(target, "wasm")
lldbutil.expect_state_changes(
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
)
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.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.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.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.LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)
)
@skipIfAsan
@skipIfXmlSupportMissing
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"""
# 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)
# 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)
thread = process.GetThreadAtIndex(0)
self.assertTrue(thread.IsValid())
# 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())
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)