From: Tim Newsome Date: Sat, 4 Jun 2016 20:19:45 +0000 (-0700) Subject: WIP on debug testing. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=f29d14a877d4873c12fa80c9df5b265474a85b05;p=riscv-tests.git WIP on debug testing. ./gdbserver.py --m2gl_m2s --openocd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl" -- RegsTest.test_write_gprs doesn't fail in a completely crazy way. --- diff --git a/debug/README.md b/debug/README.md new file mode 100644 index 0000000..2be795b --- /dev/null +++ b/debug/README.md @@ -0,0 +1,3 @@ +Debugging requires many of a system components to all work together. The goal +is to collect some tests that test gdb with spike, and gdb talking to real +hardware through openocd. diff --git a/debug/gdbserver.py b/debug/gdbserver.py new file mode 100755 index 0000000..8b7b562 --- /dev/null +++ b/debug/gdbserver.py @@ -0,0 +1,271 @@ +#!/usr/bin/python + +import os +import sys +import argparse +import testlib +import unittest +import tempfile +import time +import random +import binascii + +class DeleteServer(unittest.TestCase): + def tearDown(self): + del self.server + +class InstantHaltTest(DeleteServer): + def setUp(self): + self.binary = target.compile("debug.c") + self.server = target.server(self.binary, halted=True) + self.gdb = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + + def test_instant_halt(self): + self.assertEqual(0x1000, self.gdb.p("$pc")) + # For some reason instret resets to 0. + self.assertLess(self.gdb.p("$instret"), 8) + self.gdb.command("stepi") + self.assertNotEqual(0x1000, self.gdb.p("$pc")) + + def test_change_pc(self): + """Change the PC right as we come out of reset.""" + # 0x13 is nop + self.gdb.command("p *((int*) 0x80000000)=0x13") + self.gdb.command("p *((int*) 0x80000004)=0x13") + self.gdb.command("p *((int*) 0x80000008)=0x13") + self.gdb.command("p $pc=0x80000000") + self.gdb.command("stepi") + self.assertEqual(0x80000004, self.gdb.p("$pc")) + self.gdb.command("stepi") + self.assertEqual(0x80000008, self.gdb.p("$pc")) + +class DebugTest(DeleteServer): + def setUp(self): + self.binary = target.compile("debug.c") + self.server = target.server(self.binary, halted=False) + self.gdb = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + + def test_turbostep(self): + """Single step a bunch of times.""" + self.gdb.command("p i=0"); + last_pc = None + for _ in range(100): + self.gdb.command("stepi") + pc = self.gdb.command("p $pc") + self.assertNotEqual(last_pc, pc) + last_pc = pc + + def test_exit(self): + self.gdb.command("p i=0"); + output = self.gdb.command("c") + self.assertIn("Continuing", output) + self.assertIn("Remote connection closed", output) + + def test_breakpoint(self): + self.gdb.command("p i=0"); + self.gdb.command("b print_row") + # The breakpoint should be hit exactly 10 times. + for i in range(10): + output = self.gdb.command("c") + self.assertIn("Continuing", output) + self.assertIn("length=%d" % i, output) + self.assertIn("Breakpoint 1", output) + output = self.gdb.command("c") + self.assertIn("Continuing", output) + self.assertIn("Remote connection closed", output) + + def test_registers(self): + self.gdb.command("p i=0"); + # 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) + # 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") + + # The instret register should always be changing. + last_instret = None + for _ in range(5): + instret = self.gdb.p("$instret") + self.assertNotEqual(instret, last_instret) + last_instret = instret + self.gdb.command("stepi") + + def test_interrupt(self): + """Sending gdb ^C while the program is running should cause it to halt.""" + self.gdb.c(wait=False) + time.sleep(0.1) + self.gdb.interrupt() + self.gdb.command("p i=123"); + self.gdb.c(wait=False) + time.sleep(0.1) + self.gdb.interrupt() + self.gdb.command("p i=0"); + output = self.gdb.c() + self.assertIn("Continuing", output) + self.assertIn("Remote connection closed", output) + +class RegsTest(DeleteServer): + def setUp(self): + self.binary = target.compile("programs/regs.S") + self.server = target.server() + self.gdb = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + self.gdb.command("load") + + def test_write_gprs(self): + # Note a0 is missing from this list since it's used to hold the + # address. + regs = ("ra", "sp", "gp", "tp", "t0", "t1", "t2", "fp", "s1", + "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", + "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", + "t6") + + self.gdb.command("p $pc=write_regs") + for i, r in enumerate(regs): + self.gdb.command("p $%s=%d" % (r, (0xdeadbeef<\n") + fd.write("uint32_t length = %d;\n" % length) + fd.write("uint8_t d[%d] = {\n" % length) + self.crc = 0 + for i in range(length / 16): + fd.write(" /* 0x%04x */ " % (i * 16)); + for _ in range(16): + value = random.randrange(1<<8) + fd.write("%d, " % value) + self.crc = binascii.crc32("%c" % value, self.crc) + fd.write("\n"); + fd.write("};\n"); + fd.write("uint8_t *data = &d[0];\n"); + fd.close() + + self.binary = target.compile("checksum.c", "data.c", "start.S", + "-mcmodel=medany", + "-T", "standalone.lds", + "-nostartfiles" + ) + self.server = target.server(None, halted=True) + self.gdb = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + + def test_download(self): + output = self.gdb.command("load") + self.assertNotIn("failed", output) + self.assertIn("Transfer rate", output) + self.gdb.command("b done") + self.gdb.c() + result = self.gdb.p("$a0") + self.assertEqual(self.crc, result) + +class MprvTest(DeleteServer): + def setUp(self): + self.binary = target.compile("mprv.S", "-T", "standalone.lds", + "-nostartfiles") + self.server = target.server(None, halted=True) + self.gdb = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + self.gdb.command("load") + + def test_mprv(self): + """Test that the debugger can access memory when MPRV is set.""" + self.gdb.c(wait=False) + self.gdb.interrupt() + output = self.gdb.command("p/x *(int*)(((char*)&data)-0x80000000)") + self.assertIn("0xbead", output) + +class Target(object): + def server(self): + raise NotImplementedError + + def compile(self, *sources): + return testlib.compile(*(sources + + ("targets/%s/entry.S" % self.name, "programs/init.c", + "-I", "../env", + "-T", "targets/%s/link.lds" % self.name, + "-nostartfiles"))) + +class SpikeTarget(Target): + name = "spike" + +class MicroSemiTarget(Target): + name = "m2gl_m2s" + + def server(self): + return testlib.Openocd(cmd=parsed.openocd, + config="targets/%s/openocd.cfg" % self.name) + +targets = [ + SpikeTarget, + MicroSemiTarget + ] + +def main(): + parser = argparse.ArgumentParser() + 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("--openocd", help="The OpenOCD command to use.", + default="openocd") + parser.add_argument("unittest", nargs="*") + global parsed + parsed = parser.parse_args() + + global target + target = parsed.target() + unittest.main(argv=[sys.argv[0]] + parsed.unittest) + +# TROUBLESHOOTING TIPS +# If a particular test fails, run just that one test, eg.: +# ./tests/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()) diff --git a/debug/programs/checksum.c b/debug/programs/checksum.c new file mode 100644 index 0000000..36152fc --- /dev/null +++ b/debug/programs/checksum.c @@ -0,0 +1,47 @@ +#include + +// CRC code from http://www.hackersdelight.org/hdcodetxt/crc.c.txt + +// Reverses (reflects) bits in a 32-bit word. +unsigned reverse(unsigned x) { + x = ((x & 0x55555555) << 1) | ((x >> 1) & 0x55555555); + x = ((x & 0x33333333) << 2) | ((x >> 2) & 0x33333333); + x = ((x & 0x0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F); + x = (x << 24) | ((x & 0xFF00) << 8) | + ((x >> 8) & 0xFF00) | (x >> 24); + return x; +} + +// ----------------------------- crc32a -------------------------------- + +/* This is the basic CRC algorithm with no optimizations. It follows the +logic circuit as closely as possible. */ + +unsigned int crc32a(uint8_t *message, unsigned int size) { + int i, j; + unsigned int byte, crc; + + i = 0; + crc = 0xFFFFFFFF; + while (i < size) { + byte = message[i]; // Get next byte. + byte = reverse(byte); // 32-bit reversal. + for (j = 0; j <= 7; j++) { // Do eight times. + if ((int)(crc ^ byte) < 0) + crc = (crc << 1) ^ 0x04C11DB7; + else crc = crc << 1; + byte = byte << 1; // Ready next msg bit. + } + i = i + 1; + } + return reverse(~crc); +} + +extern uint8_t *data; +extern uint32_t length; + +uint32_t main() +{ + /* Compute a simple checksum. */ + return crc32a(data, length); +} diff --git a/debug/programs/debug.c b/debug/programs/debug.c new file mode 100644 index 0000000..2cad88f --- /dev/null +++ b/debug/programs/debug.c @@ -0,0 +1,27 @@ +#include + +char c = 'x'; + +void print_row(int length) +{ + for (int x=0; x> 2) | PTE_V | PTE_TYPE_URWX_SRWX) + .word 0 diff --git a/debug/programs/regs.S b/debug/programs/regs.S new file mode 100644 index 0000000..e6456e1 --- /dev/null +++ b/debug/programs/regs.S @@ -0,0 +1,43 @@ + .global main +main: + j main + +write_regs: + sd x1, 0(a0) + sd x2, 8(a0) + sd x3, 16(a0) + sd x4, 24(a0) + sd x5, 32(a0) + sd x6, 40(a0) + sd x7, 48(a0) + sd x8, 56(a0) + sd x9, 64(a0) + sd x11, 72(a0) + sd x12, 80(a0) + sd x13, 88(a0) + sd x14, 96(a0) + sd x15, 104(a0) + sd x16, 112(a0) + sd x17, 120(a0) + sd x18, 128(a0) + sd x19, 136(a0) + sd x20, 144(a0) + sd x21, 152(a0) + sd x22, 160(a0) + sd x23, 168(a0) + sd x24, 176(a0) + sd x25, 184(a0) + sd x26, 192(a0) + sd x27, 200(a0) + sd x28, 208(a0) + sd x29, 216(a0) + sd x30, 224(a0) + sd x31, 232(a0) + + csrr x1, 1 # fflags + +all_done: + j all_done + +data: + .fill 64, 8, 0 diff --git a/debug/programs/start.S b/debug/programs/start.S new file mode 100644 index 0000000..76c37bb --- /dev/null +++ b/debug/programs/start.S @@ -0,0 +1,12 @@ + .global _start + +_start: + la sp, stack_end + jal main +done: + j done + + .data +stack: + .fill 4096, 1, 0 +stack_end: diff --git a/debug/targets/m2gl_m2s/entry.S b/debug/targets/m2gl_m2s/entry.S new file mode 100755 index 0000000..ff49cf6 --- /dev/null +++ b/debug/targets/m2gl_m2s/entry.S @@ -0,0 +1,131 @@ +#ifndef ENTRY_S +#define ENTRY_S + +#include "encoding.h" + +#define STACK_SIZE ((1 << 12) - 128) + +#ifdef __riscv64 +# define LREG ld +# define SREG sd +# define REGBYTES 8 +#else +# define LREG lw +# define SREG sw +# define REGBYTES 4 +#endif + + .section .text.entry + .globl _start +_start: + j handle_reset + +nmi_vector: + j nmi_vector + +trap_vector: + j trap_entry + +handle_reset: + la t0, trap_entry + csrw mtvec, t0 + csrwi mstatus, 0 + csrwi mideleg, 0 + csrwi medeleg, 0 + csrwi mie, 0 + + # initialize global pointer + la gp, _gp + + # initialize stack pointer + la sp, stack_top + + # perform the rest of initialization in C + j _init + +trap_entry: + addi sp, sp, -32*REGBYTES + + SREG x1, 1*REGBYTES(sp) + SREG x2, 2*REGBYTES(sp) + SREG x3, 3*REGBYTES(sp) + SREG x4, 4*REGBYTES(sp) + SREG x5, 5*REGBYTES(sp) + SREG x6, 6*REGBYTES(sp) + SREG x7, 7*REGBYTES(sp) + SREG x8, 8*REGBYTES(sp) + SREG x9, 9*REGBYTES(sp) + SREG x10, 10*REGBYTES(sp) + SREG x11, 11*REGBYTES(sp) + SREG x12, 12*REGBYTES(sp) + SREG x13, 13*REGBYTES(sp) + SREG x14, 14*REGBYTES(sp) + SREG x15, 15*REGBYTES(sp) + SREG x16, 16*REGBYTES(sp) + SREG x17, 17*REGBYTES(sp) + SREG x18, 18*REGBYTES(sp) + SREG x19, 19*REGBYTES(sp) + SREG x20, 20*REGBYTES(sp) + SREG x21, 21*REGBYTES(sp) + SREG x22, 22*REGBYTES(sp) + SREG x23, 23*REGBYTES(sp) + SREG x24, 24*REGBYTES(sp) + SREG x25, 25*REGBYTES(sp) + SREG x26, 26*REGBYTES(sp) + SREG x27, 27*REGBYTES(sp) + SREG x28, 28*REGBYTES(sp) + SREG x29, 29*REGBYTES(sp) + SREG x30, 30*REGBYTES(sp) + SREG x31, 31*REGBYTES(sp) + + csrr a0, mcause + csrr a1, mepc + mv a2, sp + jal handle_trap + csrw mepc, a0 + + # Remain in M-mode after mret + li t0, MSTATUS_MPP + csrs mstatus, t0 + + LREG x1, 1*REGBYTES(sp) + LREG x2, 2*REGBYTES(sp) + LREG x3, 3*REGBYTES(sp) + LREG x4, 4*REGBYTES(sp) + LREG x5, 5*REGBYTES(sp) + LREG x6, 6*REGBYTES(sp) + LREG x7, 7*REGBYTES(sp) + LREG x8, 8*REGBYTES(sp) + LREG x9, 9*REGBYTES(sp) + LREG x10, 10*REGBYTES(sp) + LREG x11, 11*REGBYTES(sp) + LREG x12, 12*REGBYTES(sp) + LREG x13, 13*REGBYTES(sp) + LREG x14, 14*REGBYTES(sp) + LREG x15, 15*REGBYTES(sp) + LREG x16, 16*REGBYTES(sp) + LREG x17, 17*REGBYTES(sp) + LREG x18, 18*REGBYTES(sp) + LREG x19, 19*REGBYTES(sp) + LREG x20, 20*REGBYTES(sp) + LREG x21, 21*REGBYTES(sp) + LREG x22, 22*REGBYTES(sp) + LREG x23, 23*REGBYTES(sp) + LREG x24, 24*REGBYTES(sp) + LREG x25, 25*REGBYTES(sp) + LREG x26, 26*REGBYTES(sp) + LREG x27, 27*REGBYTES(sp) + LREG x28, 28*REGBYTES(sp) + LREG x29, 29*REGBYTES(sp) + LREG x30, 30*REGBYTES(sp) + LREG x31, 31*REGBYTES(sp) + + addi sp, sp, 32*REGBYTES + mret + + .bss + .align 4 +stack_bottom: + .skip STACK_SIZE +stack_top: +#endif diff --git a/debug/targets/m2gl_m2s/link.lds b/debug/targets/m2gl_m2s/link.lds new file mode 100755 index 0000000..260b014 --- /dev/null +++ b/debug/targets/m2gl_m2s/link.lds @@ -0,0 +1,35 @@ +OUTPUT_ARCH( "riscv" ) + +SECTIONS +{ + . = 0x60040000; + .text : + { + *(.text.entry) + *(.text) + } + + /* data segment */ + .data : { *(.data) } + + .sdata : { + _gp = . + 0x800; + *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) + *(.srodata*) + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + + . = 0x80000000; + + /* bss segment */ + .sbss : { + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : { *(.bss) } + + /* End of uninitalized data segement */ + _end = .; + _heap_end = .; +} + diff --git a/debug/targets/m2gl_m2s/openocd.cfg b/debug/targets/m2gl_m2s/openocd.cfg new file mode 100644 index 0000000..d58116b --- /dev/null +++ b/debug/targets/m2gl_m2s/openocd.cfg @@ -0,0 +1,17 @@ +adapter_khz 100 + +source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg] + +set _CHIPNAME riscv +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1525600b + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME riscv -chain-position $_TARGETNAME + +#reset_config trst_and_srst separate +# Stupid long so I can see the LEDs +#adapter_nsrst_delay 2000 +#jtag_ntrst_delay 1000 +# +init +#reset diff --git a/debug/testlib.py b/debug/testlib.py new file mode 100644 index 0000000..0902983 --- /dev/null +++ b/debug/testlib.py @@ -0,0 +1,140 @@ +import os.path +import pexpect +import shlex +import subprocess +import tempfile +import testlib +import unittest + +# Note that gdb comes with its own testsuite. I was unable to figure out how to +# run that testsuite against the spike simulator. + +def find_file(path): + for directory in (os.getcwd(), os.path.dirname(testlib.__file__)): + fullpath = os.path.join(directory, path) + if os.path.exists(fullpath): + return fullpath + return None + +def compile(*args): + """Compile a single .c file into a binary.""" + dst = os.path.splitext(args[0])[0] + cc = os.path.expandvars("$RISCV/bin/riscv64-unknown-elf-gcc") + cmd = [cc, "-g", "-O", "-o", dst] + for arg in args: + found = find_file(arg) + if found: + cmd.append(found) + else: + cmd.append(arg) + cmd = " ".join(cmd) + result = os.system(cmd) + assert result == 0, "%r failed" % cmd + return dst + +def unused_port(): + # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python/2838309#2838309 + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("",0)) + port = s.getsockname()[1] + s.close() + return port + +class Spike(object): + def __init__(self, binary, halted=False, with_gdb=True, timeout=None): + """Launch spike. Return tuple of its process and the port it's running on.""" + cmd = [] + if timeout: + cmd += ["timeout", str(timeout)] + + cmd += [find_file("spike")] + if halted: + cmd.append('-H') + if with_gdb: + self.port = unused_port() + cmd += ['--gdb-port', str(self.port)] + cmd.append('pk') + if binary: + cmd.append(binary) + logfile = open("spike.log", "w") + self.process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=logfile, + stderr=logfile) + + def __del__(self): + try: + self.process.kill() + self.process.wait() + except OSError: + pass + + def wait(self, *args, **kwargs): + return self.process.wait(*args, **kwargs) + +class Openocd(object): + def __init__(self, cmd=None, config=None, debug=True): + if cmd: + cmd = shlex.split(cmd) + else: + cmd = ["openocd"] + if config: + cmd += ["-f", find_file(config)] + if debug: + cmd.append("-d") + logfile = open("openocd.log", "w") + self.process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=logfile, + stderr=logfile) + # TODO: Pick a random port + self.port = 3333 + + def __del__(self): + try: + self.process.kill() + self.process.wait() + except OSError: + pass + +class Gdb(object): + def __init__(self): + path = os.path.expandvars("$RISCV/bin/riscv64-unknown-elf-gdb") + self.child = pexpect.spawn(path) + self.child.logfile = file("gdb.log", "w") + self.wait() + self.command("set width 0") + self.command("set height 0") + # Force consistency. + self.command("set print entry-values no") + + def wait(self): + """Wait for prompt.""" + self.child.expect("\(gdb\)") + + def command(self, command, timeout=-1): + self.child.sendline(command) + self.child.expect("\n", timeout=timeout) + self.child.expect("\(gdb\)", timeout=timeout) + return self.child.before.strip() + + def c(self, wait=True): + if wait: + return self.command("c") + else: + self.child.sendline("c") + self.child.expect("Continuing") + + def interrupt(self): + self.child.send("\003"); + self.child.expect("\(gdb\)") + + def x(self, address, size='w'): + output = self.command("x/%s %s" % (size, address)) + value = int(output.split(':')[1].strip(), 0) + return value + + def p(self, obj): + output = self.command("p %s" % obj) + value = int(output.split('=')[-1].strip()) + return value + + def stepi(self): + return self.command("stepi")