diff --git a/debug/gdbserver.py b/debug/gdbserver.py index 3a6f93218..cb3ab4329 100755 --- a/debug/gdbserver.py +++ b/debug/gdbserver.py @@ -1863,6 +1863,7 @@ def test(self): self.gdb.p("$pc=_start") self.exit() + class CeaseStepiTest(ProgramTest): """Test that we work correctly when the hart we're debugging ceases to respond.""" @@ -1950,6 +1951,66 @@ def test(self): 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