#!/usr/bin/env python import argparse import binascii import random import sys import tempfile import time import targets import testlib from testlib import assertEqual, assertNotEqual, assertIn from testlib import assertGreater, assertTrue, assertRegexpMatches, assertLess from testlib import GdbTest MSTATUS_UIE = 0x00000001 MSTATUS_SIE = 0x00000002 MSTATUS_HIE = 0x00000004 MSTATUS_MIE = 0x00000008 MSTATUS_UPIE = 0x00000010 MSTATUS_SPIE = 0x00000020 MSTATUS_HPIE = 0x00000040 MSTATUS_MPIE = 0x00000080 MSTATUS_SPP = 0x00000100 MSTATUS_HPP = 0x00000600 MSTATUS_MPP = 0x00001800 MSTATUS_FS = 0x00006000 MSTATUS_XS = 0x00018000 MSTATUS_MPRV = 0x00020000 MSTATUS_PUM = 0x00040000 MSTATUS_MXR = 0x00080000 MSTATUS_VM = 0x1F000000 MSTATUS32_SD = 0x80000000 MSTATUS64_SD = 0x8000000000000000 # pylint: disable=abstract-method def ihex_line(address, record_type, data): assert len(data) < 128 line = ":%02X%04X%02X" % (len(data), address, record_type) check = len(data) check += address % 256 check += address >> 8 check += record_type for char in data: value = ord(char) check += value line += "%02X" % value line += "%02X\n" % ((256-check)%256) return line def ihex_parse(line): assert line.startswith(":") line = line[1:] data_len = int(line[:2], 16) address = int(line[2:6], 16) record_type = int(line[6:8], 16) data = "" for i in range(data_len): data += "%c" % int(line[8+2*i:10+2*i], 16) return record_type, address, data def readable_binary_string(s): return "".join("%02x" % ord(c) for c in s) class SimpleRegisterTest(GdbTest): def check_reg(self, name): a = random.randrange(1< 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. assertGreater(jumps, 10) assertGreater(advances, 50) class DebugExit(DebugTest): def test(self): self.exit() class DebugSymbols(DebugTest): def test(self): self.gdb.b("main") self.gdb.b("rot13") output = self.gdb.c() assertIn(", main ", output) output = self.gdb.c() assertIn(", rot13 ", output) class DebugBreakpoint(DebugTest): def test(self): self.gdb.b("rot13") # The breakpoint should be hit exactly 2 times. for _ in range(2): output = self.gdb.c() self.gdb.p("$pc") assertIn("Breakpoint ", output) assertIn("rot13 ", output) self.exit() 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 _ in range(2): output = self.gdb.c() self.gdb.p("$pc") assertRegexpMatches(output, r"[bB]reakpoint") assertIn("rot13 ", output) self.exit() 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 expected in ("main", "rot13", "rot13"): output = self.gdb.c() self.gdb.p("$pc") assertRegexpMatches(output, r"[bB]reakpoint") assertIn("%s " % expected, output) self.exit() class TooManyHwbp(DebugTest): def run(self): for i in range(30): self.gdb.hbreak("*rot13 + %d" % (i * 4)) output = self.gdb.c() assertIn("Cannot insert hardware breakpoint", output) # Clean up, otherwise the hardware breakpoints stay set and future # tests may fail. self.gdb.command("D") class Registers(DebugTest): def test(self): # Get to a point in the code where some registers have actually been # used. self.gdb.b("rot13") self.gdb.c() self.gdb.c() # Try both forms to test gdb. for cmd in ("info all-registers", "info registers all"): output = self.gdb.command(cmd) for reg in ('zero', 'ra', 'sp', 'gp', 'tp'): 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). #assertRegexpMatches(output, ".*mcpuid *0x80") #TODO: # The instret register should always be changing. #last_instret = None #for _ in range(5): # instret = self.gdb.p("$instret") # assertNotEqual(instret, last_instret) # last_instret = instret # self.gdb.stepi() self.exit() 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.c(wait=False) time.sleep(0.5) output = self.gdb.interrupt() assert "main" in output assertGreater(self.gdb.p("j"), 10) self.gdb.p("i=0") self.exit() class StepTest(GdbTest): compile_args = ("programs/step.S", ) def setup(self): self.gdb.load() self.gdb.b("main") self.gdb.c() def test(self): main_address = self.gdb.p("$pc") if self.target.extensionSupported("c"): sequence = (4, 8, 0xc, 0xe, 0x14, 0x18, 0x22, 0x1c, 0x24, 0x24) else: sequence = (4, 8, 0xc, 0x10, 0x18, 0x1c, 0x28, 0x20, 0x2c, 0x2c) for expected in sequence: self.gdb.stepi() pc = self.gdb.p("$pc") assertEqual("%x" % (pc - main_address), "%x" % expected) 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() class WriteGprs(RegsTest): def test(self): regs = [("x%d" % n) for n in range(2, 32)] self.gdb.p("$pc=write_regs") for i, r in enumerate(regs): self.gdb.p("$%s=%d" % (r, (0xdeadbeef<\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)) 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("uint32_t main() { return crc32a(data, length); }\n") download_c.flush() if self.crc < 0: self.crc += 2**32 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() assertEqual(self.gdb.p("status"), self.crc) class MprvTest(GdbTest): compile_args = ("programs/mprv.S", ) def setup(self): self.gdb.load() 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)") assertIn("0xbead", output) class PrivTest(GdbTest): compile_args = ("programs/priv.S", ) def setup(self): # pylint: disable=attribute-defined-outside-init self.gdb.load() misa = self.target.misa self.supported = set() if misa & (1<<20): self.supported.add(0) if misa & (1<<18): self.supported.add(1) if misa & (1<<7): self.supported.add(2) self.supported.add(3) 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") assertIn(actual, self.supported) if privilege in self.supported: assertEqual(actual, privilege) class PrivChange(PrivTest): def test(self): """Test that the core's privilege level actually changes.""" if 0 not in self.supported: return 'not_applicable' self.gdb.b("main") self.gdb.c() # Machine mode self.gdb.p("$priv=3") main_address = self.gdb.p("$pc") self.gdb.stepi() 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") 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-e300 --cmd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl -d" Simple """) targets.add_target_options(parser) testlib.add_test_run_options(parser) # TODO: remove global global parsed # pylint: disable=global-statement parsed = parser.parse_args() target = parsed.target(parsed.cmd, parsed.run, parsed.isolate) if parsed.xlen: target.xlen = parsed.xlen module = sys.modules[__name__] return testlib.run_all_tests(module, target, parsed) # TROUBLESHOOTING TIPS # If a particular test fails, run just that one test, eg.: # ./gdbserver.py MprvTest.test_mprv # Then inspect gdb.log and spike.log to see what happened in more detail. if __name__ == '__main__': sys.exit(main())