From: Justin Wind Date: Thu, 12 Apr 2012 05:31:31 +0000 (-0700) Subject: vm-dcpu16 shell rewritten to be use command table X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=eb3e31c1bfba67227dfd02890cc84330b1580918;p=dcpu16 vm-dcpu16 shell rewritten to be use command table minor cleanups all around, redid entirety of main driver routine in vm shell --- diff --git a/dcpu16.c b/dcpu16.c index 6f5617c..cfa6d8a 100644 --- a/dcpu16.c +++ b/dcpu16.c @@ -33,9 +33,7 @@ static const char * const src_id_ = "$Id$"; static const char regnames_[] = "ABCXYZIJ"; - -static unsigned int trace_mode_ = 0; /* spew overly verbose internals */ - +/* some default warning and debug reporting functions, which can be overridden by clients */ #define WARN(...) do { if (warn_cb_) warn_cb_(__VA_ARGS__); } while (0) static inline void warn_(char *fmt, ...) __attribute__((format(printf, 1, 2))); static inline @@ -54,7 +52,8 @@ void dcpu16_warn_cb_set(void (*fn)(char *fmt, ...)) { warn_cb_ = fn; } -#define TRACE(...) do { if (trace_cb_ && trace_mode_) trace_cb_(__VA_ARGS__); } while (0) +#ifdef DEBUG +#define TRACE(...) do { if (trace_cb_) trace_cb_(__VA_ARGS__); } while (0) static inline void trace_(char *fmt, ...) __attribute__((format(printf, 1, 2))); static inline void trace_(char *fmt, ...) { @@ -67,12 +66,20 @@ void trace_(char *fmt, ...) { fprintf(stdout, "\n"); fflush(stdout); } -static void (*trace_cb_)(char *fmt, ...) = trace_; +#else /* DEBUG */ +#define TRACE(...) do {} while(0) +#endif /* DEBUG */ +static void (*trace_cb_)(char *fmt, ...) = +#ifdef DEBUG + trace_ +#else /* DEBUG */ + NULL +#endif + ; void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) { trace_cb_ = fn; } - /* value_decode_ * sets *v to be the destination of the value * workv is buffer to use to accumulate literal value before use @@ -543,7 +550,7 @@ void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) { assert(a <= 0x3f); assert(b <= 0x3f); - printf(" next instr 0x%04x: %04x", addr, d->ram[addr]); + printf("%04x", d->ram[addr]); if (opcode != 0) { @@ -574,8 +581,6 @@ void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) { } dump_value(b, d->ram[addr + 1]); - - printf("\n"); } void dcpu16_step(struct dcpu16 *d) { @@ -603,26 +608,42 @@ void dcpu16_step(struct dcpu16 *d) { void dcpu16_state_print(struct dcpu16 *d) { unsigned int i; - printf("---- cycle:0x%08llx %2s:0x%04x %2s:0x%04x %2s:0x%04x\n", + printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", d->cycle, - "PC", d->pc, + "O", d->o, "SP", d->sp, - "O", d->o); - printf(" "); + "PC", d->pc, + "PC"); + + dcpu16_disassemble_print(d, d->pc); + printf("\n "); + for (i = 0; i < 8; i++) printf(" %c:0x%04x", regnames_[i], d->reg[i]); printf("\n"); } -void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD stop) { +/* dcpu16_dump_ram + * print raw ram contents from start to stop + */ +void dcpu16_dump_ram(struct dcpu16 *d, WORD start, WORD end) { unsigned int i, j; const unsigned int n = 8; /* words per line */ - for (i = start, j = 0; i <= stop; i++, j++) { + for (i = start, j = 0; i <= end; i++, j++) { if (j % n == 0) printf("0x%04x:\t", i); printf(" %04x%s", d->ram[i], (j % n) == (n - 1) ? "\n" : ""); } + if ((j % n) != (n - 1)) + printf("\n"); +} + +/* dcpu16_reset + * resets a dcpu16 instance to initial state + */ +void dcpu16_reset(struct dcpu16 *d) { + memset(d, 0, sizeof *d); } /* dcpu16_new diff --git a/dcpu16.h b/dcpu16.h index f0e4d8b..edfccdf 100644 --- a/dcpu16.h +++ b/dcpu16.h @@ -22,6 +22,9 @@ struct dcpu16 { /* instantiate a new core */ struct dcpu16 *dcpu16_new(void); +/* reset a core to initial state */ +void dcpu16_reset(struct dcpu16 *); + /* print the current state of a core */ void dcpu16_state_print(struct dcpu16 *); @@ -38,7 +41,7 @@ void dcpu16_step(struct dcpu16 *); /* release a core */ void dcpu16_delete(struct dcpu16 **); -/* register callbacks to handle warning and debug messages, defaults to writing to stderr */ +/* register callbacks to handle warning and debug messages, default is writing to stderr, may be set to null */ void dcpu16_warn_cb_set(void (*)(char *, ...)); void dcpu16_trace_cb_set(void (*)(char *, ...)); diff --git a/vm-dcpu16.c b/vm-dcpu16.c index c3f45ea..0045d48 100644 --- a/vm-dcpu16.c +++ b/vm-dcpu16.c @@ -7,7 +7,6 @@ #include #include -#include #include "dcpu16.h" @@ -54,19 +53,52 @@ static void usage_(char *prog, unsigned int full) { } } +/* simplified strtoul with range checking */ static -int file_load_(struct dcpu16 *vm, char *filename) { +int str_to_word_(char *s) { + unsigned long l; + char *ep; + + assert(s); + + errno = 0; + l = strtoul(s, &ep, 0); + + if (errno + || !(*s && *ep == '\0') ) { + /* out of range of conversion, or invalid character encountered */ + return -1; + } + + if (l >= DCPU16_RAM) { + /* out of range for our needs */ + errno = ERANGE; + return -1; + } + + return l; +} + +/* clears the instance and loads an image into ram starting at addr */ +static +int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) { FILE *f; size_t r; + assert(addr < DCPU16_RAM); + + dcpu16_reset(vm); + f = fopen(filename, "rb"); if (f == NULL) { fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno)); return -1; } - r = fread(vm->ram, sizeof(DCPU16_WORD), DCPU16_RAM, f); + r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f); VERBOSE_PRINTF("read %zu words", r); + if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr); + VERBOSE_PRINTF("\n"); if (ferror(f)) fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno)); @@ -75,11 +107,265 @@ int file_load_(struct dcpu16 *vm, char *filename) { return 0; } +/* the commands the vm shell can execute */ + +struct command_ { + char *name; + int args_min; + int args_max; + int (*func)(struct dcpu16 *, int c, char **v); + void (*help)(FILE *f, unsigned int); +}; + +#define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int token_count, char **token_vector) +#define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary) +#define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ } + + +COMMAND_IMPL(quit) { + (void)vm, (void)token_count, (void)token_vector; + VERBOSE_PRINTF("done\n"); + return -1; +} +COMMAND_HELP(quit) { + fprintf(f, "quit\n"); + if (summary) return; + + fprintf(f, "\tExits the emulator.\n"); +} + + +COMMAND_IMPL(load) { + int addr = 0; + + if (token_count > 1) { + addr = str_to_word_(token_vector[1]); + if (addr < 0) { + fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[1], strerror(errno)); + return 0; + } + } + + if (file_load_(vm, token_vector[0], addr)) { + fprintf(stderr, "failed to load '%s'\n", token_vector[0]); + return 0; + } + printf("loaded '%s'", token_vector[0]); + if (addr) printf(" starting at 0x%04x", addr); + printf("\n"); + + return 0; +} +COMMAND_HELP(load) { + fprintf(f, "load file [addr]\n"); + if (summary) return; + + fprintf(f, "Usage: load file [addr]\n" + "\tAttempts to load binary image from 'file' at addr.\n"); +} + + +COMMAND_IMPL(dump) { + int addr[2]; + int i; + + for (i = 0; i < token_count; i++) { + addr[i] = str_to_word_(token_vector[i]); + if (addr[i] < 0) { + fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[i], strerror(errno)); + return 0; + } + } + if (token_count < 1) addr[0] = vm->pc; + if (token_count < 2) addr[1] = addr[0]; + + if (addr[1] < addr[0]) { + fprintf(stderr, "\t'addr_start' must be before addr_end\n"); + return 0; + } + + dcpu16_dump_ram(vm, addr[0], addr[1]); + + return 0; +} +COMMAND_HELP(dump) { + fprintf(f, "dump [addr_start [addr_end]]\n"); + if (summary) return; + + fprintf(f, "\tDisplays contents of ram from addr_start to addr_end.\n"); +} + + +COMMAND_IMPL(disassemble) { + int addr[2]; + int i; + + for (i = 0; i < token_count; i++) { + addr[i] = str_to_word_(token_vector[i]); + if (addr[i] < 0) { + fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[i], strerror(errno)); + return 0; + } + } + if (token_count < 1) addr[0] = vm->pc; + if (token_count < 2) addr[1] = addr[0]; + + if (addr[1] < addr[0]) { + fprintf(stderr, "\t'addr_start' must be before addr_end\n"); + return 0; + } + + for (i = addr[0]; i <= addr[1]; i++) + dcpu16_disassemble_print(vm, i); + + return 0; +} +COMMAND_HELP(disassemble) { + fprintf(f, "disassemble [addr_start [addr_end]]\n"); + if (summary) return; + + fprintf(f, "\tDisplays contents of ram parsed into instructions.\n"); +} + + +COMMAND_IMPL(step) { + unsigned long count; + char *ep; + + (void)token_count; + + errno = 0; + count = strtoul(token_vector[0], &ep, 0); + if (errno + || !(*token_vector[0] && *ep == '\0') ) { + fprintf(stderr, "count '%s' is not a valid number: %s\n", token_vector[0], strerror(errno)); + return 0; + } + + if (count <= 0) { + fprintf(stderr, "count must be positive\n"); + return 0; + } + + while (count--) { + VERBOSE_PRINTF("executing next cycle, instruction: "); + dcpu16_disassemble_print(vm, vm->pc), printf("\n"); + + dcpu16_step(vm); + + if (opt_.verbose) + dcpu16_state_print(vm); + } + + return 0; +} +COMMAND_HELP(step) { + fprintf(f, "step [count]\n"); + if (summary) return; + + fprintf(f, "\tExecutes the next instruction, or the next count instructions.\n"); +} + + +/* catch sigint while running, stop running */ +static volatile unsigned int running_ = 0; +static +void sigint_handler_(int sig) { + (void)sig; + running_ = 0; +} +COMMAND_IMPL(run) { + sig_t osig; + (void)token_count, (void)token_vector; + + running_ = 1; + + /* install our new interrupt signal handler */ + if ( (osig = signal(SIGINT, sigint_handler_)) ) { + fprintf(stderr, "%s():%s\n", "signal", strerror(errno)); + return -1; + } + + while(running_) { + dcpu16_step(vm); + if (opt_.verbose) + dcpu16_state_print(vm); + } + + /* restore the old interrupt signal handler */ + if (signal(SIGINT, osig) == SIG_ERR) { + fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno)); + return -1; + } + + VERBOSE_PRINTF("interrupted...\n"); + + return 0; +} +COMMAND_HELP(run) { + fprintf(f, "run\n"); + if (summary) return; + + fprintf(f, "\tBegins executing continuously.\n"); +} + +/* gather all these together into a searchable table */ +/* help command gets some assistance in declarations */ +COMMAND_IMPL(help); +COMMAND_HELP(help); + +static struct command_ command_table_[] = { + COMMAND_ENTRY(help, 0, 1), + COMMAND_ENTRY(quit, 0, -1), + COMMAND_ENTRY(load, 1, 2), + COMMAND_ENTRY(dump, 0, 2), + COMMAND_ENTRY(disassemble, 0, 2), + COMMAND_ENTRY(step, 0, 1), + COMMAND_ENTRY(run, 0, 0), + { NULL, 0, 0, NULL, NULL } +}; + +COMMAND_IMPL(help) { + struct command_ *c; + (void)vm; + + if (token_count) { + while (token_count) { + for (c = command_table_; c->func; c++) { + if (strcasecmp(*token_vector, c->name) == 0) { + if (c->help) + c->help(stdout, 0); + break; + } + } + token_count--; + token_vector++; + } + return 0; + } + + for (c = command_table_; c->func; c++) { + if (c->help) + c->help(stdout, 1); + } + return 0; +} +COMMAND_HELP(help) { + if (summary) { + fprintf(f, "help [command]\n"); + return; + } + + fprintf(f, "Usage: help [command]\n" + "\tDisplays a list of available commands, or help on a specific command.\n"); +} + int main(int argc, char **argv) { int c; - char *command; - char *prompt = "dcpu16> "; + char *line, *line_prev; struct dcpu16 *vm; + char prompt[32]; + const char prompt_fmt[] = "PC:%04x> "; while ( (c = getopt(argc, argv, "hv")) != EOF) { switch (c) { @@ -111,21 +397,80 @@ int main(int argc, char **argv) { } if (argc) { - file_load_(vm, *argv); + file_load_(vm, *argv, 0); } - dcpu16_state_print(vm); - dcpu16_disassemble_print(vm, vm->pc); - while ( (command = readline(prompt)) ) { - if (strcasecmp(command, "quit") == 0) + /* show state, read commands */ + for (line_prev = NULL, + snprintf(prompt, sizeof prompt, prompt_fmt, vm->pc), + dcpu16_state_print(vm); + + (line = readline(prompt)); + + printf("\n"), + snprintf(prompt, sizeof prompt, prompt_fmt, vm->pc), + dcpu16_state_print(vm)) { + const char whitespace[] = " \t"; + char *rest, *line_start, *command; + struct command_ *c; + int token_count; + char *token_vector[] = { NULL, NULL }; + int r = 0; + + /* skip whitespaces */ + line_start = line + strspn(line, whitespace); + + if (*line_start) { + /* a new command, it will be the prior command now */ + free(line_prev); + line_prev = line; + } else { + /* empty command, read another line if there's no prior command to repeat */ + if (line_prev == NULL || *line_prev == '\0') { + continue; + } + + /* otherwise discard new line and repeat prior */ + free(line); + line_start = line_prev + strspn(line, whitespace); + VERBOSE_PRINTF("repeating previous command '%s'\n", line_start); + } + + /* first word */ + command = strtok_r(line_start, whitespace, &rest); + + /* look up command */ + /* FIXME: tokenize 'rest' into proper argv */ + token_count = 0; + if (rest) + token_count++, token_vector[0] = rest; + for (c = command_table_; c->name; c++) { + if (strcasecmp(command, c->name) == 0) { + if (c->args_min > token_count) { + fprintf(stderr, "%s: not enough arguments\n", c->name); + c->help(stderr, 1); + break; + } + + if (c->args_max > 0 + && token_count > c->args_max) { + fprintf(stderr, "%s: too many arguments\n", c->name); + c->help(stderr, 1); + break; + } + + r = c->func(vm, token_count, token_vector); + break; + } + } + if (r) break; - dcpu16_step(vm); - dcpu16_state_print(vm); - dcpu16_disassemble_print(vm, vm->pc); + if (!c->func) + fprintf(stderr, "didn't recognize '%s'\n", command); } - printf("finished\n"); + printf("\nfinished\n"); dcpu16_delete(&vm);