[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