/* tab:8 * * lc3sim.c - the main source file for the LC-3 simulator * * "Copyright (c) 2003 by Steven S. Lumetta." * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without written * agreement is hereby granted, provided that the above copyright notice * and the following two paragraphs appear in all copies of this software, * that the files COPYING and NO_WARRANTY are included verbatim with * any distribution, and that the contents of the file README are included * verbatim as part of a file named README with any distribution. * * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE AUTHOR * HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" * BASIS, AND THE AUTHOR NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, * UPDATES, ENHANCEMENTS, OR MODIFICATIONS." * * Author: Steve Lumetta * Version: 1 * Creation Date: 18 October 2003 * Filename: lc3sim.c * History: * SSL 1 18 October 2003 * Copyright notices and Gnu Public License marker added. */ #include <ctype.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <strings.h> #include <sys/poll.h> #include <sys/termios.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <time.h> #include <unistd.h> #if defined(USE_READLINE) #include <readline/readline.h> #include <readline/history.h> #endif #include "lc3sim.h" #include "symbol.h" /* Disassembly format specification. */ #define OPCODE_WIDTH 6 /* NOTE: hardcoded in scanfs! */ #define MAX_CMD_WORD_LEN 41 /* command word limit + 1 */ #define MAX_FILE_NAME_LEN 251 /* file name limit + 1 */ #define MAX_LABEL_LEN 81 /* label limit + 1 */ #define MAX_SCRIPT_DEPTH 10 /* prevent infinite recursion in scripts */ #define MAX_FINISH_DEPTH 10000000 /* avoid waiting to finish subroutine */ /* that recurses infinitely */ #define TOO_MANY_ARGS "WARNING: Ignoring excess arguments." #define BAD_ADDRESS \ "Addresses must be labels or values in the range x0000 to xFFFF." /* Types of breakpoints. Currently only user breakpoints are handled in this manner; the system breakpoint used for the "next" command is specified by sys_bpt_addr. */ typedef enum bpt_type_t bpt_type_t; enum bpt_type_t {BPT_NONE, BPT_USER}; static int launch_gui_connection (); static char* simple_readline (const char* prompt); static void init_machine (); static void print_register (int which); static void print_registers (); static void dump_delayed_mem_updates (); static void show_state_if_stop_visible (); static int read_obj_file (const unsigned char* filename, int* startp, int* endp); static int read_sym_file (const unsigned char* filename); static void squash_symbols (int addr_s, int addr_e); static int execute_instruction (); static void disassemble_one (int addr); static void disassemble (int addr_s, int addr_e); static void dump_memory (int addr_s, int addr_e); static void run_until_stopped (); static void clear_breakpoint (int addr); static void clear_all_breakpoints (); static void list_breakpoints (); static void set_breakpoint (int addr); static void warn_too_many_args (); static void no_args_allowed (const unsigned char* args); static int parse_address (const unsigned char* addr); static int parse_range (const unsigned char* cmd, int* startptr, int* endptr, int last_end, int scale); static void flush_console_input (); static void gui_stop_and_dump (); static void cmd_break (const unsigned char* args); static void cmd_continue (const unsigned char* args); static void cmd_dump (const unsigned char* args); static void cmd_execute (const unsigned char* args); static void cmd_file (const unsigned char* args); static void cmd_finish (const unsigned char* args); static void cmd_help (const unsigned char* args); static void cmd_list (const unsigned char* args); static void cmd_memory (const unsigned char* args); static void cmd_next (const unsigned char* args); static void cmd_option (const unsigned char* args); static void cmd_printregs (const unsigned char* args); static void cmd_quit (const unsigned char* args); static void cmd_register (const unsigned char* args); static void cmd_reset (const unsigned char* args); static void cmd_step (const unsigned char* args); static void cmd_translate (const unsigned char* args); static void cmd_lc3_stop (const unsigned char* args); typedef enum cmd_flag_t cmd_flag_t; enum cmd_flag_t { CMD_FLAG_NONE = 0, CMD_FLAG_REPEATABLE = 1, /* pressing ENTER repeats command */ CMD_FLAG_LIST_TYPE = 2, /* pressing ENTER shows more */ CMD_FLAG_GUI_ONLY = 4 /* only valid in GUI mode */ }; typedef struct command_t command_t; struct command_t { unsigned char* command; /* string for command */ int min_len; /* minimum length for abbrevation--typically 1 */ void (*cmd_func) (const unsigned char*); /* function implementing command */ cmd_flag_t flags; /* flags for command properties */ }; static const struct command_t command[] = { {"break", 1, cmd_break, CMD_FLAG_NONE }, {"continue", 1, cmd_continue, CMD_FLAG_REPEATABLE}, {"dump", 1, cmd_dump, CMD_FLAG_LIST_TYPE }, {"execute", 1, cmd_execute, CMD_FLAG_NONE }, {"file", 1, cmd_file, CMD_FLAG_NONE }, {"finish", 3, cmd_finish, CMD_FLAG_REPEATABLE}, {"help", 1, cmd_help, CMD_FLAG_NONE }, {"list", 1, cmd_list, CMD_FLAG_LIST_TYPE }, {"memory", 1, cmd_memory, CMD_FLAG_NONE }, {"next", 1, cmd_next, CMD_FLAG_REPEATABLE}, {"option", 1, cmd_option, CMD_FLAG_NONE }, {"printregs", 1, cmd_printregs, CMD_FLAG_NONE }, {"quit", 4, cmd_quit, CMD_FLAG_NONE }, {"register", 1, cmd_register, CMD_FLAG_NONE }, {"reset", 5, cmd_reset, CMD_FLAG_NONE }, {"step", 1, cmd_step, CMD_FLAG_REPEATABLE}, {"translate", 1, cmd_translate, CMD_FLAG_NONE }, {"x", 1, cmd_lc3_stop, CMD_FLAG_GUI_ONLY }, {NULL, 0, NULL, CMD_FLAG_NONE } }; static int lc3_register[NUM_REGS]; #define REG(i) lc3_register[(i)] static int lc3_memory[65536]; static int lc3_show_later[65536]; static bpt_type_t lc3_breakpoints[65536]; /* startup script or file */ static char* start_script = NULL; static char* start_file = NULL; static int should_halt = 1, last_KBSR_read = 0, last_DSR_read = 0, gui_mode; static int interrupted_at_gui_request = 0, stop_scripts = 0, in_init = 0; static int have_mem_to_dump = 0, need_a_stop_notice = 0; static int sys_bpt_addr = -1, finish_depth = 0; static inst_flag_t last_flags; /* options and script recursion level */ static int flush_on_start = 1, keep_input_on_stop = 1; static int rand_device = 1, delay_mem_update = 1; static int script_uses_stdin = 1, script_depth = 0; static FILE* lc3in; static FILE* lc3out; static FILE* sim_in; static char* (*lc3readline) (const char*) = simple_readline; static const char* const ccodes[8] = { "BAD_CC", "POSITIVE", "ZERO", "BAD_CC", "NEGATIVE", "BAD_CC", "BAD_CC", "BAD_CC" }; static int execute_instruction () { /* Fetch the instruction. */ REG (R_IR) = read_memory (REG (R_PC)); REG (R_PC) = (REG (R_PC) + 1) & 0xFFFF; /* Try to execute it. */ #define ADD_FLAGS(value) (last_flags |= (value)) #define DEF_INST(name,format,mask,match,flags,code) \ if ((REG (R_IR) & (mask)) == (match)) { \ last_flags = (flags); \ code; \ goto executed; \ } #define DEF_P_OP(name,format,mask,match) #include "lc3.def" #undef DEF_P_OP #undef DEF_INST #undef ADD_FLAGS REG (R_PC) = (REG (R_PC) - 1) & 0xFFFF; if (gui_mode) printf ("ERR {Illegal instruction at x%04X!}\n", REG (R_PC)); else printf ("Illegal instruction at x%04X!\n", REG (R_PC)); return 0; executed: /* Check for user breakpoints. */ if (lc3_breakpoints[REG (R_PC)] == BPT_USER) { if (!gui_mode) printf ("The LC-3 hit a breakpoint...\n"); return 0; } /* Check for system breakpoint (associated with "next" command). */ if (REG (R_PC) == sys_bpt_addr) return 0; if (finish_depth > 0) { if ((last_flags & FLG_SUBROUTINE) && ++finish_depth == MAX_FINISH_DEPTH) { if (gui_mode) puts ("ERR {Stopping due to possibly infinite " "recursion.}"); else puts ("Stopping due to possibly infinite recursion."); finish_depth = 0; return 0; } else if ((last_flags & FLG_RETURN) && --finish_depth == 0) { /* Done with finish command; stop execution. */ return 0; } } /* Check for GUI needs. */ if (!in_init && gui_mode) { struct pollfd p; p.fd = fileno (sim_in); p.events = POLLIN; if (poll (&p, 1, 0) == 1 && (p.revents & POLLIN) != 0) { interrupted_at_gui_request = 1; return 0; } } return 1; } void halt_lc3 (int sig) { /* Set the signal handler again, which has the effect of overriding the Solaris behavior and making signal look like sigset, which is non-standard and non-portable, but the desired behavior. */ signal (SIGINT, halt_lc3); /* has no effect unless LC-3 is running... */ should_halt = 1; /* print a stop notice after ^C */ need_a_stop_notice = 1; } static int launch_gui_connection () { u_short port; int fd; /* server socket file descriptor */ struct sockaddr_in addr; /* server socket address */ /* wait for the GUI to tell us the portfor the LC-3 console socket */ if (fscanf (sim_in, "%hd", &port) != 1) return -1; /* don't buffer output to GUI */ if (setvbuf (stdout, NULL, _IONBF, 0) == -1) return -1; /* create a TCP socket */ if ((fd = socket (PF_INET, SOCK_STREAM, 0)) == -1) return -1; /* bind the port to the loopback address with any port */ bzero (&addr, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); addr.sin_port = 0; if (bind (fd, (struct sockaddr*)&addr, sizeof (addr)) == -1) { close (fd); return -1; } /* now connect to the given port */ addr.sin_port = htons (port); if (connect (fd, (struct sockaddr*)&addr, sizeof (struct sockaddr_in)) == -1) { close (fd); return -1; } /* use it for LC-3 keyboard and display I/O */ if ((lc3in = fdopen (fd, "r")) == NULL || (lc3out = fdopen (fd, "w")) == NULL || setvbuf (lc3out, NULL, _IONBF, 0) == -1) { close (fd); return -1; } return 0; } static char* simple_readline (const char* prompt) { char buf[200]; char* strip_nl; struct pollfd p; /* If we exhaust all commands after being interrupted by the GUI, start running again... */ if (gui_mode) { p.fd = fileno (sim_in); p.events = POLLIN; if ((poll (&p, 1, 0) != 1 || (p.revents & POLLIN) == 0) && interrupted_at_gui_request) { /* flag is reset to 0 in cmd_continue */ return strdup ("c"); } } /* Prompt and read a line until successful. */ while (1) { #if !defined(USE_READLINE) if (!gui_mode && script_depth == 0) printf ("%s", prompt); #endif /* read a line */ if (fgets (buf, 200, sim_in) != NULL) break; /* no more input? */ if (feof (sim_in)) return NULL; /* Otherwise, probably a CTRL-C, so print a blank line and (possibly) another prompt, then try again. */ puts (""); } /* strip carriage returns and linefeeds */ for (strip_nl = buf + strlen (buf) - 1; strip_nl >= buf && (*strip_nl == '\n' || *strip_nl == '\r'); strip_nl--); *++strip_nl = 0; return strdup (buf); } static void command_loop () { int cword_len; unsigned char* cmd = NULL; unsigned char* start; unsigned char* last_cmd = NULL; unsigned char cword[MAX_CMD_WORD_LEN]; const command_t* a_command; while (!stop_scripts && (cmd = lc3readline ("(lc3sim) ")) != NULL) { /* Skip white space. */ for (start = cmd; isspace (*start); start++); if (*start == '\0') { /* An empty line repeats the last command, if allowed. */ free (cmd); if ((cmd = last_cmd) == NULL) continue; /* Skip white space. */ for (start = cmd; isspace (*start); start++); } else if (last_cmd != NULL) free (last_cmd); last_cmd = NULL; /* Should never fail; just ignore the command if it does. */ /* 40 below == MAX_CMD_WORD_LEN - 1 */ if (sscanf (start, "%40s", cword) != 1) { free (cmd); break; } /* Record command word length, then point to arguments. */ cword_len = strlen (cword); for (start += cword_len; isspace (*start); start++); /* Match command word to list of commands. */ a_command = command; while (1) { if (a_command->command == NULL) { /* No match found--complain! */ free (cmd); printf ("Unknown command. Type 'h' for help.\n"); break; } /* Try to match a_command. */ if (strncasecmp (cword, a_command->command, cword_len) == 0 && cword_len >= a_command->min_len && (gui_mode || (a_command->flags & CMD_FLAG_GUI_ONLY) == 0)) { /* Execute the command. */ (*a_command->cmd_func) (start); /* Handle list type and repeatable commands. */ if (a_command->flags & CMD_FLAG_LIST_TYPE) { unsigned char buf[MAX_CMD_WORD_LEN + 5]; strcpy (buf, cword); strcat (buf, " more"); last_cmd = strdup (buf); } else if (a_command->flags & CMD_FLAG_REPEATABLE && script_depth == 0) { last_cmd = cmd; } else { free (cmd); } break; } a_command++; } } } int main (int argc, char** argv) { /* check for -gui argument */ sim_in = stdin; if (argc > 1 && strcmp (argv[1], "-gui") == 0) { if (launch_gui_connection () != 0) { printf ("failed to connect to GUI\n"); return 1; } /* skip the -gui argument in later parsing */ argc--; argv++; gui_mode = 1; } else { lc3out = stdout; lc3in = stdin; gui_mode = 0; #if defined(USE_READLINE) lc3readline = readline; #endif } /* used to simulate random device timing behavior */ srandom (time (NULL)); /* used to halt LC-3 when CTRL-C pressed */ signal (SIGINT, halt_lc3); /* load any object, symbol, or script files requested on command line */ if (argc == 3 && strcmp (argv[1], "-s") == 0) { start_script = argv[2]; init_machine (); /* also executes script */ return 0; } else if (argc == 2 && strcmp (argv[1], "-h") != 0) { start_file = strdup (argv[1]); init_machine (); /* also loads file */ } else if (argc != 1) { /* argv[0] may not be valid if -gui entered */ printf ("syntax: lc3sim [<object file>|<symbol file>]\n"); printf (" lc3sim [-s <script file>]\n"); printf (" lc3sim -h\n"); return 0; } else init_machine (); command_loop (); puts (""); return 0; } int read_memory (int addr) { struct pollfd p; switch (addr) { case 0xFE00: /* KBSR */ if (!last_KBSR_read) { p.fd = fileno (lc3in); p.events = POLLIN; if (poll (&p, 1, 0) == 1 && (p.revents & POLLIN) != 0) last_KBSR_read = (!rand_device || (random () & 15) == 0); } return (last_KBSR_read ? 0x8000 : 0x0000); case 0xFE02: /* KBDR */ if (last_KBSR_read && (lc3_memory[0xFE02] = fgetc (lc3in)) == -1) { /* Should not happen in GUI mode. */ /* FIXME: This won't show up correctly in GUI. Exit is likely to be detected first, and error message given (LC-3 sim. died), followed by message below (read past end), then Tcl/Tk error caused by bad window access after sim died. Confusing sequence if it occurs. */ if (gui_mode) puts ("ERR {LC-3 read past end of input stream.}"); else puts ("LC-3 read past end of input stream."); exit (3); } last_KBSR_read = 0; return lc3_memory[0xFE02]; case 0xFE04: /* DSR */ if (!last_DSR_read) last_DSR_read = (!rand_device || (random () & 15) == 0); return (last_DSR_read ? 0x8000 : 0x0000); case 0xFE06: /* DDR */ return 0x0000; case 0xFFFE: return 0x8000; /* MCR */ } return lc3_memory[addr]; } void write_memory (int addr, int value) { switch (addr) { case 0xFE00: /* KBSR */ case 0xFE02: /* KBDR */ case 0xFE04: /* DSR */ return; case 0xFE06: /* DDR */ if (last_DSR_read == 0) return; fprintf (lc3out, "%c", value); fflush (lc3out); last_DSR_read = 0; return; case 0xFFFE: if ((value & 0x8000) == 0) should_halt = 1; return; } /* No need to write/update GUI if the same value is already in memory. */ if (value != lc3_memory[addr]) { lc3_memory[addr] = value; if (gui_mode) { if (!delay_mem_update) disassemble_one (addr); else { lc3_show_later[addr] = 1; have_mem_to_dump = 1; /* a hint */ } } } } static int read_obj_file (const unsigned char* filename, int* startp, int* endp) { FILE* f; int start, addr; unsigned char buf[2]; if ((f = fopen (filename, "r")) == NULL) return -1; if (fread (buf, 2, 1, f) != 1) { fclose (f); return -1; } addr = start = (buf[0] << 8) | buf[1]; while (fread (buf, 2, 1, f) == 1) { write_memory (addr, (buf[0] << 8) | buf[1]); addr = (addr + 1) & 0xFFFF; } fclose (f); squash_symbols (start, addr); *startp = start; *endp = addr; return 0; } static int read_sym_file (const unsigned char* filename) { FILE* f; int adding = 0; unsigned char buf[100]; unsigned char sym[81]; int addr; if ((f = fopen (filename, "r")) == NULL) return -1; while (fgets (buf, 100, f) != NULL) { if (!adding) { if (sscanf (buf, "%*s%*s%80s", sym) == 1 && strcmp (sym, "------------") == 0) adding = 1; continue; } if (sscanf (buf, "%*s%80s%x", sym, &addr) != 2) break; add_symbol (sym, addr, 1); } fclose (f); return 0; } static void squash_symbols (int addr_s, int addr_e) { while (addr_s != addr_e) { remove_symbol_at_addr (addr_s); addr_s = (addr_s + 1) & 0xFFFF; } } static void init_machine () { int os_start, os_end; in_init = 1; bzero (lc3_register, sizeof (lc3_register)); REG (R_PSR) = (2L << 9); /* set to condition ZERO */ bzero (lc3_memory, sizeof (lc3_memory)); bzero (lc3_show_later, sizeof (lc3_show_later)); bzero (lc3_sym_names, sizeof (lc3_sym_names)); bzero (lc3_sym_hash, sizeof (lc3_sym_hash)); clear_all_breakpoints (); if (read_obj_file (INSTALL_DIR "/lc3os.obj", &os_start, &os_end) == -1) { if (gui_mode) puts ("ERR {Failed to read LC-3 OS code.}"); else puts ("Failed to read LC-3 OS code."); show_state_if_stop_visible (); } else { if (read_sym_file (INSTALL_DIR "/lc3os.sym") == -1) { if (gui_mode) puts ("ERR {Failed to read LC-3 OS symbols.}"); else puts ("Failed to read LC-3 OS symbols."); } if (gui_mode) /* load new code into GUI display */ disassemble (os_start, os_end); REG (R_PC) = 0x0200; run_until_stopped (); } in_init = 0; if (start_script != NULL) cmd_execute (start_script); else if (start_file != NULL) cmd_file (start_file); } /* only called in GUI mode */ static void print_register (int which) { printf ("REG R%d x%04X\n", which, REG (which)); /* condition codes are not stored outside of PSR */ if (which == R_PSR) printf ("REG R%d %s\n", NUM_REGS, ccodes[(REG (R_PSR) >> 9) & 7]); /* change focus in GUI */ printf ("TOCODE\n"); } static void print_registers () { int regnum; if (!gui_mode) { printf ("PC=x%04X IR=x%04X PSR=x%04X (%s)\n", REG (R_PC), REG (R_IR), REG (R_PSR), ccodes[(REG (R_PSR) >> 9) & 7]); for (regnum = 0; regnum < R_PC; regnum++) printf ("R%d=x%04X ", regnum, REG (regnum)); puts (""); disassemble_one (REG (R_PC)); } else { for (regnum = 0; regnum < NUM_REGS; regnum++) printf ("REG R%d x%04X\n", regnum, REG (regnum)); /* regnum is now NUM_REGS */ printf ("REG R%d %s\n", regnum, ccodes[(REG (R_PSR) >> 9) & 7]); } } static void dump_delayed_mem_updates () { int addr; if (!have_mem_to_dump) return; have_mem_to_dump = 0; /* FIXME: Could use a hash table here, but hint is probably enough. */ for (addr = 0; addr < 65536; addr++) { if (lc3_show_later[addr]) { disassemble_one (addr); lc3_show_later[addr] = 0; } } } static void show_state_if_stop_visible () { /* If the GUI has interrupted the simulator (e.g., to set or clear a breakpoint), print nothing. The simulator restarts automatically unless a new file is loaded, in which case cmd_file performs the updates. */ if (interrupted_at_gui_request) return; if (gui_mode && delay_mem_update) dump_delayed_mem_updates (); print_registers (); } static void print_operands (int addr, int inst, format_t fmt) { int found = 0, tgt; if (fmt & FMT_R1) { printf ("%sR%d", (found ? "," : ""), F_DR (inst)); found = 1; } if (fmt & FMT_R2) { printf ("%sR%d", (found ? "," : ""), F_SR1 (inst)); found = 1; } if (fmt & FMT_R3) { printf ("%sR%d", (found ? "," : ""), F_SR2 (inst)); found = 1; } if (fmt & FMT_IMM5) { printf ("%s#%d", (found ? "," : ""), F_imm5 (inst)); found = 1; } if (fmt & FMT_IMM6) { printf ("%s#%d", (found ? "," : ""), F_imm6 (inst)); found = 1; } if (fmt & FMT_VEC8) { printf ("%sx%02X", (found ? "," : ""), F_vec8 (inst)); found = 1; } if (fmt & FMT_ASC8) { printf ("%s", (found ? "," : "")); found = 1; switch (F_vec8 (inst)) { case 7: printf ("'\\a'"); break; case 8: printf ("'\\b'"); break; case 9: printf ("'\\t'"); break; case 10: printf ("'\\n'"); break; case 11: printf ("'\\v'"); break; case 12: printf ("'\\f'"); break; case 13: printf ("'\\r'"); break; case 27: printf ("'\\e'"); break; case 34: printf ("'\\\"'"); break; case 44: printf ("'\\''"); break; case 92: printf ("'\\\\'"); break; default: if (isprint (F_vec8 (inst))) printf ("'%c'", F_vec8 (inst)); else printf ("x%02X", F_vec8 (inst)); break; } } if (fmt & FMT_IMM9) { printf ("%s", (found ? "," : "")); found = 1; tgt = (addr + 1 + F_imm9 (inst)) & 0xFFFF; if (lc3_sym_names[tgt] != NULL) printf ("%s", lc3_sym_names[tgt]->name); else printf ("x%04X", tgt); } if (fmt & FMT_IMM11) { printf ("%s", (found ? "," : "")); found = 1; tgt = (addr + 1 + F_imm11 (inst)) & 0xFFFF; if (lc3_sym_names[tgt] != NULL) printf ("%s", lc3_sym_names[tgt]->name); else printf ("x%04X", tgt); } if (fmt & FMT_IMM16) { printf ("%s", (found ? "," : "")); found = 1; if (lc3_sym_names[inst] != NULL) printf ("%s", lc3_sym_names[inst]->name); else printf ("x%04X", inst); } } static void disassemble_one (int addr) { static const char* const dis_cc[8] = { "", "P", "Z", "ZP", "N", "NP", "NZ", "NZP" }; int inst = read_memory (addr); /* GUI prefix */ if (gui_mode) printf ("CODE%c%5d", (!in_init && addr == lc3_register[R_PC] ? 'P' : ' '), addr + 1); /* Try to find a label. */ if (lc3_sym_names[addr] != NULL) printf ("%c %16.16s x%04X x%04X ", (lc3_breakpoints[addr] == BPT_USER ? 'B' : ' '), lc3_sym_names[addr]->name, addr, inst); else printf ("%c %17sx%04X x%04X ", (lc3_breakpoints[addr] == BPT_USER ? 'B' : ' '), "", addr, inst); /* Try to disassemble it. */ #define DEF_INST(name,format,mask,match,flags,code) \ if ((inst & (mask)) == (match)) { \ if ((format) & FMT_CC) \ printf ("%s%-*s", #name, (int)(OPCODE_WIDTH - strlen (#name)), \ dis_cc[F_CC (inst) >> 9]); \ else \ printf ("%-*s", OPCODE_WIDTH, #name); \ print_operands (addr, inst, (format)); \ goto printed; \ } #define DEF_P_OP(name,format,mask,match) \ DEF_INST(name,format,mask,match,FLG_NONE,{}) #include "lc3.def" #undef DEF_P_OP #undef DEF_INST printf ("%-*s", OPCODE_WIDTH, "???"); printed: puts (""); } static void disassemble (int addr_s, int addr_e) { do { disassemble_one (addr_s); addr_s = (addr_s + 1) & 0xFFFF; } while (addr_s != addr_e); } static void dump_memory (int addr_s, int addr_e) { int start, addr, i; int a[12]; if (addr_s >= addr_e) addr_e += 0x10000; for (start = (addr_s / 12) * 12; start < addr_e; start = start + 12) { printf ("%04X: ", start & 0xFFFF); for (i = 0, addr = start; i < 12; i++, addr++) { if (addr >= addr_s && addr < addr_e) printf ("%04X ", (a[i] = read_memory (addr & 0xFFFF))); else printf (" "); } printf (" "); for (i = 0, addr = start; i < 12; i++, addr++) { if (addr >= addr_s && addr < addr_e) printf ("%c", (a[i] < 0x100 && isprint (a[i])) ? a[i] : '.'); else printf (" "); } puts (""); } } static void run_until_stopped () { struct termios tio; int old_lflag, old_min, old_time, tty_fail; should_halt = 0; if (gui_mode) { /* removes PC marker in GUI */ printf ("CONT\n"); tty_fail = 1; } else if (!isatty (fileno (lc3in)) || tcgetattr (fileno (lc3in), &tio) != 0) tty_fail = 1; else { tty_fail = 0; old_lflag = tio.c_lflag; old_min = tio.c_cc[VMIN]; old_time = tio.c_cc[VTIME]; tio.c_lflag &= ~(ICANON | ECHO); tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; (void)tcsetattr (fileno (lc3in), TCSANOW, &tio); } while (!should_halt && execute_instruction ()); if (!tty_fail) { tio.c_lflag = old_lflag; tio.c_cc[VMIN] = old_min; tio.c_cc[VTIME] = old_time; (void)tcsetattr (fileno (lc3in), TCSANOW, &tio); /* Discard any remaining input if requested. This flush occurs when the LC-3 stops, in which case any remaining input to the console will be treated as simulator commands if it is not discarded. However, discarding can interfere with command sequences that include moderately long execution periods. As with gdb, not discarding is the default, since typing in a bunch of random junk that happens to look like valid commands happens less frequently than the case above, although I myself have been bitten a few times in gdb by pressing return once too often after issuing a repeatable command. */ if (!keep_input_on_stop) (void)tcflush (fileno (lc3in), TCIFLUSH); } /* stopped by CTRL-C? Check if we need a stop notice... */ if (need_a_stop_notice) { printf ("\nLC-3 stopped.\n\n"); need_a_stop_notice = 0; } /* If stopped for any reason other than interruption by GUI, clear system breakpoint and terminate any "finish" command. */ if (!interrupted_at_gui_request) { sys_bpt_addr = -1; finish_depth = 0; } /* Dump memory and registers if necessary. */ show_state_if_stop_visible (); } static void clear_breakpoint (int addr) { if (lc3_breakpoints[addr] != BPT_USER) { if (!gui_mode) printf ("No such breakpoint was set.\n"); } else { if (gui_mode) printf ("BCLEAR %d\n", addr + 1); else printf ("Cleared breakpoint at x%04X.\n", addr); } lc3_breakpoints[addr] = BPT_NONE; } static void clear_all_breakpoints () { /* If other breakpoint types are to be supported, this code needs to avoid clobbering non-user breakpoints. */ bzero (lc3_breakpoints, sizeof (lc3_breakpoints)); } static void list_breakpoints () { int i, found = 0; /* A bit hokey, but no big deal for this few. */ for (i = 0; i < 65536; i++) { if (lc3_breakpoints[i] == BPT_USER) { if (!found) { printf ("The following instructions are set as " "breakpoints:\n"); found = 1; } disassemble_one (i); } } if (!found) printf ("No breakpoints are set.\n"); } static void set_breakpoint (int addr) { if (lc3_breakpoints[addr] == BPT_USER) { if (!gui_mode) printf ("That breakpoint is already set.\n"); } else { lc3_breakpoints[addr] = BPT_USER; if (gui_mode) printf ("BREAK %d\n", addr + 1); else printf ("Set breakpoint at x%04X.\n", addr); } } static void cmd_break (const unsigned char* args) { unsigned char opt[11], addr_str[MAX_LABEL_LEN], trash[2]; int num_args, opt_len, addr; /* 80 == MAX_LABEL_LEN - 1 */ num_args = sscanf (args, "%10s%80s%1s", opt, addr_str, trash); if (num_args > 0) { opt_len = strlen (opt); if (strncasecmp (opt, "list", opt_len) == 0) { if (num_args > 1) warn_too_many_args (); list_breakpoints (); return; } if (num_args > 1) { if (num_args > 2) warn_too_many_args (); addr = parse_address (addr_str); if (strncasecmp (opt, "clear", opt_len) == 0) { if (strcasecmp (addr_str, "all") == 0) { clear_all_breakpoints (); if (!gui_mode) printf ("Cleared all breakpoints.\n"); return; } if (addr != -1) clear_breakpoint (addr); else puts (BAD_ADDRESS); return; } else if (strncasecmp (opt, "set", opt_len) == 0) { if (addr != -1) set_breakpoint (addr); else puts (BAD_ADDRESS); return; } } } printf ("breakpoint options include:\n"); printf (" break clear <addr>|all -- clear one or all breakpoints\n"); printf (" break list -- list all breakpoints\n"); printf (" break set <addr> -- set a breakpoint\n"); } static void warn_too_many_args () { /* Spaces in entry boxes in the GUI appear as extra arguments when handed to the command line; we silently ignore them. */ if (!gui_mode) puts (TOO_MANY_ARGS); } static void no_args_allowed (const unsigned char* args) { if (*args != '\0') warn_too_many_args (); } static void cmd_continue (const unsigned char* args) { no_args_allowed (args); if (interrupted_at_gui_request) interrupted_at_gui_request = 0; else flush_console_input (); run_until_stopped (); } static void cmd_dump (const unsigned char* args) { static int last_end = 0; int start, end; if (parse_range (args, &start, &end, last_end, 48) == 0) { dump_memory (start, end); last_end = end; return; } printf ("dump options include:\n"); printf (" dump -- dump memory around PC\n"); printf (" dump <addr> -- dump memory starting from an " "address or label\n"); printf (" dump <addr> <addr> -- dump a range of memory\n"); printf (" dump more -- continue previous dump (or press " "<Enter>)\n"); } static void cmd_execute (const unsigned char* args) { FILE* previous_input; FILE* script; if (script_depth == MAX_SCRIPT_DEPTH) { /* Safer to exit than to bury a warning arbitrarily deep. */ printf ("Cannot execute more than %d levels of scripts!\n", MAX_SCRIPT_DEPTH); stop_scripts = 1; return; } if ((script = fopen (args, "r")) == NULL) { printf ("Cannot open script file \"%s\".\n", args); stop_scripts = 1; return; } script_depth++; previous_input = sim_in; sim_in = script; if (!script_uses_stdin) lc3in = script; #if defined(USE_READLINE) lc3readline = simple_readline; #endif command_loop (); sim_in = previous_input; if (--script_depth == 0) { if (gui_mode) { lc3in = lc3out; } else { lc3in = stdin; #if defined(USE_READLINE) lc3readline = readline; #endif } stop_scripts = 0; } else if (!script_uses_stdin) { /* executing previous script level--take LC-3 console input from script */ lc3in = previous_input; } fclose (script); } static void cmd_file (const unsigned char* args) { /* extra 4 chars in buf for ".obj" possibly added later */ unsigned char buf[MAX_FILE_NAME_LEN + 4]; unsigned char* ext; int len, start, end, warn = 0; len = strlen (args); if (len == 0 || len > MAX_FILE_NAME_LEN - 1) { if (gui_mode) printf ("ERR {Could not parse file name!}\n"); else printf ("syntax: file <file to load>\n"); return; } strcpy (buf, args); /* FIXME: Need to use portable path element separator characters rather than assuming use of '/'. */ if ((ext = strrchr (buf, '.')) == NULL || strchr (ext, '/') != NULL) { ext = buf + len; strcat (buf, ".obj"); } else { if (!gui_mode && strcasecmp (ext, ".sym") == 0) { if (read_sym_file (buf)) printf ("Failed to read symbols from \"%s.\"\n", buf); else printf ("Read symbols from \"%s.\"\n", buf); return; } if (strcasecmp (ext, ".obj") != 0) { if (gui_mode) printf ("ERR {Only .obj files can be loaded.}\n"); else printf ("Only .obj or .sym files can be loaded.\n"); return; } } if (read_obj_file (buf, &start, &end) == -1) { if (gui_mode) printf ("ERR {Failed to load \"%s.\"}\n", buf); else printf ("Failed to load \"%s.\"\n", buf); return; } /* Success: reload same file next time machine is reset. */ if (start_file != NULL) free (start_file); start_file = strdup (buf); strcpy (ext, ".sym"); if (read_sym_file (buf)) warn = 1; REG (R_PC) = start; /* GUI requires printing of new PC to reorient code display to line */ if (gui_mode) { /* load new code into GUI display */ disassemble (start, end); /* change focus in GUI */ printf ("TOCODE\n"); print_register (R_PC); if (warn) printf ("ERR {WARNING: No symbols are available.}\n"); } else { strcpy (ext, ".obj"); printf ("Loaded \"%s\" and set PC to x%04X\n", buf, start); if (warn) printf ("WARNING: No symbols are available.\n"); } /* Should not immediately start, even if we stopped simulator to load file. We do need to update registers and dump delayed memory changes in that case, though. Similarly, loading a file forces the simulator to forget completion of an executing "next" command. */ if (interrupted_at_gui_request) gui_stop_and_dump (); } static void cmd_finish (const unsigned char* args) { no_args_allowed (args); flush_console_input (); finish_depth = 1; run_until_stopped (); } static void cmd_help (const unsigned char* args) { printf ("file <file> -- file load (also sets PC to start of " "file)\n\n"); printf ("break ... -- breakpoint management\n\n"); printf ("continue -- continue execution\n"); printf ("finish -- execute to end of current subroutine\n"); printf ("next -- execute next instruction (full " "subroutine/trap)\n"); printf ("step -- execute one step (into " "subroutine/trap)\n\n"); printf ("list ... -- list instructions at the PC, an " "address, a label\n"); printf ("dump ... -- dump memory at the PC, an address, " "a label\n"); printf ("translate <addr> -- show the value of a label and print the " "contents\n"); printf ("printregs -- print registers and current " "instruction\n\n"); printf ("memory <addr> <val> -- set the value held in a memory " "location\n"); printf ("register <reg> <val> -- set a register to a value\n\n"); printf ("execute <file name> -- execute a script file\n\n"); printf ("reset -- reset LC-3 and reload last file\n\n"); printf ("quit -- quit the simulator\n\n"); printf ("help -- print this help\n\n"); printf ("All commands except quit can be abbreviated.\n"); } static int parse_address (const unsigned char* addr) { symbol_t* label; unsigned char* fmt; int value, negated; unsigned char trash[2]; /* default matching order: symbol, hexadecimal */ /* hexadecimal can optionally be preceded by x or X */ /* decimal must be preceded by # */ if (addr[0] == '-') { addr++; negated = 1; } else negated = 0; if ((label = find_symbol (addr, NULL)) != NULL) value = label->addr; else { if (*addr == '#') fmt = "#%d%1s"; else if (tolower (*addr) == 'x') fmt = "x%x%1s"; else fmt = "%x%1s"; if (sscanf (addr, fmt, &value, trash) != 1 || value > 0xFFFF || ((negated && value < 0) || (!negated && value < -0xFFFF))) return -1; } if (negated) value = -value; if (value < 0) value += 0x10000; return value; } static int parse_range (const unsigned char* args, int* startptr, int* endptr, int last_end, int scale) { unsigned char arg1[MAX_LABEL_LEN], arg2[MAX_LABEL_LEN], trash[2]; int num_args, start, end; /* Split and count the arguments. */ /* 80 == MAX_LABEL_LEN - 1 */ num_args = sscanf (args, "%80s%80s%1s", arg1, arg2, trash); /* If we have no automatic scaling for the range, we need both the start and the end to be specified. */ if (scale < 0 && num_args < 2) return -1; /* With no arguments, use automatic scaling around the PC. */ if (num_args < 1) { start = (REG (R_PC) + 0x10000 - scale) & 0xFFFF; end = (REG (R_PC) + scale) & 0xFFFF; goto success; } /* If the first argument is "more," start from the last stopping point. Note that "more" also requires automatic scaling. */ if (last_end >= 0 && strcasecmp (arg1, "more") == 0) { start = last_end; end = (start + 2 * scale) & 0xFFFF; if (num_args > 1) warn_too_many_args (); goto success; } /* Parse the starting address. */ if ((start = parse_address (arg1)) == -1) return -1; /* Scale to find the ending address if necessary. */ if (num_args < 2) { end = (start + 2 * scale) & 0xFFFF; goto success; } /* Parse the ending address. */ if ((end = parse_address (arg2)) == -1) return -1; /* For ranges, add 1 to specified ending address for inclusion in output. */ if (scale >= 0) end = (end + 1) & 0xFFFF; /* Check for superfluous arguments. */ if (num_args > 2) warn_too_many_args (); /* Store the results and return success. */ success: *startptr = start; *endptr = end; return 0; } static void cmd_list (const unsigned char* args) { static int last_end = 0; int start, end; if (parse_range (args, &start, &end, last_end, 10) == 0) { disassemble (start, end); last_end = end; return; } printf ("list options include:\n"); printf (" list -- list instructions around PC\n"); printf (" list <addr> -- list instructions starting from an " "address or label\n"); printf (" list <addr> <addr> -- list a range of instructions\n"); printf (" list more -- continue previous listing (or press " "<Enter>)\n"); } static void cmd_memory (const unsigned char* args) { int addr, value; if (parse_range (args, &addr, &value, -1, -1) == 0) { write_memory (addr, value); if (gui_mode) { printf ("TRANS x%04X x%04X\n", addr, value); disassemble_one (addr); } else printf ("Wrote x%04X to address x%04X.\n", value, addr); } else { if (gui_mode) { /* Address is provided by the GUI, so only the value can be bad in this case. */ printf ("ERR {No address or label corresponding to the " "desired value exists.}\n"); } else printf ("syntax: memory <addr> <value>\n"); } } static void cmd_option (const unsigned char* args) { unsigned char opt[11], onoff[6], trash[2]; int num_args, opt_len, oval; num_args = sscanf (args, "%10s%5s%1s", opt, onoff, trash); if (num_args >= 2) { opt_len = strlen (opt); if (strcasecmp (onoff, "on") == 0) oval = 1; else if (strcasecmp (onoff, "off") == 0) oval = 0; else goto show_syntax; if (num_args > 2) warn_too_many_args (); if (strncasecmp (opt, "flush", opt_len) == 0) { flush_on_start = oval; if (!gui_mode) printf ("Will %sflush the console input when starting.\n", oval ? "" : "not "); return; } if (strncasecmp (opt, "keep", opt_len) == 0) { keep_input_on_stop = oval; if (!gui_mode) printf ("Will %skeep remaining input when the LC-3 stops.\n", oval ? "" : "not "); return; } if (strncasecmp (opt, "device", opt_len) == 0) { rand_device = oval; if (!gui_mode) printf ("Will %srandomize device interactions.\n", oval ? "" : "not "); return; } /* GUI-only option: Delay memory updates to GUI until LC-3 stops? */ if (gui_mode && strncasecmp (opt, "delay", opt_len) == 0) { /* Make sure that if the option is turned off while the GUI thinks that the processor is running, state is dumped immediately. */ if (delay_mem_update && oval == 0) dump_delayed_mem_updates (); delay_mem_update = oval; return; } /* Use stdin for LC-3 console input while running script? */ if (strncasecmp (opt, "stdin", opt_len) == 0) { script_uses_stdin = oval; if (!gui_mode) printf ("Will %suse stdin for LC-3 console input during " "script execution.\n", oval ? "" : "not "); if (script_depth > 0) { if (!oval) lc3in = sim_in; else if (!gui_mode) lc3in = stdin; else lc3in = lc3out; } return; } } show_syntax: printf ("syntax: option <option> on|off\n options include:\n"); printf (" device -- simulate random device (keyboard/display)" "timing\n"); printf (" flush -- flush console input each time LC-3 starts\n"); printf (" keep -- keep remaining input when the LC-3 stops\n"); printf (" stdin -- use stdin for LC-3 console input during script " "execution\n"); printf ("NOTE: all options are ON by default\n"); } static void cmd_next (const unsigned char* args) { int next_pc = (REG (R_PC) + 1) & 0xFFFF; no_args_allowed (args); flush_console_input (); /* Note that we might hit a breakpoint immediately. */ if (execute_instruction ()) { if ((last_flags & FLG_SUBROUTINE) != 0) { /* Mark system breakpoint. This approach allows the GUI to interrupt the simulator without the simulator forgetting about the completion of this command (i.e., next). Nesting of such commands is not supported, and should not be possible to issue with the GUI. */ sys_bpt_addr = next_pc; run_until_stopped (); return; } } /* Dump memory and registers if necessary. */ show_state_if_stop_visible (); } static void cmd_printregs (const unsigned char* args) { no_args_allowed (args); print_registers (); } static void cmd_quit (const unsigned char* args) { no_args_allowed (args); exit (0); } static void cmd_register (const unsigned char* args) { static const unsigned char* const rname[NUM_REGS + 1] = { "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "PC", "IR", "PSR", "CC" }; static const unsigned char* const cc_val[4] = { "POSITIVE", "ZERO", "", "NEGATIVE" }; unsigned char arg1[MAX_LABEL_LEN], arg2[MAX_LABEL_LEN], trash[2]; int num_args, rnum, value, len; /* 80 == MAX_LABEL_LEN - 1 */ num_args = sscanf (args, "%80s%80s%1s", arg1, arg2, trash); if (num_args < 2) { /* should never happen in GUI mode */ printf ("syntax: register <reg> <value>\n"); return; } /* Determine which register is to be set. */ for (rnum = 0; ; rnum++) { if (rnum == NUM_REGS + 1) { /* No match (should never happen in GUI mode). */ puts ("Registers are R0...R7, PC, IR, PSR, and CC."); return; } if (strcasecmp (rname[rnum], arg1) == 0) break; } /* Condition codes are a special case. */ if (rnum == NUM_REGS) { len = strlen (arg2); for (value = 0; value < 4; value++) { if (strncasecmp (arg2, cc_val[value], len) == 0) { REG (R_PSR) &= ~0x0E00; REG (R_PSR) |= ((value + 1) << 9); if (gui_mode) /* printing PSR prints both PSR and CC */ print_register (R_PSR); else printf ("Set CC to %s.\n", cc_val[value]); return; } } if (gui_mode) printf ("ERR {CC can only be set to NEGATIVE, ZERO, or" "POSITIVE.}\n"); else printf ("CC can only be set to NEGATIVE, ZERO, or " "POSITIVE.\n"); return; } /* Parse the value and set the register, or complain if it's a bad value. */ if ((value = parse_address (arg2)) != -1) { REG (rnum) = value; if (gui_mode) print_register (rnum); else printf ("Set %s to x%04X.\n", rname[rnum], value); } else if (gui_mode) printf ("ERR {No address or label corresponding to the " "desired value exists.}\n"); else puts ("No address or label corresponding to the " "desired value exists."); } static void cmd_reset (const unsigned char* args) { int addr; if (script_depth > 0) { /* Should never be executing a script in GUI mode, but check... */ if (!gui_mode) puts ("Cannot reset the LC-3 from within a script."); else puts ("ERR {Cannot reset the LC-3 from within a script.}"); return; } no_args_allowed (args); /* If in GUI mode, we need to write over all memory with zeroes rather than just setting (so that disassembly info gets sent to GUI). */ if (gui_mode) { interrupted_at_gui_request = 0; for (addr = 0; addr < 65536; addr++) write_memory (addr, 0); gui_stop_and_dump (); } /* various bits of state to reset */ last_KBSR_read = 0; last_DSR_read = 0; have_mem_to_dump = 0; need_a_stop_notice = 0; sys_bpt_addr = -1; finish_depth = 0; init_machine (); /* change focus in GUI, and turn off delay cursor */ if (gui_mode) printf ("TOCODE\n"); } static void cmd_step (const unsigned char* args) { no_args_allowed (args); flush_console_input (); execute_instruction (); /* Dump memory and registers if necessary. */ show_state_if_stop_visible (); } static void cmd_translate (const unsigned char* args) { unsigned char arg1[81], trash[2]; int num_args, value; /* 80 == MAX_LABEL_LEN - 1 */ if ((num_args = sscanf (args, "%80s%1s", arg1, trash)) > 1) warn_too_many_args (); if (num_args < 1) { puts ("syntax: translate <addr>"); return; } /* Try to translate the value. */ if ((value = parse_address (arg1)) == -1) { if (gui_mode) printf ("ERR {No such address or label exists.}\n"); else puts (BAD_ADDRESS); return; } if (gui_mode) printf ("TRANS x%04X x%04X\n", value, read_memory (value)); else printf ("Address x%04X has value x%04x.\n", value, read_memory (value)); } static void gui_stop_and_dump () { /* Do not restart simulator automatically. */ interrupted_at_gui_request = 0; /* Clear any breakpoint from an executing "next" command. */ sys_bpt_addr = -1; /* Clear any "finish" command state. */ finish_depth = 0; /* Tell the GUI about any changes to memory or registers. */ dump_delayed_mem_updates (); print_registers (); } static void cmd_lc3_stop (const unsigned char* args) { /* GUI only, so no need to warn about args. */ /* Stop execution and dump state. */ gui_stop_and_dump (); } static void flush_console_input () { struct pollfd p; /* Check option and script level. Flushing would consume remainder of a script. */ if (!flush_on_start || script_depth > 0) return; /* Read a character at a time... */ p.fd = fileno (lc3in); p.events = POLLIN; while (poll (&p, 1, 0) == 1 && (p.revents & POLLIN) != 0) fgetc (lc3in); }