#include #include #include #include #include #include #include #include #include #include #include "dcpu16.h" #include "common.h" #include "hw_lem1802.h" /* * shell-like driver for dcpu16 core * provides a basic interface to control a single emulation instance * * Justin Wind * 2012 04 10 - implementation started * 2012 04 12 - cleanup, better shell loop * 2012 05 12 - support v1.7 style devices * * TODO * handle quotes in shell command parsing * use readline/history.h, since we're using readline anyhow * ncurses windowing or something, for future display capabilities */ static const char * const src_id_ = "$Id$"; /* global invocation options */ struct options { unsigned int verbose; } opt_ = { .verbose = 0, }; /* global run state, first sigint caught will drop out of run loop and back into shell */ static volatile unsigned int running_ = 0; static void sigint_handler_(int sig) { (void)sig; running_ = 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 -- dcpu16 emulator core shell\n\n", prog); fprintf(f, "Usage: %s [-v] [file]\n", prog); if (full) { fprintf(f, "\nOptions:\n" "\t [file] -- ram image to load initially\n" "\t -v -- prints slightly more information while operating\n" "\t -h -- this screen\n"); fprintf(f, "\n%78s\n", src_id_); } } /* flense a buffer into a newly-allocated argument list */ static int buf_tok_vect_(char ***v, int *c, char *buf) { const char *sep = " \t"; const char *quot = "\"'`"; const size_t v_grow = 32; size_t v_sz = 32; char *st, *qt; *c = 0; *v = malloc(v_sz * sizeof **v); if (*v == NULL) { fprintf(stderr, "%s():%s\n", "malloc", strerror(errno)); return -1; } for ( (*v)[*c] = strqtok_r(buf, sep, '\\', quot, &qt, &st); (*v)[*c]; (*v)[*c] = strqtok_r(NULL, sep, '\\', quot, &qt, &st) ) { (*c)++; if ((size_t)(*c) == v_sz) { void *tmp_ptr = realloc(*v, (v_sz + v_grow) * sizeof **v); if (tmp_ptr == NULL) { fprintf(stderr, "%s():%s\n", "realloc", strerror(errno)); free(*v); *v = NULL; return -1; } v_sz += v_grow; } } return 0; } /* resets the vm if addr is zero then loads an image from filename into ram starting at addr */ static int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) { FILE *f; size_t r; if (!addr) 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; } /* Here follows the various commands the shell can execute. At invocation, a command function will have already had its number of arguments vetted, but will need command-specific argument verifications done. The arg_vector contains the command as the first entry, and as such, arg_count will always be at least 1. However, the args_min and args_max entries in struct command_ only refer to the counts of arguments, not the entries in the argv. */ 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 arg_count, char **arg_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)arg_count, (void)arg_vector; return -1; } COMMAND_HELP(quit) { fprintf(f, "\tquit\n"); if (summary) return; fprintf(f, "Exits the emulator.\n"); } COMMAND_IMPL(reset) { (void)arg_count, (void)arg_vector; dcpu16_reset(vm); printf("initialized\n"); return 0; } COMMAND_HELP(reset) { fprintf(f, "\treset\n"); if (summary) return; fprintf(f, "Clears and reinitializes emulator.\n"); } COMMAND_IMPL(load) { int addr = 0; if (arg_count > 2) { addr = str_to_word(arg_vector[2]); if (addr < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno)); return 0; } } if (file_load_(vm, arg_vector[1], addr)) { fprintf(stderr, "failed to load '%s'\n", arg_vector[1]); return 0; } printf("loaded '%s'", arg_vector[1]); if (addr) printf(" starting at 0x%04x", addr); printf("\n"); return 0; } COMMAND_HELP(load) { fprintf(f, "\tload file [addr]\n"); if (summary) return; fprintf(f, "Load binary image from 'file' into ram.\n"); } COMMAND_IMPL(dump) { int addr[2]; int i; for (i = 1; i < arg_count; i++) { addr[i-1] = str_to_word(arg_vector[i]); if (addr[i-1] < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno)); return 0; } } if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC]; if (arg_count < 3) 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, "\tdump [addr_start [addr_end]]\n"); if (summary) return; fprintf(f, "Displays contents of ram from addr_start to addr_end.\n"); } COMMAND_IMPL(disassemble) { int addr[2]; int i; for (i = 1; i < arg_count; i++) { addr[i-1] = str_to_word(arg_vector[i]); if (addr[i-1] < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno)); return 0; } } if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC]; if (arg_count < 3) 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]; /* */ ) { printf("0x%04x: ", i); i += dcpu16_disassemble_print(vm, i); printf("\n"); } return 0; } COMMAND_HELP(disassemble) { fprintf(f, "\tdisassemble [addr_start [addr_end]]\n"); if (summary) return; fprintf(f, "Displays contents of ram parsed into instructions.\n"); } COMMAND_IMPL(step) { unsigned long count = 1; char *ep; if (arg_count == 2) { errno = 0; count = strtoul(arg_vector[1], &ep, 0); if (errno || !(*arg_vector[1] && *ep == '\0') ) { fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno)); return 0; } if (count <= 0) { fprintf(stderr, "count must be positive\n"); return 0; } } while (count--) { dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]); printf("\n"); dcpu16_step(vm); if (count > 1 && opt_.verbose) dcpu16_state_print(vm); } return 0; } COMMAND_HELP(step) { fprintf(f, "\tstep [count]\n"); if (summary) return; fprintf(f, "Executes the next instruction, or the next count instructions.\n"); } COMMAND_IMPL(set) { int addr, value; DCPU16_WORD *v; (void)arg_count; /* check if addr is a register */ for (addr = 0; dcpu16_reg_names[addr]; addr++) { if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0) break; } if (addr < DCPU16_REG__NUM) { v = vm->reg + addr; } else { addr = str_to_word(arg_vector[1]); if (addr < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno)); return 0; } v = vm->ram + addr; } value = str_to_word(arg_vector[2]); if (value < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno)); return 0; } *v = value; return 0; } COMMAND_HELP(set) { fprintf(f, "\tset addr value\n"); if (summary) return; fprintf(f, "Sets addr to value."); } COMMAND_IMPL(run) { struct sigaction act; (void)arg_count, (void)arg_vector; running_ = 1; memset(&act, 0, sizeof act); act.sa_handler = sigint_handler_; act.sa_flags = SA_RESETHAND; if (sigaction(SIGINT, &act, NULL)) { fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno)); return -1; } while(running_) { dcpu16_step(vm); if (opt_.verbose > 1) dcpu16_state_print(vm); else if (opt_.verbose) { dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]); printf("\n"); } } printf("interrupted...\n"); return 0; } COMMAND_HELP(run) { fprintf(f, "\trun\n"); if (summary) return; fprintf(f, "Begins executing continuously.\n" "May be interrupted with SIGINT.\n"); } static const char * const display_filename_default_ = #ifdef HAVE_LIBPNG "dcpu16-display.png" #else /* HAVE_LIBPNG */ "dcpu16-display.pnm" #endif /* HAVE_LIBPNG */ ; COMMAND_IMPL(display) { struct dcpu16_hw *hw = lem1802_new(vm); const char *renderer = arg_vector[1]; const char *renderer_arg = NULL; void *renderer_data = NULL; if (arg_count == 3) renderer_arg = arg_vector[2]; if (hw == NULL) { fprintf(stderr, "failed to initialize new display\n"); return 0; } /* handle per-renderer setup of data.. */ /* FIXME: these are awkward */ if (strcmp(renderer, "pnm") == 0) { if (renderer_arg == NULL) renderer_arg = display_filename_default_; renderer_data = (void *)renderer_arg; } #ifdef HAVE_LIBPNG if (strcmp(renderer, "png") == 0) { if (renderer_arg == NULL) renderer_arg = display_filename_default_; renderer_data = (void *)renderer_arg; } #endif /* HAVE_LIBPNG */ #ifdef HAVE_LIBVNCSERVER if (strcmp(renderer, "vnc") == 0) { int argc = 1; char *argv[] = { "vm-dcpu16", NULL }; renderer_data = lem1802_vnc_init_data(argc, argv, hw); /* FIXME: keep refs to vnc displays around somewhere, in global list maybe.. */ /* keyboards will want to attach to them as well.. */ if (renderer_data == NULL) { fprintf(stderr, "failed to initialize vnc\n"); lem1802_del(&hw); return 0; } } #endif /* HAVE_LIBVNCSERVER */ if (lem1802_renderer_set(hw, renderer, renderer_data)) { fprintf(stderr, "failed to set back-end renderer for display\n"); lem1802_del(&hw); return 0; } if (dcpu16_hw_add(vm, hw)) { fprintf(stderr, "failed to attach new display\n"); lem1802_del(&hw); return 0; } return 0; } COMMAND_HELP(display) { char *name, *args; void *iter; fprintf(f, "\tdisplay renderer [renderer data]\n"); if (summary) return; fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n" ); fprintf(f, "Supported renderers:\n"); iter = NULL; while ( (lem1802_renderers_iter(&iter, &name, &args)) ) { fprintf(f, "\t%s %s\n", name, args); } } /* 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), COMMAND_ENTRY(set, 2, 2), COMMAND_ENTRY(reset, 0, 0), COMMAND_ENTRY(display, 1, 2), { NULL, 0, 0, NULL, NULL } }; COMMAND_IMPL(help) { struct command_ *c; (void)vm; if (arg_count == 2) { for (c = command_table_; c->func; c++) { if (strcasecmp(arg_vector[1], c->name) == 0) { if (c->help) c->help(stdout, 0); break; } } return 0; } for (c = command_table_; c->func; c++) { if (c->help) c->help(stdout, 1); } return 0; } COMMAND_HELP(help) { fprintf(f, "\thelp [command]\n"); if (summary) return; fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n"); } int main(int argc, char **argv) { const char prompt_fmt[] = "PC:%04x> "; char prompt[32]; struct dcpu16 *vm; char *line, *line_prev; char **tok_v, **tok_v_prev; int tok_c, tok_c_prev; int c; 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 dcpu16 instance\n"); exit(EX_UNAVAILABLE); } if (argc) { if (file_load_(vm, *argv, 0)) { fprintf(stderr, "couldn't load '%s'\n", *argv); exit(EX_NOINPUT); } } /* show state, read commands */ for (line = line_prev = NULL, tok_v = tok_v_prev = NULL, tok_c = tok_c_prev= 0, snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]), dcpu16_state_print(vm); (line = readline(prompt)); printf("\n"), snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]), dcpu16_state_print(vm)) { const char whitespace[] = " \t"; char *line_start; struct command_ *c; int r = 0; /* skip whitespaces */ line_start = line + strspn(line, whitespace); if (*line_start) { /* a new command, remember previous for possible repetition */ /* turn new line into new arg array */ if (buf_tok_vect_(&tok_v, &tok_c, line_start)) { fprintf(stderr, "failed to process command\n"); continue; } /* and keep track if it all for the next time around */ if (line_prev) free(line_prev); line_prev = line; if (tok_v_prev) free(tok_v_prev); tok_v_prev = tok_v; tok_c_prev = tok_c; } else { /* blank new command, but no prior command to repeat? ask again */ if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') { free(line); continue; } /* otherwise discard new line and promote prior */ free(line); tok_v = tok_v_prev; tok_c = tok_c_prev; line = line_prev; } /* look up command */ for (c = command_table_; c->name; c++) { if (strcasecmp(tok_v[0], c->name) == 0) { if (c->args_min > tok_c - 1) { fprintf(stderr, "%s: not enough arguments\n", c->name); c->help(stderr, 1); break; } if (c->args_max > 0 && tok_c - 1 > c->args_max) { fprintf(stderr, "%s: too many arguments\n", c->name); c->help(stderr, 1); break; } r = c->func(vm, tok_c, tok_v); break; } } if (r) break; if (!c->func) fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]); } printf("\nfinished\n"); dcpu16_delete(&vm); exit(EX_OK); }