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