diff options
Diffstat (limited to 'debug/gdbserver.py')
-rwxr-xr-x | debug/gdbserver.py | 184 |
1 files changed, 158 insertions, 26 deletions
diff --git a/debug/gdbserver.py b/debug/gdbserver.py index ff0f7ab..f00dcc9 100755 --- a/debug/gdbserver.py +++ b/debug/gdbserver.py @@ -11,6 +11,7 @@ import os import re import itertools +from datetime import datetime import targets import testlib from testlib import assertEqual, assertNotEqual @@ -19,7 +20,8 @@ from testlib import assertGreater, assertRegex, assertLess from testlib import GdbTest, GdbSingleHartTest, TestFailed from testlib import TestNotApplicable, CompileError from testlib import UnknownThread -from testlib import CouldNotReadRegisters +from testlib import CouldNotReadRegisters, CommandException +from testlib import ThreadTerminated MSTATUS_UIE = 0x00000001 MSTATUS_SIE = 0x00000002 @@ -220,14 +222,15 @@ class CustomRegisterTest(SimpleRegisterTest): class SimpleNoExistTest(GdbTest): def test(self): + nonexist_csr = self.hart.nonexist_csr try: - self.gdb.p("$csr2288") - assert False, "Reading csr2288 should have failed" + self.gdb.p(f"${nonexist_csr}") + assert False, f"Reading the ${nonexist_csr} should have failed" except testlib.CouldNotFetch: pass try: - self.gdb.p("$csr2288=5") - assert False, "Writing csr2288 should have failed" + self.gdb.p(f"${nonexist_csr}=5") + assert False, f"Writing the ${nonexist_csr} should have failed" except testlib.CouldNotFetch: pass @@ -911,7 +914,7 @@ class RepeatReadTest(DebugTest): def test(self): self.gdb.b("main:start") self.gdb.c() - mtime_addr = 0x02000000 + 0xbff8 + mtime_addr = self.target.clint_addr + 0xbff8 count = 1024 output = self.gdb.command( f"monitor riscv repeat_read {count} 0x{mtime_addr:x} 4") @@ -1034,15 +1037,17 @@ class InterruptTest(GdbSingleHartTest): local = self.gdb.p("local") if interrupt_count > 1000 and \ local > 1000: + self.disable_timer() return + self.disable_timer() assertGreater(interrupt_count, 1000) assertGreater(local, 1000) def postMortem(self): GdbSingleHartTest.postMortem(self) - self.gdb.p("*((long long*) 0x200bff8)") - self.gdb.p("*((long long*) 0x2004000)") + self.gdb.p(f"*((long long*) 0x{self.target.clint_addr + 0xbff8:x})") + self.gdb.p(f"*((long long*) 0x{self.target.clint_addr + 0x4000:x})") self.gdb.p("interrupt_count") self.gdb.p("local") @@ -1186,6 +1191,8 @@ class MulticoreRunAllHaltOne(GdbTest): time.sleep(1) self.gdb.p("buf", fmt="") + self.disable_timer(interrupt=True) + class MulticoreRtosSwitchActiveHartTest(GdbTest): compile_args = ("programs/multicore.c", "-DMULTICORE") @@ -1215,6 +1222,8 @@ class MulticoreRtosSwitchActiveHartTest(GdbTest): assertIn("set_trap_handler", output) assertNotIn("received signal SIGTRAP", output) + self.disable_timer() + class SmpSimultaneousRunHalt(GdbTest): compile_args = ("programs/run_halt_timing.S", "-DMULTICORE") @@ -1403,7 +1412,7 @@ class TriggerDmode(TriggerTest): i = 0 for i in range(16): tdata1 = self.gdb.p(f"(({xlen_type} *)&data)[{2*i}]") - if tdata1 == 0: + if (tdata1 == 0) or (tdata1 >> (self.hart.xlen-4) == 15): break tdata2 = self.gdb.p(f"(({xlen_type} *)&data)[{2*i+1}]") @@ -1557,6 +1566,7 @@ class DownloadTest(GdbTest): # assertIn("0xbead", output) class PrivTest(GdbSingleHartTest): + """Base class for a few tests that change privilege levels.""" compile_args = ("programs/priv.S", ) def setup(self): # pylint: disable=attribute-defined-outside-init @@ -1583,8 +1593,8 @@ class PrivTest(GdbSingleHartTest): pass class PrivRw(PrivTest): + """Test reading/writing priv.""" def test(self): - """Test reading/writing priv.""" self.write_nop_program(4) for privilege in range(4): self.gdb.p(f"$priv={privilege}") @@ -1595,9 +1605,9 @@ class PrivRw(PrivTest): assertEqual(actual, privilege) class PrivChange(PrivTest): + """Test that the core's privilege level actually changes when the debugger + writes it.""" def test(self): - """Test that the core's privilege level actually changes.""" - if 0 not in self.supported: raise TestNotApplicable @@ -1666,6 +1676,10 @@ class TranslateTest(GdbSingleHartTest): assertEqual(0xdeadbeef, self.gdb.p("virtual[0]")) assertEqual(0x55667788, self.gdb.p("virtual[1]")) + # disable mmu + self.gdb.p("$mstatus=$mstatus & ~0x20000") + self.gdb.p("$satp=0") + SATP_MODE_OFF = 0 SATP_MODE_SV32 = 1 SATP_MODE_SV39 = 8 @@ -1803,22 +1817,28 @@ class EbreakTest(GdbSingleHartTest): output = self.gdb.c() assertIn("_exit", output) -class CeaseMultiTest(GdbTest): - """Test that we work correctly when a hart ceases to respond (e.g. because +class UnavailableMultiTest(GdbTest): + """Test that we work correctly when a hart becomes unavailable (e.g. because it's powered down).""" compile_args = ("programs/counting_loop.c", "-DDEFINE_MALLOC", "-DDEFINE_FREE") def early_applicable(self): - return self.hart.support_cease and len(self.target.harts) > 1 + return (self.hart.support_cease or + self.target.support_unavailable_control) \ + and len(self.target.harts) > 1 def setup(self): ProgramTest.setup(self) - self.parkOtherHarts("precease") + self.parkOtherHarts() def test(self): # Run all the way to the infinite loop in exit - self.gdb.c(wait=False) + self.gdb.c_all(wait=False) + # Other hart should have become unavailable. + if self.target.support_unavailable_control: + self.server.wait_until_running(self.target.harts) + self.server.set_available([self.hart]) self.gdb.expect(r"\S+ became unavailable.") self.gdb.interrupt() @@ -1830,7 +1850,7 @@ class CeaseMultiTest(GdbTest): self.gdb.p("$misa") assert False, \ "Shouldn't be able to access unavailable hart." - except UnknownThread: + except (UnknownThread, CommandException): pass # Check that the main hart can still be debugged. @@ -1845,6 +1865,7 @@ class CeaseMultiTest(GdbTest): self.gdb.p("$pc=_start") self.exit() + class CeaseStepiTest(ProgramTest): """Test that we work correctly when the hart we're debugging ceases to respond.""" @@ -1868,11 +1889,12 @@ class CeaseStepiTest(ProgramTest): except CouldNotReadRegisters: pass -class CeaseRunTest(ProgramTest): +class UnavailableRunTest(ProgramTest): """Test that we work correctly when the hart we're debugging ceases to respond.""" def early_applicable(self): - return self.hart.support_cease + return self.hart.support_cease or \ + self.target.support_unavailable_control def test(self): self.gdb.b("main") @@ -1880,10 +1902,23 @@ class CeaseRunTest(ProgramTest): assertIn("Breakpoint", output) assertIn("main", output) - self.gdb.p("$pc=precease") + if self.target.support_unavailable_control: + self.gdb.p("$pc=loop_forever") + else: + self.gdb.p("$pc=cease") self.gdb.c(wait=False) + if self.target.support_unavailable_control: + self.server.wait_until_running([self.hart]) + self.server.set_available( + [h for h in self.target.harts if h != self.hart]) self.gdb.expect(r"\S+ became unavailable.") self.gdb.interrupt() + # gdb might automatically switch to the available hart. + try: + self.gdb.select_hart(self.hart) + except ThreadTerminated: + # GDB sees that the thread is gone. Count this as success. + return try: self.gdb.p("$pc") assert False, ("Registers shouldn't be accessible when the hart is " @@ -1891,6 +1926,93 @@ class CeaseRunTest(ProgramTest): except CouldNotReadRegisters: pass +class UnavailableCycleTest(ProgramTest): + """Test that harts can be debugged after becoming temporarily + unavailable.""" + def early_applicable(self): + return self.target.support_unavailable_control + + def test(self): + self.gdb.b("main") + output = self.gdb.c() + assertIn("Breakpoint", output) + assertIn("main", output) + + self.gdb.p("$pc=loop_forever") + self.gdb.c(wait=False) + self.server.wait_until_running([self.hart]) + self.server.set_available( + [h for h in self.target.harts if h != self.hart]) + self.gdb.expect(r"\S+ became unavailable.") + + # Now send a DMI command through OpenOCD to make the hart available + # again. + + self.server.set_available(self.target.harts) + self.gdb.expect(r"\S+ became available") + self.gdb.interrupt() + self.gdb.p("$pc") + +class UnavailableHaltedTest(ProgramTest): + """Test behavior when the current hart becomes unavailable while halted.""" + def early_applicable(self): + return self.target.support_unavailable_control + + def test_resume(self, c_expect=None): + # Confirm things don't completely fall apart on `c` + self.gdb.c(wait=False) + if c_expect: + self.gdb.expect(c_expect) + else: + time.sleep(1) + + # Now send a DMI command through OpenOCD to make the hart available + # again. + self.server.set_available(self.target.harts) + + # The hart will show up as halted. That's just how spike behaves when we + # make a hart unavailable while it's halted. + + self.gdb.expect("became available") + self.gdb.p("$minstret") + + def test(self): + self.gdb.b("main") + output = self.gdb.c() + assertIn("Breakpoint", output) + assertIn("main", output) + + self.server.set_available( + [h for h in self.target.harts if h != self.hart]) + self.gdb.command(f"# disabled hart {self.hart.id}") + # gdb won't show that the hart became unavailable, because it thinks + # nothing can changed on a halted Linux thread. + try: + # We can't try this with something reasonable like $pc, because gdb + # has cached it, and it assumes the target can't change while it's + # halted. + self.gdb.p("$minstret") + assert False, ("Registers shouldn't be accessible when the hart is " + "unavailable.") + except testlib.CouldNotFetch: + pass + + # There's a breakpoint set, so gdb will single step. You can't single + # step an unavailable target, so gdb should get a message to that + # effect. + self.test_resume(c_expect="unavailable") + + # Delete breakpoints + self.gdb.command("delete") + self.server.set_available( + [h for h in self.target.harts if h != self.hart]) + + # Resume again. With breakpoints cleared, gdb will send vCont;c instead + # of step. There should be no error this time, since there is no + # observable difference between an unavailable thread and a running + # thread. + self.test_resume() + class FreeRtosTest(GdbTest): def early_applicable(self): return self.target.freertos_binary @@ -1985,7 +2107,6 @@ class EtriggerTest(DebugTest): self.gdb.b("handle_trap") def test(self): - self.gdb.command(f"monitor targets {self.hart.id}") # Set trigger on Load access fault self.gdb.command("monitor riscv etrigger set m 0x20") # Set fox to a null pointer so we'll get a load access exception later. @@ -2005,17 +2126,21 @@ class IcountTest(DebugTest): DebugTest.setup(self) self.gdb.b("main") self.gdb.c() - self.gdb.command(f"monitor targets {self.hart.id}") def test(self): # Execute 2 instructions. output = self.gdb.command("monitor riscv icount set m 2") - assertNotIn("Failed", output) + if self.target.icount_limit > 1: + assertNotIn("Failed", output) + else: + assertIn("Failed", output) + self.gdb.b("main_post_csrr") output = self.gdb.c() - assertIn("breakpoint", output) + assertIn("main_post_csrr", output) main_post_csrr = self.gdb.p("&main_post_csrr") assertEqual(self.gdb.p("$pc"), main_post_csrr) + self.gdb.command("delete") self.gdb.command("monitor riscv icount clear") # Execute 1 instruction. @@ -2035,7 +2160,6 @@ class ItriggerTest(GdbSingleHartTest): self.gdb.load() def test(self): - self.gdb.command(f"monitor targets {self.hart.id}") output = self.gdb.command("monitor riscv itrigger set 0x80") assertIn("Doesn't make sense", output) output = self.gdb.command("monitor riscv itrigger set m 0") @@ -2072,6 +2196,14 @@ def main(): module = sys.modules[__name__] + # initialize PRNG + selected_seed = parsed.seed + if parsed.seed is None: + selected_seed = int(datetime.now().timestamp()) + print(f"PRNG seed for {target.name} is generated automatically") + print(f"PRNG seed for {target.name} is {selected_seed}") + random.seed(selected_seed) + return testlib.run_all_tests(module, target, parsed) # TROUBLESHOOTING TIPS |