From: Andrew Waterman Date: Wed, 20 Jul 2016 00:10:24 +0000 (-0700) Subject: Merge pull request #17 from timsifive/debug X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=a27ddca0570517e7e40a91c16e4c96c6c5f329e1;hp=0849aad0c23f24ed7728128873fc852839e988b1;p=riscv-tests.git Merge pull request #17 from timsifive/debug Add end-to-end debug tests --- diff --git a/Makefile.in b/Makefile.in index 62a95b2..d2b088e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -4,8 +4,9 @@ XLEN := @XLEN@ instbasedir := $(DESTDIR)$(prefix) bmarkdir := $(abs_top_src_dir)/benchmarks isa_src_dir := $(abs_top_src_dir)/isa +debug_src_dir := $(abs_top_src_dir)/debug -all: benchmarks isa +all: benchmarks isa debug install: all install -d $(instbasedir)/share/riscv-tests/isa @@ -21,9 +22,14 @@ isa: mkdir -p isa $(MAKE) -C isa -f $(isa_src_dir)/Makefile src_dir=$(isa_src_dir) XLEN=$(XLEN) +debug: + mkdir -p debug + $(MAKE) -C debug -f $(debug_src_dir)/Makefile src_dir=$(debug_src_dir) XLEN=$(XLEN) + clean: $(MAKE) -C isa -f $(isa_src_dir)/Makefile src_dir=$(isa_src_dir) clean $(MAKE) -C benchmarks -f $(bmarkdir)/Makefile src_dir=$(bmarkdir) clean + $(MAKE) -C debug -f $(bmarkdir)/Makefile src_dir=$(debug_src_dir) clean .PHONY: benchmarks isa clean diff --git a/debug/Makefile b/debug/Makefile new file mode 100644 index 0000000..1d90e12 --- /dev/null +++ b/debug/Makefile @@ -0,0 +1,18 @@ +RISCV_SIM ?= spike +XLEN ?= 64 + +src_dir ?= . +GDBSERVER_PY = $(src_dir)/gdbserver.py + +default: spike$(XLEN).log + +all: spike32.log spike64.log + +spike32.log: + $(GDBSERVER_PY) --isolate --spike32 --cmd $(RISCV_SIM) -- -v > $@ 2>&1 + +spike64.log: + $(GDBSERVER_PY) --isolate --spike --cmd $(RISCV_SIM) -- -v > $@ 2>&1 + +clean: + rm -f *.log diff --git a/debug/README.md b/debug/README.md new file mode 100644 index 0000000..829a285 --- /dev/null +++ b/debug/README.md @@ -0,0 +1,38 @@ +Debug Tests +=========== + +Debugging requires many system components to all work together. The tests here +perform an end-to-end test, communicating only with gdb. If a simulator or +hardware passes all these tests, then you can be pretty confident that the +actual debug interface is functioning correctly. + +Targets +======= + +64-bit Spike +------------ + +`./gdbserver.py --spike --cmd $RISCV/bin/spike` + +32-bit Spike +------------ + +`./gdbserver.py --spike32 --cmd $RISCV/bin/spike` + +32-bit SiFive Core on Microsemi FPGA board +------------------------------------------ + +`./gdbserver.py --m2gl_m2s` + +Debug Tips +========== + +You can run just a single test by specifying . on the command +line, eg: `./gdbserver.py --spike --cmd $RISCV/bin/spike +SimpleRegisterTest.test_s0`. +Once that test has failed, you can look at gdb.log and (in this case) spike.log +to get an idea of what might have gone wrong. + +You can see what spike is doing by add `-l` to the spike command, eg.: +`./gdbserver.py --spike32 --cmd "$RISCV/bin/spike -l" +DebugTest.test_breakpoint`. (Then look at spike.log.) diff --git a/debug/gdbserver.py b/debug/gdbserver.py new file mode 100755 index 0000000..0ae75a7 --- /dev/null +++ b/debug/gdbserver.py @@ -0,0 +1,558 @@ +#!/usr/bin/python + +import os +import sys +import argparse +import testlib +import unittest +import tempfile +import time +import random +import binascii + +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 + +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 + +class DeleteServer(unittest.TestCase): + def tearDown(self): + del self.server + +class SimpleRegisterTest(DeleteServer): + def setUp(self): + self.server = target.server() + self.gdb = testlib.Gdb() + # For now gdb has to be told what the architecture is when it's not + # given an ELF file. + self.gdb.command("set arch riscv:rv%d" % target.xlen) + + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + + # 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 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. + self.assertGreater(jumps, 10) + self.assertGreater(advances, 50) + + def test_exit(self): + self.exit() + + def test_symbols(self): + self.gdb.b("main") + self.gdb.b("rot13") + output = self.gdb.c() + self.assertIn(", main ", output) + output = self.gdb.c() + self.assertIn(", rot13 ", output) + + def test_breakpoint(self): + self.gdb.b("rot13") + # The breakpoint should be hit exactly 2 times. + for i in range(2): + output = self.gdb.c() + self.gdb.p("$pc") + self.assertIn("Breakpoint ", output) + #TODO self.assertIn("rot13 ", output) + self.exit() + + def test_hwbp_1(self): + if target.instruction_hardware_breakpoint_count < 1: + return + + self.gdb.hbreak("rot13") + # The breakpoint should be hit exactly 2 times. + for i in range(2): + output = self.gdb.c() + self.gdb.p("$pc") + self.assertIn("Breakpoint ", output) + #TODO self.assertIn("rot13 ", output) + self.exit() + + def test_hwbp_2(self): + if target.instruction_hardware_breakpoint_count < 2: + return + + self.gdb.hbreak("main") + self.gdb.hbreak("rot13") + # We should hit 3 breakpoints. + for i in range(3): + output = self.gdb.c() + self.gdb.p("$pc") + self.assertIn("Breakpoint ", output) + #TODO self.assertIn("rot13 ", output) + self.exit() + + def test_too_many_hwbp(self): + for i in range(30): + self.gdb.hbreak("*rot13 + %d" % (i * 4)) + + output = self.gdb.c() + self.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): + # 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) + self.assertNotIn("Could not", output) + for reg in ('zero', 'ra', 'sp', 'gp', 'tp'): + self.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") + + #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) + # 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.""" + self.gdb.b("main:start") + self.gdb.c() + 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"); + self.exit() + +class StepTest(DeleteServer): + def setUp(self): + self.binary = target.compile("programs/step.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.load() + self.gdb.b("main") + self.gdb.c() + + def test_step(self): + main = 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)) + +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.load() + self.gdb.b("main") + self.gdb.b("handle_trap") + self.gdb.c() + + def test_write_gprs(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.command("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 = target.compile(download_c.name, "programs/checksum.c") + 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) + + def test_download(self): + output = self.gdb.load() + self.gdb.command("b _exit") + self.gdb.c() + self.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 = testlib.Gdb() + self.gdb.command("file %s" % self.binary) + self.gdb.command("target extended-remote localhost:%d" % self.server.port) + self.gdb.load() + + def test_mprv(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) + +class Target(object): + directory = None + + def server(self): + raise NotImplementedError + + def compile(self, *sources): + binary_name = "%s_%s" % ( + self.name, + os.path.basename(os.path.splitext(sources[0])[0])) + 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 Spike64Target(Target): + name = "spike" + xlen = 64 + ram = 0x80010000 + ram_size = 5 * 1024 * 1024 + instruction_hardware_breakpoint_count = 0 + reset_vector = 0x1000 + + def server(self): + return testlib.Spike(parsed.cmd, halted=True) + +class Spike32Target(Target): + name = "spike32" + directory = "spike" + xlen = 32 + ram = 0x80010000 + ram_size = 5 * 1024 * 1024 + instruction_hardware_breakpoint_count = 0 + reset_vector = 0x1000 + + def server(self): + return testlib.Spike(parsed.cmd, halted=True, xlen=32) + +class MicroSemiTarget(Target): + name = "m2gl_m2s" + 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) + +targets = [ + Spike32Target, + Spike64Target, + MicroSemiTarget + ] + +def main(): + parser = argparse.ArgumentParser( + epilog=""" + Example command line from the real world: + Run all RegsTest cases against a MicroSemi m2gl_m2s board, with custom openocd command: + ./gdbserver.py --m2gl_m2s --cmd "$HOME/SiFive/openocd/src/openocd -s $HOME/SiFive/openocd/tcl -d" -- -vf RegsTest + """) + 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("--cmd", + help="The command to use to start the debug server.") + 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 + 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..e076f8a --- /dev/null +++ b/debug/programs/checksum.c @@ -0,0 +1,38 @@ +#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); +} diff --git a/debug/programs/debug.c b/debug/programs/debug.c new file mode 100644 index 0000000..20b1cdc --- /dev/null +++ b/debug/programs/debug.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +unsigned int crc32a(uint8_t *message, unsigned int size); + +void rot13(char *buf) +{ + while (*buf) { + if ((*buf >= 'a' && *buf <= 'm') || + (*buf >= 'A' && *buf <= 'M')) { + *buf += 13; + } else if ((*buf >= 'n' && *buf <= 'z') || + (*buf >= 'N' && *buf <= 'Z')) { + *buf -= 13; + } + buf++; + } +} + +size_t strlen(const char *buf) +{ + int len = 0; + while (buf[len]) + len++; + return len; +} + +extern void *__malloc_freelist; + +int main() +{ + __malloc_freelist = 0; + + volatile int i = 0; + int j = 0; + char *fox = "The quick brown fox jumps of the lazy dog."; + unsigned int checksum = 0; + +start: + while (i) + j++; + + rot13(fox); + checksum ^= crc32a(fox, strlen(fox)); + rot13(fox); + checksum ^= crc32a(fox, strlen(fox)); + + return checksum; +} diff --git a/debug/programs/entry.S b/debug/programs/entry.S new file mode 100755 index 0000000..80904cd --- /dev/null +++ b/debug/programs/entry.S @@ -0,0 +1,132 @@ +#ifndef ENTRY_S +#define ENTRY_S + +#include "encoding.h" + +#define STACK_SIZE 512 + +#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 + + // Fill the stack with data so we can see if it was overrun. + .align 4 +stack_bottom: + .fill STACK_SIZE/4, 4, 0x22446688 +stack_top: +#endif diff --git a/debug/programs/init.c b/debug/programs/init.c new file mode 100644 index 0000000..a2b41b0 --- /dev/null +++ b/debug/programs/init.c @@ -0,0 +1,24 @@ +int main(void); + +void handle_trap(unsigned int mcause, unsigned int mepc, unsigned int sp) +{ + while (1) + ; +} + +void _exit(int status) +{ + // Make sure gcc doesn't inline _exit, so we can actually set a breakpoint + // on it. + volatile int i = 42; + while (i) + ; + // _exit isn't supposed to return. + while (1) + ; +} + +void _init() +{ + _exit(main()); +} diff --git a/debug/programs/mprv.S b/debug/programs/mprv.S new file mode 100644 index 0000000..115ccb5 --- /dev/null +++ b/debug/programs/mprv.S @@ -0,0 +1,46 @@ +#include "../../env/encoding.h" +#define PGSHIFT 12 + + .global main + + .section .text +main: + # Set up a page table entry that maps 0x0... to 0x8... + la t0, page_table + srli t0, t0, PGSHIFT + csrw CSR_SPTBR, t0 + + # update mstatus + csrr t1, CSR_MSTATUS +#ifdef __riscv32 + li t0, (MSTATUS_MPRV | (VM_SV32 << 24)) +#else + li t0, (MSTATUS_MPRV | (VM_SV39 << 24)) +#endif + #li t0, ((VM_SV39 << 24)) + or t1, t0, t1 + csrw CSR_MSTATUS, t1 + + la t0, (loop - 0x80000000) + csrw CSR_MEPC, t0 + + # Exit supervisor mode, entering user mode at loop. + mret + +loop: + la t0, data + lw t1, 0(t0) + j loop + + .section .data +data: + .word 0xbead + + .balign 0x1000 +page_table: +#ifdef __riscv32 + .word ((0x80000000 >> 2) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_U) +#else + .word ((0x80000000 >> 2) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_G | PTE_U) + .word 0 +#endif diff --git a/debug/programs/regs.S b/debug/programs/regs.S new file mode 100644 index 0000000..2cacd4f --- /dev/null +++ b/debug/programs/regs.S @@ -0,0 +1,57 @@ +#ifdef __riscv64 +# define LREG ld +# define SREG sd +# define REGBYTES 8 +#else +# define LREG lw +# define SREG sw +# define REGBYTES 4 +#endif + +#include "../../env/encoding.h" + + .global main +main: + nop + j main + +write_regs: + SREG x2, 0(x1) + SREG x3, 8(x1) + SREG x4, 16(x1) + SREG x5, 24(x1) + SREG x6, 32(x1) + SREG x7, 40(x1) + SREG x8, 48(x1) + SREG x9, 56(x1) + SREG x10, 64(x1) + SREG x11, 72(x1) + SREG x12, 80(x1) + SREG x13, 88(x1) + SREG x14, 96(x1) + SREG x15, 104(x1) + SREG x16, 112(x1) + SREG x17, 120(x1) + SREG x18, 128(x1) + SREG x19, 136(x1) + SREG x20, 144(x1) + SREG x21, 152(x1) + SREG x22, 160(x1) + SREG x23, 168(x1) + SREG x24, 176(x1) + SREG x25, 184(x1) + SREG x26, 192(x1) + SREG x27, 200(x1) + SREG x28, 208(x1) + SREG x29, 216(x1) + SREG x30, 224(x1) + SREG x31, 232(x1) + + csrr x1, CSR_MSCRATCH + +all_done: + j all_done + + .balign 16 +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/programs/step.S b/debug/programs/step.S new file mode 100644 index 0000000..6601548 --- /dev/null +++ b/debug/programs/step.S @@ -0,0 +1,24 @@ +// Test stepping over a variety of instructions. + + .global main + +main: + la t0, trap_entry // 0, 4 + csrw mtvec, t0 // 0x8 + + li t0, 5 // 0xc + beq zero, zero, one // 0x10 + nop // 0x14 +one: + beq zero, t0, one // 0x18 + jal two // 0x1c + +three: + .word 0 // 0x20 + nop // 0x24 + +two: + ret // 0x28 + +trap_entry: + j trap_entry // 0x2c diff --git a/debug/programs/tiny-malloc.c b/debug/programs/tiny-malloc.c new file mode 100644 index 0000000..699660c --- /dev/null +++ b/debug/programs/tiny-malloc.c @@ -0,0 +1,600 @@ +// https://github.com/32bitmicro/newlib-nano-1.0/blob/master/newlib/libc/machine/xstormy16/tiny-malloc.c + +/* A replacement malloc with: + - Much reduced code size; + - Smaller RAM footprint; + - The ability to handle downward-growing heaps; + but + - Slower; + - Probably higher memory fragmentation; + - Doesn't support threads (but, if it did support threads, + it wouldn't need a global lock, only a compare-and-swap instruction); + - Assumes the maximum alignment required is the alignment of a pointer; + - Assumes that memory is already there and doesn't need to be allocated. + +* Synopsis of public routines + + malloc(size_t n); + Return a pointer to a newly allocated chunk of at least n bytes, or null + if no space is available. + free(void* p); + Release the chunk of memory pointed to by p, or no effect if p is null. + realloc(void* p, size_t n); + Return a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. The returned pointer may or may not be + the same as p. If p is null, equivalent to malloc. Unless the + #define REALLOC_ZERO_BYTES_FREES below is set, realloc with a + size argument of zero (re)allocates a minimum-sized chunk. + memalign(size_t alignment, size_t n); + Return a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument, which must be a power of + two. Will fail if 'alignment' is too large. + calloc(size_t unit, size_t quantity); + Returns a pointer to quantity * unit bytes, with all locations + set to zero. + cfree(void* p); + Equivalent to free(p). + malloc_trim(size_t pad); + Release all but pad bytes of freed top-most memory back + to the system. Return 1 if successful, else 0. + malloc_usable_size(void* p); + Report the number usable allocated bytes associated with allocated + chunk p. This may or may not report more bytes than were requested, + due to alignment and minimum size constraints. + malloc_stats(); + Prints brief summary statistics on stderr. + mallinfo() + Returns (by copy) a struct containing various summary statistics. + mallopt(int parameter_number, int parameter_value) + Changes one of the tunable parameters described below. Returns + 1 if successful in changing the parameter, else 0. Actually, returns 0 + always, as no parameter can be changed. +*/ + +#ifdef __xstormy16__ +#define MALLOC_DIRECTION -1 +#endif + +#ifndef MALLOC_DIRECTION +#define MALLOC_DIRECTION 1 +#endif + +#include + +void* malloc(size_t); +void free(void*); +void* realloc(void*, size_t); +void* memalign(size_t, size_t); +void* valloc(size_t); +void* pvalloc(size_t); +void* calloc(size_t, size_t); +void cfree(void*); +int malloc_trim(size_t); +size_t malloc_usable_size(void*); +void malloc_stats(void); +int mallopt(int, int); +struct mallinfo mallinfo(void); + +typedef struct freelist_entry { + size_t size; + struct freelist_entry *next; +} *fle; + +extern void * __malloc_end; +extern fle __malloc_freelist; + +/* Return the number of bytes that need to be added to X to make it + aligned to an ALIGN boundary. ALIGN must be a power of 2. */ +#define M_ALIGN(x, align) (-(size_t)(x) & ((align) - 1)) + +/* Return the number of bytes that need to be subtracted from X to make it + aligned to an ALIGN boundary. ALIGN must be a power of 2. */ +#define M_ALIGN_SUB(x, align) ((size_t)(x) & ((align) - 1)) + +extern char *__malloc_start; + +/* This is the minimum gap allowed between __malloc_end and the top of + the stack. This is only checked for when __malloc_end is + decreased; if instead the stack grows into the heap, silent data + corruption will result. */ +#define MALLOC_MINIMUM_GAP 32 + +#ifdef __xstormy16__ +register void * stack_pointer asm ("r15"); +#define MALLOC_LIMIT stack_pointer +#else +#define MALLOC_LIMIT __builtin_frame_address (0) +#endif + +#if MALLOC_DIRECTION < 0 +#define CAN_ALLOC_P(required) \ + (((size_t) __malloc_end - (size_t)MALLOC_LIMIT \ + - MALLOC_MINIMUM_GAP) >= (required)) +#else +#define CAN_ALLOC_P(required) \ + (((size_t)MALLOC_LIMIT - (size_t) __malloc_end \ + - MALLOC_MINIMUM_GAP) >= (required)) +#endif + +/* real_size is the size we actually have to allocate, allowing for + overhead and alignment. */ +#define REAL_SIZE(sz) \ + ((sz) < sizeof (struct freelist_entry) - sizeof (size_t) \ + ? sizeof (struct freelist_entry) \ + : sz + sizeof (size_t) + M_ALIGN(sz, sizeof (size_t))) + +#ifdef DEFINE_MALLOC + +void * __malloc_end = &__malloc_start; +fle __malloc_freelist; + +void * +malloc (size_t sz) +{ + fle *nextfree; + fle block; + + /* real_size is the size we actually have to allocate, allowing for + overhead and alignment. */ + size_t real_size = REAL_SIZE (sz); + + /* Look for the first block on the freelist that is large enough. */ + for (nextfree = &__malloc_freelist; + *nextfree; + nextfree = &(*nextfree)->next) + { + block = *nextfree; + + if (block->size >= real_size) + { + /* If the block found is just the right size, remove it from + the free list. Otherwise, split it. */ + if (block->size < real_size + sizeof (struct freelist_entry)) + { + *nextfree = block->next; + return (void *)&block->next; + } + else + { + size_t newsize = block->size - real_size; + fle newnext = block->next; + *nextfree = (fle)((size_t)block + real_size); + (*nextfree)->size = newsize; + (*nextfree)->next = newnext; + goto done; + } + } + + /* If this is the last block on the freelist, and it was too small, + enlarge it. */ + if (! block->next + && __malloc_end == (void *)((size_t)block + block->size)) + { + size_t moresize = real_size - block->size; + if (! CAN_ALLOC_P (moresize)) + return NULL; + + *nextfree = NULL; + if (MALLOC_DIRECTION < 0) + { + block = __malloc_end = (void *)((size_t)block - moresize); + } + else + { + __malloc_end = (void *)((size_t)block + real_size); + } + + goto done; + } + } + + /* No free space at the end of the free list. Allocate new space + and use that. */ + + if (! CAN_ALLOC_P (real_size)) + return NULL; + + if (MALLOC_DIRECTION > 0) + { + block = __malloc_end; + __malloc_end = (void *)((size_t)__malloc_end + real_size); + } + else + { + block = __malloc_end = (void *)((size_t)__malloc_end - real_size); + } + done: + block->size = real_size; + return (void *)&block->next; +} + +#endif + +#ifdef DEFINE_FREE + +void +free (void *block_p) +{ + fle *nextfree; + fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next)); + + if (block_p == NULL) + return; + + /* Look on the freelist to see if there's a free block just before + or just after this block. */ + for (nextfree = &__malloc_freelist; + *nextfree; + nextfree = &(*nextfree)->next) + { + fle thisblock = *nextfree; + if ((size_t)thisblock + thisblock->size == (size_t) block) + { + thisblock->size += block->size; + if (MALLOC_DIRECTION > 0 + && thisblock->next + && (size_t) block + block->size == (size_t) thisblock->next) + { + thisblock->size += thisblock->next->size; + thisblock->next = thisblock->next->next; + } + return; + } + else if ((size_t) thisblock == (size_t) block + block->size) + { + if (MALLOC_DIRECTION < 0 + && thisblock->next + && (size_t) block == ((size_t) thisblock->next + + thisblock->next->size)) + { + *nextfree = thisblock->next; + thisblock->next->size += block->size + thisblock->size; + } + else + { + block->size += thisblock->size; + block->next = thisblock->next; + *nextfree = block; + } + return; + } + else if ((MALLOC_DIRECTION > 0 + && (size_t) thisblock > (size_t) block) + || (MALLOC_DIRECTION < 0 + && (size_t) thisblock < (size_t) block)) + break; + } + + block->next = *nextfree; + *nextfree = block; + return; +} +#endif + +#ifdef DEFINE_REALLOC +void * +realloc (void *block_p, size_t sz) +{ + fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next)); + size_t real_size = REAL_SIZE (sz); + size_t old_real_size; + + if (block_p == NULL) + return malloc (sz); + + old_real_size = block->size; + + /* Perhaps we need to allocate more space. */ + if (old_real_size < real_size) + { + void *result; + size_t old_size = old_real_size - sizeof (size_t); + + /* Need to allocate, copy, and free. */ + result = malloc (sz); + if (result == NULL) + return NULL; + memcpy (result, block_p, old_size < sz ? old_size : sz); + free (block_p); + return result; + } + /* Perhaps we can free some space. */ + if (old_real_size - real_size >= sizeof (struct freelist_entry)) + { + fle newblock = (fle)((size_t)block + real_size); + block->size = real_size; + newblock->size = old_real_size - real_size; + free (&newblock->next); + } + return block_p; +} +#endif + +#ifdef DEFINE_CALLOC +void * +calloc (size_t n, size_t elem_size) +{ + void *result; + size_t sz = n * elem_size; + result = malloc (sz); + if (result != NULL) + memset (result, 0, sz); + return result; +} +#endif + +#ifdef DEFINE_CFREE +void +cfree (void *p) +{ + free (p); +} +#endif + +#ifdef DEFINE_MEMALIGN +void * +memalign (size_t align, size_t sz) +{ + fle *nextfree; + fle block; + + /* real_size is the size we actually have to allocate, allowing for + overhead and alignment. */ + size_t real_size = REAL_SIZE (sz); + + /* Some sanity checking on 'align'. */ + if ((align & (align - 1)) != 0 + || align <= 0) + return NULL; + + /* Look for the first block on the freelist that is large enough. */ + /* One tricky part is this: We want the result to be a valid pointer + to free. That means that there has to be room for a size_t + before the block. If there's additional space before the block, + it should go on the freelist, or it'll be lost---we could add it + to the size of the block before it in memory, but finding the + previous block is expensive. */ + for (nextfree = &__malloc_freelist; + ; + nextfree = &(*nextfree)->next) + { + size_t before_size; + size_t old_size; + + /* If we've run out of free blocks, allocate more space. */ + if (! *nextfree) + { + old_size = real_size; + if (MALLOC_DIRECTION < 0) + { + old_size += M_ALIGN_SUB (((size_t)__malloc_end + - old_size + sizeof (size_t)), + align); + if (! CAN_ALLOC_P (old_size)) + return NULL; + block = __malloc_end = (void *)((size_t)__malloc_end - old_size); + } + else + { + block = __malloc_end; + old_size += M_ALIGN ((size_t)__malloc_end + sizeof (size_t), + align); + if (! CAN_ALLOC_P (old_size)) + return NULL; + __malloc_end = (void *)((size_t)__malloc_end + old_size); + } + *nextfree = block; + block->size = old_size; + block->next = NULL; + } + else + { + block = *nextfree; + old_size = block->size; + } + + + before_size = M_ALIGN (&block->next, align); + if (before_size != 0) + before_size = sizeof (*block) + M_ALIGN (&(block+1)->next, align); + + /* If this is the last block on the freelist, and it is too small, + enlarge it. */ + if (! block->next + && old_size < real_size + before_size + && __malloc_end == (void *)((size_t)block + block->size)) + { + if (MALLOC_DIRECTION < 0) + { + size_t moresize = real_size - block->size; + moresize += M_ALIGN_SUB ((size_t)&block->next - moresize, align); + if (! CAN_ALLOC_P (moresize)) + return NULL; + block = __malloc_end = (void *)((size_t)block - moresize); + block->next = NULL; + block->size = old_size = old_size + moresize; + before_size = 0; + } + else + { + if (! CAN_ALLOC_P (before_size + real_size - block->size)) + return NULL; + __malloc_end = (void *)((size_t)block + before_size + real_size); + block->size = old_size = before_size + real_size; + } + + /* Two out of the four cases below will now be possible; which + two depends on MALLOC_DIRECTION. */ + } + + if (old_size >= real_size + before_size) + { + /* This block will do. If there needs to be space before it, + split the block. */ + if (before_size != 0) + { + fle old_block = block; + + old_block->size = before_size; + block = (fle)((size_t)block + before_size); + + /* If there's no space after the block, we're now nearly + done; just make a note of the size required. + Otherwise, we need to create a new free space block. */ + if (old_size - before_size + <= real_size + sizeof (struct freelist_entry)) + { + block->size = old_size - before_size; + return (void *)&block->next; + } + else + { + fle new_block; + new_block = (fle)((size_t)block + real_size); + new_block->size = old_size - before_size - real_size; + if (MALLOC_DIRECTION > 0) + { + new_block->next = old_block->next; + old_block->next = new_block; + } + else + { + new_block->next = old_block; + *nextfree = new_block; + } + goto done; + } + } + else + { + /* If the block found is just the right size, remove it from + the free list. Otherwise, split it. */ + if (old_size <= real_size + sizeof (struct freelist_entry)) + { + *nextfree = block->next; + return (void *)&block->next; + } + else + { + size_t newsize = old_size - real_size; + fle newnext = block->next; + *nextfree = (fle)((size_t)block + real_size); + (*nextfree)->size = newsize; + (*nextfree)->next = newnext; + goto done; + } + } + } + } + + done: + block->size = real_size; + return (void *)&block->next; +} +#endif + +#ifdef DEFINE_VALLOC +void * +valloc (size_t sz) +{ + return memalign (128, sz); +} +#endif +#ifdef DEFINE_PVALLOC +void * +pvalloc (size_t sz) +{ + return memalign (128, sz + M_ALIGN (sz, 128)); +} +#endif + +#ifdef DEFINE_MALLINFO +#include "malloc.h" + +struct mallinfo +mallinfo (void) +{ + struct mallinfo r; + fle fr; + size_t free_size; + size_t total_size; + size_t free_blocks; + + memset (&r, 0, sizeof (r)); + + free_size = 0; + free_blocks = 0; + for (fr = __malloc_freelist; fr; fr = fr->next) + { + free_size += fr->size; + free_blocks++; + if (! fr->next) + { + int atend; + if (MALLOC_DIRECTION > 0) + atend = (void *)((size_t)fr + fr->size) == __malloc_end; + else + atend = (void *)fr == __malloc_end; + if (atend) + r.keepcost = fr->size; + } + } + + if (MALLOC_DIRECTION > 0) + total_size = (char *)__malloc_end - (char *)&__malloc_start; + else + total_size = (char *)&__malloc_start - (char *)__malloc_end; + +#ifdef DEBUG + /* Fixme: should walk through all the in-use blocks and see if + they're valid. */ +#endif + + r.arena = total_size; + r.fordblks = free_size; + r.uordblks = total_size - free_size; + r.ordblks = free_blocks; + return r; +} +#endif + +#ifdef DEFINE_MALLOC_STATS +#include "malloc.h" +#include + +void +malloc_stats(void) +{ + struct mallinfo i; + FILE *fp; + + fp = stderr; + i = mallinfo(); + fprintf (fp, "malloc has reserved %u bytes between %p and %p\n", + i.arena, &__malloc_start, __malloc_end); + fprintf (fp, "there are %u bytes free in %u chunks\n", + i.fordblks, i.ordblks); + fprintf (fp, "of which %u bytes are at the end of the reserved space\n", + i.keepcost); + fprintf (fp, "and %u bytes are in use.\n", i.uordblks); +} +#endif + +#ifdef DEFINE_MALLOC_USABLE_SIZE +size_t +malloc_usable_size (void *block_p) +{ + fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next)); + return block->size - sizeof (size_t); +} +#endif + +#ifdef DEFINE_MALLOPT +int +mallopt (int n, int v) +{ + (void)n; (void)v; + return 0; +} +#endif diff --git a/debug/targets/m2gl_m2s/link.lds b/debug/targets/m2gl_m2s/link.lds new file mode 100755 index 0000000..1dbb99c --- /dev/null +++ b/debug/targets/m2gl_m2s/link.lds @@ -0,0 +1,34 @@ +OUTPUT_ARCH( "riscv" ) + +SECTIONS +{ + . = 0x80000000; + .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.*) + } + + /* bss segment */ + .sbss : { + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : { *(.bss) } + + __malloc_start = .; + . = . + 512; + + /* End of uninitalized data segement */ + _end = .; +} diff --git a/debug/targets/m2gl_m2s/openocd.cfg b/debug/targets/m2gl_m2s/openocd.cfg new file mode 100644 index 0000000..3884a3e --- /dev/null +++ b/debug/targets/m2gl_m2s/openocd.cfg @@ -0,0 +1,19 @@ +adapter_khz 10000 + +source [find interface/ftdi/olimex-arm-usb-tiny-h.cfg] + +set _CHIPNAME riscv +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10e31913 + +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 + +halt diff --git a/debug/targets/spike/link.lds b/debug/targets/spike/link.lds new file mode 100755 index 0000000..52e4472 --- /dev/null +++ b/debug/targets/spike/link.lds @@ -0,0 +1,36 @@ +OUTPUT_ARCH( "riscv" ) + +SECTIONS +{ + /* Leave some space for pk's data structures, which includes tohost/fromhost + * which are special addresses we ought to leave alone. */ + . = 0x80010000; + .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.*) + } + + /* bss segment */ + .sbss : { + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : { *(.bss) } + + __malloc_start = .; + . = . + 512; + + /* End of uninitalized data segement */ + _end = .; +} diff --git a/debug/testlib.py b/debug/testlib.py new file mode 100644 index 0000000..5e7f366 --- /dev/null +++ b/debug/testlib.py @@ -0,0 +1,170 @@ +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, xlen=32): + cc = os.path.expandvars("$RISCV/bin/riscv%d-unknown-elf-gcc" % xlen) + cmd = [cc, "-g"] + 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 + +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, cmd, binary=None, halted=False, with_gdb=True, timeout=None, + xlen=64): + """Launch spike. Return tuple of its process and the port it's running on.""" + if cmd: + cmd = shlex.split(cmd) + else: + cmd = ["spike"] + if (xlen == 32): + cmd += ["--isa", "RV32"] + + if timeout: + cmd = ["timeout", str(timeout)] + cmd + + 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") + logfile.write("+ %s\n" % " ".join(cmd)) + logfile.flush() + 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=False): + 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") + logfile.write("+ %s\n" % " ".join(cmd)) + 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.child.logfile.write("+ %s\n" % path) + self.wait() + self.command("set confirm off") + 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: + output = self.command("c") + assert "Continuing" in output + return output + else: + self.child.sendline("c") + self.child.expect("Continuing") + + def interrupt(self): + self.child.send("\003"); + self.child.expect("\(gdb\)") + return self.child.before.strip() + + 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/x %s" % obj) + value = int(output.split('=')[-1].strip(), 0) + return value + + def stepi(self): + output = self.command("stepi") + assert "Cannot" not in output + return output + + def load(self): + output = self.command("load", timeout=60) + assert "failed" not in output + assert "Transfer rate" in output + + def b(self, location): + output = self.command("b %s" % location) + assert "not defined" not in output + assert "Breakpoint" in output + return output + + def hbreak(self, location): + output = self.command("hbreak %s" % location) + assert "not defined" not in output + assert "Hardware assisted breakpoint" in output + return output