382 lines · python
1import lldb2import os3import binascii4from lldbsuite.test.lldbtest import *5from lldbsuite.test.decorators import *6from lldbsuite.test.gdbclientutils import *7from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase8 9MODULE_ID = 410LOAD_ADDRESS = MODULE_ID << 3211WASM_LOCAL_ADDR = 0x103E012 13 14def format_register_value(val):15 """16 Encode each byte by two hex digits in little-endian order.17 """18 result = ""19 mask = 0xFF20 shift = 021 for i in range(0, 8):22 x = (val & mask) >> shift23 result += format(x, "02x")24 mask <<= 825 shift += 826 return result27 28 29class WasmStackFrame:30 def __init__(self, address):31 self._address = address32 33 def __str__(self):34 return format_register_value(LOAD_ADDRESS | self._address)35 36 37class WasmCallStack:38 def __init__(self, wasm_stack_frames):39 self._wasm_stack_frames = wasm_stack_frames40 41 def __str__(self):42 result = ""43 for frame in self._wasm_stack_frames:44 result += str(frame)45 return result46 47 48class FakeMemory:49 def __init__(self, start_addr, end_addr):50 self._base_addr = start_addr51 self._memory = bytearray(end_addr - start_addr)52 self._memoryview = memoryview(self._memory)53 54 def store_bytes(self, addr, bytes_obj):55 assert addr > self._base_addr56 assert addr < self._base_addr + len(self._memoryview)57 offset = addr - self._base_addr58 chunk = self._memoryview[offset : offset + len(bytes_obj)]59 for i in range(len(bytes_obj)):60 chunk[i] = bytes_obj[i]61 62 def get_bytes(self, addr, length):63 assert addr > self._base_addr64 assert addr < self._base_addr + len(self._memoryview)65 66 offset = addr - self._base_addr67 return self._memoryview[offset : offset + length]68 69 def contains(self, addr):70 return addr - self._base_addr < len(self._memoryview)71 72 73class MyResponder(MockGDBServerResponder):74 current_pc = LOAD_ADDRESS | 0x01AD75 76 def __init__(self, obj_path, module_name="", wasm_call_stacks=[], memory=None):77 self._obj_path = obj_path78 self._module_name = module_name or obj_path79 self._wasm_call_stacks = wasm_call_stacks80 self._call_stack_request_count = 081 self._memory = memory82 MockGDBServerResponder.__init__(self)83 84 def respond(self, packet):85 if packet[0:13] == "qRegisterInfo":86 return self.qRegisterInfo(packet[13:])87 if packet.startswith("qWasmCallStack"):88 return self.qWasmCallStack()89 if packet.startswith("qWasmLocal"):90 return self.qWasmLocal(packet)91 return MockGDBServerResponder.respond(self, packet)92 93 def qSupported(self, client_supported):94 return "qXfer:libraries:read+;PacketSize=1000;vContSupported-"95 96 def qHostInfo(self):97 return ""98 99 def QEnableErrorStrings(self):100 return ""101 102 def qfThreadInfo(self):103 return "m1,"104 105 def qRegisterInfo(self, index):106 if index == 0:107 return "name:pc;alt-name:pc;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;gcc:16;dwarf:16;generic:pc;"108 return "E45"109 110 def qProcessInfo(self):111 return "pid:1;ppid:1;uid:1;gid:1;euid:1;egid:1;name:%s;triple:%s;ptrsize:4" % (112 hex_encode_bytes("lldb"),113 hex_encode_bytes("wasm32-unknown-unknown-wasm"),114 )115 116 def haltReason(self):117 return "T02thread:1;"118 119 def readRegister(self, register):120 return format_register_value(self.current_pc)121 122 def qXferRead(self, obj, annex, offset, length):123 if obj == "libraries":124 xml = (125 '<library-list><library name="%s"><section address="%d"/></library></library-list>'126 % (self._module_name, LOAD_ADDRESS)127 )128 return xml, False129 else:130 return None, False131 132 def readMemory(self, addr, length):133 if self._memory and self._memory.contains(addr):134 chunk = self._memory.get_bytes(addr, length)135 return chunk.hex()136 if addr < LOAD_ADDRESS:137 return "E02"138 result = ""139 with open(self._obj_path, mode="rb") as file:140 file_content = bytearray(file.read())141 if addr >= LOAD_ADDRESS + len(file_content):142 return "E03"143 addr_from = addr - LOAD_ADDRESS144 addr_to = addr_from + min(length, len(file_content) - addr_from)145 for i in range(addr_from, addr_to):146 result += format(file_content[i], "02x")147 file.close()148 return result149 150 def setBreakpoint(self, packet):151 bp_data = packet[1:].split(",")152 self._bp_address = bp_data[1]153 return "OK"154 155 def qfThreadInfo(self):156 return "m1"157 158 def cont(self):159 # Continue execution. Simulates running the Wasm engine until a breakpoint is hit.160 return (161 "T05thread-pcs:"162 + format(int(self._bp_address, 16) & 0x3FFFFFFFFFFFFFFF, "x")163 + ";thread:1"164 )165 166 def qWasmCallStack(self):167 if len(self._wasm_call_stacks) == 0:168 return ""169 result = str(self._wasm_call_stacks[self._call_stack_request_count])170 self._call_stack_request_count += 1171 return result172 173 def qWasmLocal(self, packet):174 # Format: qWasmLocal:frame_index;index175 data = packet.split(":")176 data = data[1].split(";")177 frame_index, local_index = data178 if frame_index == "0" and local_index == "2":179 return format_register_value(WASM_LOCAL_ADDR)180 return "E03"181 182 183class TestWasm(GDBRemoteTestBase):184 @skipIfAsan185 @skipIfXmlSupportMissing186 def test_load_module_with_embedded_symbols_from_remote(self):187 """Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with embedded DWARF symbols"""188 189 yaml_path = "test_wasm_embedded_debug_sections.yaml"190 yaml_base, ext = os.path.splitext(yaml_path)191 obj_path = self.getBuildArtifact(yaml_base)192 self.yaml2obj(yaml_path, obj_path)193 194 self.server.responder = MyResponder(obj_path, "test_wasm")195 196 target = self.dbg.CreateTarget("")197 process = self.connect(target, "wasm")198 lldbutil.expect_state_changes(199 self, self.dbg.GetListener(), process, [lldb.eStateStopped]200 )201 202 num_modules = target.GetNumModules()203 self.assertEqual(1, num_modules)204 205 module = target.GetModuleAtIndex(0)206 num_sections = module.GetNumSections()207 self.assertEqual(5, num_sections)208 209 code_section = module.GetSectionAtIndex(0)210 self.assertEqual("code", code_section.GetName())211 self.assertEqual(212 LOAD_ADDRESS | code_section.GetFileOffset(),213 code_section.GetLoadAddress(target),214 )215 216 debug_info_section = module.GetSectionAtIndex(1)217 self.assertEqual(".debug_info", debug_info_section.GetName())218 self.assertEqual(219 LOAD_ADDRESS | debug_info_section.GetFileOffset(),220 debug_info_section.GetLoadAddress(target),221 )222 223 debug_abbrev_section = module.GetSectionAtIndex(2)224 self.assertEqual(".debug_abbrev", debug_abbrev_section.GetName())225 self.assertEqual(226 LOAD_ADDRESS | debug_abbrev_section.GetFileOffset(),227 debug_abbrev_section.GetLoadAddress(target),228 )229 230 debug_line_section = module.GetSectionAtIndex(3)231 self.assertEqual(".debug_line", debug_line_section.GetName())232 self.assertEqual(233 LOAD_ADDRESS | debug_line_section.GetFileOffset(),234 debug_line_section.GetLoadAddress(target),235 )236 237 debug_str_section = module.GetSectionAtIndex(4)238 self.assertEqual(".debug_str", debug_str_section.GetName())239 self.assertEqual(240 LOAD_ADDRESS | debug_line_section.GetFileOffset(),241 debug_line_section.GetLoadAddress(target),242 )243 244 @skipIfAsan245 @skipIfXmlSupportMissing246 def test_load_module_with_stripped_symbols_from_remote(self):247 """Test connecting to a WebAssembly engine via GDB-remote and loading a Wasm module with symbols stripped into a separate Wasm file"""248 249 sym_yaml_path = "test_sym.yaml"250 sym_yaml_base, ext = os.path.splitext(sym_yaml_path)251 sym_obj_path = self.getBuildArtifact(sym_yaml_base) + ".wasm"252 self.yaml2obj(sym_yaml_path, sym_obj_path)253 254 yaml_path = "test_wasm_external_debug_sections.yaml"255 yaml_base, ext = os.path.splitext(yaml_path)256 obj_path = self.getBuildArtifact(yaml_base) + ".wasm"257 self.yaml2obj(yaml_path, obj_path)258 259 self.server.responder = MyResponder(obj_path, "test_wasm")260 261 folder, _ = os.path.split(obj_path)262 self.runCmd(263 "settings set target.debug-file-search-paths " + os.path.abspath(folder)264 )265 266 target = self.dbg.CreateTarget("")267 process = self.connect(target, "wasm")268 lldbutil.expect_state_changes(269 self, self.dbg.GetListener(), process, [lldb.eStateStopped]270 )271 272 num_modules = target.GetNumModules()273 self.assertEqual(1, num_modules)274 275 module = target.GetModuleAtIndex(0)276 num_sections = module.GetNumSections()277 self.assertEqual(5, num_sections)278 279 code_section = module.GetSectionAtIndex(0)280 self.assertEqual("code", code_section.GetName())281 self.assertEqual(282 LOAD_ADDRESS | code_section.GetFileOffset(),283 code_section.GetLoadAddress(target),284 )285 286 debug_info_section = module.GetSectionAtIndex(1)287 self.assertEqual(".debug_info", debug_info_section.GetName())288 self.assertEqual(289 lldb.LLDB_INVALID_ADDRESS, debug_info_section.GetLoadAddress(target)290 )291 292 debug_abbrev_section = module.GetSectionAtIndex(2)293 self.assertEqual(".debug_abbrev", debug_abbrev_section.GetName())294 self.assertEqual(295 lldb.LLDB_INVALID_ADDRESS, debug_abbrev_section.GetLoadAddress(target)296 )297 298 debug_line_section = module.GetSectionAtIndex(3)299 self.assertEqual(".debug_line", debug_line_section.GetName())300 self.assertEqual(301 lldb.LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)302 )303 304 debug_str_section = module.GetSectionAtIndex(4)305 self.assertEqual(".debug_str", debug_str_section.GetName())306 self.assertEqual(307 lldb.LLDB_INVALID_ADDRESS, debug_line_section.GetLoadAddress(target)308 )309 310 @skipIfAsan311 @skipIfXmlSupportMissing312 def test_simple_wasm_debugging_session(self):313 """Test connecting to a WebAssembly engine via GDB-remote, loading a314 Wasm module with embedded DWARF symbols, setting a breakpoint and315 checking the debuggee state"""316 317 # simple.yaml was created by compiling simple.c to wasm and using318 # obj2yaml on the output.319 #320 # $ clang -target wasm32 -nostdlib -Wl,--no-entry -Wl,--export-all -O0 -g -o simple.wasm simple.c321 # $ obj2yaml simple.wasm -o simple.yaml322 yaml_path = "simple.yaml"323 yaml_base, _ = os.path.splitext(yaml_path)324 obj_path = self.getBuildArtifact(yaml_base)325 self.yaml2obj(yaml_path, obj_path)326 327 # Create a fake call stack.328 call_stacks = [329 WasmCallStack(330 [WasmStackFrame(0x019C), WasmStackFrame(0x01E5), WasmStackFrame(0x01FE)]331 ),332 ]333 334 # Create fake memory for our wasm locals.335 self.memory = FakeMemory(0x10000, 0x20000)336 self.memory.store_bytes(337 WASM_LOCAL_ADDR,338 bytes.fromhex(339 "0000000000000000020000000100000000000000020000000100000000000000"340 ),341 )342 343 self.server.responder = MyResponder(344 obj_path, "test_wasm", call_stacks, self.memory345 )346 347 target = self.dbg.CreateTarget("")348 breakpoint = target.BreakpointCreateByName("add")349 process = self.connect(target, "wasm")350 lldbutil.expect_state_changes(351 self, self.dbg.GetListener(), process, [lldb.eStateStopped]352 )353 354 location = breakpoint.GetLocationAtIndex(0)355 self.assertTrue(location and location.IsEnabled(), VALID_BREAKPOINT_LOCATION)356 357 num_modules = target.GetNumModules()358 self.assertEqual(1, num_modules)359 360 thread = process.GetThreadAtIndex(0)361 self.assertTrue(thread.IsValid())362 363 # Check that our frames match our fake call stack.364 frame0 = thread.GetFrameAtIndex(0)365 self.assertTrue(frame0.IsValid())366 self.assertEqual(frame0.GetPC(), LOAD_ADDRESS | 0x019C)367 self.assertIn("add", frame0.GetFunctionName())368 369 frame1 = thread.GetFrameAtIndex(1)370 self.assertTrue(frame1.IsValid())371 self.assertEqual(frame1.GetPC(), LOAD_ADDRESS | 0x01E5)372 self.assertIn("main", frame1.GetFunctionName())373 374 # Check that we can resolve local variables.375 a = frame0.FindVariable("a")376 self.assertTrue(a.IsValid())377 self.assertEqual(a.GetValueAsUnsigned(), 1)378 379 b = frame0.FindVariable("b")380 self.assertTrue(b.IsValid())381 self.assertEqual(b.GetValueAsUnsigned(), 2)382