+ if (i == ARRAY_SIZE(op_table))
+ aerr("unhandled op: '%s'\n", words[0]);
+ w++;
+
+ op->op = op_table[i].op;
+ op->flags = op_table[i].flags | prefix_flags;
+ op->regmask_src = op->regmask_dst = 0;
+
+ for (opr = 0; opr < op_table[i].minopr; opr++) {
+ regmask = regmask_ind = 0;
+ w = parse_operand(&op->operand[opr], ®mask, ®mask_ind,
+ words, wordc, w, op->flags);
+
+ if (opr == 0 && (op->flags & OPF_DATA))
+ op->regmask_dst = regmask;
+ // for now, mark dst as src too
+ op->regmask_src |= regmask | regmask_ind;
+ }
+
+ for (; w < wordc && opr < op_table[i].maxopr; opr++) {
+ w = parse_operand(&op->operand[opr],
+ &op->regmask_src, &op->regmask_src,
+ words, wordc, w, op->flags);
+ }
+
+ if (w < wordc)
+ aerr("parse_op %s incomplete: %d/%d\n",
+ words[0], w, wordc);
+
+ // special cases
+ op->operand_cnt = opr;
+ if (!strncmp(op_table[i].name, "set", 3))
+ op->operand[0].lmod = OPLM_BYTE;
+
+ // ops with implicit argumets
+ switch (op->op) {
+ case OP_CDQ:
+ op->operand_cnt = 2;
+ setup_reg_opr(&op->operand[0], xDX, OPLM_DWORD, &op->regmask_dst);
+ setup_reg_opr(&op->operand[1], xAX, OPLM_DWORD, &op->regmask_src);
+ break;
+
+ case OP_STOS:
+ if (op->operand_cnt != 0)
+ break;
+ if (IS(words[op_w], "stosb"))
+ lmod = OPLM_BYTE;
+ else if (IS(words[op_w], "stosw"))
+ lmod = OPLM_WORD;
+ else if (IS(words[op_w], "stosd"))
+ lmod = OPLM_DWORD;
+ op->operand_cnt = 3;
+ setup_reg_opr(&op->operand[0], xDI, lmod, &op->regmask_src);
+ setup_reg_opr(&op->operand[1], xCX, OPLM_DWORD, &op->regmask_src);
+ op->regmask_dst = op->regmask_src;
+ setup_reg_opr(&op->operand[2], xAX, OPLM_DWORD, &op->regmask_src);
+ break;
+
+ case OP_MOVS:
+ if (op->operand_cnt != 0)
+ break;
+ if (IS(words[op_w], "movsb"))
+ lmod = OPLM_BYTE;
+ else if (IS(words[op_w], "movsw"))
+ lmod = OPLM_WORD;
+ else if (IS(words[op_w], "movsd"))
+ lmod = OPLM_DWORD;
+ op->operand_cnt = 3;
+ setup_reg_opr(&op->operand[0], xDI, lmod, &op->regmask_src);
+ setup_reg_opr(&op->operand[1], xSI, OPLM_DWORD, &op->regmask_src);
+ setup_reg_opr(&op->operand[2], xCX, OPLM_DWORD, &op->regmask_src);
+ op->regmask_dst = op->regmask_src;
+ break;
+
+ case OP_IMUL:
+ if (op->operand_cnt != 1)
+ break;
+ // fallthrough
+ case OP_MUL:
+ // singleop mul
+ op->regmask_dst = (1 << xDX) | (1 << xAX);
+ op->regmask_src |= (1 << xAX);
+ if (op->operand[0].lmod == OPLM_UNSPEC)
+ op->operand[0].lmod = OPLM_DWORD;
+ break;
+
+ case OP_DIV:
+ case OP_IDIV:
+ // we could set up operands for edx:eax, but there is no real need to
+ // (see is_opr_modified())
+ regmask = (1 << xDX) | (1 << xAX);
+ op->regmask_dst = regmask;
+ op->regmask_src |= regmask;
+ if (op->operand[0].lmod == OPLM_UNSPEC)
+ op->operand[0].lmod = OPLM_DWORD;
+ break;
+
+ case OP_SHL:
+ case OP_SHR:
+ case OP_SAR:
+ case OP_ROL:
+ case OP_ROR:
+ if (op->operand[1].lmod == OPLM_UNSPEC)
+ op->operand[1].lmod = OPLM_BYTE;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static const char *op_name(enum op_op op)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(op_table); i++)
+ if (op_table[i].op == op)
+ return op_table[i].name;
+
+ return "???";
+}
+
+// debug
+static const char *dump_op(struct parsed_op *po)
+{
+ static char out[128];
+ char *p = out;
+ int i;
+
+ if (po == NULL)
+ return "???";
+
+ snprintf(out, sizeof(out), "%s", op_name(po->op));
+ for (i = 0; i < po->operand_cnt; i++) {
+ p += strlen(p);
+ if (i > 0)
+ *p++ = ',';
+ snprintf(p, sizeof(out) - (p - out),
+ po->operand[i].type == OPT_REGMEM ? " [%s]" : " %s",
+ po->operand[i].name);
+ }
+
+ return out;
+}
+
+static const char *lmod_type_u(struct parsed_op *po,
+ enum opr_lenmod lmod)
+{
+ switch (lmod) {
+ case OPLM_DWORD:
+ return "u32";
+ case OPLM_WORD:
+ return "u16";
+ case OPLM_BYTE:
+ return "u8";
+ default:
+ ferr(po, "invalid lmod: %d\n", lmod);
+ return "(_invalid_)";
+ }
+}
+
+static const char *lmod_cast_u(struct parsed_op *po,
+ enum opr_lenmod lmod)
+{
+ switch (lmod) {
+ case OPLM_DWORD:
+ return "";
+ case OPLM_WORD:
+ return "(u16)";
+ case OPLM_BYTE:
+ return "(u8)";
+ default:
+ ferr(po, "invalid lmod: %d\n", lmod);
+ return "(_invalid_)";
+ }
+}
+
+static const char *lmod_cast_u_ptr(struct parsed_op *po,
+ enum opr_lenmod lmod)
+{
+ switch (lmod) {
+ case OPLM_DWORD:
+ return "*(u32 *)";
+ case OPLM_WORD:
+ return "*(u16 *)";
+ case OPLM_BYTE:
+ return "*(u8 *)";
+ default:
+ ferr(po, "invalid lmod: %d\n", lmod);
+ return "(_invalid_)";
+ }
+}
+
+static const char *lmod_cast_s(struct parsed_op *po,
+ enum opr_lenmod lmod)
+{
+ switch (lmod) {
+ case OPLM_DWORD:
+ return "(s32)";
+ case OPLM_WORD:
+ return "(s16)";
+ case OPLM_BYTE:
+ return "(s8)";
+ default:
+ ferr(po, "%s: invalid lmod: %d\n", __func__, lmod);
+ return "(_invalid_)";
+ }
+}
+
+static const char *lmod_cast(struct parsed_op *po,
+ enum opr_lenmod lmod, int is_signed)
+{
+ return is_signed ?
+ lmod_cast_s(po, lmod) :
+ lmod_cast_u(po, lmod);
+}
+
+static int lmod_bytes(struct parsed_op *po, enum opr_lenmod lmod)
+{
+ switch (lmod) {
+ case OPLM_DWORD:
+ return 4;
+ case OPLM_WORD:
+ return 2;
+ case OPLM_BYTE:
+ return 1;
+ default:
+ ferr(po, "%s: invalid lmod: %d\n", __func__, lmod);
+ return 0;
+ }
+}
+
+static const char *opr_name(struct parsed_op *po, int opr_num)
+{
+ if (opr_num >= po->operand_cnt)
+ ferr(po, "opr OOR: %d/%d\n", opr_num, po->operand_cnt);
+ return po->operand[opr_num].name;
+}
+
+static unsigned int opr_const(struct parsed_op *po, int opr_num)
+{
+ if (opr_num >= po->operand_cnt)
+ ferr(po, "opr OOR: %d/%d\n", opr_num, po->operand_cnt);
+ if (po->operand[opr_num].type != OPT_CONST)
+ ferr(po, "opr %d: const expected\n", opr_num);
+ return po->operand[opr_num].val;
+}
+
+static const char *opr_reg_p(struct parsed_op *po, struct parsed_opr *popr)
+{
+ if ((unsigned int)popr->reg >= MAX_REGS)
+ ferr(po, "invalid reg: %d\n", popr->reg);
+ return regs_r32[popr->reg];
+}
+
+static struct parsed_equ *equ_find(struct parsed_op *po, const char *name,
+ int *extra_offs)
+{
+ const char *p;
+ char *endp;
+ int namelen;
+ int i;
+
+ *extra_offs = 0;
+ namelen = strlen(name);
+
+ p = strchr(name, '+');
+ if (p != NULL) {
+ namelen = p - name;
+ if (namelen <= 0)
+ ferr(po, "equ parse failed for '%s'\n", name);
+
+ if (IS_START(p, "0x"))
+ p += 2;
+ *extra_offs = strtol(p, &endp, 16);
+ if (*endp != 0)
+ ferr(po, "equ parse failed for '%s'\n", name);
+ }
+
+ for (i = 0; i < g_eqcnt; i++)
+ if (strncmp(g_eqs[i].name, name, namelen) == 0
+ && g_eqs[i].name[namelen] == 0)
+ break;
+ if (i >= g_eqcnt) {
+ if (po != NULL)
+ ferr(po, "unresolved equ name: '%s'\n", name);
+ return NULL;
+ }
+
+ return &g_eqs[i];
+}
+
+static void stack_frame_access(struct parsed_op *po,
+ enum opr_lenmod lmod, char *buf, size_t buf_size,
+ const char *name, int is_src, int is_lea)
+{
+ const char *prefix = "";
+ char ofs_reg[16] = { 0, };
+ struct parsed_equ *eq;
+ int i, arg_i, arg_s;
+ const char *bp_arg;
+ int stack_ra = 0;
+ int offset = 0;
+ int sf_ofs;
+
+ bp_arg = parse_stack_el(name, ofs_reg);
+ snprintf(g_comment, sizeof(g_comment), "%s", bp_arg);
+ eq = equ_find(po, bp_arg, &offset);
+ if (eq == NULL)
+ ferr(po, "detected but missing eq\n");
+
+ offset += eq->offset;
+
+ if (!strncmp(name, "ebp", 3))
+ stack_ra = 4;
+
+ if (stack_ra <= offset && offset < stack_ra + 4)
+ ferr(po, "reference to ra? %d %d\n", offset, stack_ra);
+
+ if (offset > stack_ra) {
+ arg_i = (offset - stack_ra - 4) / 4;
+ if (arg_i < 0 || arg_i >= g_func_pp.argc_stack)
+ ferr(po, "offset %d (%s) doesn't map to any arg\n",
+ offset, bp_arg);
+ if (ofs_reg[0] != 0)
+ ferr(po, "offset reg on arg acecss?\n");
+
+ for (i = arg_s = 0; i < g_func_pp.argc; i++) {
+ if (g_func_pp.arg[i].reg != NULL)
+ continue;
+ if (arg_s == arg_i)
+ break;
+ arg_s++;
+ }
+ if (i == g_func_pp.argc)
+ ferr(po, "arg %d not in prototype?\n", arg_i);
+ if (is_lea)
+ ferr(po, "lea to arg?\n");
+
+ snprintf(buf, buf_size, "%sa%d", is_src ? "(u32)" : "", i + 1);
+ }
+ else {
+ if (g_stack_fsz == 0)
+ ferr(po, "stack var access without stackframe\n");
+
+ sf_ofs = g_stack_fsz + offset;
+ if (sf_ofs < 0)
+ ferr(po, "bp_stack offset %d/%d\n", offset, g_stack_fsz);
+
+ if (is_lea)
+ prefix = "(u32)&";
+
+ switch (lmod)
+ {
+ case OPLM_BYTE:
+ if (ofs_reg[0] != 0)
+ snprintf(buf, buf_size, "%ssf.b[%d+%s]",
+ prefix, sf_ofs, ofs_reg);
+ else
+ snprintf(buf, buf_size, "%ssf.b[%d]",
+ prefix, sf_ofs);
+ break;
+ case OPLM_WORD:
+ if (ofs_reg[0] != 0)
+ snprintf(buf, buf_size, "%ssf.w[%d+%s/2]",
+ prefix, sf_ofs / 2, ofs_reg);
+ else
+ snprintf(buf, buf_size, "%ssf.w[%d]",
+ prefix, sf_ofs / 2);
+ break;
+ case OPLM_DWORD:
+ if (ofs_reg[0] != 0)
+ snprintf(buf, buf_size, "%ssf.d[%d+%s/4]",
+ prefix, sf_ofs / 4, ofs_reg);
+ else
+ snprintf(buf, buf_size, "%ssf.d[%d]",
+ prefix, sf_ofs / 4);
+ break;
+ default:
+ ferr(po, "bp_stack bad lmod: %d\n", lmod);
+ }
+ }
+}
+
+static char *out_src_opr(char *buf, size_t buf_size,
+ struct parsed_op *po, struct parsed_opr *popr, int is_lea)
+{
+ char tmp1[256], tmp2[256];
+ char expr[256];
+ int ret;
+
+ switch (popr->type) {
+ case OPT_REG:
+ if (is_lea)
+ ferr(po, "lea from reg?\n");
+
+ switch (popr->lmod) {
+ case OPLM_DWORD:
+ snprintf(buf, buf_size, "%s", opr_reg_p(po, popr));
+ break;
+ case OPLM_WORD:
+ snprintf(buf, buf_size, "(u16)%s", opr_reg_p(po, popr));
+ break;
+ case OPLM_BYTE:
+ if (popr->name[1] == 'h') // XXX..
+ snprintf(buf, buf_size, "(u8)(%s >> 8)", opr_reg_p(po, popr));
+ else
+ snprintf(buf, buf_size, "(u8)%s", opr_reg_p(po, popr));
+ break;
+ default:
+ ferr(po, "invalid src lmod: %d\n", popr->lmod);
+ }
+ break;
+
+ case OPT_REGMEM:
+ if (parse_stack_el(popr->name, NULL)) {
+ stack_frame_access(po, popr->lmod, buf, buf_size,
+ popr->name, 1, is_lea);
+ break;
+ }
+
+ strcpy(expr, popr->name);
+ if (strchr(expr, '[')) {
+ // special case: '[' can only be left for label[reg] form
+ ret = sscanf(expr, "%[^[][%[^]]]", tmp1, tmp2);
+ if (ret != 2)
+ ferr(po, "parse failure for '%s'\n", expr);
+ snprintf(expr, sizeof(expr), "(u32)&%s + %s", tmp1, tmp2);
+ }
+
+ // XXX: do we need more parsing?
+ if (is_lea) {
+ snprintf(buf, buf_size, "%s", expr);
+ break;
+ }
+
+ snprintf(buf, buf_size, "%s(%s)",
+ lmod_cast_u_ptr(po, popr->lmod), expr);
+ break;
+
+ case OPT_LABEL:
+ if (is_lea)
+ snprintf(buf, buf_size, "(u32)&%s", popr->name);
+ else
+ snprintf(buf, buf_size, "(u32)%s", popr->name);
+ break;
+
+ case OPT_OFFSET:
+ if (is_lea)
+ ferr(po, "lea an offset?\n");
+ snprintf(buf, buf_size, "(u32)&%s", popr->name);
+ break;
+
+ case OPT_CONST:
+ if (is_lea)
+ ferr(po, "lea from const?\n");
+
+ printf_number(buf, buf_size, popr->val);
+ break;
+
+ default:
+ ferr(po, "invalid src type: %d\n", popr->type);
+ }
+
+ return buf;
+}
+
+static char *out_dst_opr(char *buf, size_t buf_size,
+ struct parsed_op *po, struct parsed_opr *popr)
+{
+ switch (popr->type) {
+ case OPT_REG:
+ switch (popr->lmod) {
+ case OPLM_DWORD:
+ snprintf(buf, buf_size, "%s", opr_reg_p(po, popr));
+ break;
+ case OPLM_WORD:
+ // ugh..
+ snprintf(buf, buf_size, "LOWORD(%s)", opr_reg_p(po, popr));
+ break;
+ case OPLM_BYTE:
+ // ugh..
+ if (popr->name[1] == 'h') // XXX..
+ snprintf(buf, buf_size, "BYTE1(%s)", opr_reg_p(po, popr));
+ else
+ snprintf(buf, buf_size, "LOBYTE(%s)", opr_reg_p(po, popr));
+ break;
+ default:
+ ferr(po, "invalid dst lmod: %d\n", popr->lmod);
+ }
+ break;
+
+ case OPT_REGMEM:
+ if (parse_stack_el(popr->name, NULL)) {
+ stack_frame_access(po, popr->lmod, buf, buf_size,
+ popr->name, 0, 0);
+ break;
+ }
+
+ return out_src_opr(buf, buf_size, po, popr, 0);
+
+ case OPT_LABEL:
+ snprintf(buf, buf_size, "%s", popr->name);
+ break;
+
+ default:
+ ferr(po, "invalid dst type: %d\n", popr->type);
+ }
+
+ return buf;
+}
+
+static enum parsed_flag_op split_cond(struct parsed_op *po,
+ enum op_op op, int *is_inv)
+{
+ *is_inv = 0;
+
+ switch (op) {
+ case OP_JO:
+ return PFO_O;
+ case OP_JC:
+ return PFO_C;
+ case OP_JZ:
+ return PFO_Z;
+ case OP_JBE:
+ return PFO_BE;
+ case OP_JS:
+ return PFO_S;
+ case OP_JP:
+ return PFO_P;
+ case OP_JL:
+ return PFO_L;
+ case OP_JLE:
+ return PFO_LE;
+
+ case OP_JNO:
+ *is_inv = 1;
+ return PFO_O;
+ case OP_JNC:
+ *is_inv = 1;
+ return PFO_C;
+ case OP_JNZ:
+ *is_inv = 1;
+ return PFO_Z;
+ case OP_JA:
+ *is_inv = 1;
+ return PFO_BE;
+ case OP_JNS:
+ *is_inv = 1;
+ return PFO_S;
+ case OP_JNP:
+ *is_inv = 1;
+ return PFO_P;
+ case OP_JGE:
+ *is_inv = 1;
+ return PFO_L;
+ case OP_JG:
+ *is_inv = 1;
+ return PFO_LE;
+
+ case OP_ADC:
+ case OP_SBB:
+ return PFO_C;
+
+ default:
+ ferr(po, "split_cond: bad op %d\n", op);
+ return -1;
+ }
+}
+
+static void out_test_for_cc(char *buf, size_t buf_size,
+ struct parsed_op *po, enum parsed_flag_op pfo, int is_inv,
+ enum opr_lenmod lmod, const char *expr)
+{
+ const char *cast, *scast;
+
+ cast = lmod_cast_u(po, lmod);
+ scast = lmod_cast_s(po, lmod);
+
+ switch (pfo) {
+ case PFO_Z:
+ case PFO_BE: // CF=1||ZF=1; CF=0
+ snprintf(buf, buf_size, "(%s%s %s 0)",
+ cast, expr, is_inv ? "!=" : "==");
+ break;
+
+ case PFO_S:
+ case PFO_L: // SF!=OF; OF=0
+ snprintf(buf, buf_size, "(%s%s %s 0)",
+ scast, expr, is_inv ? ">=" : "<");
+ break;
+
+ case PFO_LE: // ZF=1||SF!=OF; OF=0
+ snprintf(buf, buf_size, "(%s%s %s 0)",
+ scast, expr, is_inv ? ">" : "<=");
+ break;
+
+ default:
+ ferr(po, "%s: unhandled parsed_flag_op: %d\n", __func__, pfo);
+ }
+}
+
+static void out_cmp_for_cc(char *buf, size_t buf_size,
+ struct parsed_op *po, enum parsed_flag_op pfo, int is_inv,
+ enum opr_lenmod lmod, const char *expr1, const char *expr2)
+{
+ const char *cast, *scast;
+
+ cast = lmod_cast_u(po, lmod);
+ scast = lmod_cast_s(po, lmod);
+
+ switch (pfo) {
+ case PFO_C:
+ // note: must be unsigned compare
+ snprintf(buf, buf_size, "(%s%s %s %s%s)",
+ cast, expr1, is_inv ? ">=" : "<", cast, expr2);
+ break;
+
+ case PFO_Z:
+ snprintf(buf, buf_size, "(%s%s %s %s%s)",
+ cast, expr1, is_inv ? "!=" : "==", cast, expr2);
+ break;
+
+ case PFO_BE: // !a
+ // note: must be unsigned compare
+ snprintf(buf, buf_size, "(%s%s %s %s%s)",
+ cast, expr1, is_inv ? ">" : "<=", cast, expr2);
+ break;
+
+ // note: must be signed compare
+ case PFO_S:
+ snprintf(buf, buf_size, "(%s(%s - %s) %s 0)",
+ scast, expr1, expr2, is_inv ? ">=" : "<");
+ break;
+
+ case PFO_L: // !ge
+ snprintf(buf, buf_size, "(%s%s %s %s%s)",
+ scast, expr1, is_inv ? ">=" : "<", scast, expr2);
+ break;
+
+ case PFO_LE:
+ snprintf(buf, buf_size, "(%s%s %s %s%s)",
+ scast, expr1, is_inv ? ">" : "<=", scast, expr2);
+ break;
+
+ default:
+ ferr(po, "%s: unhandled parsed_flag_op: %d\n", __func__, pfo);
+ }
+}
+
+static void out_cmp_test(char *buf, size_t buf_size,
+ struct parsed_op *po, enum parsed_flag_op pfo, int is_inv)
+{
+ char buf1[256], buf2[256], buf3[256];
+
+ if (po->op == OP_TEST) {
+ if (IS(opr_name(po, 0), opr_name(po, 1))) {
+ out_src_opr(buf3, sizeof(buf3), po, &po->operand[0], 0);
+ }
+ else {
+ out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], 0);
+ out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], 0);
+ snprintf(buf3, sizeof(buf3), "(%s & %s)", buf1, buf2);
+ }
+ out_test_for_cc(buf, buf_size, po, pfo, is_inv,
+ po->operand[0].lmod, buf3);
+ }
+ else if (po->op == OP_CMP) {
+ out_src_opr(buf2, sizeof(buf2), po, &po->operand[0], 0);
+ out_src_opr(buf3, sizeof(buf3), po, &po->operand[1], 0);
+ out_cmp_for_cc(buf, buf_size, po, pfo, is_inv,
+ po->operand[0].lmod, buf2, buf3);
+ }
+ else
+ ferr(po, "%s: unhandled op: %d\n", __func__, po->op);
+}
+
+static void propagate_lmod(struct parsed_op *po, struct parsed_opr *popr1,
+ struct parsed_opr *popr2)
+{
+ if (popr1->lmod == OPLM_UNSPEC && popr2->lmod == OPLM_UNSPEC)
+ ferr(po, "missing lmod for both operands\n");
+
+ if (popr1->lmod == OPLM_UNSPEC)
+ popr1->lmod = popr2->lmod;
+ else if (popr2->lmod == OPLM_UNSPEC)
+ popr2->lmod = popr1->lmod;
+ else if (popr1->lmod != popr2->lmod)
+ ferr(po, "conflicting lmods: %d vs %d\n", popr1->lmod, popr2->lmod);
+}
+
+static const char *op_to_c(struct parsed_op *po)
+{
+ switch (po->op)
+ {
+ case OP_ADD:
+ case OP_ADC:
+ return "+";
+ case OP_SUB:
+ case OP_SBB:
+ return "-";
+ case OP_AND:
+ return "&";
+ case OP_OR:
+ return "|";
+ case OP_XOR:
+ return "^";
+ case OP_SHL:
+ return "<<";
+ case OP_SHR:
+ return ">>";
+ case OP_MUL:
+ case OP_IMUL:
+ return "*";
+ default:
+ ferr(po, "op_to_c was supplied with %d\n", po->op);
+ }
+}
+
+static void set_flag_no_dup(struct parsed_op *po, enum op_flags flag,
+ enum op_flags flag_check)
+{
+ if (po->flags & flag)
+ ferr(po, "flag %x already set\n", flag);
+ if (po->flags & flag_check)
+ ferr(po, "flag_check %x already set\n", flag_check);
+
+ po->flags |= flag;
+}
+
+static int scan_for_pop(int i, int opcnt, const char *reg,
+ int magic, int depth, int *maxdepth, int do_flags)
+{
+ struct parsed_op *po;
+ int ret = 0;
+ int j;
+
+ for (; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->cc_scratch == magic)
+ break; // already checked
+ po->cc_scratch = magic;
+
+ if (po->flags & OPF_TAIL)
+ return -1; // deadend
+
+ if ((po->flags & OPF_RMD)
+ || (po->op == OP_PUSH && po->argmask)) // arg push
+ continue;
+
+ if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+ if (po->btj != NULL) {
+ // jumptable
+ for (j = 0; j < po->btj->count - 1; j++) {
+ ret |= scan_for_pop(po->btj->d[j].bt_i, opcnt, reg, magic,
+ depth, maxdepth, do_flags);
+ if (ret < 0)
+ return ret; // dead end
+ }
+ // follow last jumptable entry
+ i = po->btj->d[j].bt_i - 1;
+ continue;
+ }
+
+ if (po->bt_i < 0) {
+ ferr(po, "dead branch\n");
+ return -1;
+ }
+
+ if (po->flags & OPF_CC) {
+ ret |= scan_for_pop(po->bt_i, opcnt, reg, magic,
+ depth, maxdepth, do_flags);
+ if (ret < 0)
+ return ret; // dead end
+ }
+ else {
+ i = po->bt_i - 1;
+ }
+ continue;
+ }
+
+ if ((po->op == OP_POP || po->op == OP_PUSH)
+ && po->operand[0].type == OPT_REG
+ && IS(po->operand[0].name, reg))
+ {
+ if (po->op == OP_PUSH) {
+ depth++;
+ if (depth > *maxdepth)
+ *maxdepth = depth;
+ if (do_flags)
+ set_flag_no_dup(po, OPF_RSAVE, OPF_RMD);
+ }
+ else if (depth == 0) {
+ if (do_flags)
+ set_flag_no_dup(po, OPF_RMD, OPF_RSAVE);
+ return 1;
+ }
+ else {
+ depth--;
+ if (depth < 0) // should not happen
+ ferr(po, "fail with depth\n");
+ if (do_flags)
+ set_flag_no_dup(po, OPF_RSAVE, OPF_RMD);
+ }
+ }
+ }
+
+ return ret;
+}
+
+// scan for pop starting from 'ret' op (all paths)
+static int scan_for_pop_ret(int i, int opcnt, const char *reg,
+ int flag_set)
+{
+ int found = 0;
+ int j;
+
+ for (; i < opcnt; i++) {
+ if (!(ops[i].flags & OPF_TAIL))
+ continue;
+
+ for (j = i - 1; j >= 0; j--) {
+ if (ops[j].flags & OPF_RMD)
+ continue;
+ if (ops[j].flags & OPF_JMP)
+ return -1;
+
+ if (ops[j].op == OP_POP && ops[j].operand[0].type == OPT_REG
+ && IS(ops[j].operand[0].name, reg))
+ {
+ found = 1;
+ ops[j].flags |= flag_set;
+ break;
+ }
+
+ if (g_labels[j][0] != 0)
+ return -1;
+ }
+ }
+
+ return found ? 0 : -1;
+}
+
+// is operand 'opr modified' by parsed_op 'po'?
+static int is_opr_modified(const struct parsed_opr *opr,
+ const struct parsed_op *po)
+{
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+ return 0;
+
+ if (opr->type == OPT_REG && po->operand[0].type == OPT_REG) {
+ if (po->regmask_dst & (1 << opr->reg))
+ return 1;
+ else
+ return 0;
+ }
+
+ return IS(po->operand[0].name, opr->name);
+}
+
+// is any operand of parsed_op 'po_test' modified by parsed_op 'po'?
+static int is_any_opr_modified(const struct parsed_op *po_test,
+ const struct parsed_op *po)
+{
+ int i;
+
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+ return 0;
+
+ if (po_test->regmask_src & po->regmask_dst)
+ return 1;
+
+ for (i = 0; i < po_test->operand_cnt; i++)
+ if (IS(po_test->operand[i].name, po->operand[0].name))
+ return 1;
+
+ return 0;
+}
+
+// scan for any po_test operand modification in range given
+static int scan_for_mod(struct parsed_op *po_test, int i, int opcnt)
+{
+ for (; i < opcnt; i++) {
+ if (is_any_opr_modified(po_test, &ops[i]))
+ return i;
+ }
+
+ return -1;
+}
+
+// scan for po_test operand[0] modification in range given
+static int scan_for_mod_opr0(struct parsed_op *po_test,
+ int i, int opcnt)
+{
+ for (; i < opcnt; i++) {
+ if (is_opr_modified(&po_test->operand[0], &ops[i]))
+ return i;
+ }
+
+ return -1;
+}
+
+static int scan_for_flag_set(int i)
+{
+ for (; i >= 0; i--) {
+ if (ops[i].flags & OPF_FLAGS)
+ return i;
+
+ if ((ops[i].flags & OPF_JMP) && !(ops[i].flags & OPF_CC))
+ return -1;
+ if (g_labels[i][0] != 0)
+ return -1;
+ }
+
+ return -1;
+}
+
+// scan back for cdq, if anything modifies edx, fail
+static int scan_for_cdq_edx(int i)
+{
+ for (; i >= 0; i--) {
+ if (ops[i].op == OP_CDQ)
+ return i;
+
+ if (ops[i].regmask_dst & (1 << xDX))
+ return -1;
+ if (g_labels[i][0] != 0)
+ return -1;
+ }
+
+ return -1;
+}
+
+static int scan_for_reg_clear(int i, int reg)
+{
+ for (; i >= 0; i--) {
+ if (ops[i].op == OP_XOR
+ && ops[i].operand[0].lmod == OPLM_DWORD
+ && ops[i].operand[0].reg == ops[i].operand[1].reg
+ && ops[i].operand[0].reg == reg)
+ return i;
+
+ if (ops[i].regmask_dst & (1 << reg))
+ return -1;
+ if (g_labels[i][0] != 0)
+ return -1;
+ }
+
+ return -1;
+}
+
+// scan for positive, constant esp adjust
+static int scan_for_esp_adjust(int i, int opcnt, int *adj)
+{
+ for (; i < opcnt; i++) {
+ if (ops[i].op == OP_ADD && ops[i].operand[0].reg == xSP) {
+ if (ops[i].operand[1].type != OPT_CONST)
+ ferr(&ops[i], "non-const esp adjust?\n");
+ *adj = ops[i].operand[1].val;
+ if (*adj & 3)
+ ferr(&ops[i], "unaligned esp adjust: %x\n", *adj);
+ return i;
+ }
+
+ if ((ops[i].flags & (OPF_JMP|OPF_TAIL))
+ || ops[i].op == OP_PUSH || ops[i].op == OP_POP)
+ return -1;
+ if (g_labels[i][0] != 0)
+ return -1;
+ }
+
+ return -1;
+}
+
+static void add_label_ref(struct label_ref *lr, int op_i)
+{
+ struct label_ref *lr_new;
+
+ if (lr->i == -1) {
+ lr->i = op_i;
+ return;
+ }
+
+ lr_new = calloc(1, sizeof(*lr_new));
+ lr_new->i = op_i;
+ lr->next = lr_new;
+}
+
+static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
+{
+ struct parsed_op *po, *delayed_flag_op = NULL, *tmp_op;
+ struct parsed_opr *last_arith_dst = NULL;
+ char buf1[256], buf2[256], buf3[256];
+ struct parsed_proto *pp, *pp_tmp;
+ struct parsed_data *pd;
+ const char *tmpname;
+ enum parsed_flag_op pfo;
+ int save_arg_vars = 0;
+ int cmp_result_vars = 0;
+ int need_mul_var = 0;
+ int had_decl = 0;
+ int regmask_save = 0;
+ int regmask_arg = 0;
+ int regmask = 0;
+ int pfomask = 0;
+ int found = 0;
+ int depth = 0;
+ int no_output;
+ int i, j, l;
+ int dummy;
+ int arg;
+ int reg;
+ int ret;
+
+ g_bp_frame = g_sp_frame = g_stack_fsz = 0;
+
+ ret = proto_parse(fhdr, funcn, &g_func_pp);
+ if (ret)
+ ferr(ops, "proto_parse failed for '%s'\n", funcn);
+
+ fprintf(fout, "%s %s(", g_func_pp.ret_type, funcn);
+ for (i = 0; i < g_func_pp.argc; i++) {
+ if (i > 0)
+ fprintf(fout, ", ");
+ fprintf(fout, "%s a%d", g_func_pp.arg[i].type, i + 1);
+ }
+ fprintf(fout, ")\n{\n");
+
+ // pass1:
+ // - handle ebp/esp frame, remove ops related to it
+ if (ops[0].op == OP_PUSH && IS(opr_name(&ops[0], 0), "ebp")
+ && ops[1].op == OP_MOV
+ && IS(opr_name(&ops[1], 0), "ebp")
+ && IS(opr_name(&ops[1], 1), "esp"))
+ {
+ int ecx_push = 0;
+
+ g_bp_frame = 1;
+ ops[0].flags |= OPF_RMD;
+ ops[1].flags |= OPF_RMD;
+ i = 2;
+
+ if (ops[2].op == OP_SUB && IS(opr_name(&ops[2], 0), "esp")) {
+ g_stack_fsz = opr_const(&ops[2], 1);
+ ops[2].flags |= OPF_RMD;
+ i++;
+ }
+ else {
+ // another way msvc builds stack frame..
+ i = 2;
+ while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
+ g_stack_fsz += 4;
+ ops[i].flags |= OPF_RMD;
+ ecx_push++;
+ i++;
+ }
+ // and another way..
+ if (i == 2 && ops[i].op == OP_MOV && ops[i].operand[0].reg == xAX
+ && ops[i].operand[1].type == OPT_CONST
+ && ops[i + 1].op == OP_CALL
+ && IS(opr_name(&ops[i + 1], 0), "__alloca_probe"))
+ {
+ g_stack_fsz += ops[i].operand[1].val;
+ ops[i].flags |= OPF_RMD;
+ i++;
+ ops[i].flags |= OPF_RMD;
+ i++;
+ }
+ }
+
+ found = 0;
+ do {
+ for (; i < opcnt; i++)
+ if (ops[i].op == OP_RET)
+ break;
+ if (i == opcnt && (ops[i - 1].flags & OPF_JMP) && found)
+ break;
+
+ if (ops[i - 1].op != OP_POP || !IS(opr_name(&ops[i - 1], 0), "ebp"))
+ ferr(&ops[i - 1], "'pop ebp' expected\n");
+ ops[i - 1].flags |= OPF_RMD;
+
+ if (g_stack_fsz != 0) {
+ if (ops[i - 2].op != OP_MOV
+ || !IS(opr_name(&ops[i - 2], 0), "esp")
+ || !IS(opr_name(&ops[i - 2], 1), "ebp"))
+ {
+ ferr(&ops[i - 2], "esp restore expected\n");
+ }
+ ops[i - 2].flags |= OPF_RMD;
+
+ if (ecx_push && ops[i - 3].op == OP_POP
+ && IS(opr_name(&ops[i - 3], 0), "ecx"))
+ {
+ ferr(&ops[i - 3], "unexpected ecx pop\n");
+ }
+ }
+
+ found = 1;
+ i++;
+ } while (i < opcnt);
+ }
+ else {
+ for (i = 0; i < opcnt; i++) {
+ if (ops[i].op == OP_PUSH || (ops[i].flags & (OPF_JMP|OPF_TAIL)))
+ break;
+ if (ops[i].op == OP_SUB && ops[i].operand[0].reg == xSP
+ && ops[i].operand[1].type == OPT_CONST)
+ {
+ g_sp_frame = 1;
+ break;
+ }
+ }
+
+ if (g_sp_frame)
+ {
+ g_stack_fsz = ops[i].operand[1].val;
+ ops[i].flags |= OPF_RMD;
+
+ i++;
+ do {
+ for (; i < opcnt; i++)
+ if (ops[i].op == OP_RET)
+ break;
+ if (ops[i - 1].op != OP_ADD
+ || !IS(opr_name(&ops[i - 1], 0), "esp")
+ || ops[i - 1].operand[1].type != OPT_CONST
+ || ops[i - 1].operand[1].val != g_stack_fsz)
+ ferr(&ops[i - 1], "'add esp' expected\n");
+ ops[i - 1].flags |= OPF_RMD;
+
+ i++;
+ } while (i < opcnt);
+ }
+ }
+
+ // pass2:
+ // - resolve all branches
+ for (i = 0; i < opcnt; i++) {
+ po = &ops[i];
+ po->bt_i = -1;
+ po->btj = NULL;
+
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_JMP)
+ || po->op == OP_CALL || po->op == OP_RET)
+ continue;
+
+ if (po->operand[0].type == OPT_REGMEM) {
+ char *p = strchr(po->operand[0].name, '[');
+ if (p == NULL)
+ ferr(po, "unhandled indirect branch\n");
+ ret = p - po->operand[0].name;
+ strncpy(buf1, po->operand[0].name, ret);
+ buf1[ret] = 0;
+
+ for (j = 0, pd = NULL; j < g_func_pd_cnt; j++) {
+ if (IS(g_func_pd[j].label, buf1)) {
+ pd = &g_func_pd[j];
+ break;
+ }
+ }
+ if (pd == NULL)
+ ferr(po, "label '%s' not parsed?\n", buf1);
+ if (pd->type != OPT_OFFSET)
+ ferr(po, "label '%s' with non-offset data?\n", buf1);
+
+ // find all labels, link
+ for (j = 0; j < pd->count; j++) {
+ for (l = 0; l < opcnt; l++) {
+ if (g_labels[l][0] && IS(g_labels[l], pd->d[j].u.label)) {
+ add_label_ref(&g_label_refs[l], i);
+ pd->d[j].bt_i = l;
+ break;
+ }
+ }
+ }
+
+ po->btj = pd;
+ continue;
+ }
+
+ for (l = 0; l < opcnt; l++) {
+ if (g_labels[l][0] && IS(po->operand[0].name, g_labels[l])) {
+ add_label_ref(&g_label_refs[l], i);
+ po->bt_i = l;
+ break;
+ }
+ }
+
+ if (po->bt_i != -1)
+ continue;
+
+ if (po->operand[0].type == OPT_LABEL) {
+ // assume tail call
+ po->op = OP_CALL;
+ po->flags |= OPF_TAIL;
+ continue;
+ }
+
+ ferr(po, "unhandled branch\n");
+ }
+
+ // pass3:
+ // - process calls
+ for (i = 0; i < opcnt; i++)
+ {
+ po = &ops[i];
+ if (po->flags & OPF_RMD)
+ continue;
+
+ if (po->op == OP_CALL)
+ {
+ pp = calloc(1, sizeof(*pp));
+ my_assert_not(pp, NULL);
+ tmpname = opr_name(po, 0);
+ if (po->operand[0].type != OPT_LABEL)
+ {
+ ret = scan_for_esp_adjust(i + 1, opcnt, &j);
+ if (ret < 0)
+ ferr(po, "non-__cdecl indirect call unhandled yet\n");
+ j /= 4;
+ if (j > ARRAY_SIZE(pp->arg))
+ ferr(po, "esp adjust too large?\n");
+ pp->ret_type = strdup("int");
+ pp->argc = pp->argc_stack = j;
+ for (arg = 0; arg < pp->argc; arg++)
+ pp->arg[arg].type = strdup("int");
+ }
+ else {
+ ret = proto_parse(fhdr, tmpname, pp);
+ if (ret)
+ ferr(po, "proto_parse failed for call '%s'\n", tmpname);
+ }
+
+ ret = scan_for_esp_adjust(i + 1, opcnt, &j);
+ if (ret >= 0) {
+ if (pp->argc_stack != j / 4)
+ ferr(po, "stack tracking failed: %x %x\n",
+ pp->argc_stack, j);
+ ops[ret].flags |= OPF_RMD;
+ }
+
+ // can't call functions with non-__cdecl callbacks yet
+ for (arg = 0; arg < pp->argc; arg++) {
+ if (pp->arg[arg].fptr != NULL) {
+ pp_tmp = pp->arg[arg].fptr;
+ if (pp_tmp->is_stdcall || pp_tmp->argc != pp_tmp->argc_stack)
+ ferr(po, "'%s' has a non-__cdecl callback\n", tmpname);
+ }
+ }
+
+ // collect all stack args
+ for (arg = 0; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+
+ for (j = i; j >= 0 && arg < pp->argc; )
+ {
+ if (g_labels[j][0] != 0) {
+ if (j > 0 && ((ops[j - 1].flags & OPF_TAIL)
+ || (ops[j - 1].flags & (OPF_JMP|OPF_CC)) == OPF_JMP))
+ {
+ // follow the branch in reverse
+ if (g_label_refs[j].i == -1)
+ ferr(po, "no refs for '%s'?\n", g_labels[j]);
+ if (g_label_refs[j].next != NULL)
+ ferr(po, "unhandled multiple refs to '%s'\n", g_labels[j]);
+ j = g_label_refs[j].i + 1;
+ continue;
+ }
+ break;
+ }
+ j--;
+
+ if (ops[j].op == OP_CALL)
+ {
+ pp_tmp = ops[j].datap;
+ if (pp_tmp == NULL)
+ ferr(po, "arg collect hit unparsed call\n");
+ if (pp_tmp->argc_stack > 0)
+ ferr(po, "arg collect hit '%s' with %d stack args\n",
+ opr_name(&ops[j], 0), pp_tmp->argc_stack);
+ }
+ else if ((ops[j].flags & OPF_TAIL)
+ || (ops[j].flags & (OPF_JMP|OPF_CC)) == OPF_JMP)
+ {
+ break;
+ }
+ else if (ops[j].op == OP_PUSH)
+ {
+ pp->arg[arg].datap = &ops[j];
+ ret = scan_for_mod(&ops[j], j + 1, i);
+ if (ret >= 0) {
+ // mark this push as one that needs operand saving
+ ops[j].flags &= ~OPF_RMD;
+ ops[j].argmask |= 1 << arg;
+ save_arg_vars |= 1 << arg;
+ }
+ else
+ ops[j].flags |= OPF_RMD;
+
+ // next arg
+ for (arg++; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+ }
+ }
+ if (arg < pp->argc)
+ ferr(po, "arg collect failed for '%s': %d/%d\n",
+ tmpname, arg, pp->argc);
+ po->datap = pp;
+ }
+ }
+
+ // pass4:
+ // - find POPs for PUSHes, rm both
+ // - scan for all used registers
+ // - find flag set ops for their users
+ // - declare indirect functions
+ for (i = 0; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->flags & OPF_RMD)
+ continue;
+
+ if (po->op == OP_PUSH
+ && po->argmask == 0 && !(po->flags & OPF_RSAVE)
+ && po->operand[0].type == OPT_REG)
+ {
+ reg = po->operand[0].reg;
+ if (reg < 0)
+ ferr(po, "reg not set for push?\n");
+
+ depth = 0;
+ ret = scan_for_pop(i + 1, opcnt,
+ po->operand[0].name, i + opcnt, 0, &depth, 0);
+ if (ret == 1) {
+ if (depth > 1)
+ ferr(po, "too much depth: %d\n", depth);
+ if (depth > 0)
+ regmask_save |= 1 << reg;
+
+ po->flags |= OPF_RMD;
+ scan_for_pop(i + 1, opcnt, po->operand[0].name,
+ i + opcnt * 2, 0, &depth, 1);
+ continue;
+ }
+ ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0);
+ if (ret == 0) {
+ arg = OPF_RMD;
+ if (regmask & (1 << reg)) {
+ if (regmask_save & (1 << reg))
+ ferr(po, "%s already saved?\n", po->operand[0].name);
+ arg = OPF_RSAVE;
+ }
+ po->flags |= arg;
+ scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, arg);
+ continue;
+ }
+ }
+
+ regmask |= po->regmask_src | po->regmask_dst;
+
+ if (po->flags & OPF_CC)
+ {
+ ret = scan_for_flag_set(i - 1);
+ if (ret < 0)
+ ferr(po, "unable to trace flag setter\n");
+
+ tmp_op = &ops[ret]; // flag setter
+ pfo = split_cond(po, po->op, &dummy);
+ pfomask = 0;