aboutsummaryrefslogtreecommitdiff
path: root/debug/gdbserver.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/gdbserver.py')
-rwxr-xr-xdebug/gdbserver.py184
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