[Lldb-commits] [lldb] [lldb] Implement basic support for reverse-continue (PR #112079)

via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 11 21:54:33 PDT 2024


github-actions[bot] wrote:

<!--LLVM CODE FORMAT COMMENT: {darker}-->


:warning: Python code formatter, darker found issues in your code. :warning:

<details>
<summary>
You can test this locally with the following command:
</summary>

``````````bash
darker --check --diff -r 79d695f049343c96eccbce9c06357256bc567be3...6bd33589417195eafe945f2d2f57b01352f56568 lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py lldb/packages/Python/lldbsuite/test/lldbreverse.py lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py lldb/test/API/functionalities/reverse-execution/TestReverseContinueNotSupported.py lldb/packages/Python/lldbsuite/test/gdbclientutils.py lldb/packages/Python/lldbsuite/test/lldbtest.py
``````````

</details>

<details>
<summary>
View the diff from darker here.
</summary>

``````````diff
--- packages/Python/lldbsuite/test/lldbgdbproxy.py	2024-10-12 04:49:36.000000 +0000
+++ packages/Python/lldbsuite/test/lldbgdbproxy.py	2024-10-12 04:54:05.752538 +0000
@@ -53,13 +53,11 @@
         self.setUpBaseLogging()
 
         if self.isVerboseLoggingRequested():
             # If requested, full logs go to a log file
             log_file_name = self.getLogBasenameForCurrentTest() + "-proxy.log"
-            self._verbose_log_handler = logging.FileHandler(
-               log_file_name
-            )
+            self._verbose_log_handler = logging.FileHandler(log_file_name)
             self._verbose_log_handler.setFormatter(self._log_formatter)
             self._verbose_log_handler.setLevel(logging.DEBUG)
             self.logger.addHandler(self._verbose_log_handler)
 
         lldb_server_exe = lldbgdbserverutils.get_lldb_server_exe()
@@ -129,11 +127,15 @@
         self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
         reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
         self.assertEqual(reply, "+")
 
     def get_debug_monitor_command_line_args(self, connect_address, launch_args):
-        return self.debug_monitor_extra_args + ["--reverse-connect", connect_address] + launch_args
+        return (
+            self.debug_monitor_extra_args
+            + ["--reverse-connect", connect_address]
+            + launch_args
+        )
 
     def launch_debug_monitor(self, launch_args):
         family, type, proto, _, addr = socket.getaddrinfo(
             "localhost", 0, proto=socket.IPPROTO_TCP
         )[0]
@@ -159,11 +161,13 @@
         self.monitor_sock.settimeout(self.DEFAULT_TIMEOUT)
         return monitor_process
 
     def connect_to_debug_monitor(self, launch_args):
         monitor_process = self.launch_debug_monitor(launch_args)
-        self.monitor_server = lldbgdbserverutils.Server(self.monitor_sock, monitor_process)
+        self.monitor_server = lldbgdbserverutils.Server(
+            self.monitor_sock, monitor_process
+        )
 
     def respond(self, packet):
         """Subclasses can override this to change how packets are handled."""
         return self.pass_through(packet)
 
--- packages/Python/lldbsuite/test/lldbreverse.py	2024-10-12 04:49:36.000000 +0000
+++ packages/Python/lldbsuite/test/lldbreverse.py	2024-10-12 04:54:05.890316 +0000
@@ -102,16 +102,18 @@
         if self.is_command(packet, "vCont", ";"):
             if self.recording_enabled:
                 return self.continue_with_recording(packet)
             snapshots = []
         if packet[0] == "c" or packet[0] == "s" or packet[0] == "C" or packet[0] == "S":
-            raise ValueError("LLDB should not be sending old-style continuation packets")
+            raise ValueError(
+                "LLDB should not be sending old-style continuation packets"
+            )
         if packet == "bc":
             return self.reverse_continue()
         if packet == "bs":
             return self.reverse_step()
-        if packet == 'jThreadsInfo':
+        if packet == "jThreadsInfo":
             # Suppress this because it contains thread stop reasons which we might
             # need to modify, and we don't want to have to implement that.
             return ""
         if packet[0] == "z" or packet[0] == "Z":
             reply = self.pass_through(packet)
@@ -131,20 +133,20 @@
         Reverse execution is still supported until the next forward continue.
         """
         self.recording_enabled = False
 
     def is_command(self, packet, cmd, follow_token):
-        return packet == cmd or packet[0:len(cmd) + 1] == cmd + follow_token
+        return packet == cmd or packet[0 : len(cmd) + 1] == cmd + follow_token
 
     def update_breakpoints(self, packet):
         m = re.match("([zZ])([01234]),([0-9a-f]+),([0-9a-f]+)", packet)
         if m is None:
             raise ValueError("Invalid breakpoint packet: " + packet)
         t = int(m.group(2))
         addr = int(m.group(3), 16)
         kind = int(m.group(4), 16)
-        if m.group(1) == 'Z':
+        if m.group(1) == "Z":
             self.breakpoints[t].add((addr, kind))
         else:
             self.breakpoints[t].discard((addr, kind))
 
     def breakpoint_triggered_at(self, pc):
@@ -157,14 +159,19 @@
     def watchpoint_triggered(self, new_value_block, current_contents):
         """Returns the address or None."""
         for watch_addr, kind in breakpoints[WRITE_WATCHPOINTS]:
             for offset in range(0, kind):
                 addr = watch_addr + offset
-                if (addr >= new_value_block.address and
-                    addr < new_value_block.address + len(new_value_block.data)):
+                if (
+                    addr >= new_value_block.address
+                    and addr < new_value_block.address + len(new_value_block.data)
+                ):
                     index = addr - new_value_block.address
-                    if new_value_block.data[index*2:(index + 1)*2] != current_contents[index*2:(index + 1)*2]:
+                    if (
+                        new_value_block.data[index * 2 : (index + 1) * 2]
+                        != current_contents[index * 2 : (index + 1) * 2]
+                    ):
                         return watch_addr
         return None
 
     def continue_with_recording(self, packet):
         self.logger.debug("Continue with recording enabled")
@@ -174,11 +181,11 @@
             requested_step = False
         else:
             m = re.match("vCont;(c|s)(.*)", packet)
             if m is None:
                 raise ValueError("Unsupported vCont packet: " + packet)
-            requested_step = m.group(1) == 's'
+            requested_step = m.group(1) == "s"
             step_packet += m.group(2)
 
         while True:
             snapshot = self.capture_snapshot()
             reply = self.pass_through(step_packet)
@@ -189,11 +196,11 @@
             thread_id = None
             for key, value in stop_pairs.items():
                 if key == "thread":
                     thread_id = self.parse_thread_id(value)
                     continue
-                if re.match('[0-9a-f]+', key):
+                if re.match("[0-9a-f]+", key):
                     continue
                 if key == "swbreak" or (key == "reason" and value == "breakpoint"):
                     is_swbreak = True
                     continue
                 if key in ["name", "threads", "thread-pcs", "reason"]:
@@ -213,11 +220,11 @@
     def parse_stop(self, reply):
         result = {}
         if not reply:
             raise ValueError("Invalid empty packet")
         if reply[0] == "T" and len(reply) >= 3:
-            result = {k:v for k, v in self.parse_pairs(reply[3:])}
+            result = {k: v for k, v in self.parse_pairs(reply[3:])}
             return (int(reply[1:3], 16), result)
         raise "Unsupported stop reply: " + reply
 
     def parse_pairs(self, text):
         for pair in text.split(";"):
@@ -235,17 +242,21 @@
         thread_snapshots = []
         memory = []
         for thread_id in self.get_thread_list():
             registers = {}
             for index in sorted(self.general_purpose_register_info.keys()):
-                reply =  self.pass_through(f"p{index:x};thread:{thread_id:x};")
-                if reply == "" or reply[0] == 'E':
+                reply = self.pass_through(f"p{index:x};thread:{thread_id:x};")
+                if reply == "" or reply[0] == "E":
                     raise ValueError("Can't read register")
                 registers[index] = reply
             thread_snapshot = ThreadSnapshot(thread_id, registers)
-            thread_sp = self.get_register(self.sp_register_info, thread_snapshot.registers)
-            memory += self.read_memory(thread_sp - BELOW_STACK_POINTER, thread_sp + ABOVE_STACK_POINTER)
+            thread_sp = self.get_register(
+                self.sp_register_info, thread_snapshot.registers
+            )
+            memory += self.read_memory(
+                thread_sp - BELOW_STACK_POINTER, thread_sp + ABOVE_STACK_POINTER
+            )
             thread_snapshots.append(thread_snapshot)
         self.set_current_thread(current_thread)
         return StateSnapshot(thread_snapshots, memory)
 
     def restore_snapshot(self, snapshot):
@@ -259,28 +270,38 @@
         stop_reasons = []
         for thread_snapshot in snapshot.thread_snapshots:
             thread_id = thread_snapshot.thread_id
             for lldb_index in sorted(thread_snapshot.registers.keys()):
                 data = thread_snapshot.registers[lldb_index]
-                reply = self.pass_through(f"P{lldb_index:x}={data};thread:{thread_id:x};")
+                reply = self.pass_through(
+                    f"P{lldb_index:x}={data};thread:{thread_id:x};"
+                )
                 if reply != "OK":
                     raise ValueError("Can't restore thread register")
             if thread_id == snapshot.thread_id:
-                new_pc = self.get_register(self.pc_register_info, thread_snapshot.registers)
+                new_pc = self.get_register(
+                    self.pc_register_info, thread_snapshot.registers
+                )
                 if self.breakpoint_triggered_at(new_pc):
                     stop_reasons.append([("reason", "breakpoint")])
         self.set_current_thread(current_thread)
         for block in snapshot.memory:
-            current_memory = self.pass_through(f"m{block.address:x},{(len(block.data)/2):x}")
-            if not current_memory or current_memory[0] == 'E':
+            current_memory = self.pass_through(
+                f"m{block.address:x},{(len(block.data)/2):x}"
+            )
+            if not current_memory or current_memory[0] == "E":
                 raise ValueError("Can't read back memory")
-            reply = self.pass_through(f"M{block.address:x},{len(block.data)/2:x}:" + block.data)
+            reply = self.pass_through(
+                f"M{block.address:x},{len(block.data)/2:x}:" + block.data
+            )
             if reply != "OK":
                 raise ValueError("Can't restore memory")
             watch_addr = self.watchpoint_triggered(block, current_memory[1:])
             if watch_addr is not None:
-                stop_reasons.append([("reason", "watchpoint"), ("watch", f"{watch_addr:x}")])
+                stop_reasons.append(
+                    [("reason", "watchpoint"), ("watch", f"{watch_addr:x}")]
+                )
         if stop_reasons:
             pairs = ";".join(f"{key}:{value}" for key, value in stop_reasons[0])
             return f"T05thread:{self.pid:x}.{snapshot.thread_id:x};{pairs};"
         return None
 
@@ -348,14 +369,14 @@
         if register_info.bitsize % 8 != 0:
             raise ValueError("Register size must be a multiple of 8 bits")
         if register_info.lldb_index not in registers:
             raise ValueError("Register value not captured")
         data = registers[register_info.lldb_index]
-        num_bytes = register_info.bitsize//8
+        num_bytes = register_info.bitsize // 8
         bytes = []
         for i in range(0, num_bytes):
-            bytes.append(int(data[i*2:(i + 1)*2], 16))
+            bytes.append(int(data[i * 2 : (i + 1) * 2], 16))
         if register_info.little_endian:
             bytes.reverse()
         result = 0
         for byte in bytes:
             result = (result << 8) + byte
@@ -373,29 +394,34 @@
         regions = []
         start_addr = start_addr & (BLOCK_SIZE - 1)
         end_addr = (end_addr + BLOCK_SIZE - 1) & (BLOCK_SIZE - 1)
         for addr in range(start_addr, end_addr, BLOCK_SIZE):
             reply = self.pass_through(f"m{addr:x},{(BLOCK_SIZE - 1):x}")
-            if reply and reply[0] != 'E':
+            if reply and reply[0] != "E":
                 block = MemoryBlockSnapshot(addr, reply[1:])
                 regions.append(block)
         return regions
 
     def ensure_register_info(self):
         if self.general_purpose_register_info is not None:
             return
         reply = self.pass_through("qHostInfo")
-        little_endian = any(kv == ("endian", "little") for kv in self.parse_pairs(reply))
+        little_endian = any(
+            kv == ("endian", "little") for kv in self.parse_pairs(reply)
+        )
         self.general_purpose_register_info = {}
         lldb_index = 0
         while True:
             reply = self.pass_through(f"qRegisterInfo{lldb_index:x}")
-            if not reply or reply[0] == 'E':
+            if not reply or reply[0] == "E":
                 break
-            info = {k:v for k, v in self.parse_pairs(reply)}
+            info = {k: v for k, v in self.parse_pairs(reply)}
             reg_info = RegisterInfo(lldb_index, int(info["bitsize"]), little_endian)
-            if info["set"] == "General Purpose Registers" and not "container-regs" in info:
+            if (
+                info["set"] == "General Purpose Registers"
+                and not "container-regs" in info
+            ):
                 self.general_purpose_register_info[lldb_index] = reg_info
             if "generic" in info:
                 if info["generic"] == "pc":
                     self.pc_register_info = reg_info
                 elif info["generic"] == "sp":
@@ -408,11 +434,11 @@
         threads = []
         reply = self.pass_through("qfThreadInfo")
         while True:
             if not reply:
                 raise ValueError("Missing reply packet")
-            if reply[0] == 'm':
+            if reply[0] == "m":
                 for id in reply[1:].split(","):
                     threads.append(self.parse_thread_id(id))
-            elif reply[0] == 'l':
+            elif reply[0] == "l":
                 return threads
             reply = self.pass_through("qsThreadInfo")
--- test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py	2024-10-12 04:49:36.000000 +0000
+++ test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py	2024-10-12 04:54:06.341568 +0000
@@ -21,20 +21,24 @@
         target, process, initial_threads = self.setup_recording(async_mode)
 
         # Reverse-continue. We'll stop at the point where we started recording.
         status = process.Continue(lldb.eRunReverse)
         self.assertSuccess(status)
-        self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
+        self.expect_async_state_changes(
+            async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
+        )
         self.expect(
             "thread list",
             STOPPED_DUE_TO_HISTORY_BOUNDARY,
             substrs=["stopped", "stop reason = history boundary"],
         )
 
         # Continue forward normally until the target exits.
         status = process.Continue()
-        self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateExited])
+        self.expect_async_state_changes(
+            async_mode, process, [lldb.eStateRunning, lldb.eStateExited]
+        )
         self.assertSuccess(status)
         self.assertState(process.GetState(), lldb.eStateExited)
         self.assertEqual(process.GetExitStatus(), 0)
 
     def test_reverse_continue_breakpoint(self):
@@ -48,11 +52,13 @@
 
         # Reverse-continue to the function "trigger_breakpoint".
         trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
         status = process.Continue(lldb.eRunReverse)
         self.assertSuccess(status)
-        self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
+        self.expect_async_state_changes(
+            async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
+        )
         threads_now = lldbutil.get_threads_stopped_at_breakpoint(process, trigger_bkpt)
         self.assertEqual(threads_now, initial_threads)
 
     def test_reverse_continue_skip_breakpoint(self):
         self.reverse_continue_skip_breakpoint_internal(async_mode=False)
@@ -68,11 +74,13 @@
         # This tests that we continue in the correct direction after hitting
         # the breakpoint.
         trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
         trigger_bkpt.SetCondition("false_condition")
         status = process.Continue(lldb.eRunReverse)
-        self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
+        self.expect_async_state_changes(
+            async_mode, process, [lldb.eStateRunning, lldb.eStateStopped]
+        )
         self.assertSuccess(status)
         self.expect(
             "thread list",
             STOPPED_DUE_TO_HISTORY_BOUNDARY,
             substrs=["stopped", "stop reason = history boundary"],

``````````

</details>


https://github.com/llvm/llvm-project/pull/112079


More information about the lldb-commits mailing list