X-Git-Url: https://git.libre-soc.org/?p=riscv-tests.git;a=blobdiff_plain;f=debug%2Fgdbserver.py;h=000b52c464954307d4b9ce0e1dfd4de6f1668d71;hp=de8241ce33fb6c503308971090b8929f5369b8f2;hb=c5b99270af1deed953ac856b7b5bf3e5f84dd9e6;hpb=5ad886f909376920d345c7cf1f7b70c7ef37392f diff --git a/debug/gdbserver.py b/debug/gdbserver.py index de8241c..000b52c 100755 --- a/debug/gdbserver.py +++ b/debug/gdbserver.py @@ -1,16 +1,16 @@ #!/usr/bin/env python -import os -import sys import argparse -import unittest +import binascii +import random +import sys import tempfile import time -import random -import binascii - -import .testlib +import targets +import testlib +from testlib import assertEqual, assertNotEqual, assertIn +from testlib import assertGreater, assertTrue, assertRegexpMatches, assertLess MSTATUS_UIE = 0x00000001 MSTATUS_SIE = 0x00000002 @@ -32,29 +32,32 @@ MSTATUS_VM = 0x1F000000 MSTATUS32_SD = 0x80000000 MSTATUS64_SD = 0x8000000000000000 +# pylint: disable=abstract-method + def gdb( target=None, port=None, binary=None ): - gdb = None + g = None if parsed.gdb: - gdb = testlib.Gdb(parsed.gdb) + g = testlib.Gdb(parsed.gdb) else: - gdb = testlib.Gdb() + g = testlib.Gdb() if binary: - gdb.command("file %s" % binary) + g.command("file %s" % binary) if target: - gdb.command("set arch riscv:rv%d" % target.xlen) - gdb.command("set remotetimeout %d" % target.timeout_sec) + g.command("set arch riscv:rv%d" % target.xlen) + g.command("set remotetimeout %d" % target.timeout_sec) if port: - gdb.command("target extended-remote localhost:%d" % port) + g.command("target extended-remote localhost:%d" % port) + + g.p("$priv=3") - return gdb + return g - def ihex_line(address, record_type, data): assert len(data) < 128 line = ":%02X%04X%02X" % (len(data), address, record_type) @@ -83,240 +86,306 @@ def ihex_parse(line): def readable_binary_string(s): return "".join("%02x" % ord(c) for c in s) -class DeleteServer(unittest.TestCase): - def tearDown(self): - del self.server +class GdbTest(testlib.BaseTest): + def __init__(self, target): + testlib.BaseTest.__init__(self, target) + self.gdb = None -class SimpleRegisterTest(DeleteServer): - def setUp(self): - self.server = target.server() - self.gdb = gdb(target, self.server.port) + def classSetup(self): + testlib.BaseTest.classSetup(self) + self.logs.append("gdb.log") + self.gdb = gdb(self.target, self.server.port, self.binary) - # 0x13 is nop - self.gdb.command("p *((int*) 0x%x)=0x13" % target.ram) - self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 4)) - self.gdb.command("p *((int*) 0x%x)=0x13" % (target.ram + 8)) - self.gdb.p("$pc=0x%x" % target.ram) + def classTeardown(self): + del self.gdb + testlib.BaseTest.classTeardown(self) +class SimpleRegisterTest(GdbTest): def check_reg(self, name): - a = random.randrange(1< last_pc and pc - last_pc <= 4): + assertNotEqual(last_pc, pc) + if last_pc and pc > last_pc and pc - last_pc <= 4: advances += 1 else: jumps += 1 last_pc = pc # Some basic sanity that we're not running between breakpoints or # something. - self.assertGreater(jumps, 10) - self.assertGreater(advances, 50) + assertGreater(jumps, 10) + assertGreater(advances, 50) - def test_exit(self): +class DebugExit(DebugTest): + def test(self): self.exit() - def test_symbols(self): +class DebugSymbols(DebugTest): + def test(self): self.gdb.b("main") self.gdb.b("rot13") output = self.gdb.c() - self.assertIn(", main ", output) + assertIn(", main ", output) output = self.gdb.c() - self.assertIn(", rot13 ", output) + assertIn(", rot13 ", output) - def test_breakpoint(self): +class DebugBreakpoint(DebugTest): + def test(self): self.gdb.b("rot13") # The breakpoint should be hit exactly 2 times. - for i in range(2): + for _ in range(2): output = self.gdb.c() self.gdb.p("$pc") - self.assertIn("Breakpoint ", output) - #TODO self.assertIn("rot13 ", output) + assertIn("Breakpoint ", output) + assertIn("rot13 ", output) self.exit() - def test_hwbp_1(self): - if target.instruction_hardware_breakpoint_count < 1: - return +class Hwbp1(DebugTest): + def test(self): + if self.target.instruction_hardware_breakpoint_count < 1: + return 'not_applicable' self.gdb.hbreak("rot13") # The breakpoint should be hit exactly 2 times. - for i in range(2): + for _ in range(2): output = self.gdb.c() self.gdb.p("$pc") - self.assertIn("Breakpoint ", output) - #TODO self.assertIn("rot13 ", output) + assertRegexpMatches(output, r"[bB]reakpoint") + assertIn("rot13 ", output) self.exit() - def test_hwbp_2(self): - if target.instruction_hardware_breakpoint_count < 2: - return +class Hwbp2(DebugTest): + def test(self): + if self.target.instruction_hardware_breakpoint_count < 2: + return 'not_applicable' self.gdb.hbreak("main") self.gdb.hbreak("rot13") # We should hit 3 breakpoints. - for i in range(3): + for expected in ("main", "rot13", "rot13"): output = self.gdb.c() self.gdb.p("$pc") - self.assertIn("Breakpoint ", output) - #TODO self.assertIn("rot13 ", output) + assertRegexpMatches(output, r"[bB]reakpoint") + assertIn("%s " % expected, output) self.exit() - def test_too_many_hwbp(self): +class TooManyHwbp(DebugTest): + def run(self): for i in range(30): self.gdb.hbreak("*rot13 + %d" % (i * 4)) output = self.gdb.c() - self.assertIn("Cannot insert hardware breakpoint", output) + assertIn("Cannot insert hardware breakpoint", output) # Clean up, otherwise the hardware breakpoints stay set and future # tests may fail. self.gdb.command("D") - def test_registers(self): +class Registers(DebugTest): + def test(self): # Get to a point in the code where some registers have actually been # used. self.gdb.b("rot13") @@ -325,68 +394,176 @@ class DebugTest(DeleteServer): # Try both forms to test gdb. for cmd in ("info all-registers", "info registers all"): output = self.gdb.command(cmd) - self.assertNotIn("Could not", output) for reg in ('zero', 'ra', 'sp', 'gp', 'tp'): - self.assertIn(reg, output) + assertIn(reg, output) #TODO # mcpuid is one of the few registers that should have the high bit set # (for rv64). # Leave this commented out until gdb and spike agree on the encoding of # mcpuid (which is going to be renamed to misa in any case). - #self.assertRegexpMatches(output, ".*mcpuid *0x80") + #assertRegexpMatches(output, ".*mcpuid *0x80") #TODO: # The instret register should always be changing. #last_instret = None #for _ in range(5): # instret = self.gdb.p("$instret") - # self.assertNotEqual(instret, last_instret) + # assertNotEqual(instret, last_instret) # last_instret = instret # self.gdb.stepi() self.exit() - def test_interrupt(self): - """Sending gdb ^C while the program is running should cause it to halt.""" +class UserInterrupt(DebugTest): + def test(self): + """Sending gdb ^C while the program is running should cause it to + halt.""" self.gdb.b("main:start") self.gdb.c() - self.gdb.p("i=123"); + self.gdb.p("i=123") self.gdb.c(wait=False) time.sleep(0.1) output = self.gdb.interrupt() - #TODO: assert "main" in output - self.assertGreater(self.gdb.p("j"), 10) - self.gdb.p("i=0"); + assert "main" in output + assertGreater(self.gdb.p("j"), 10) + self.gdb.p("i=0") self.exit() -class StepTest(DeleteServer): - def setUp(self): - self.binary = target.compile("programs/step.S") - self.server = target.server() - self.gdb = gdb(target, self.server.port, self.binary) +class StepTest(GdbTest): + compile_args = ("programs/step.S", ) + + def setup(self): self.gdb.load() self.gdb.b("main") self.gdb.c() - def test_step(self): - main = self.gdb.p("$pc") + def test(self): + main_address = self.gdb.p("$pc") for expected in (4, 8, 0xc, 0x10, 0x18, 0x1c, 0x28, 0x20, 0x2c, 0x2c): self.gdb.stepi() pc = self.gdb.p("$pc") - self.assertEqual("%x" % pc, "%x" % (expected + main)) + assertEqual("%x" % pc, "%x" % (expected + main_address)) -class RegsTest(DeleteServer): - def setUp(self): - self.binary = target.compile("programs/regs.S") - self.server = target.server() - self.gdb = gdb(target, self.server.port, self.binary) +class TriggerTest(GdbTest): + compile_args = ("programs/trigger.S", ) + def setup(self): + self.gdb.load() + self.gdb.b("_exit") + self.gdb.b("main") + self.gdb.c() + + def exit(self): + output = self.gdb.c() + assertIn("Breakpoint", output) + assertIn("_exit", output) + +class TriggerExecuteInstant(TriggerTest): + """Test an execute breakpoint on the first instruction executed out of + debug mode.""" + def test(self): + main_address = self.gdb.p("$pc") + self.gdb.command("hbreak *0x%x" % (main_address + 4)) + self.gdb.c() + assertEqual(self.gdb.p("$pc"), main_address+4) + +class TriggerLoadAddress(TriggerTest): + def test(self): + self.gdb.command("rwatch *((&data)+1)") + output = self.gdb.c() + assertIn("read_loop", output) + assertEqual(self.gdb.p("$a0"), + self.gdb.p("(&data)+1")) + self.exit() + +class TriggerLoadAddressInstant(TriggerTest): + """Test a load address breakpoint on the first instruction executed out of + debug mode.""" + def test(self): + self.gdb.command("b just_before_read_loop") + self.gdb.c() + read_loop = self.gdb.p("&read_loop") + self.gdb.command("rwatch data") + self.gdb.c() + # Accept hitting the breakpoint before or after the load instruction. + assertIn(self.gdb.p("$pc"), [read_loop, read_loop + 4]) + assertEqual(self.gdb.p("$a0"), self.gdb.p("&data")) + +class TriggerStoreAddress(TriggerTest): + def test(self): + self.gdb.command("watch *((&data)+3)") + output = self.gdb.c() + assertIn("write_loop", output) + assertEqual(self.gdb.p("$a0"), + self.gdb.p("(&data)+3")) + self.exit() + +class TriggerStoreAddressInstant(TriggerTest): + def test(self): + """Test a store address breakpoint on the first instruction executed out + of debug mode.""" + self.gdb.command("b just_before_write_loop") + self.gdb.c() + write_loop = self.gdb.p("&write_loop") + self.gdb.command("watch data") + self.gdb.c() + # Accept hitting the breakpoint before or after the store instruction. + assertIn(self.gdb.p("$pc"), [write_loop, write_loop + 4]) + assertEqual(self.gdb.p("$a0"), self.gdb.p("&data")) + +class TriggerDmode(TriggerTest): + def check_triggers(self, tdata1_lsbs, tdata2): + dmode = 1 << (self.target.xlen-5) + + triggers = [] + + if self.target.xlen == 32: + xlen_type = 'int' + elif self.target.xlen == 64: + xlen_type = 'long long' + else: + raise NotImplementedError + + dmode_count = 0 + i = 0 + for i in range(16): + tdata1 = self.gdb.p("((%s *)&data)[%d]" % (xlen_type, 2*i)) + if tdata1 == 0: + break + tdata2 = self.gdb.p("((%s *)&data)[%d]" % (xlen_type, 2*i+1)) + + if tdata1 & dmode: + dmode_count += 1 + else: + assertEqual(tdata1 & 0xffff, tdata1_lsbs) + assertEqual(tdata2, tdata2) + + assertGreater(i, 1) + assertEqual(dmode_count, 1) + + return triggers + + def test(self): + self.gdb.command("hbreak write_load_trigger") + self.gdb.b("clear_triggers") + self.gdb.p("$pc=write_store_trigger") + output = self.gdb.c() + assertIn("write_load_trigger", output) + self.check_triggers((1<<6) | (1<<1), 0xdeadbee0) + output = self.gdb.c() + assertIn("clear_triggers", output) + self.check_triggers((1<<6) | (1<<0), 0xfeedac00) + +class RegsTest(GdbTest): + compile_args = ("programs/regs.S", ) + def setup(self): self.gdb.load() self.gdb.b("main") self.gdb.b("handle_trap") self.gdb.c() - def test_write_gprs(self): +class WriteGprs(RegsTest): + def test(self): regs = [("x%d" % n) for n in range(2, 32)] self.gdb.p("$pc=write_regs") @@ -395,87 +572,88 @@ class RegsTest(DeleteServer): self.gdb.p("$x1=data") self.gdb.command("b all_done") output = self.gdb.c() - self.assertIn("Breakpoint ", output) + assertIn("Breakpoint ", output) # Just to get this data in the log. self.gdb.command("x/30gx data") self.gdb.command("info registers") for n in range(len(regs)): - self.assertEqual(self.gdb.x("data+%d" % (8*n), 'g'), - ((0xdeadbeef<\n") - download_c.write("unsigned int crc32a(uint8_t *message, unsigned int size);\n") + download_c.write( + "unsigned int crc32a(uint8_t *message, unsigned int size);\n") download_c.write("uint32_t length = %d;\n" % length) download_c.write("uint8_t d[%d] = {\n" % length) self.crc = 0 for i in range(length / 16): - download_c.write(" /* 0x%04x */ " % (i * 16)); + download_c.write(" /* 0x%04x */ " % (i * 16)) for _ in range(16): value = random.randrange(1<<8) download_c.write("%d, " % value) self.crc = binascii.crc32("%c" % value, self.crc) - download_c.write("\n"); - download_c.write("};\n"); - download_c.write("uint8_t *data = &d[0];\n"); + download_c.write("\n") + download_c.write("};\n") + download_c.write("uint8_t *data = &d[0];\n") download_c.write("uint32_t main() { return crc32a(data, length); }\n") download_c.flush() if self.crc < 0: self.crc += 2**32 - self.binary = target.compile(download_c.name, "programs/checksum.c") - self.server = target.server() - self.gdb = gdb(target, self.server.port, self.binary) - - def test_download(self): - output = self.gdb.load() + self.binary = self.target.compile(download_c.name, + "programs/checksum.c") + self.gdb.command("file %s" % self.binary) + + def test(self): + self.gdb.load() self.gdb.command("b _exit") self.gdb.c() - self.assertEqual(self.gdb.p("status"), self.crc) + assertEqual(self.gdb.p("status"), self.crc) -class MprvTest(DeleteServer): - def setUp(self): - self.binary = target.compile("programs/mprv.S") - self.server = target.server() - self.gdb = gdb(target, self.server.port, self.binary) +class MprvTest(GdbTest): + compile_args = ("programs/mprv.S", ) + def setup(self): self.gdb.load() - def test_mprv(self): + def test(self): """Test that the debugger can access memory when MPRV is set.""" self.gdb.c(wait=False) time.sleep(0.5) self.gdb.interrupt() output = self.gdb.command("p/x *(int*)(((char*)&data)-0x80000000)") - self.assertIn("0xbead", output) + assertIn("0xbead", output) -class PrivTest(DeleteServer): - def setUp(self): - self.binary = target.compile("programs/priv.S") - self.server = target.server() - self.gdb = gdb(target, self.server.port, self.binary) +class PrivTest(GdbTest): + compile_args = ("programs/priv.S", ) + def setup(self): + # pylint: disable=attribute-defined-outside-init self.gdb.load() misa = self.gdb.p("$misa") @@ -488,188 +666,66 @@ class PrivTest(DeleteServer): self.supported.add(2) self.supported.add(3) - def test_rw(self): +class PrivRw(PrivTest): + def test(self): """Test reading/writing priv.""" for privilege in range(4): self.gdb.p("$priv=%d" % privilege) self.gdb.stepi() actual = self.gdb.p("$priv") - self.assertIn(actual, self.supported) + assertIn(actual, self.supported) if privilege in self.supported: - self.assertEqual(actual, privilege) + assertEqual(actual, privilege) - def test_change(self): +class PrivChange(PrivTest): + def test(self): """Test that the core's privilege level actually changes.""" if 0 not in self.supported: - # TODO: return not applicable - return + return 'not_applicable' self.gdb.b("main") self.gdb.c() # Machine mode self.gdb.p("$priv=3") - main = self.gdb.p("$pc") + main_address = self.gdb.p("$pc") self.gdb.stepi() - self.assertEqual("%x" % self.gdb.p("$pc"), "%x" % (main+4)) + assertEqual("%x" % self.gdb.p("$pc"), "%x" % (main_address+4)) # User mode self.gdb.p("$priv=0") self.gdb.stepi() # Should have taken an exception, so be nowhere near main. pc = self.gdb.p("$pc") - self.assertTrue(pc < main or pc > main + 0x100) - -class Target(object): - directory = None - timeout_sec = 2 - - def server(self): - raise NotImplementedError - - def compile(self, *sources): - binary_name = "%s_%s-%d" % ( - self.name, - os.path.basename(os.path.splitext(sources[0])[0]), - self.xlen) - if parsed.isolate: - self.temporary_binary = tempfile.NamedTemporaryFile( - prefix=binary_name + "_") - binary_name = self.temporary_binary.name - testlib.compile(sources + - ("programs/entry.S", "programs/init.c", - "-I", "../env", - "-T", "targets/%s/link.lds" % (self.directory or self.name), - "-nostartfiles", - "-mcmodel=medany", - "-o", binary_name), - xlen=self.xlen) - return binary_name - -class SpikeTarget(Target): - directory = "spike" - ram = 0x80010000 - ram_size = 5 * 1024 * 1024 - instruction_hardware_breakpoint_count = 0 - reset_vector = 0x1000 - -class Spike64Target(SpikeTarget): - name = "spike64" - xlen = 64 - - def server(self): - return testlib.Spike(parsed.cmd, halted=True) - -class Spike32Target(SpikeTarget): - name = "spike32" - xlen = 32 - - def server(self): - return testlib.Spike(parsed.cmd, halted=True, xlen=32) - -class FreedomE300Target(Target): - name = "freedom-e300" - xlen = 32 - ram = 0x80000000 - ram_size = 16 * 1024 - instruction_hardware_breakpoint_count = 2 - - def server(self): - return testlib.Openocd(cmd=parsed.cmd, - config="targets/%s/openocd.cfg" % self.name) - -class FreedomE300SimTarget(Target): - name = "freedom-e300-sim" - xlen = 32 - timeout_sec = 240 - ram = 0x80000000 - ram_size = 256 * 1024 * 1024 - instruction_hardware_breakpoint_count = 2 - - def server(self): - sim = testlib.VcsSim(simv=parsed.run, debug=False) - openocd = testlib.Openocd(cmd=parsed.cmd, - config="targets/%s/openocd.cfg" % self.name, - otherProcess = sim) - time.sleep(20) - return openocd - -class FreedomU500Target(Target): - name = "freedom-u500" - xlen = 64 - ram = 0x80000000 - ram_size = 16 * 1024 - instruction_hardware_breakpoint_count = 2 - - def server(self): - return testlib.Openocd(cmd=parsed.cmd, - config="targets/%s/openocd.cfg" % self.name) - -class FreedomU500SimTarget(Target): - name = "freedom-u500-sim" - xlen = 64 - timeout_sec = 240 - ram = 0x80000000 - ram_size = 256 * 1024 * 1024 - instruction_hardware_breakpoint_count = 2 - - def server(self): - sim = testlib.VcsSim(simv=parsed.run, debug=False) - openocd = testlib.Openocd(cmd=parsed.cmd, - config="targets/%s/openocd.cfg" % self.name, - otherProcess = sim) - time.sleep(20) - return openocd - -targets = [ - Spike32Target, - Spike64Target, - FreedomE300Target, - FreedomU500Target, - FreedomE300SimTarget, - FreedomU500SimTarget] + assertTrue(pc < main_address or pc > main_address + 0x100) +parsed = None def main(): parser = argparse.ArgumentParser( + description="Test that gdb can talk to a RISC-V target.", epilog=""" Example command line from the real world: Run all RegsTest cases against a physical FPGA, with custom openocd command: - ./gdbserver.py --freedom-e-300 --cmd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl -d" -- -vf RegsTest + ./gdbserver.py --freedom-e300 --cmd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl -d" Simple """) - group = parser.add_mutually_exclusive_group(required=True) - for t in targets: - group.add_argument("--%s" % t.name, action="store_const", const=t, - dest="target") - parser.add_argument("--run", - help="The command to use to start the actual target (e.g. simulation)") - parser.add_argument("--cmd", - help="The command to use to start the debug server.") + targets.add_target_options(parser) parser.add_argument("--gdb", help="The command to use to start gdb.") - xlen_group = parser.add_mutually_exclusive_group() - xlen_group.add_argument("--32", action="store_const", const=32, dest="xlen", - help="Force the target to be 32-bit.") - xlen_group.add_argument("--64", action="store_const", const=64, dest="xlen", - help="Force the target to be 64-bit.") + testlib.add_test_run_options(parser) - parser.add_argument("--isolate", action="store_true", - help="Try to run in such a way that multiple instances can run at " - "the same time. This may make it harder to debug a failure if it " - "does occur.") - - parser.add_argument("unittest", nargs="*") - global parsed + # TODO: remove global + global parsed # pylint: disable=global-statement parsed = parser.parse_args() - global target - target = parsed.target() - + target = parsed.target(parsed.cmd, parsed.run, parsed.isolate) if parsed.xlen: target.xlen = parsed.xlen - unittest.main(argv=[sys.argv[0]] + parsed.unittest) + module = sys.modules[__name__] + + return testlib.run_all_tests(module, target, parsed.test, parsed.fail_fast) # TROUBLESHOOTING TIPS # If a particular test fails, run just that one test, eg.: