12 #define ADS ":::AGSSim " VERSION " by rofl0r:::"
15 #define MAX(a, b) ((a) > (b) ? (a) : (b))
18 #define MIN(a, b) ((a) < (b) ? (a) : (b))
22 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
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
;
40 enum RegisterUsage ru
;
41 } registers
[MAX(AR_MAX
, 256)];
51 #define EIP registers[AR_NULL].i
57 static void vm_signal(int sig
, int param
) {
60 fprintf(stderr
, "illegal instruction at IP %u\n", EIP
);
63 fprintf(stderr
, "segmentation fault: invalid access at %u\n", EIP
);
66 fprintf(stderr
, "aborted (assertlte check failed at IP %u)\n", EIP
);
69 fprintf(stderr
, "unknown signal\n");
74 #define memory (mem.mem)
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)
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
) {
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
)) {
95 memcpy(text
+l
->insoff
, &insoff
, 4);
96 tglist_delete(label_refs
, i
);
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
) {
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
) {
130 for(; i
< AR_MAX
; i
++)
131 if(strcmp(regnames
[i
], regname
) == 0)
136 static size_t mnemolen
[SCMD_MAX
];
137 static int mnemolen_initdone
= 0;
139 static void init_mnemolen(void) {
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)
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
) {
160 char* e
= x
+ strlen(x
);
161 assert(e
> x
&& e
< end
&& *e
== 0);
163 while(isspace(*e
)) e
--;
164 if(*e
!= '"') return (size_t) -1;
166 result
= unescape(x
, convbuf
, convbuflen
);
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
) {
174 size_t l
= get_length_and_convert(*p
+ 1, pend
, convbuf
+1, convbuflen
- 1);
175 if(l
== (size_t) -1) return 0;
178 *p
= 0; /* make it crash if its accessed again, since a string should always be the last arg */
182 while(*p
< pend
&& **p
!= ',' && !isspace(**p
)) (*p
)++;
185 while(*p
< pend
&& isspace(**p
)) (*p
)++;
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
)
203 size_t l
, i
; char *s
;
204 for(i
= 0; i
< argc
; ++i
) {
207 memcpy(stack_mem
+so
, s
, l
);
210 if(envp
) for(i
= 0; envp
[i
]; ++i
) {
213 memcpy(stack_mem
+so
, s
, l
);
217 int* stack
= (void*)(stack_mem
+ so
);
220 for(i
= 0; i
< argc
; ++i
) {
221 stack
[si
++] = text_end
+ so
;
222 l
= strlen(argv
[i
]) + 1;
226 if(envp
) for(i
= 0; envp
[i
]; ++i
) {
227 stack
[si
++] = text_end
+ so
;
228 l
= strlen(envp
[i
]) + 1;
232 /* op points to start of stack where the argv stuff is stored */
233 registers
[AR_OP
].i
= text_end
+ so
;
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
;
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
);
255 fprintf(stderr
, "error: could not allocate stack!\n");
261 registers
[AR_SP
].i
= text_end
;
262 registers
[AR_OP
].i
= text_end
;
263 vm_setup_startenv_s();
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
) {
274 fprintf(stderr
, "error: cannot enlarge text segment once execution started!\n");
277 size_t want
= ALIGN(need
, 4096);
278 unsigned char *p
= realloc(mem
.mem
, want
);
280 fprintf(stderr
, "error: allocating memory failed!\n");
289 static int append_code(int *code
, size_t cnt
) {
290 if(!grow_text((cnt
+1)*4)) return 0;
292 for(i
= 0; i
< cnt
; i
++) {
293 memcpy(text
+mem
.ltext
, &code
[i
], 4);
296 memcpy(text
+mem
.ltext
, "\0\0\0\0", 4);
300 static void vm_reset_register_usage() {
302 for(i
= AR_NULL
+ 1; i
< AR_MAX
; i
++)
303 registers
[i
].ru
= RU_NONE
;
306 static void vm_init() {
308 /* initialize registers to an easily recognisable junk value */
309 for(i
= AR_NULL
+ 1; i
< ARRAY_SIZE(registers
); i
++) {
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 */
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
) {
328 static void change_reg_usage(int regno
, enum RegisterAccess ra
) {
329 if(regno
>= AR_MAX
) {
330 vm_signal(VM_SIGSEGV
, 0);
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
= ®access_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
;
348 static void write_mem2(int off
, int val
) {
349 unsigned short *m
= (void*) (memory
+off
);
352 static void write_mem(int off
, int val
) {
353 int *m
= (void*) (memory
+off
);
357 static int read_mem(int off
) {
359 memcpy(&ret
, memory
+off
, 4);
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;
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
);
379 static int vm_syscall(void) {
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 */
387 case 0: /* SYS_read (fd, buf, size) */
389 case 1: /* SYS_write (fd, buf, size) */
390 if(!canread(arg2
, arg3
)) return -EFAULT
;
392 ret
= read(arg1
, ((char*)memory
)+arg2
, arg3
);
394 ret
= write(arg1
, ((char*)memory
)+arg2
, arg3
);
395 if(ret
== -1) return -errno
;
397 case 60: /* SYS_exit (exitcode) */
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
);
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
);
426 // breakpoints can be set only in interactive mode
429 vm_signal(VM_SIGILL
, 0);
433 if(*eip
& BREAKONCE_FLAG
) {
434 *eip
&= ~BREAKONCE_FLAG
;
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);
444 int eip_inc
= 1 + opcodes
[op
].argcount
;
449 /* don't modify IP */
451 fprintf(stderr
, "no code at IP %u.\n", EIP
);
454 REGI(1) += CODE_INT(2);
457 REGI(1) -= CODE_INT(2);
463 REGI(1) = CODE_INT(2);
484 REGI(1) = !!(REGI(1) == REGI(2));
487 REGI(1) = !!(REGI(1) != REGI(2));
490 REGI(1) = !!(REGI(1) > REGI(2));
493 REGI(1) = !!(REGI(1) < REGI(2));
496 REGI(1) = !!(REGI(1) >= REGI(2));
499 REGI(1) = !!(REGI(1) <= REGI(2));
502 REGI(1) = !!(REGI(1) && REGI(2));
505 REGI(1) = !!(REGI(1) || REGI(2));
507 case SCMD_LOADSPOFFS
:
508 registers
[AR_MAR
].i
= registers
[AR_SP
].i
- CODE_INT(1);
511 if(!vm_push(REGI(1))) goto oob
;
514 if(!vm_pop(®I(1))) goto oob
;
517 REGI(1) *= CODE_INT(2);
534 case SCMD_SHIFTRIGHT
:
538 REGF(1) += CODE_FLOAT(2);
541 REGF(1) -= CODE_FLOAT(2);
556 REGI(1) = !!(REGF(1) > REGF(2));
559 REGI(1) = !!(REGF(1) < REGF(2));
562 REGI(1) = !!(REGF(1) >= REGF(2));
565 REGI(1) = !!(REGF(1) <= REGF(2));
567 case SCMD_ZEROMEMORY
:
569 if(canwrite(registers
[AR_MAR
].i
, tmp
)) {
570 memset(((char*)memory
)+registers
[AR_MAR
].i
,0,tmp
);
575 if(tmp
<= 0 || tmp
> 4 || tmp
== 3) {
576 fprintf(stderr
, "VM: invalid memcpy use at IP %u\n", EIP
);
593 if(canwrite(registers
[AR_MAR
].i
, 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;
601 vm_signal(VM_SIGSEGV
, 0);
614 if(canread(registers
[AR_MAR
].i
, tmp
)) {
616 memcpy(&val
, memory
+registers
[AR_MAR
].i
, 4);
618 case 4: REGI(1) = val
; break;
619 case 2: REGI(1) = val
& 0xffff; break;
620 case 1: REGI(1) = val
& 0xff; break;
625 if(registers
[AR_AX
].i
== 0) goto jump
;
628 if(registers
[AR_AX
].i
== 0) break;
634 if((unsigned)tmp
< text_end
&& !(tmp
&3))
635 registers
[AR_NULL
].i
= tmp
;
637 vm_signal(VM_SIGSEGV
, tmp
);
643 if(!vm_push(registers
[AR_NULL
].i
+ eip_inc
*4)) goto oob
;
647 registers
[AR_SP
].i
-= 4;
648 tmp
= read_mem(registers
[AR_SP
].i
);
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();
660 case SCMD_CHECKBOUNDS
:
661 if(REGI(1) > CODE_INT(2)) vm_signal(VM_SIGABRT
, 0);
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
:
672 case SCMD_MEMINITPTR
:
673 case SCMD_MEMZEROPTR
:
674 case SCMD_MEMREADPTR
:
675 case SCMD_MEMWRITEPTR
:
677 case SCMD_NUMFUNCARGS
:
678 case SCMD_SUBREALSTACK
:
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
);
688 vm_signal(VM_SIGILL
, 0);
691 registers
[AR_NULL
].i
+= eip_inc
*4;
695 static inline char *int_to_str(int value
, char* out
) {
696 sprintf(out
, "%d", value
);
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
;
705 ret
+=1+opcodes
[op
].argcount
;
712 static const char *get_regname(unsigned regno
) {
713 if(regno
< AR_MAX
) return regnames
[regno
];
717 static void vm_state() {
718 if(!interactive
) return;
719 static const char ru_strings
[][3] = {
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};
729 for(j
=0; j
< ARRAY_SIZE(regorder
)-1; ++j
) {
731 if(i
== -1) printf("\n");
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];
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
);
744 sprintf(stackview
[j
],
745 "SL %s %3zu %d", i
== registers
[AR_SP
].i
? ">" : " ", i
, read_mem(i
));
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];
752 int *nip
= get_next_ip(eip
, i
-2),
753 op
= *nip
& OPCODE_MASK
;
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
;
766 sprintf(inst
, " %s %s %s %s", i
==2?">":" ", opcodes
[op
].mnemonic
, arg1
, arg2
);
769 sprintf(inst
, "%d", *nip
);
773 printf("%-52s %s\n", inst
, stackview
[i
]);
778 if(!label_check()) return;
780 if(!vm_step(1) || vm_return
) break;
784 void vm_trace(void) {
785 if(!label_check()) return;
787 if(!vm_step(0) || vm_return
) break;
792 static int usage(FILE *out
, char *a0
) {
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"
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"
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"
812 static int lastcommand
;
815 UC_NEXT
, /* step-over */
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
;
829 static void execute_user_command_i(int uc
, char* param
) {
830 int restore_breakpoint
= 0;
834 int* eip
= (void*)(text
+ EIP
);
835 restore_breakpoint
= toggle_bp(eip
, 0);
837 if(restore_breakpoint
) toggle_bp(eip
, 1);
842 if(isdigit(param
[0]))
845 ptr
= get_label_offset(param
);
847 fprintf(stderr
, "label %s not found!\n", param
);
852 if(addr
>= text_end
) {
853 fprintf(stderr
, "breakpoint offset %d out of bounds\n", addr
);
857 memcpy(&insn
, text
+addr
, 4);
858 insn
|= BREAKPOINT_FLAG
;
859 memcpy(text
+addr
, &insn
, 4);
863 *get_next_ip((void*)(text
+EIP
), 1) |= BREAKONCE_FLAG
;
867 int* eip
= (void*)(text
+ EIP
);
868 if(toggle_bp(eip
, 0)) {
872 if(uc
== UC_TRACE
) vm_trace();
876 case UC_INIT
: vm_init(); break;
877 case UC_QUIT
: exit(0); break;
878 case UC_HELP
: usage(stdout
, "agssim"); break;
883 static void execute_user_command(char *cmd
) {
884 if(!vm_init_stack(stacksize
)) return;
887 while(!isspace(*param
)) param
++;
888 while(isspace(*param
)) param
++;
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
;
899 fprintf(stderr
, "unknown command\n");
902 execute_user_command_i(uc
, param
);
905 int main(int argc
, char** argv
) {
907 stacksize
= DEFAULT_STACKSIZE
;
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");
918 fprintf(stderr
, "error opening %s\n", argv
[optind
]);
926 vm_push_arg(argv
[0]);
927 if(argv
[optind
] && !strcmp(argv
[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 */
934 if(interactive
) printf(ADS
" - type !h for help\n");
937 while(fgets(buf
, sizeof buf
, in
)) {
941 char* p
= buf
, *pend
= buf
+ sizeof buf
;
942 if(*p
== '\n' && lastcommand
) {
943 execute_user_command_i(lastcommand
, "");
946 if(*p
== '#' || *p
== ';') continue;
948 char *n
= strchr(p
, '\n');
950 execute_user_command(p
+1);
953 while(isspace(*p
) && p
< pend
) p
++;
957 while(!isspace(*p
) && p
< pend
) p
++;
959 size_t l
= strlen(sym
);
960 if(l
> 1 && sym
[l
-1] == ':') {
961 // functionstart or label
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
);
969 unsigned instr
= find_insn(sym
);
971 fprintf(stderr
, "line %zu: error: unknown instruction '%s'\n", lineno
, sym
);
976 for(arg
= 0; arg
< opcodes
[instr
].argcount
; arg
++) {
977 sym
= finalize_arg(&p
, pend
, convbuf
, sizeof(convbuf
));
979 fprintf(stderr
, "line %zu: error: expected \"\n", lineno
);
983 if(arg
< opcodes
[instr
].regcount
) {
985 if(value
== AR_NULL
) {
987 fprintf(stderr
, "line %zu: error: expected register name!\n", lineno
);
990 if(instr
== SCMD_REGTOREG
) {
991 /* fix reversed order of arguments */
994 while(p
< pend
&& *p
!= ',' && !isspace(*p
)) p
++;
998 if(value
== AR_NULL
) goto needreg_err
;
1006 /* immediate can be function name, string,
1007 * variable name, stack fixup, or numeric value */
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
;
1013 while((ssize_t
)l
>= 0) {
1015 memcpy(&x
, p
, l
>=4?4:l
);
1016 if(!append_code(&x
, 1)) goto loop_footer
;
1021 } else if(sym
[0] == '@') {
1022 fprintf(stderr
, "error: global variable handling not implemented\n");
1024 } else if(sym
[0] == '.') {
1025 if(memcmp(sym
+1, "stack", 5)) {
1026 fprintf(stderr
, "error: expected stack\n");
1029 fprintf(stderr
, "error: stack fixup not implemented\n");
1031 } else if(isdigit(sym
[0]) || sym
[0] == '-') {
1032 if(sym
[0] == '-') assert(isdigit(sym
[1]));
1038 case SCMD_JMP
: case SCMD_JZ
: case SCMD_JNZ
: {
1040 unsigned *loff
= get_label_offset(sym
);
1042 add_label_ref(sym
, mem
.ltext
+pos
*4);
1044 } else value
= *loff
;
1047 if(!isdigit(sym
[0])) {
1048 fprintf(stderr
, "line %zu: error: expected number\n", lineno
);
1054 code
[pos
++] = value
;
1056 append_code(code
, pos
);
1059 if(!interactive
) execute_user_command("r");
1060 else if(in
!= stdin
) {