Author: Yao Qi Date: 2026-08-14T11:04:23+01:00 New Revision: 22a6dc13d968985513e0aff91e3575c79aab964a
URL: https://github.com/llvm/llvm-project/commit/22a6dc13d968985513e0aff91e3575c79aab964a DIFF: https://github.com/llvm/llvm-project/commit/22a6dc13d968985513e0aff91e3575c79aab964a.diff LOG: [lldb][test] Check debugserver's expedited memory in its replies (#216165) debugserver expedites memory in two replies and no test asserts any of it is there. `test_stop_reply_expedites_frame_pointer_backchain` checks the stop reply: every `memory:` entry carries `2 * ptrsize` bytes, and there are at most two, the cap that keeps the reply small. The count is not pinned to exactly two, how far the walk gets depends on where the backchain terminates. `test_threads_info_expedites_stopped_frame_stack` checks `jThreadsInfo` by chunk size: every thread carries at least one backchain entry, the stopped thread carries one or two chunks that are not, and no other thread carries any. The addresses are not checked, debugserver anchors frame 0's window at `$fp` or at `$sp` depending on the inferior's prologue. Sizes come from `qProcessInfo`, not a hard-coded 16, so they hold on a 32-bit target. `gather_threads_info` is split out of `gather_threads_info_pcs`. debugserver only. Added: Modified: lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py Removed: ################################################################################ diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py index b2e991fe3d848..1f0f9c02e819a 100644 --- a/lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteThreadsInStopReply.py @@ -34,6 +34,7 @@ def gather_stop_reply_fields(self, thread_count, field_names): result = dict() result["pc_register"] = hw_info["pc_register"] result["little_endian"] = hw_info["little_endian"] + result["ptrsize"] = hw_info["ptrsize"] for key_field in field_names: result[key_field] = kv_dict.get(key_field) @@ -85,9 +86,11 @@ def parse_hw_info(self, context): hw_info = dict() hw_info["pc_register"] = pc_lldb_reg_index hw_info["little_endian"] = endian == "little" + hw_info["ptrsize"] = int(process_info.get("ptrsize", 0)) return hw_info - def gather_threads_info_pcs(self, pc_register, little_endian): + def gather_threads_info(self): + """The parsed jThreadsInfo reply: one dict per thread.""" self.reset_test_sequence() self.test_sequence.add_log_lines( [ @@ -104,10 +107,12 @@ def gather_threads_info_pcs(self, pc_register, little_endian): context = self.expect_gdbremote_sequence() self.assertIsNotNone(context) threads_info = context.get("threads_info") + # A literal '}' is escaped as '}]' on the wire. + return json.loads(re.sub(r"}]", "}", threads_info)) + + def gather_threads_info_pcs(self, pc_register, little_endian): + jthreads_info = self.gather_threads_info() register = str(pc_register) - # The jThreadsInfo response is not valid JSON data, so we have to - # clean it up first. - jthreads_info = json.loads(re.sub(r"}]", "}", threads_info)) thread_pcs = dict() for thread_info in jthreads_info: tid = thread_info["tid"] @@ -176,6 +181,75 @@ def test_stop_reply_reports_correct_threads(self): for tid in threads: self.assertIn(tid, stop_reply_threads) + @add_test_categories(["debugserver"]) + def test_stop_reply_expedites_frame_pointer_backchain(self): + """The stop reply expedites at most the two innermost backchain + entries, one `memory:` entry each of 2 * ptrsize bytes.""" + self.build() + self.set_inferior_startup_launch() + results = self.gather_stop_reply_fields(1, ["memory"]) + + memory = results["memory"] + self.assertIsNotNone(memory, "the stop reply carried no memory: entry") + # parse_key_val_dict promotes a repeated key to a list. + entries = memory if isinstance(memory, list) else [memory] + + # A backchain entry is 2 pointers at $fp: the previous FP and PC. + backchain_entry_size = 2 * results["ptrsize"] + for entry in entries: + addr, sep, hex_bytes = entry.partition("=") + self.assertEqual(sep, "=", "malformed memory entry %r" % entry) + self.assertEqual( + len(hex_bytes), + 2 * backchain_entry_size, + "memory:%s= should carry %d bytes (2 * ptrsize), got %d: %r" + % (addr, backchain_entry_size, len(hex_bytes) // 2, entry), + ) + # The stop reply caps the backchain at 2 entries. How many it actually + # walks depends on where the backchain terminates. + self.assertLessEqual(len(entries), 2, "expected at most 2 backchain entries") + + @add_test_categories(["debugserver"]) + def test_threads_info_expedites_stopped_frame_stack(self): + """jThreadsInfo expedites every thread's frame pointer backchain, plus + frame 0's stack memory for the stopped thread only.""" + self.build() + self.set_inferior_startup_launch() + results = self.gather_stop_reply_fields(5, ["thread"]) + backchain_entry_size = 2 * results["ptrsize"] + stopped_tid = int(results["thread"], 16) + + # Chunk sizes tell the two apart: a backchain entry is 2 pointers, + # frame 0's stack memory is a larger range. + def classify(thread_info): + sizes = [len(m["bytes"]) // 2 for m in thread_info.get("memory", [])] + stack_backchain = [n for n in sizes if n == backchain_entry_size] + frame_0_stack_memory = [n for n in sizes if n != backchain_entry_size] + return sizes, stack_backchain, frame_0_stack_memory + + saw_stopped = False + for thread_info in self.gather_threads_info(): + sizes, stack_backchain, frame_0_stack_memory = classify(thread_info) + where = "thread %x, chunk sizes %s" % (thread_info["tid"], sizes) + self.assertGreaterEqual( + len(stack_backchain), 1, "no backchain entry for %s" % where + ) + if thread_info["tid"] == stopped_tid: + saw_stopped = True + # One chunk, or two when $fp is usable. + self.assertIn( + len(frame_0_stack_memory), + (1, 2), + "no frame 0 stack memory for stopped %s" % where, + ) + else: + self.assertEqual( + frame_0_stack_memory, + [], + "unexpected frame 0 stack memory for %s" % where, + ) + self.assertTrue(saw_stopped, "no jThreadsInfo entry for the stopped thread") + @skipIfNetBSD @skipIfWindows # Flaky on Windows def test_stop_reply_contains_thread_pcs(self): _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
