| 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) |