campctf2023-chall-tis256

Zachtronics TIS100-inspired reversing challenge for CampCTF 2023
git clone https://git.sinitax.com/sinitax/campctf2023-chall-tis256
Log | Files | Refs | Submodules | README | sfeed.txt

asm.c (11877B)


      1#include "asm.h"
      2#include "util.h"
      3#include "tpu.h"
      4
      5#include <stdarg.h>
      6#include <stdbool.h>
      7#include <string.h>
      8#include <stdint.h>
      9
     10#define TEXTALPH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
     11#define NUMALPH "0123456789"
     12#define NAMEALPH TEXTALPH NUMALPH
     13#define WHITESPACE " \t\v\r\n,"
     14
     15enum asm_tok {
     16	/* Global */
     17	TOK_STDIN, TOK_STDOUT, TOK_TPU, TOK_END,
     18
     19	/* Operands (order like OP_*) */
     20	TOK_ACC, TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP,
     21	TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT, TOK_NAME,
     22
     23	/* Instructions (order like INST_*) */
     24	TOK_NOP, TOK_MOV, TOK_SWP, TOK_SAV, TOK_ADD,
     25	TOK_SUB, TOK_NEG, TOK_XOR, TOK_AND, TOK_JMP,
     26	TOK_JEQ, TOK_JNE, TOK_JRO, TOK_SHL, TOK_SHR,
     27
     28	/* Misc */
     29	TOK_COMMENT, TOK_LABEL, TOK_XPOS, TOK_YPOS, TOK_NL, TOK_EOF
     30};
     31
     32struct asm_tokenizer {
     33	const char *filepath;
     34	FILE *file;
     35	enum asm_tok tok;
     36	char *tokstr;
     37	size_t lineno, off;
     38	char linebuf[256];
     39};
     40
     41static const char *tok_strs[] = {
     42	/* Global */
     43	"stdin", "stdout", "tpu", "end",
     44
     45	/* Operands (order like OP_*) */
     46	"acc", "nil", "left", "right", "up", "down",
     47	"any", "last", NULL, NULL,
     48
     49	/* Instructions (order like INST_*) */
     50	"nop", "mov", "swp", "sav", "add",
     51	"sub", "neg", "xor", "and", "jmp",
     52	"jeq", "jne", "jro", "shl", "shr",
     53
     54	/* Misc */
     55	NULL, NULL, NULL, NULL, NULL, NULL
     56};
     57
     58static const char *tok_reprs[] = {
     59	/* Global */
     60	"'STDIN'", "'STDOUT'", "'TPU'", "'END'",
     61
     62	/* Operands (order like OP_*) */
     63	"'ACC'", "'NIL'", "'LEFT'", "'RIGHT'", "'UP'", "'DOWN'",
     64	"'ANY'", "'LAST'", "<LIT>", "<NAME>",
     65
     66	/* Instructions (order like INST_*) */
     67	"'NOP'", "'MOV'", "'SWP'", "'SAV'", "'ADD'",
     68	"'SUB'", "'NEG'", "'XOR'", "'AND'", "'JMP'",
     69	"'JEZ'", "'JNZ'", "'JRO'", "'SHL'", "'SHR'",
     70
     71	/* Misc */
     72	"#<COMMENT>", "<LABEL>:", "X<INT>", "Y<INT>", "<NL>", "<EOF>"
     73};
     74
     75static bool
     76is_lit(const char *str)
     77{
     78	unsigned long v;
     79	const char *s;
     80	char *end;
     81
     82	if (!strncmp(str, "0b", 2)) {
     83		for (v = 0, s = str + 2; *s; s++)
     84			v = (2 * v) + (*s == '1');
     85	} else {
     86		v = strtoul(str, &end, 0);
     87		if (!end || *end) return false;
     88	}
     89
     90	return v < 256;
     91}
     92
     93static uint8_t
     94str_to_lit(const char *str)
     95{
     96	const char *s;
     97	unsigned v;
     98
     99	if (!strncmp(str, "0b", 2)) {
    100		for (v = 0, s = str + 2; *s; s++)
    101			v = 2 * v + (*s == '1');
    102		return (uint8_t) v;
    103	} else {
    104		return (uint8_t) strtoul(str, NULL, 0);
    105	}
    106}
    107
    108static enum tpu_port_dir
    109tok_to_dir(enum asm_tok tok)
    110{
    111	return tok - TOK_LEFT + DIR_LEFT;
    112}
    113
    114static bool
    115is_int(const char *str)
    116{
    117	char *end;
    118	long v;
    119
    120	v = strtol(str, &end, 10);
    121	if (!end || *end) return false;
    122	if (v < INT32_MIN || v > INT32_MAX) return false;
    123
    124	return true;
    125}
    126
    127enum tpu_inst_type
    128tok_to_inst(enum asm_tok tok)
    129{
    130	if (tok < TOK_NOP || tok > TOK_SHR) abort();
    131	return tok - TOK_NOP + INST_NOP;
    132}
    133
    134enum tpu_inst_op_type
    135tok_to_optype(enum asm_tok tok)
    136{
    137	if (tok < TOK_ACC || tok > TOK_NAME) abort();
    138	return tok - TOK_ACC + OP_ACC;
    139}
    140
    141struct tpu_inst_op
    142tok_to_op(struct asm_tokenizer *tokenizer, enum asm_tok tok)
    143{
    144	struct tpu_inst_op op;
    145
    146	op.type = tok_to_optype(tok);
    147	if (op.type == OP_LIT) {
    148		op.val.lit = str_to_lit(tokenizer->tokstr);
    149	} else if (op.type == OP_LABEL) {
    150		op.val.label = strdup(tokenizer->tokstr);
    151		if (!op.val.label) die("strdup:");
    152	}
    153
    154	return op;
    155}
    156
    157static size_t
    158strlcat_op_name(char *buf, struct tpu_inst_op *op, size_t n)
    159{
    160	char hhbuf[4];
    161
    162	if (op->type == OP_LIT) {
    163		snprintf(hhbuf, 4, "%hhu", op->val.lit);
    164		return strdcat(buf, hhbuf, n);
    165	} else if (op->type == OP_LABEL) {
    166		return strdcat(buf, op->val.label, n);
    167	} else {
    168		return strdcat(buf, op_reprs[op->type], n);
    169	}
    170}
    171
    172size_t
    173asm_print_inst(char *buf, size_t n, struct tpu_inst *inst)
    174{
    175	size_t len;
    176
    177	len = strdcpy(buf, inst_reprs[inst->type], n);
    178	if (inst->opcnt >= 1) {
    179		len += strdcat(buf, " ", n);
    180		len += strlcat_op_name(buf, &inst->ops[0], n);
    181	}
    182	if (inst->opcnt >= 2) {
    183		len += strdcat(buf, ", ", n);
    184		len += strlcat_op_name(buf, &inst->ops[1], n);
    185	}
    186
    187	return len;
    188}
    189
    190static enum asm_tok
    191tok_next(struct asm_tokenizer *tok)
    192{
    193	size_t len;
    194	char *s;
    195	int i;
    196
    197	if (!tok->linebuf[tok->off]) {
    198		if (feof(tok->file)) return TOK_EOF;
    199		s = fgets(tok->linebuf, sizeof(tok->linebuf), tok->file);
    200		if (!s && !feof(tok->file)) die("fgets:");
    201		if (!s) return TOK_NL;
    202
    203		len = strlen(s);
    204		if (len && s[len-1] != '\n' && !feof(tok->file))
    205			die("load: line %lu too long", tok->lineno);
    206		if (len && s[len-1] == '\n') s[len-1] = '\0';
    207
    208		tok->lineno += 1;
    209		tok->tokstr = s;
    210		tok->off = 0;
    211		return TOK_NL;
    212	}
    213
    214	s = tok->linebuf + tok->off;
    215	len = strspn(s, WHITESPACE);
    216	tok->off += len;
    217	if (!s[len]) return TOK_NL;
    218	tok->tokstr = (s += len);
    219
    220	len = strcspn(s, WHITESPACE);
    221	tok->off += len;
    222	if (s[len]) {
    223		s[len] = '\0';
    224		tok->off += 1;
    225	}
    226
    227	for (i = 0; i <= TOK_EOF; i++) {
    228		if (tok_strs[i] && !strcasecmp(s, tok_strs[i]))
    229			return (enum asm_tok) i;
    230	}
    231
    232	if (is_lit(s)) {
    233		return TOK_LIT;
    234	} else if (*s == '#') {
    235		tok->off += strlen(tok->linebuf + tok->off);
    236		return TOK_COMMENT;
    237	} else if (len && strchr(TEXTALPH, *s)
    238			&& strspn(s, NAMEALPH) == len-1 && s[len-1] == ':') {
    239		s[len-1] = '\0';
    240		return TOK_LABEL;
    241	} else if (*s == 'X' && is_int(s+1)) {
    242		return TOK_XPOS;
    243	} else if (*s == 'Y' && is_int(s+1)) {
    244		return TOK_YPOS;
    245	} else if (strchr(TEXTALPH, *s)
    246			&& strspn(s, NAMEALPH) == strlen(s)) {
    247		return TOK_NAME;
    248	} else {
    249		die("load: line %lu, invalid token '%s'", tok->lineno, s);
    250	}
    251}
    252
    253static enum asm_tok
    254tok_next_in(struct asm_tokenizer *tokenizer, ...)
    255{
    256	va_list ap, cpy;
    257	enum asm_tok tok;
    258	bool first;
    259	int arg;
    260
    261	tok = tok_next(tokenizer);
    262
    263	va_copy(cpy, ap);
    264
    265	va_start(cpy, tokenizer);
    266	while ((arg = va_arg(cpy, int)) > 0) {
    267		if (tok == arg) return tok;
    268	}
    269	va_end(cpy);
    270
    271	fprintf(stderr, "tis-as: load: ");
    272	fprintf(stderr, "line %lu, got tok %s, expected one of (",
    273		tokenizer->lineno, tok_reprs[tok]);
    274	first = true;
    275	va_start(ap, tokenizer);
    276	while ((arg = va_arg(ap, int)) > 0) {
    277		if (!first) fputc(',', stderr);
    278		fputs(tok_reprs[arg], stderr);
    279		first = false;
    280	}
    281	va_end(ap);
    282	fputs(")\n", stderr);
    283
    284	exit(1);
    285}
    286
    287static void
    288tpu_validate(struct tpu *tpu)
    289{
    290	size_t dst;
    291	int i;
    292
    293	for (i = 0; i < tpu->inst_cnt; i++) {
    294		if (tpu->insts[i].opcnt >= 1
    295				&& tpu->insts[i].ops[0].type == OP_LABEL) {
    296			dst = tpu_label_get(tpu, tpu->insts[i].ops[0].val.label);
    297			if (dst == TPU_MAX_INST)
    298				die("load: tpu X%i Y%i, label '%s' not defined",
    299					tpu->x, tpu->y,
    300					tpu->insts[i].ops[0].val.label);
    301		}
    302	}
    303}
    304
    305void
    306tis_load(struct tis *tis, const char *filepath, FILE *tis_stdin, FILE *tis_stdout)
    307{
    308	struct asm_tokenizer tokenizer;
    309	struct tpu_inst_op op1, op2;
    310	enum tpu_inst_type inst;
    311	struct tpu *tpu = NULL;
    312	struct tpu_port *port;
    313	enum tpu_port_dir stdin_dir, stdout_dir;
    314	bool stdin_set, stdout_set;
    315	int stdin_x, stdin_y;
    316	int stdout_x, stdout_y;
    317	enum asm_tok tok, optok;
    318	size_t i;
    319
    320	tis_deinit(tis);
    321	tis_init(tis, tis_stdin, tis_stdout);
    322
    323	stdin_set = stdout_set = false;
    324
    325	tokenizer.filepath = filepath;
    326	tokenizer.file = fopen(filepath, "r");
    327	if (!tokenizer.file) die("load: fopen '%s':", filepath);
    328
    329	tokenizer.lineno = 0;
    330	tokenizer.off = 0;
    331	tokenizer.tokstr = NULL;
    332	tokenizer.linebuf[tokenizer.off] = '\0';
    333	while ((tok = tok_next(&tokenizer)) != TOK_EOF) {
    334		switch (tok) {
    335		case TOK_STDIN:
    336			if (tpu || stdin_set) goto disallowed;
    337			tok_next_in(&tokenizer, TOK_XPOS, -1);
    338			stdin_x = atoi(tokenizer.tokstr + 1);
    339			tok_next_in(&tokenizer, TOK_YPOS, -1);
    340			stdin_y = atoi(tokenizer.tokstr + 1);
    341			optok = tok_next_in(&tokenizer, TOK_COMMENT,
    342				TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_NL, -1);
    343			if (optok != TOK_NL && optok != TOK_COMMENT) {
    344				stdin_dir = tok_to_dir(optok);
    345				tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1);
    346			} else {
    347				stdin_dir = DIR_UP;
    348			}
    349			stdin_set = true;
    350			break;
    351		case TOK_STDOUT:
    352			if (tpu || stdout_set) goto disallowed;
    353			tok_next_in(&tokenizer, TOK_XPOS, -1);
    354			stdout_x = atoi(tokenizer.tokstr + 1);
    355			tok_next_in(&tokenizer, TOK_YPOS, -1);
    356			stdout_y = atoi(tokenizer.tokstr + 1);
    357			optok = tok_next_in(&tokenizer, TOK_COMMENT,
    358				TOK_LEFT, TOK_RIGHT, TOK_UP, TOK_DOWN, TOK_NL, -1);
    359			if (optok != TOK_NL && optok != TOK_COMMENT) {
    360				stdout_dir = tok_to_dir(optok);
    361				tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1);
    362			} else {
    363				stdout_dir = DIR_DOWN;
    364			}
    365			stdout_set = true;
    366			break;
    367		case TOK_TPU:
    368			if (tpu) goto disallowed;
    369			tpu = tis_add_tpu(tis);
    370			tpu_init(tpu);
    371			tpu->tis = tis;
    372			tok_next_in(&tokenizer, TOK_XPOS, -1);
    373			tpu->x = atoi(tokenizer.tokstr + 1);
    374			tok_next_in(&tokenizer, TOK_YPOS, -1);
    375			tpu->y = atoi(tokenizer.tokstr + 1);
    376			tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1);
    377			if (!tpu_map_add(&tis->tpu_map, tpu))
    378				die("load: duplicate tpu location X%i Y%i",
    379					tpu->x, tpu->y);
    380			break;
    381		case TOK_END:
    382			if (!tpu) goto disallowed;
    383			tpu_validate(tpu);
    384			tpu = NULL;
    385			tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1);
    386			break;
    387		case TOK_NOP: case TOK_MOV: case TOK_SWP: case TOK_SAV:
    388		case TOK_ADD: case TOK_SUB: case TOK_NEG: case TOK_XOR:
    389		case TOK_AND: case TOK_JMP: case TOK_JEQ: case TOK_JNE:
    390		case TOK_JRO: case TOK_SHL: case TOK_SHR:
    391			if (!tpu) goto disallowed;
    392			inst = tok_to_inst(tok);
    393
    394			optok = tok_next_in(&tokenizer, TOK_ACC,
    395				TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP,
    396				TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT,
    397				TOK_NAME, TOK_COMMENT, TOK_NL, -1);
    398			if (optok == TOK_COMMENT || optok == TOK_NL) {
    399				if (!tpu_add_inst(tpu, inst, 0, op1, op2))
    400					die("load: line %lu, invalid instruction",
    401						tokenizer.lineno-1);
    402				break;
    403			}
    404			op1 = tok_to_op(&tokenizer, optok);
    405
    406			optok = tok_next_in(&tokenizer, TOK_ACC,
    407				TOK_NIL, TOK_LEFT, TOK_RIGHT, TOK_UP,
    408				TOK_DOWN, TOK_ANY, TOK_LAST, TOK_LIT,
    409				TOK_NAME, TOK_COMMENT, TOK_NL, -1);
    410			if (optok == TOK_COMMENT || optok == TOK_NL) {
    411				if (!tpu_add_inst(tpu, inst, 1, op1, op2))
    412					die("load: line %lu, invalid instruction",
    413						tokenizer.lineno-1);
    414				break;
    415			}
    416			op2 = tok_to_op(&tokenizer, optok);
    417
    418			tok_next_in(&tokenizer, TOK_COMMENT, TOK_NL, -1);
    419			if (!tpu_add_inst(tpu, inst, 2, op1, op2))
    420				die("load: line %lu, invalid instruction",
    421					tokenizer.lineno-1);
    422			break;
    423		case TOK_COMMENT:
    424			tok_next_in(&tokenizer, TOK_NL, -1);
    425			break;
    426		case TOK_LABEL:
    427			if (!tpu_label_add(tpu, tokenizer.tokstr, tpu->inst_cnt))
    428				die("load: line %lu, duplicate label (pos)",
    429					tokenizer.lineno);
    430			break;
    431		case TOK_NL:
    432			break;
    433		default:
    434			goto disallowed;
    435		}
    436	}
    437
    438	for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++)
    439		tpu_init_ports(tpu, &tis->tpu_map);
    440
    441	for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++) {
    442		tpu_attach_ports(tpu);
    443
    444		if (stdin_set && tpu->x == stdin_x && tpu->y == stdin_y) {
    445			port = &tpu->ports[stdin_dir];
    446			if (port->dst_tpu) die("load: stdin port in use");
    447			port->attached = true;
    448			port->dst_tpu = NULL;
    449			port->dst_port = &tis->stdin_port;
    450			port->type = PORT_IN;
    451			tis->stdin_port.attached = true;
    452			tis->stdin_port.dst_tpu = tpu;
    453			tis->stdin_port.dst_port = port;
    454		}
    455
    456		if (stdout_set && tpu->x == stdout_x && tpu->y == stdout_y) {
    457			port = &tpu->ports[stdout_dir];
    458			if (port->dst_tpu) die("load: stdout port in use");
    459			port->attached = true;
    460			port->dst_tpu = NULL;
    461			port->dst_port = &tis->stdout_port;
    462			port->type = PORT_OUT;
    463			tis->stdout_port.attached = true;
    464			tis->stdout_port.dst_tpu = tpu;
    465			tis->stdout_port.dst_port = port;
    466		}
    467	}
    468
    469	if (stdin_set && !tis->stdin_port.attached)
    470		die("load: stdin tpu (X%i Y%i) not found",
    471			stdin_x, stdin_y);
    472
    473	if (stdout_set && !tis->stdout_port.attached)
    474		die("load: stdout tpu (X%i Y%i) not found",
    475			stdout_x, stdout_y);
    476
    477	fclose(tokenizer.file);
    478
    479	return;
    480
    481disallowed:
    482	if (tok == TOK_NAME) {
    483		die("load: line %lu, unexpected token '%s'",
    484			tokenizer.lineno, tokenizer.tokstr);
    485	} else {
    486		die("load: line %lu, token %s not allowed here",
    487			tokenizer.lineno, tok_reprs[tok]);
    488	}
    489}