#include #include #include #include #include #include #include #include #include "dcpu16.h" /* * cli driver for dcpu16 core * * Justin Wind * 2012 04 10 - implementation started * */ static const char * const src_id_ = "$Id$"; /* global invocation options */ struct options { unsigned int verbose; } opt_ = { .verbose = 0, }; #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0) static void usage_(char *prog, unsigned int full) { FILE *f = full ? stdout : stderr; char *x = strrchr(prog, '/'); if (x && *(x + 1)) prog = x + 1; if (full) fprintf(f, "%s -- \n\n", prog); fprintf(f, "Usage: %s [file]\n", prog); if (full) { fprintf(f, "\nOptions:\n" "\t [file] -- ram image to load initially\n" "\t -h -- this screen\n" "\t -v -- verbose execution tracing\n"); fprintf(f, "\n%78s\n", src_id_); } } /* simplified strtoul with range checking */ static 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 + 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)); fclose(f); 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 *line, *line_prev; struct dcpu16 *vm; char prompt[32]; const char prompt_fmt[] = "PC:%04x> "; while ( (c = getopt(argc, argv, "hv")) != EOF) { switch (c) { case 'v': opt_.verbose++; break; case 'h': usage_(argv[0], 1); exit(EX_OK); default: usage_(argv[0], 0); exit(EX_USAGE); } } if (opt_.verbose < 1) { dcpu16_warn_cb_set(NULL); dcpu16_trace_cb_set(NULL); } else if (opt_.verbose < 2) { dcpu16_trace_cb_set(NULL); } argc -= optind; argv += optind; if ((vm = dcpu16_new()) == NULL) { fprintf(stderr, "could not allocate new dcpu instance\n"); exit(EX_UNAVAILABLE); } if (argc) { file_load_(vm, *argv, 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; if (!c->func) fprintf(stderr, "didn't recognize '%s'\n", command); } printf("\nfinished\n"); dcpu16_delete(&vm); exit(EX_OK); }