Currently, the tests that check stepping through atomic sequences use a hardcoded step distance, which is unreliable because this distance depends on LLVM's codegeneration. The relocations that clang emits can change the distance of the step. Additionally, it was a poor idea to compute and check the step distance because that is not what we should actually be verifying. In the tests we already know where execution should stop after the step - for example, at a branch instruction - therefore, it is better to check the opcode of the instruction rather than the step distance. The step distance itself is not important and can sometimes be misleading. This patch rewrites the tests, so now they checks the opcode of the instruction after the step instead of the step distance.
87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""
|
|
Test software step-inst, also known as instruction level single step, in risc-v atomic sequence.
|
|
For more information about atomic sequences, see the RISC-V Unprivileged ISA specification.
|
|
"""
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class TestSoftwareStep(TestBase):
|
|
def do_sequence_test(self, filename, bkpt_name):
|
|
source_file = filename + ".c"
|
|
exe_file = filename + ".x"
|
|
|
|
self.build(dictionary={"C_SOURCES": source_file, "EXE": exe_file})
|
|
(target, process, cur_thread, bkpt) = lldbutil.run_to_name_breakpoint(
|
|
self, bkpt_name, exe_name=exe_file
|
|
)
|
|
entry_pc = cur_thread.GetFrameAtIndex(0).GetPC()
|
|
|
|
self.runCmd("thread step-inst")
|
|
self.expect(
|
|
"thread list",
|
|
substrs=["stopped", "stop reason = instruction step into"],
|
|
)
|
|
|
|
# Get the instruction we stopped at
|
|
pc = cur_thread.GetFrameAtIndex(0).GetPCAddress()
|
|
inst = target.ReadInstructions(pc, 1).GetInstructionAtIndex(0)
|
|
|
|
inst_mnemonic = inst.GetMnemonic(target)
|
|
inst_operands = inst.GetOperands(target)
|
|
if not inst_operands:
|
|
return inst_mnemonic
|
|
|
|
return f"{inst_mnemonic} {inst_operands}"
|
|
|
|
@skipIf(archs=no_match("^riscv.*"))
|
|
def test_cas(self):
|
|
"""
|
|
This test verifies LLDB instruction step handling of a proper lr/sc pair.
|
|
"""
|
|
instruction = self.do_sequence_test("main", "cas")
|
|
self.assertEqual(instruction, "nop")
|
|
|
|
@skipIf(archs=no_match("^riscv.*"))
|
|
def test_branch_cas(self):
|
|
"""
|
|
LLDB cannot predict the actual state of registers within a critical section (i.e., inside an atomic
|
|
sequence). Therefore, it should identify all forward branches inside the atomic sequence and set
|
|
breakpoints at every jump address that lies beyond the end of the sequence (after the sc instruction).
|
|
This ensures that if any such branch is taken, execution will pause at its target address.
|
|
|
|
This test includes an lr/sc sequence containing an active forward branch with a jump address located
|
|
after the end of the atomic section. LLDB should correctly stop at this branch's target address. The
|
|
test is nearly identical to the previous one, except for the branch condition, which is inverted and
|
|
will result in a taken jump.
|
|
"""
|
|
instruction = self.do_sequence_test("branch", "branch_cas")
|
|
self.assertEqual(instruction, "ret")
|
|
|
|
@skipIf(archs=no_match("^riscv.*"))
|
|
def test_incomplete_sequence_without_lr(self):
|
|
"""
|
|
This test verifies the behavior of a standalone sc instruction without a preceding lr. Since the sc
|
|
lacks the required lr pairing, LLDB should treat it as a non-atomic store rather than part of an
|
|
atomic sequence.
|
|
"""
|
|
instruction = self.do_sequence_test(
|
|
"incomplete_sequence_without_lr", "incomplete_cas"
|
|
)
|
|
self.assertEqual(instruction, "and a5, a2, a4")
|
|
|
|
@skipIf(archs=no_match("^riscv.*"))
|
|
def test_incomplete_sequence_without_sc(self):
|
|
"""
|
|
This test checks the behavior of a standalone lr instruction without a subsequent sc. Since the lr
|
|
lacks its required sc counterpart, LLDB should treat it as a non-atomic load rather than part of an
|
|
atomic sequence.
|
|
"""
|
|
instruction = self.do_sequence_test(
|
|
"incomplete_sequence_without_sc", "incomplete_cas"
|
|
)
|
|
self.assertEqual(instruction, "and a5, a2, a4")
|