/* qm - The Quadruple Machine Copyright (C) 2004 Tuukka Hastrup Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* TODO: * - process syscalls, scheduler * - immediate data as long word dividend * - compiled data in float types * - MOD for floats * - long long type in DIV and MUL * - DIV and MOD by zero * - clean up segment info in machine_t, segment_t and registers * - check header CRC32 * later: * - file syscalls */ #include /* malloc, realloc: */ #include #include "time.h" #include "types.h" #include "qm-ds.h" #include "syscalls.h" #include "errors.h" #include "util.h" #include "segment.h" #include "disassembly.h" #define MAXADDR 0 /* 2^32 as word_t */ static int verbose, trace, debugger, insecure; typedef struct machine machine_t; typedef struct queue queue_t; struct machine { const char *filename; /* initial segment info from binary */ word_t text_start, text_length; word_t data_start, data_length; word_t bss_start, bss_length; /* current length is ZL-bss_start */ /* stack starts at SP, current length is 2^32-SP */ word_t sp, fp, ip, zl, ar; /* registers */ segment_t *text,*data,*bss,*stack; FILE **files; unsigned long filecount; long id; int state; #define LOADED 0 #define RUNNING 1 #define EXITED 2 #define FORKED 3 #define WAITING 4 /* in some wait queue */ #define WAITING_MSG 5 /* from anyone */ signed long exit_code; queue_t *exit_waiters, *exit_waiters_last; queue_t *senders, *senders_last; machine_t *clones; /* next in circle. NULL means no clones. */ int debugger; word_t breakpoint; /* new fields must be handled in load_machine, SYS_FORK, SYS_CLONE */ }; machine_t **machines; unsigned long machines_size; struct queue { machine_t *machine; struct queue *rest; }; /* returns truth value */ static int in_stackframe(machine_t *m, word_t address) { return ((m->sp != 0 && address >= m->sp) && (m->fp == 0 || address < m->fp)); } /* In real machines, stack is never shrunk, even though it is nice for debugging purposes. */ static word_t stack_limit(machine_t *m) { if (insecure) return MAXADDR-segment_size(m->stack); else return m->sp; } /* given an address in the machine, returns the segment, and offset in the segment. segment == NULL means failure. */ static void resolve_address(machine_t *m, word_t address, segment_t **segment, word_t *offset, unsigned long bytes) { if (address >= m->text_start && address < m->text_start + m->text_length) { *segment = m->text; *offset = address - m->text_start; } else if (address >= m->data_start && address < m->data_start + m->data_length) { *segment = m->data; *offset = address - m->data_start; } else if (address >= m->bss_start && (m->zl == 0 || address < m->zl)) { *segment = m->bss; *offset = address - m->bss_start; } else if (stack_limit(m) != 0 && address >= stack_limit(m)) { *segment = m->stack; *offset = address - stack_limit(m); } else { error("Address outside of segments: %ld, 0x%.8lx", address, address); *segment = NULL; return; } if (*offset + bytes > segment_size(*segment)) { error("Tried to access memory crossing segment end."); *segment = NULL; } } /* return value: NULL means failure. */ static byte_t *get_mem(machine_t *m, word_t address, unsigned long bytes, int write, int alignmentlength) { segment_t *segment; word_t offset; if (alignmentlength == 8) alignmentlength = 4; if (address == 0) { error("Tried to access memory at null address."); return NULL; } if (address % alignmentlength != 0) { error("Address %lu is not a multiple of %d.", address, alignmentlength); return NULL; } resolve_address(m, address, &segment, &offset, bytes); if (segment == NULL) return NULL; if (write && segment == m->text) { error("Tried to write to text segment."); return NULL; } return segment_cells(segment)+offset; } /* return value: 0 means success. */ static int read_mem(machine_t *m, word_t address, word_t *data) { byte_t *addr = get_mem(m, address, 4, 0==1, 4); if (addr == NULL) return -1; *data = *(word_t *) addr; return 0; } /* return value: 0 means success. */ static int write_mem(machine_t *m, word_t address, word_t data) { byte_t *addr = get_mem(m, address, 4, 0==0, 4); if (addr == NULL) return -1; *(word_t *) addr = data; return 0; } static int type_len(unsigned long type) { switch(type) { case FL_FD: return 8; case FL_WU: case FL_WS: case FL_FS: return 4; case FL_HU: case FL_HS: return 2; case FL_BU: case FL_BS: return 1; default: fatal("Tried to get the length for an unknown type."); } } static int move_mem(byte_t *dest, byte_t *source, unsigned long type, int neg) { int i; if (dest == NULL || source == NULL) return -1; for(i = 0; istate != LOADED && m->state != FORKED) fatal("Tried to start a machine in state %d.", m->state); m->state = RUNNING; m->id = machines_size-1; machines[machines_size-1] = m; if (verbose || trace) printf("Starting machine %ld...\n", m->id); } static void load_machine(FILE *binary, const char *filename) { struct exe_hdr header; machine_t *m = malloc(sizeof(machine_t)); check_alloc(m, "machine info"); m->filename = filename; if (verbose) printf("Loading a machine from file %s...\n", m->filename); header = read_header(binary); m->text = read_segment(binary, header.text_length, 0==1); if (verbose) hexdump_segment(m->text); m->data = read_segment(binary, header.data_length, 0==1); if (verbose) hexdump_segment(m->data); m->bss = create_segment(0==1); realloc_segment(m->bss, header.bss_length); m->stack = create_segment(0==0); m->text_start = header.text_start; m->text_length = header.text_length; m->data_start = header.data_start; m->data_length = header.data_length; m->bss_start = header.data_start + header.data_length; m->bss_length = header.bss_length; m->sp = m->fp = MAXADDR; /* 0? */ m->ip = header.start_addr; m->zl = m->bss_start + m->bss_length; m->ar = 0; m->files = realloc(NULL, sizeof(FILE *)*3); check_alloc(m->files, "initial file descriptors"); m->files[0] = stdin; m->files[1] = stdout; m->files[2] = stderr; m->filecount = 3; m->id = 0; /* meaningless before start_machine */ m->state = LOADED; m->exit_code = 0; /* meaningless before stop_machine */ m->exit_waiters = m->exit_waiters_last = NULL; m->senders = m->senders_last = NULL; m->clones = NULL; m->debugger = debugger; m->breakpoint = 0; start_machine(m); } static int assign_sp(machine_t *m, word_t value) { if (value % 4 != 0) { error("Assigned SP not word-aligned."); return -1; } if (value != 0 && (m->zl == 0 || value < m->zl )) { error("Assigned SP below ZL."); return -1; } if (m->fp != 0 && value > m->fp) { error("Assigned SP above FP."); return -1; } if (insecure) { if (value == 0 || (stack_limit(m) != 0 && value >= stack_limit(m))) { m->sp = value; return 0; /* stack size is already enough */ } } m->sp = value; realloc_segment(m->stack, MAXADDR-m->sp); if (verbose) printf("New stack size %lu.\n", MAXADDR-m->sp); return 0; } static int assign_fp(machine_t *m, word_t value) { m->fp = value; if (m->fp != 0 && m->fp < m->sp) { error("Assigned FP below SP."); return -1; } return 0; } static int assign_ip(machine_t *m, word_t value) { m->ip = value; if (m->ip % 4 != 0) { error("Assigned IP not word-aligned."); return -1; } if (m->ip == 0) { error("Assigned IP to null address."); return -1; } if (!insecure && (m->ip < m->text_start || m->ip >= m->text_start + m->text_length)) { error("Assigned IP outside of text segment."); return -1; } return 0; } /* return value: 0 means ok, <0 means not. */ static int check_zl(machine_t *m, word_t value) { if (value != 0 && value < m->bss_start) { error("Assigned ZL below the segment."); return -1; } else if (stack_limit(m) != 0 && value > stack_limit(m)) { error("Assigned ZL above stack start."); return -1; } return 0; } static int assign_zl(machine_t *m, word_t value) { machine_t *clone; int ret = check_zl(m, value); if (ret != 0) return ret; /* check ZL for clones, and get the segment from them */ if (m->clones != NULL) { for (clone = m->clones; clone != m; clone = clone->clones) { ret = check_zl(clone, value); if (ret != 0) break; assert (m->bss == clone->bss); segment_freeref(clone->bss); clone->bss = NULL; } } if (ret == 0) { /* if ok for all clones */ m->zl = value; realloc_segment(m->bss, m->zl - m->bss_start); if (verbose) printf("New bss size %lu.\n", m->zl - m->bss_start); } /* give the segment back to clones, and set their ZL */ if (m->clones != NULL) for (clone = m->clones; clone != m; clone = clone->clones) { if (clone->bss != NULL) break; clone->bss = segment_newref(m->bss); clone->zl = m->zl; } return ret; } static int assign_ar(machine_t *m, word_t value) { m->ar = value; return 0; } static int push_stack(machine_t *m, word_t data) { int ret = assign_sp(m, m->sp - 4); if (ret != 0) return ret; return write_mem(m, m->sp, data); } /* return value: 0 means success. */ static int pop_stack(machine_t *m, word_t *data) { int ret = read_mem(m, m->sp, data); if (ret != 0) return ret; return assign_sp(m, m->sp + 4); } /* return value: 0 means success in getting an id. target can be NULL. */ static int pop_id(machine_t *m, machine_t **target) { word_t id; int ret = pop_stack(m, &id); if (ret != 0) return ret; if (id > 0x7FFFFFFFUL) { error("Machine ID can't be negative."); return -1; } if (id >= machines_size || machines[id] == NULL) *target = NULL; else *target = machines[id]; return 0; } static void stop_machine(machine_t *m, signed long code) { queue_t *nodes; m->exit_code = code; if (m->state != RUNNING) fatal("Tried to stop a machine in state %d.", m->state); m->state = EXITED; if (m->clones != NULL) { machine_t *clone = m->clones; while (clone->clones != m) clone = clone->clones; if (clone == m->clones) /* if just two were left */ clone->clones = NULL; else clone->clones = m->clones; /* skip m */ m->clones = NULL; } segment_freeref(m->text); segment_freeref(m->data); segment_freeref(m->bss); segment_freeref(m->stack); m->text = m->data = m->bss = m->stack = NULL; if (verbose || trace) printf("Machine %ld shut down with exit code %ld...\n", m->id, code); for (nodes = m->exit_waiters; nodes != NULL; ) { queue_t *waiter = nodes; int ret = push_stack(waiter->machine, code); waiter->machine->state = RUNNING; if (ret != 0) stop_machine(waiter->machine, -1); nodes = nodes->rest; free(waiter); } m->exit_waiters = m->exit_waiters_last = NULL; } /* return value: 0 means success, <0 means the sending machine crashes. */ static int perform_ipc(machine_t *sender, machine_t *receiver) { word_t addr, len, len_padded, i; int ret; byte_t *source, *dest; sender->state = RUNNING; ret = pop_stack(sender, &addr); if (ret != 0) return ret; ret = pop_stack(sender, &len); if (ret != 0) return ret; len_padded = (len + 3) / 4 * 4; source = get_mem(sender, addr, len, 0==1, 1); if (source == NULL) return -1; receiver->state = RUNNING; if (receiver->zl == 0 || receiver->zl > receiver->sp - len_padded - 4) { /* The message can't be sent because it doesn't fit. */ ret = push_stack(receiver, 0); if (ret != 0) stop_machine(receiver, ret); return push_stack(sender, -1); } receiver->sp = receiver->sp - len_padded - 4; if (!insecure || stack_limit(receiver) == 0 || stack_limit(receiver) > receiver->sp) { realloc_segment(receiver->stack, MAXADDR-receiver->sp); if (verbose) printf("New stack size %lu.\n", MAXADDR-receiver->sp); } dest = get_mem(receiver, receiver->sp+4, len_padded, 0==0, 4); assert (dest != NULL); memcpy(dest, source, len); for (i=len; isp, len_padded); assert (ret == 0); return push_stack(sender, 0); } static int perform_syscall(machine_t *m, word_t n) { if (n == SYS_READ || n == SYS_WRITE) { word_t did, fd, addr, len; int ret; byte_t *mem; ret = pop_stack(m, &fd); /* [SP+0] = parameter1 */ if (ret != 0) return ret; ret = pop_stack(m, &addr); /* [SP+4] = parameter2 */ if (ret != 0) return ret; ret = pop_stack(m, &len); /* [SP+8] = parameter3 */ if (ret != 0) return ret; mem = get_mem(m, addr, len, n != SYS_WRITE, 1); if (mem == NULL) return -1; if (verbose) printf("syscall %lu: %lu %lu(%p) %lu\n", n,fd,addr,mem,len); if (fd > 0x7FFFFFFFUL) { error("File descriptor can't be negative."); return -1; } if (fd >= m->filecount || m->files[fd] == NULL) { error("File descriptor %ld not open.", fd); return -1; } if (n == SYS_READ) { word_t i = 0; if (verbose || trace) printf("> "); while (ifiles[fd]); if (ret < 0) break; mem[i++] = (unsigned char) ret; if (ret == '\n') break; } did = i; } else { if (verbose || trace) printf("< "); did = fwrite(mem, sizeof(byte_t), len, m->files[fd]); } return push_stack(m, did); /* [SP+0] = return value */ } else if (n == SYS_FORK || n == SYS_CLONE) { int ret; unsigned long i; machine_t *child = malloc(sizeof(machine_t)); check_alloc(m, "child machine info"); /* first a shallow copy */ memcpy(child, m, sizeof(machine_t)); child->text = segment_newref(m->text); if (n == SYS_CLONE) { child->data = segment_newref(m->data); child->bss = segment_newref(m->bss); } else { child->data = segment_copy(m->data); child->bss = segment_copy(m->bss); } child->stack = segment_copy(m->stack); child->files = malloc(m->filecount*sizeof(FILE *)); check_alloc(child->files, "child file descriptors"); for (i=0; ifilecount; i++) child->files[i] = m->files[i]; /* FIXME this is clone! */ child->id = 0; child->state = FORKED; child->exit_waiters = child->exit_waiters_last = NULL; child->senders = child->senders_last = NULL; if (n == SYS_CLONE) { if (m->clones == NULL) { child->clones = m; m->clones = child; } else { child->clones = m->clones; m->clones = child; } } else { child->clones = NULL; } start_machine(child); ret = push_stack(child, 0); if (ret != 0) { stop_machine(child, -1); return push_stack(m, -1); } return push_stack(m, child->id); } else if (n == SYS_SEND) { machine_t *target; int ret = pop_id(m, &target); if (ret != 0) return ret; if (target == NULL) { return push_stack(m, -1); } if (target->state == WAITING_MSG) return perform_ipc(m, target); else { queue_t *node = malloc(sizeof(queue_t)); check_alloc(node, "send queue node"); m->state = WAITING; node->machine = m; node->rest = NULL; if (target->senders == NULL) { target->senders = node; target->senders_last = node; } else { target->senders_last->rest = node; target->senders_last = node; } } return 0; } else if (n == SYS_RECV) { queue_t *source; int ret; while (m->senders != NULL) { source = m->senders; m->senders = source->rest; if (m->senders == NULL) m->senders_last = NULL; ret = perform_ipc(source->machine, m); if (ret != 0) stop_machine(source->machine, ret); free(source); if (ret == 0) return 0; } m->state = WAITING_MSG; return 0; } else if (n == SYS_EXIT) { word_t code; int ret = pop_stack(m, &code); if (ret != 0) return ret; if (code > 0x7FFFFFFFUL) { error("Return code can't be negative."); return -1; } stop_machine(m, code); return code; } else if (n == SYS_WAIT) { machine_t *target; int ret = pop_id(m, &target); if (ret != 0) return ret; if (target == NULL) { ret = push_stack(m, -1); return ret; } if (target->state == EXITED) { ret = push_stack(m, target->exit_code); /* XXX free(target) */ return ret; } else { queue_t *node = malloc(sizeof(queue_t)); check_alloc(node, "wait queue node"); m->state = WAITING; node->machine = m; node->rest = NULL; if (target->exit_waiters == NULL) { target->exit_waiters = node; target->exit_waiters_last = node; } else { target->exit_waiters_last->rest = node; target->exit_waiters_last = node; } } return 0; } else if (n == SYS_TIME) { word_t time = get_ms_since_epoch(); return push_stack(m, time); } else if (n == SYS_HELLO) { printf("Hello, world! --syscall %lu\n", n); } else if (n == SYS_STATE) { printf("IP: 0x%.8lx, %lu\n", m->ip,m->ip); printf("SP: 0x%.8lx, %lu\n", m->sp,m->sp); printf("FP: 0x%.8lx, %lu\n", m->fp,m->fp); printf("ZL: 0x%.8lx, %lu\n", m->zl,m->zl); printf("AR: 0x%.8lx, %lu\n", m->ar,m->ar); } else if (n == SYS_FRAME) { word_t i, data; printf("Current stackframe:\n"); for (i = m->fp - 4; in_stackframe(m, i); i -= 4) { int ret = read_mem(m, i, &data); if (ret != 0) return ret; printf("0x%.8lx: 0x%.8lx\n", i, data); } } else if (n == SYS_PUTWU) { word_t num; int ret = pop_stack(m, &num); if (ret != 0) return ret; printf("The machine prints a number: %lu\n", num); } else { error("No implementation for syscall %lu.", n); return -1; } return 0; } static word_t nvalue(word_t instr[4]) { if (C_OPR1(instr[0]) != AM_IMM) fatal("Operand for syscall and SP change must be immediate."); return instr[1]; } static word_t opr_to_lvalue(machine_t *m, int mode, word_t opr) { if (mode == AM_IMM) fatal("Immediate operand can't be assignment target."); switch (mode) { case AM_ABS : return opr; break; case AM_FPPD: return m->fp+opr; break; case AM_FPND: return m->fp-opr; break; case AM_SPPD: return m->sp+opr; break; case AM_SPND: return m->sp-opr; break; case AM_ARPD: return m->ar+opr; break; case AM_ARND: return m->ar-opr; break; default: fatal("Unknown addressing mode %d.", mode); break; } } static word_t opr1_to_addr(machine_t *m, int mode, word_t opr) { if (mode == AM_IMM) return m->ip-3*sizeof(word_t); else return opr_to_lvalue(m, mode, opr); } static word_t opr2_to_addr(machine_t *m, int mode, word_t opr) { if (mode == AM_IMM) return m->ip-2*sizeof(word_t); else return opr_to_lvalue(m, mode, opr); } static word_t opr3_to_addr(machine_t *m, int mode, word_t opr) { if (mode == AM_IMM) return m->ip-1*sizeof(word_t); else return opr_to_lvalue(m, mode, opr); } static word_t lvalue1(machine_t *m, word_t instr[4]) { return opr_to_lvalue(m, C_OPR1(instr[0]), instr[1]); } /* return value: 0 means success. */ static int rvalue(machine_t *m, int mode, word_t opr, word_t *data) { if (mode == AM_IMM) { *data = opr; return 0; } else return read_mem(m, opr_to_lvalue(m, mode, opr), data); } /* return value: 0 means success. */ static int rvalue1(machine_t *m, word_t instr[4], word_t *data) { return rvalue(m, C_OPR1(instr[0]), instr[1], data); } /* return value: 0 means success. */ static int rvalue3(machine_t *m, word_t instr[4], word_t *data) { return rvalue(m, C_OPR3(instr[0]), instr[3], data); } static int assign_ip_rvalue1(machine_t *m, word_t instr[4]) { word_t data; int ret = rvalue1(m, instr, &data); if (ret != 0) return ret; return assign_ip(m, data); } static int assign_ip_rvalue3(machine_t *m, word_t instr[4]) { word_t data; int ret = rvalue3(m, instr, &data); if (ret != 0) return ret; return assign_ip(m, data); } /* return value: 0 means success, <0 means crash. */ static int step_machine(machine_t *m) { unsigned long int i, op, type, oplen; word_t instr[4], tmp; int ret; if (verbose || trace) printf("#%ld 0x%.8lx:\t", m->id, m->ip); if (!insecure && (m->text_start > m->ip || m->text_start+m->text_length < m->ip+4*sizeof(word_t))) { error("IP %lu outside of text segment.", m->ip); return -1; } for (i = 0; i<4 ; i++) { ret = read_mem(m, m->ip+i*sizeof(word_t), &instr[i]); if (ret != 0) return ret; } m->ip += 4*sizeof(word_t); if (verbose || trace) (void) disassemble(stdout, instr); op = instr[0] & OP_MASK; type = instr[0] & FL_MASK; oplen = type_len(type); switch (op) { case OP_SYSCALL: return perform_syscall(m, nvalue(instr)); break; #define MOV(neg) if ((C_OPR2(instr[0]) == AM_IMM) && \ (type == FL_FS || type == FL_FD)) { \ error("Immediate floating-point operand not possible."); \ return -1; \ } \ return move_mem(get_mem(m, lvalue1(m, instr), oplen, 0==0, oplen), \ get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), oplen, 0==1, oplen), \ type, neg); case OP_MOVE: MOV(0); break; case OP_NOT: if (type == FL_FS || type == FL_FD) { error("'not' is not allowed on float types (FS, FD)."); return -1; } MOV(1); break; #undef MOV #define OP(op) { \ byte_t *dyn1 = get_mem(m, opr1_to_addr(m, C_OPR1(instr[0]), instr[1]), oplen, 0==0, oplen);\ byte_t *dyn2 = get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), oplen, 0==1, oplen);\ byte_t *dyn3 = get_mem(m, opr3_to_addr(m, C_OPR3(instr[0]), instr[3]), oplen, 0==1, oplen);\ if (dyn1 == NULL || dyn2 == NULL || dyn3 == NULL)\ return -1;\ switch (type) { \ case FL_WU: \ *(unsigned long *) dyn1 = *(unsigned long *) dyn2 op *(unsigned long *) dyn3; \ break; \ case FL_WS: \ *(signed long *) dyn1 = *(signed long *) dyn2 op *(signed long *) dyn3; \ break; \ case FL_HU: \ *(unsigned short *) dyn1 = *(unsigned short *) dyn2 op *(unsigned short *) dyn3; \ break; \ case FL_HS: \ *(signed short *) dyn1 = *(signed short *) dyn2 op *(signed short *) dyn3; \ break; \ case FL_BU: \ *(unsigned char *) dyn1 = *(unsigned char *) dyn2 op *(unsigned char *) dyn3; \ break; \ case FL_BS: \ *(signed char *) dyn1 = *(signed char *) dyn2 op *(signed char *) dyn3; \ break; \ case FL_FS: \ *(float *) dyn1 = *(float *) dyn2 op *(float *) dyn3; \ break; \ case FL_FD: \ *(double *) dyn1 = *(double *) dyn2 op *(double *) dyn3; \ break; \ } \ } case OP_ADD: OP(+); break; case OP_SUB: OP(-); break; #undef OP #define BIT_OP(op) { \ byte_t *dyn1 = get_mem(m, opr1_to_addr(m, C_OPR1(instr[0]), instr[1]), oplen, 0==0, oplen);\ byte_t *dyn2 = get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), oplen, 0==1, oplen);\ byte_t *dyn3 = get_mem(m, opr3_to_addr(m, C_OPR3(instr[0]), instr[3]), oplen, 0==1, oplen);\ if (dyn1 == NULL || dyn2 == NULL || dyn3 == NULL)\ return -1; \ switch (type) { \ case FL_WU: \ *(unsigned long *) dyn1 = *(unsigned long *) dyn2 op *(unsigned long *) dyn3; \ break; \ case FL_WS: \ *(signed long *) dyn1 = *(signed long *) dyn2 op *(signed long *) dyn3; \ break; \ case FL_HU: \ *(unsigned short *) dyn1 = *(unsigned short *) dyn2 op *(unsigned short *) dyn3; \ break; \ case FL_HS: \ *(signed short *) dyn1 = *(signed short *) dyn2 op *(signed short *) dyn3; \ break; \ case FL_BU: \ *(unsigned char *) dyn1 = *(unsigned char *) dyn2 op *(unsigned char *) dyn3; \ break; \ case FL_BS: \ *(signed char *) dyn1 = *(signed char *) dyn2 op *(signed char *) dyn3; \ break; \ case FL_FS: \ case FL_FD: \ error("Bit operations (and, or, xor) are not " \ "allowed on float types (FS, FD)."); \ return -1; \ break; \ } \ } case OP_AND: BIT_OP(&); break; case OP_OR : BIT_OP(|); break; case OP_XOR: BIT_OP(^); break; case OP_MOD: BIT_OP(%); break; #undef BIT_OP case OP_MUL: { int reslen = (type == FL_FS || type == FL_FD)?oplen:(oplen*2); byte_t *dyn1 = get_mem(m, opr1_to_addr(m, C_OPR1(instr[0]), instr[1]), reslen, 0==0, reslen); byte_t *dyn2 = get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), oplen, 0==1, oplen); byte_t *dyn3 = get_mem(m, opr3_to_addr(m, C_OPR3(instr[0]), instr[3]), oplen, 0==1, oplen); if (dyn1 == NULL || dyn2 == NULL || dyn3 == NULL) return -1; switch (type) { case FL_WU: *(unsigned long *) dyn1 = *(unsigned long *) dyn2 * *(unsigned long *) dyn3; *(unsigned long *) (dyn1+sizeof(word_t)) = 0; break; case FL_WS: *(signed long *) dyn1 = *(signed long *) dyn2 * *(signed long *) dyn3; if (*(signed long *) dyn1 < 0) *(signed long *) (dyn1+sizeof(word_t)) = -1; else *(signed long *) (dyn1+sizeof(word_t)) = 0; break; case FL_HU: *(unsigned long *) dyn1 = *(unsigned short *) dyn2 * *(unsigned short *) dyn3; break; case FL_HS: *(signed long *) dyn1 = *(signed short *) dyn2 * *(signed short *) dyn3; break; case FL_BU: *(unsigned short *) dyn1 = *(unsigned char *) dyn2 * *(unsigned char *) dyn3; break; case FL_BS: *(signed short *) dyn1 = *(signed char *) dyn2 * *(signed char *) dyn3; break; case FL_FS: *(float *) dyn1 = *(float *) dyn2 * *(float *) dyn3; break; case FL_FD: *(double *) dyn1 = *(double *) dyn2 * *(double *) dyn3; break; } } break; case OP_DIV: { int divlen = (type == FL_FS || type == FL_FD)?oplen:(oplen*2); byte_t *dyn1 = get_mem(m, opr1_to_addr(m, C_OPR1(instr[0]), instr[1]), oplen, 0==0, oplen); byte_t *dyn2 = get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), divlen, 0==1, divlen); byte_t *dyn3 = get_mem(m, opr3_to_addr(m, C_OPR3(instr[0]), instr[3]), oplen, 0==1, oplen); if (dyn1 == NULL || dyn2 == NULL || dyn3 == NULL) return -1; switch (type) { case FL_WU: *(unsigned long *) dyn1 = *(unsigned long *) dyn2 / *(unsigned long *) dyn3; if (*(unsigned long *) (dyn2+sizeof(word_t)) != 0) error("In long division, non-zero upper word " "was ignored by current implementation!"); break; case FL_WS: *(signed long *) dyn1 = *(signed long *) dyn2 / *(signed long *) dyn3; if ((*(signed long *) dyn2 < 0 && *(signed long *) (dyn2+sizeof(word_t)) != -1) || (*(signed long *) dyn2 >= 0 && *(signed long *) (dyn2+sizeof(word_t)) != 0)) error("In long division, non-zero upper word " "was ignored by current implementation!"); break; case FL_HU: *(unsigned short *) dyn1 = *(unsigned long *) dyn2 / *(unsigned short *) dyn3; break; case FL_HS: *(signed short *) dyn1 = *(signed long *) dyn2 / *(signed short *) dyn3; break; case FL_BU: *(unsigned char *) dyn1 = *(unsigned short *) dyn2 / *(unsigned char *) dyn3; break; case FL_BS: *(signed char *) dyn1 = *(signed short *) dyn2 / *(signed char *) dyn3; break; case FL_FS: *(float *) dyn1 = *(float *) dyn2 / *(float *) dyn3; break; case FL_FD: *(double *) dyn1 = *(double *) dyn2 / *(double *) dyn3; break; } } break; case OP_STAR: return write_mem(m, lvalue1(m, instr), m->ar); break; case OP_STSP: return write_mem(m, lvalue1(m, instr), m->sp); break; case OP_STFP: return write_mem(m, lvalue1(m, instr), m->fp); break; case OP_STZL: return write_mem(m, lvalue1(m, instr), m->zl); break; case OP_LDAR: ret = rvalue1(m, instr, &tmp); if (ret != 0) return ret; return assign_ar(m, tmp); case OP_LDSP: ret = rvalue1(m, instr, &tmp); if (ret != 0) return ret; return assign_sp(m, tmp); case OP_LDFP: ret = rvalue1(m, instr, &tmp); if (ret != 0) return ret; return assign_fp(m, tmp); case OP_LDZL: ret = rvalue1(m, instr, &tmp); if (ret != 0) return ret; return assign_zl(m, tmp); case OP_MVARSP: return assign_sp(m, m->ar); break; case OP_MVARFP: return assign_fp(m, m->ar); break; case OP_MVARZL: return assign_zl(m, m->ar); break; case OP_MVSPAR: return assign_ar(m, m->sp); break; case OP_MVSPFP: return assign_fp(m, m->sp); break; case OP_MVSPZL: return assign_zl(m, m->sp); break; case OP_MVFPAR: return assign_ar(m, m->fp); break; case OP_MVFPSP: return assign_sp(m, m->fp); break; case OP_MVFPZL: return assign_zl(m, m->fp); break; case OP_MVZLAR: return assign_ar(m, m->zl); break; case OP_MVZLSP: return assign_sp(m, m->zl); break; case OP_MVZLFP: return assign_fp(m, m->zl); break; case OP_INCSP: return assign_sp(m, m->sp + nvalue(instr)); break; case OP_DECSP: return assign_sp(m, m->sp - nvalue(instr)); break; #define IF(cmp) { \ byte_t *dyn1 = get_mem(m, opr1_to_addr(m, C_OPR1(instr[0]), instr[1]), oplen, 0==1, oplen);\ byte_t *dyn2 = get_mem(m, opr2_to_addr(m, C_OPR2(instr[0]), instr[2]), oplen, 0==1, oplen);\ if (dyn1 == NULL || dyn2 == NULL)\ return -1;\ switch (type) { \ case FL_WU: \ if (*(unsigned long *) dyn1 cmp *(unsigned long *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_WS: \ if (*(signed long *) dyn1 cmp *(signed long *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_HU: \ if (*(unsigned short *) dyn1 cmp *(unsigned short *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_HS: \ if (*(signed short *) dyn1 cmp *(signed short *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_BU: \ if (*(unsigned char *) dyn1 cmp *(unsigned char *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_BS: \ if (*(signed char *) dyn1 cmp *(signed char *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_FS: \ if (*(float *) dyn1 cmp *(float *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ case FL_FD: \ if (*(double *) dyn1 cmp *(double *) dyn2) \ return assign_ip_rvalue3(m, instr); \ break; \ } \ } case OP_EQ: IF(==); break; case OP_LT: IF(< ); break; case OP_LE: IF(<=); break; case OP_GT: IF(> ); break; case OP_GE: IF(>=); break; case OP_NE: IF(!=); break; #undef IF case OP_JMP: return assign_ip_rvalue1(m, instr); break; default: error("Opcode 0x%.8lx not known.", op); return -1; break; } return 0; /* no errors if we get to this point */ } #define READBUFSIZE 81 /* return value: 0 means success, <0 means quit. */ static int debugger_loop(machine_t *m) { int len; char buffer[READBUFSIZE]; static char last[READBUFSIZE] = ""; word_t addr; printf("#%ld 0x%.8lx:\t", m->id, m->ip); (void) disassemble(stdout, (word_t *)get_mem(m, m->ip, 4*4, 0, 4)); while (0==0) { printf("#%ld 0x%.8lx:\tdebugger> ", m->id, m->ip); if (!fgets(buffer, READBUFSIZE, stdin)) return -1; len = strlen(buffer); if (buffer[len-1] != '\n') printf("Line too long.\n"); else buffer[len-1] = '\0'; if (equals(buffer, "")) strcpy(buffer, last); else strcpy(last,buffer); if (equals(buffer, "step")) break; else if (equals(buffer, "run")) { m->debugger = 0==1; break; } else if (equals(buffer, "disassemble")) (void) disassemble(stdout, (word_t *)get_mem(m, m->ip, 4*4, 0, 4)); else if (equals(buffer, "registers")) (void) perform_syscall(m, SYS_STATE); else if (equals(buffer, "stack")) (void) perform_syscall(m, SYS_FRAME); else if (equals(buffer, "quit")) return -1; else if (starts(buffer, "breakpoint")) { m->breakpoint = strtoul(buffer+11, NULL, 0); printf("#%ld breakpoint 0x%.8lx\n", m->id, m->breakpoint); } else if ((addr = strtoul(buffer, NULL, 0))) { word_t data; int ret = read_mem(m, addr, &data); if (ret == 0) { printf("[0x%.8lx] = 0x%.8lx, %lu\n", addr, data, data); printf("%ld (S), ", *(long *)&data); printf("%u (H), ", *(unsigned short *)&data); printf("%d (HS), ", *(short *)&data); printf("%u (B), ", *(unsigned char *)&data); printf("%d (BS)\n", *(char *)&data); printf("%f (FD), ", *(double *)&data); printf("%f (FS)\n", *(float *)&data); } } else printf("Unknown command.\n" "Commands: step, run, quit\n" " disassemble, registers, stack\n" " breakpoint [address]\n" "Number prints the word in address.\n" "Empty line repeats last command.\n"); } return 0; } /* return value: exit code from the last machine, <0 means crash. */ static int main_loop(void) { int ret; unsigned long i; while (0==0) { int left = 0; for (i=0; istate != RUNNING) continue; left++; if (m->ip == m->breakpoint) m->debugger = (0==0); if (m->debugger) if (debugger_loop(m)) exit(0); ret = step_machine(m); if (ret < 0) stop_machine(m, ret); } if (left == 0) break; /* no machines left, simulation done */ } return ret; } int main(int argc, char **argv) { FILE *binary = NULL; char *binaryname; int ret, i, left; word_t j; set_epoch(); verbose = trace = debugger = insecure = (0==1); if (argc == 1) { printf("Usage: %s [-v] [-t] [-g] [-i] objectfile\n", argv[0]); printf(" %s -V\n", argv[0]); return 5; } for (i = 1; i < argc; i++) { if (equals(argv[i],"-V")) { printf("simulator (%s) %s\n", PACKAGE, VERSION); exit(0); } else if (equals(argv[i],"-v")) verbose = (0==0); else if (equals(argv[i],"-t")) trace = (0==0); else if (equals(argv[i],"-g")) debugger = (0==0); else if (equals(argv[i],"-i")) insecure = (0==0); else { if (binary) fatal("Object file already open, " "can't have '%s' as well.", argv[i]); binary = fopen(argv[i], "rb"); binaryname = argv[i]; if (!binary) pfatal("Opening object file"); } } /* set line buffering for debug modes */ if (verbose || trace || debugger) setvbuf(stdout, (char *)NULL, _IOLBF, 0); if (!binary) fatal("No object filename given."); machines = NULL; machines_size = 0; load_machine(binary, binaryname); ret = fclose(binary); if (ret != 0) pfatal("Closing object file"); if (verbose) printf("Starting simulation...\n"); ret = main_loop(); if (!verbose) return ret; left = 0; for (j=0; jstate == EXITED) continue; left++; if (left == 1) /* If this is the first to report */ printf("No more running machines, simulation in deadlock.\n\n"); if (m->state == WAITING_MSG) printf("Machine #%ld is waiting for a message.\n", m->id); for (q = m->exit_waiters; q != NULL; q = q->rest) printf("Machine #%ld is waiting for machine #%ld to exit.\n", q->machine->id, m->id); for (q = m->senders; q != NULL; q = q->rest) printf("Machine #%ld is trying to send a message to machine #%ld.\n", q->machine->id, m->id); } if (left == 0) printf("No more machines, simulation done.\n"); return ret; }