agssim: implement argv passing to program
[rofl0r-agsutils.git] / agssim.c
blobfe3d340cfb2b6d3cf3d45504581a63931b054083
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <assert.h>
6 #include <errno.h>
8 #include "ags_cpu.h"
9 #include "regusage.h"
10 #include "hbmap.h"
11 #include "version.h"
12 #define ADS ":::AGSSim " VERSION " by rofl0r:::"
14 #ifndef MAX
15 #define MAX(a, b) ((a) > (b) ? (a) : (b))
16 #endif
17 #ifndef MIN
18 #define MIN(a, b) ((a) < (b) ? (a) : (b))
19 #endif
21 #ifndef ARRAY_SIZE
22 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
23 #endif
25 #define ALIGN(X, A) ((X+(A-1)) & -(A))
27 #define BREAKPOINT_FLAG (1<<31)
28 #define OPCODE_MASK (~(BREAKPOINT_FLAG))
30 #define DEFAULT_STACKSIZE 16384
32 static int interactive, stacksize;
34 static struct rval {
35 union {
36 int i;
37 float f;
39 enum RegisterUsage ru;
40 } registers[MAX(AR_MAX, 256)];
42 static struct mem {
43 unsigned char *mem;
44 size_t capa;
45 size_t ltext;
46 size_t lstack;
47 size_t lheap;
48 } mem;
50 #define EIP registers[AR_NULL].i
52 #define VM_SIGILL 1
53 #define VM_SIGSEGV 2
54 #define VM_SIGABRT 3
55 static int vm_return;
56 static void vm_signal(int sig, int param) {
57 switch(sig) {
58 case VM_SIGILL:
59 dprintf(2, "illegal instruction at IP %u\n", EIP);
60 break;
61 case VM_SIGSEGV:
62 dprintf(2, "segmentation fault: invalid access at %u\n", EIP);
63 break;
64 case VM_SIGABRT:
65 dprintf(2, "aborted (assertlte check failed at IP %u)\n", EIP);
66 break;
67 default:
68 dprintf(2, "unknown signal\n");
70 vm_return = 1;
73 #define memory (mem.mem)
74 #define text memory
75 #define text_end ALIGN(mem.ltext, 4096)
76 #define stack_mem (mem.mem+text_end)
77 #define heap_mem (mem.mem+text_end+mem.lstack)
79 struct label_ref {
80 char *name;
81 unsigned insoff;
83 tglist(struct label_ref) *label_refs;
84 static void add_label_ref(char *name, unsigned insoff) {
85 struct label_ref new = {.name = strdup(name), .insoff = insoff};
86 tglist_add(label_refs, new);
88 static void resolve_label(char* name, unsigned insoff) {
89 size_t i;
90 for(i=0; i<tglist_getsize(label_refs); ) {
91 struct label_ref *l = &tglist_get(label_refs, i);
92 if(!strcmp(l->name, name)) {
93 free(l->name);
94 memcpy(text+l->insoff, &insoff, 4);
95 tglist_delete(label_refs, i);
96 } else ++i;
99 /* label_map */
100 hbmap(char*, unsigned, 32) *label_map;
101 static unsigned *get_label_offset(char* name) {
102 return hbmap_get(label_map, name);
104 static int add_label(char* name, int insoff) {
105 char* tmp = strdup(name);
106 return hbmap_insert(label_map, tmp, insoff) != -1;
108 static int strptrcmp(const void *a, const void *b) {
109 const char * const *x = a;
110 const char * const *y = b;
111 return strcmp(*x, *y);
113 static unsigned string_hash(const char* s) {
114 uint_fast32_t h = 0;
115 while (*s) {
116 h = 16*h + *s++;
117 h ^= h>>24 & 0xf0;
119 return h & 0xfffffff;
121 static void init_labels() {
122 label_map = hbmap_new(strptrcmp, string_hash, 32);
123 label_refs = tglist_new();
126 /* TODO: move duplicate code from Assembler.c into separate TU */
127 static int get_reg(char* regname) {
128 int i = AR_NULL + 1;
129 for(; i < AR_MAX; i++)
130 if(strcmp(regnames[i], regname) == 0)
131 return i;
132 return AR_NULL;
135 static size_t mnemolen[SCMD_MAX];
136 static int mnemolen_initdone = 0;
138 static void init_mnemolen(void) {
139 size_t i = 0;
140 for(; i< SCMD_MAX; i++)
141 mnemolen[i] = strlen(opcodes[i].mnemonic);
142 mnemolen_initdone = 1;
145 static unsigned find_insn(char* sym) {
146 if(!mnemolen_initdone) init_mnemolen();
147 size_t i = 0, l = strlen(sym);
148 for(; i< SCMD_MAX; i++)
149 if(l == mnemolen[i] && memcmp(sym, opcodes[i].mnemonic, l) == 0)
150 return i;
151 return 0;
154 #include "StringEscape.h"
155 /* expects a pointer to the first char after a opening " in a string,
156 * converts the string into convbuf, and returns the length of that string */
157 static size_t get_length_and_convert(char* x, char* end, char* convbuf, size_t convbuflen) {
158 size_t result = 0;
159 char* e = x + strlen(x);
160 assert(e > x && e < end && *e == 0);
161 e--;
162 while(isspace(*e)) e--;
163 if(*e != '"') return (size_t) -1;
164 *e = 0;
165 result = unescape(x, convbuf, convbuflen);
166 return result;
169 /* sets lets char in arg to 0, and advances pointer till the next argstart */
170 static char* finalize_arg(char **p, char* pend, char* convbuf, size_t convbuflen) {
171 if(**p == '"') {
172 convbuf[0] = '"';
173 size_t l= get_length_and_convert(*p + 1, pend, convbuf+1, convbuflen - 1);
174 if(l == (size_t) -1) return 0;
175 convbuf[l+1] = '"';
176 convbuf[l+2] = 0;
177 *p = 0; /* make it crash if its accessed again, since a string should always be the last arg */
178 return convbuf;
179 } else {
180 char* ret = *p;
181 while(*p < pend && **p != ',' && !isspace(**p)) (*p)++;
182 assert(*p < pend);
183 **p = 0; (*p)++;
184 while(*p < pend && isspace(**p)) (*p)++;
185 assert(*p < pend);
186 return ret;
190 static int canread(int index, int cnt) {
191 return index >= 0 && index+cnt < mem.capa;
193 static int canwrite(int index, int cnt) {
194 return index >= text_end && index+cnt < mem.capa;
197 #define ALIGN(X, A) ((X+(A-1)) & -(A))
199 static void vm_setup_startenv(int argc, char **argv, char **envp)
201 unsigned so = 0, si;
202 size_t l, i; char *s;
203 for(i = 0; i < argc; ++i) {
204 s = argv[i];
205 l = strlen(s) + 1;
206 memcpy(stack_mem+so, s, l);
207 so += l;
209 if(envp) for(i = 0; envp[i]; ++i) {
210 s = envp[i];
211 l = strlen(s) + 1;
212 memcpy(stack_mem+so, s, l);
213 so += l;
215 so = ALIGN(so, 4);
216 int* stack = (void*)(stack_mem + so);
217 si = so = 0;
218 stack[si++] = argc;
219 for(i = 0; i < argc; ++i) {
220 stack[si++] = text_end + so;
221 l = strlen(argv[i]) + 1;
222 so += l;
224 stack[si++] = 0;
225 if(envp) for(i = 0; envp[i]; ++i) {
226 stack[si++] = text_end + so;
227 l = strlen(envp[i]) + 1;
228 so += l;
230 so = ALIGN(so, 4);
231 /* op points to start of stack where the argv stuff is stored */
232 registers[AR_OP].i = text_end + so;
233 stack[si++] = 0;
234 stack[si++] = 0; // auxv not implemented yet
235 /* sp points to usable stack start */
236 registers[AR_SP].i = text_end + so + si*4;
239 static char** vm_args;
240 static int vm_argc;
241 static void vm_push_arg(char *arg) {
242 vm_args = realloc(vm_args, sizeof(char*)*(++vm_argc));
243 vm_args[vm_argc -1] = arg;
246 static void vm_setup_startenv_s(void) {
247 vm_setup_startenv(vm_argc, vm_args, NULL);
249 static int vm_init_stack(unsigned size) {
250 if(mem.lstack) return 1;
251 unsigned want = ALIGN(size, 4096);
252 unsigned char *p = realloc(mem.mem, mem.capa+want);
253 if(!p) {
254 dprintf(2, "error: could not allocate stack!\n");
255 return 0;
257 mem.mem = p;
258 mem.lstack = want;
259 mem.capa += want;
260 registers[AR_SP].i = text_end;
261 registers[AR_OP].i = text_end;
262 vm_setup_startenv_s();
263 return 1;
266 static int grow_text(size_t req) {
267 /* add 4 more slots than strictly necessary so we can access
268 * at least 1 full-length insn past text end without crash */
269 req += 4*sizeof(int);
270 size_t need = mem.ltext + req;
271 if(need > mem.capa-mem.lheap-mem.lstack) {
272 if(mem.lstack) {
273 dprintf(2, "error: cannot enlarge text segment once execution started!\n");
274 return 0;
276 size_t want = ALIGN(need, 4096);
277 unsigned char *p = realloc(mem.mem, want);
278 if(!p) {
279 dprintf(2, "error: allocating memory failed!\n");
280 return 0;
282 mem.mem = p;
283 mem.capa = want;
285 return 1;
288 static int append_code(int *code, size_t cnt) {
289 if(!grow_text((cnt+1)*4)) return 0;
290 size_t i;
291 for(i = 0; i < cnt; i++) {
292 memcpy(text+mem.ltext, &code[i], 4);
293 mem.ltext += 4;
295 memcpy(text+mem.ltext, "\0\0\0\0", 4);
296 return 1;
299 static void vm_reset_register_usage() {
300 size_t i;
301 for(i = AR_NULL + 1; i < AR_MAX; i++)
302 registers[i].ru = RU_NONE;
305 static void vm_init() {
306 size_t i;
307 /* initialize registers to an easily recognisable junk value */
308 for(i = AR_NULL + 1; i < ARRAY_SIZE(registers); i++) {
309 registers[i].i = -1;
311 vm_reset_register_usage();
312 registers[AR_SP].i = -1;
313 registers[AR_NULL].i = 0;
314 int was_null = text == 0;
315 /* set up EIP so vm_state() doesn't crash */
316 grow_text(16);
317 /* put NULL insn as first instruction so VM doesn't execute
318 random garbage in mem */
319 if(was_null) memcpy(text, "\0\0\0\0", 4);
322 static inline int consume_int(int **eip) {
323 *eip = *eip+1;
324 return **eip;
327 static void change_reg_usage(int regno, enum RegisterAccess ra) {
328 if(regno >= AR_MAX) {
329 vm_signal(VM_SIGSEGV, 0);
330 return;
332 registers[regno].ru = get_reg_usage(regno, registers[regno].ru, ra);
335 static void vm_update_register_usage(int *eip) {
336 const struct regaccess_info *ri = &regaccess_info[*eip];
337 if(ri->ra_reg1) change_reg_usage(eip[1], ri->ra_reg1);
338 if(ri->ra_reg2) change_reg_usage(eip[2], ri->ra_reg2);
339 if(ri->ra_mar) change_reg_usage(AR_MAR, ri->ra_mar);
340 if(ri->ra_sp) change_reg_usage(AR_SP, ri->ra_sp);
343 static void write_mem1(int off, int val) {
344 unsigned char *m = memory+off;
345 *m = val&0xff;
347 static void write_mem2(int off, int val) {
348 unsigned short *m = (void*) (memory+off);
349 *m = val&0xffff;
351 static void write_mem(int off, int val) {
352 int *m = (void*) (memory+off);
353 *m = val;
356 static int read_mem(int off) {
357 int ret;
358 memcpy(&ret, memory+off, 4);
359 return ret;
362 static int vm_push(int value) {
363 if(!canwrite(registers[AR_SP].i, 4)) return 0;
364 write_mem(registers[AR_SP].i, value);
365 registers[AR_SP].i += 4;
366 return 1;
369 static int vm_pop(int *value) {
370 if((int) registers[AR_SP].i >= 4) {
371 registers[AR_SP].i -= 4;
372 *value = read_mem(registers[AR_SP].i);
373 return 1;
375 return 0;
378 static int vm_syscall(void) {
379 int ret,
380 scno = registers[AR_AX].i,
381 arg1 = registers[AR_BX].i,
382 arg2 = registers[AR_CX].i,
383 arg3 = registers[AR_DX].i;
384 /* we follow linux x86_64 syscall numbers for simplicity */
385 switch(scno) {
386 case 0: /* SYS_read (fd, buf, size) */
387 /* fall-through */
388 case 1: /* SYS_write (fd, buf, size) */
389 if(!canread(arg2, arg3)) return -EFAULT;
390 if(scno == 0)
391 ret = read(arg1, ((char*)memory)+arg2, arg3);
392 else
393 ret = write(arg1, ((char*)memory)+arg2, arg3);
394 if(ret == -1) return -errno;
395 return ret;
396 case 60: /* SYS_exit (exitcode) */
397 exit(arg1);
398 default: return -ENOSYS;
402 static int label_check() {
403 if(tglist_getsize(label_refs)) {
404 dprintf(2, "error: unresolved label refs!\n");
405 size_t i; struct label_ref *l;
406 for(i=0; i<tglist_getsize(label_refs); ++i) {
407 l = &tglist_get(label_refs, i);
408 dprintf(2, "%s@%u\n", l->name, l->insoff);
410 return 0;
412 return 1;
415 #define CODE_INT(X) eip[X]
416 #define CODE_FLOAT(X) ((float*)eip)[X]
417 #define REGI(X) registers[CODE_INT(X)&0xff].i
418 #define REGF(X) registers[CODE_INT(X)&0xff].f
420 static int vm_step(int run_context) {
421 /* we use register AR_NULL as instruction pointer */
422 int *eip = (void*)(text + EIP);
423 unsigned op = *eip;
424 if(interactive) {
425 // breakpoints can be set only in interactive mode
426 op &= OPCODE_MASK;
427 if(op >= SCMD_MAX) {
428 vm_signal(VM_SIGILL, 0);
429 return 0;
432 if(*eip & BREAKPOINT_FLAG) {
433 *eip &= ~BREAKPOINT_FLAG;
434 return 0;
436 if(!run_context) vm_reset_register_usage();
437 vm_update_register_usage(eip);
438 } else if(op >= SCMD_MAX) {
439 vm_signal(VM_SIGILL, 0);
440 return 0;
442 int eip_inc = 1 + opcodes[op].argcount;
443 int tmp, val;
445 switch(op) {
446 case 0:
447 /* don't modify IP */
448 if(!run_context)
449 dprintf(2, "no code at IP %u.\n", EIP);
450 return 0;
451 case SCMD_ADD:
452 REGI(1) += CODE_INT(2);
453 break;
454 case SCMD_SUB:
455 REGI(1) -= CODE_INT(2);
456 break;
457 case SCMD_REGTOREG:
458 REGI(2) = REGI(1);
459 break;
460 case SCMD_LITTOREG:
461 REGI(1) = CODE_INT(2);
462 break;
463 case SCMD_MULREG:
464 REGI(1) *= REGI(2);
465 break;
466 case SCMD_DIVREG:
467 REGI(1) /= REGI(2);
468 break;
469 case SCMD_ADDREG:
470 REGI(1) += REGI(2);
471 break;
472 case SCMD_SUBREG:
473 REGI(1) -= REGI(2);
474 break;
475 case SCMD_BITAND:
476 REGI(1) &= REGI(2);
477 break;
478 case SCMD_BITOR:
479 REGI(1) |= REGI(2);
480 break;
481 case SCMD_ISEQUAL:
482 REGI(1) = !!(REGI(1) == REGI(2));
483 break;
484 case SCMD_NOTEQUAL:
485 REGI(1) = !!(REGI(1) != REGI(2));
486 break;
487 case SCMD_GREATER:
488 REGI(1) = !!(REGI(1) > REGI(2));
489 break;
490 case SCMD_LESSTHAN:
491 REGI(1) = !!(REGI(1) < REGI(2));
492 break;
493 case SCMD_GTE:
494 REGI(1) = !!(REGI(1) >= REGI(2));
495 break;
496 case SCMD_LTE:
497 REGI(1) = !!(REGI(1) <= REGI(2));
498 break;
499 case SCMD_AND:
500 REGI(1) = !!(REGI(1) && REGI(2));
501 break;
502 case SCMD_OR:
503 REGI(1) = !!(REGI(1) || REGI(2));
504 break;
505 case SCMD_LOADSPOFFS:
506 registers[AR_MAR].i = registers[AR_SP].i - CODE_INT(1);
507 break;
508 case SCMD_PUSHREG:
509 if(!vm_push(REGI(1))) goto oob;
510 break;
511 case SCMD_POPREG:
512 if(!vm_pop(&REGI(1))) goto oob;
513 break;
514 case SCMD_MUL:
515 REGI(1) *= CODE_INT(2);
516 break;
517 case SCMD_THISBASE:
518 case SCMD_LINENUM:
519 break;
520 case SCMD_MODREG:
521 REGI(1) %= REGI(2);
522 break;
523 case SCMD_XORREG:
524 REGI(1) ^= REGI(2);
525 break;
526 case SCMD_NOTREG:
527 REGI(1) = !REGI(2);
528 break;
529 case SCMD_SHIFTLEFT:
530 REGI(1) <<= REGI(2);
531 break;
532 case SCMD_SHIFTRIGHT:
533 REGI(1) >>= REGI(2);
534 break;
535 case SCMD_FADD:
536 REGF(1) += CODE_FLOAT(2);
537 break;
538 case SCMD_FSUB:
539 REGF(1) -= CODE_FLOAT(2);
540 break;
541 case SCMD_FMULREG:
542 REGF(1) *= REGF(2);
543 break;
544 case SCMD_FDIVREG:
545 REGF(1) /= REGF(2);
546 break;
547 case SCMD_FADDREG:
548 REGF(1) += REGF(2);
549 break;
550 case SCMD_FSUBREG:
551 REGF(1) -= REGF(2);
552 break;
553 case SCMD_FGREATER:
554 REGI(1) = !!(REGF(1) > REGF(2));
555 break;
556 case SCMD_FLESSTHAN:
557 REGI(1) = !!(REGF(1) < REGF(2));
558 break;
559 case SCMD_FGTE:
560 REGI(1) = !!(REGF(1) >= REGF(2));
561 break;
562 case SCMD_FLTE:
563 REGI(1) = !!(REGF(1) <= REGF(2));
564 break;
565 case SCMD_ZEROMEMORY:
566 tmp = CODE_INT(1);
567 if(canwrite(registers[AR_MAR].i, tmp)) {
568 memset(((char*)memory)+registers[AR_MAR].i,0,tmp);
569 } else goto oob;
570 break;
571 case SCMD_WRITELIT:
572 tmp = CODE_INT(1);
573 if(tmp <= 0 || tmp > 4 || tmp == 3) {
574 dprintf(2, "VM: invalid memcpy use at IP %u\n", EIP);
575 break;
577 val = CODE_INT(2);
578 goto mwrite;
579 case SCMD_MEMWRITE:
580 tmp = 4;
581 val = REGI(1);
582 goto mwrite;
583 case SCMD_MEMWRITEW:
584 tmp = 2;
585 val = REGI(1);
586 goto mwrite;
587 case SCMD_MEMWRITEB:
588 tmp = 1;
589 val = REGI(1);
590 mwrite:
591 if(canwrite(registers[AR_MAR].i, tmp)) {
592 switch(tmp) {
593 case 4: write_mem (registers[AR_MAR].i, val); break;
594 case 2: write_mem2(registers[AR_MAR].i, val); break;
595 case 1: write_mem1(registers[AR_MAR].i, val); break;
597 } else {
598 oob:
599 vm_signal(VM_SIGSEGV, 0);
600 return 0;
602 break;
603 case SCMD_MEMREAD:
604 tmp = 4;
605 goto mread;
606 case SCMD_MEMREADW:
607 tmp = 2;
608 goto mread;
609 case SCMD_MEMREADB:
610 tmp = 1;
611 mread:
612 if(canread(registers[AR_MAR].i, tmp)) {
613 int val;
614 memcpy(&val, memory+registers[AR_MAR].i, 4);
615 switch(tmp) {
616 case 4: REGI(1) = val; break;
617 case 2: REGI(1) = val & 0xffff; break;
618 case 1: REGI(1) = val & 0xff; break;
620 } else goto oob;
621 break;
622 case SCMD_JZ:
623 if(registers[AR_AX].i == 0) goto jump;
624 break;
625 case SCMD_JNZ:
626 if(registers[AR_AX].i == 0) break;
627 /* fall through */
628 case SCMD_JMP:
629 jump:
630 tmp = CODE_INT(1);
631 jump_tmp:
632 if((unsigned)tmp < text_end && !(tmp&3))
633 registers[AR_NULL].i = tmp;
634 else {
635 vm_signal(VM_SIGSEGV, tmp);
636 return 0;
638 eip_inc = 0;
639 break;
640 case SCMD_CALL:
641 if(!vm_push(registers[AR_NULL].i + eip_inc*4)) goto oob;
642 tmp = REGI(1);
643 goto jump_tmp;
644 case SCMD_RET:
645 registers[AR_SP].i -= 4;
646 tmp = read_mem(registers[AR_SP].i);
647 goto jump_tmp;
648 case SCMD_CALLAS:
649 /* we re-purpose "callscr" mnemonic to mean syscall,
650 as it is unused in ags-emitted bytecode.
651 using it is unportable, it works only in agssim.
652 the register arg for callscr instruction is ignored.
653 the arguments are passed in regs ax,bx,cx,dx,op
654 in this order, where the first arg is the syscall
655 number. return value is put in ax. */
656 registers[AR_AX].i = vm_syscall();
657 break;
658 case SCMD_CHECKBOUNDS:
659 if(REGI(1) > CODE_INT(2)) vm_signal(VM_SIGABRT, 0);
660 break;
661 case SCMD_NEWARRAY:
662 case SCMD_DYNAMICBOUNDS:
663 case SCMD_MEMZEROPTRND:
664 case SCMD_LOOPCHECKOFF:
665 case SCMD_CHECKNULLREG:
666 case SCMD_STRINGSNOTEQ:
667 case SCMD_STRINGSEQUAL:
668 case SCMD_CREATESTRING:
669 case SCMD_CHECKNULL:
670 case SCMD_MEMINITPTR:
671 case SCMD_MEMZEROPTR:
672 case SCMD_MEMREADPTR:
673 case SCMD_MEMWRITEPTR:
674 case SCMD_CALLOBJ:
675 case SCMD_NUMFUNCARGS:
676 case SCMD_SUBREALSTACK:
677 case SCMD_PUSHREAL:
678 case SCMD_CALLEXT:
679 dprintf(2, "info: %s not implemented yet\n", opcodes[*eip].mnemonic);
681 size_t i, l = opcodes[*eip].argcount;
682 for(i = 0; i < l; i++) ++(*eip);
684 break;
685 default:
686 vm_signal(VM_SIGILL, 0);
687 return 0;
689 registers[AR_NULL].i += eip_inc*4;
690 return 1;
693 static inline char *int_to_str(int value, char* out) {
694 sprintf(out, "%d", value);
695 return out;
698 static int* get_next_ip(int *eip, int off) {
699 int *ret = eip, i, op;
700 for(i=0; i<off; ++i) {
701 op = *ret & OPCODE_MASK;
702 if(op < SCMD_MAX)
703 ret+=1+opcodes[op].argcount;
704 else
705 ++ret;
707 return ret;
710 static const char *get_regname(unsigned regno) {
711 if(regno < AR_MAX) return regnames[regno];
712 return "INVALID";
715 static void vm_state() {
716 if(!interactive) return;
717 static const char ru_strings[][3] = {
718 [RU_NONE] = {0},
719 [RU_READ] = {'R', 0},
720 [RU_WRITE] = {'W', 0},
721 [RU_WRITE_AFTER_READ] = {'R', 'W', 0},
723 static const char regorder[] = {
724 0, AR_MAR, AR_OP, AR_SP, -1,
725 AR_AX, AR_BX, AR_CX, AR_DX, -1, -1};
726 size_t i, j;
727 for(j=0; j < ARRAY_SIZE(regorder)-1; ++j) {
728 i = regorder[j];
729 if(i == -1) printf("\n");
730 else {
731 printf("%-3s: %-2s %-11d", i == 0 ? "eip" : regnames[i], ru_strings[registers[i].ru], registers[i].i);
732 if(regorder[j+1] != -1) printf(" ");
735 char stackview[5][24];
736 stackview[2][0] = 0;
737 stackview[3][0] = 0;
738 stackview[4][0] = 0;
739 for(j=0,i = MIN(registers[AR_SP].i+2*4, text_end+mem.lstack);
740 i >= MAX(registers[AR_SP].i-2*4, text_end);
741 i-=4, ++j) {
742 sprintf(stackview[j],
743 "SL %s %3zu %d", i == registers[AR_SP].i ? ">" : " ", i, read_mem(i));
744 if(i <= 0) break;
746 int *eip = (void*)(text + registers[AR_NULL].i), wasnull = 0;
747 for(i = 0; i<5; i++) {
748 char a1b[32], a2b[32], a3b[32], inst[48];
749 if(i > 1) {
750 int *nip = get_next_ip(eip, i-2),
751 op = *nip & OPCODE_MASK;
752 if(op < SCMD_MAX) {
753 const char *arg1 = opcodes[op].argcount == 0 ? "" : \
754 (opcodes[op].regcount > 0 ? get_regname(nip[1]) : int_to_str(nip[1], a1b));
755 const char *arg2 = opcodes[op].argcount < 2 ? "" : \
756 (opcodes[op].regcount > 1 ? get_regname(nip[2]) : int_to_str(nip[2], a2b));
757 const char *arg3 = opcodes[op].argcount < 3 ? "" : \
758 (opcodes[op].regcount > 2 ? get_regname(nip[3]) : int_to_str(nip[2], a3b));
759 if(op == SCMD_REGTOREG) {
760 const char* tmp = arg1;
761 arg1 = arg2; arg2 = tmp;
763 if(!wasnull)
764 sprintf(inst, " %s %s %s %s", i==2?">":" ", opcodes[op].mnemonic, arg1, arg2);
765 else inst[0] = 0;
766 } else {
767 sprintf(inst, "%d", *nip);
769 if(!op) wasnull = 1;
770 } else inst[0] = 0;
771 printf("%-52s %s\n", inst, stackview[i]);
775 void vm_run(void) {
776 if(!label_check()) return;
777 while(1) {
778 if(!vm_step(1) || vm_return) break;
782 void vm_trace(void) {
783 if(!label_check()) return;
784 while(1) {
785 if(!vm_step(0) || vm_return) break;
786 vm_state();
790 static int usage(int fd, char *a0) {
791 dprintf(fd,
792 "%s [OPTIONS] [filename.s] [-- arguments for ags program]\n"
793 "simple ags vm simulator\n"
794 "useful to examine how a chunk of code modifies VM state\n"
795 "OPTIONS:\n"
796 "-s stacksize : specify stacksize in KB (default: 16)\n"
797 "-i : interpreter mode - don't print anything, run and exit\n\n"
798 "by default, mode is interactive, sporting the following commands:\n"
799 "!i - reset VM state and IP\n"
800 "!s - single-step\n"
801 "!n - step-over\n"
802 "!r - run\n"
803 "!t - trace - run till bp or end with state printed for every insn\n"
804 "!b ADDR - set a breakpoint on ADDR (address or label)\n\n"
805 "example: %s -i printargs.s -- hello world\n"
806 , a0, a0);
807 return 1;
810 static int lastcommand;
811 enum UserCommand {
812 UC_STEP = 1,
813 UC_NEXT, /* step-over */
814 UC_BP,
815 UC_RUN,
816 UC_TRACE,
817 UC_INIT,
818 UC_QUIT,
819 UC_HELP,
821 static void execute_user_command_i(int uc, char* param) {
822 switch(uc) {
823 case UC_STEP: if(label_check()) vm_step(0); break;
824 case UC_BP: {
825 int addr, *ptr;
826 if(isdigit(param[0]))
827 addr = atoi(param);
828 else {
829 ptr = get_label_offset(param);
830 if(!ptr) {
831 dprintf(2, "label %s not found!\n", param);
832 return;
834 addr = *ptr;
836 if(addr >= text_end) {
837 dprintf(2, "breakpoint offset %d out of bounds\n", addr);
838 return;
840 int insn;
841 memcpy(&insn, text+addr, 4);
842 insn |= BREAKPOINT_FLAG;
843 memcpy(text+addr, &insn, 4);
845 return;
846 case UC_NEXT: *get_next_ip((void*)(text+EIP), 1) |= BREAKPOINT_FLAG;
847 /* fall-through */
848 case UC_RUN : vm_run(); break;
849 case UC_TRACE: vm_trace(); break;
850 case UC_INIT: vm_init(); break;
851 case UC_QUIT: exit(0); break;
852 case UC_HELP: usage(1, "agssim"); break;
854 lastcommand = uc;
855 vm_state();
857 static void execute_user_command(char *cmd) {
858 if(!vm_init_stack(stacksize)) return;
859 int uc = 0;
860 char *param = cmd;
861 while(!isspace(*param)) param++;
862 while(isspace(*param)) param++;
863 if(0) ;
864 else if(!strcmp(cmd, "s")) uc = UC_STEP;
865 else if(!strcmp(cmd, "r")) uc = UC_RUN;
866 else if(!strcmp(cmd, "i")) uc = UC_INIT;
867 else if(!strcmp(cmd, "q")) uc = UC_QUIT;
868 else if(!strcmp(cmd, "h")) uc = UC_HELP;
869 else if(!strcmp(cmd, "n")) uc = UC_NEXT;
870 else if(*cmd == 't') uc = UC_TRACE;
871 else if(*cmd == 'b') uc = UC_BP;
872 else {
873 dprintf(2, "unknown command\n");
874 return;
876 execute_user_command_i(uc, param);
879 int main(int argc, char** argv) {
880 int c;
881 stacksize = DEFAULT_STACKSIZE;
882 interactive = 1;
883 FILE *in = stdin;
884 while((c = getopt(argc, argv, "is:")) != EOF) switch(c) {
885 case 'i': interactive = 0; break;
886 case 's': stacksize = ALIGN(atoi(optarg) * 1024, 4096); break;
887 default: return usage(2, argv[0]);
889 if(argv[optind] && optind >= 1 && strcmp(argv[optind-1], "--")) {
890 in = fopen(argv[optind], "r");
891 if(!in) {
892 dprintf(2, "error opening %s\n", argv[optind]);
893 return 1;
895 optind++;
898 init_labels();
899 vm_init();
900 vm_push_arg(argv[0]);
901 if(argv[optind] && !strcmp(argv[optind], "--"))
902 optind++;
903 while(argv[optind]) vm_push_arg(argv[optind++]);
905 char buf[1024], *sym;
906 char convbuf[sizeof(buf)]; /* to convert escaped string into non-escaped version */
907 int lineno = 0;
908 if(interactive) printf(ADS " - type !h for help\n");
910 mainloop:
911 while(fgets(buf, sizeof buf, in)) {
912 int code[4];
913 size_t pos = 0;
914 lineno++;
915 char* p = buf, *pend = buf + sizeof buf;
916 if(*p == '\n' && lastcommand) {
917 execute_user_command_i(lastcommand, "");
918 continue;
920 if(*p == '#' || *p == ';') continue;
921 if(*p == '!') {
922 char *n = strchr(p, '\n');
923 if(n) *n = 0;
924 execute_user_command(p+1);
925 continue;
927 while(isspace(*p) && p < pend) p++;
928 assert(p < pend);
929 if(!*p) continue;
930 char* sym = p;
931 while(!isspace(*p) && p < pend) p++;
932 *p = 0; p++;
933 size_t l = strlen(sym);
934 if(l > 1 && sym[l-1] == ':') {
935 // functionstart or label
936 sym[l-1] = 0;
937 resolve_label(sym, mem.ltext);
938 unsigned *loff = get_label_offset(sym);
939 if(loff) dprintf(2, "warning: label %s overwritten\n", sym);
940 add_label(sym, mem.ltext);
941 continue;
943 unsigned instr = find_insn(sym);
944 if(!instr) {
945 dprintf(2, "line %zu: error: unknown instruction '%s'\n", lineno, sym);
946 continue;
948 code[pos++] = instr;
949 size_t arg;
950 for(arg = 0; arg < opcodes[instr].argcount; arg++) {
951 sym = finalize_arg(&p, pend, convbuf, sizeof(convbuf));
952 if(sym == 0) {
953 dprintf(2, "line %zu: error: expected \"\n", lineno);
954 goto loop_footer;
956 int value = 0;
957 if(arg < opcodes[instr].regcount) {
958 value=get_reg(sym);
959 if(value == AR_NULL) {
960 needreg_err:
961 dprintf(2, "line %zu: error: expected register name!\n", lineno);
962 goto loop_footer;
964 if(instr == SCMD_REGTOREG) {
965 /* fix reversed order of arguments */
966 int dst = value;
967 sym = p;
968 while(p < pend && *p != ',' && !isspace(*p)) p++;
969 assert(p < pend);
970 *p = 0;
971 value=get_reg(sym);
972 if(value == AR_NULL) goto needreg_err;
973 code[pos++] = value;
974 code[pos++] = dst;
975 break;
977 } else {
978 switch(instr) {
979 case SCMD_LITTOREG:
980 /* immediate can be function name, string,
981 * variable name, stack fixup, or numeric value */
982 if(sym[0] == '"') {
983 size_t l = strlen(sym)-1, tl = mem.ltext;
984 if(!append_code((int[2]){SCMD_JMP, tl+8+ALIGN(l, 4)}, 2)) goto loop_footer;
985 char*p = sym+1;
986 --l;
987 while((ssize_t)l >= 0) {
988 int x = 0;
989 memcpy(&x, p, l>=4?4:l);
990 if(!append_code(&x, 1)) goto loop_footer;
991 l -= 4;
992 p += 4;
994 value = tl+8;
995 } else if(sym[0] == '@') {
996 dprintf(2, "error: global variable handling not implemented\n");
997 goto loop_footer;
998 } else if(sym[0] == '.') {
999 if(memcmp(sym+1, "stack", 5)) {
1000 dprintf(2, "error: expected stack\n");
1001 goto loop_footer;;
1003 dprintf(2, "error: stack fixup not implemented\n");
1004 goto loop_footer;
1005 } else if(isdigit(sym[0]) || sym[0] == '-') {
1006 if(sym[0] == '-') assert(isdigit(sym[1]));
1007 value = atoi(sym);
1008 } else {
1009 goto label_ref;
1011 break;
1012 case SCMD_JMP: case SCMD_JZ: case SCMD_JNZ: {
1013 label_ref:;
1014 unsigned *loff = get_label_offset(sym);
1015 if(!loff) {
1016 add_label_ref(sym, mem.ltext+pos*4);
1017 value = -1;
1018 } else value = *loff;
1019 } break;
1020 default:
1021 if(!isdigit(sym[0])) {
1022 dprintf(2, "line %zu: error: expected number\n", lineno);
1023 goto loop_footer;
1025 value = atoi(sym);
1028 code[pos++] = value;
1030 append_code(code, pos);
1031 loop_footer: ;
1033 if(!interactive) execute_user_command("r");
1034 else if(in != stdin) {
1035 in = stdin;
1036 goto mainloop;
1038 return vm_return;