+ 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->pfo = op_table[i].pfo;
+ op->pfo_inv = op_table[i].pfo_inv;
+ op->regmask_src = op->regmask_dst = 0;
+ op->asmln = asmln;
+
+ 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_LODS:
+ case OP_STOS:
+ case OP_SCAS:
+ if (op->operand_cnt != 0)
+ break;
+ if (words[op_w][4] == 'b')
+ lmod = OPLM_BYTE;
+ else if (words[op_w][4] == 'w')
+ lmod = OPLM_WORD;
+ else if (words[op_w][4] == 'd')
+ lmod = OPLM_DWORD;
+ op->operand_cnt = 3;
+ setup_reg_opr(&op->operand[0], op->op == OP_LODS ? xSI : 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->op == OP_LODS ? &op->regmask_dst : &op->regmask_src);
+ break;
+
+ case OP_MOVS:
+ case OP_CMPS:
+ if (op->operand_cnt != 0)
+ break;
+ if (words[op_w][4] == 'b')
+ lmod = OPLM_BYTE;
+ else if (words[op_w][4] == 'w')
+ lmod = OPLM_WORD;
+ else if (words[op_w][4] == 'd')
+ 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_XCHG:
+ op->regmask_src |= op->regmask_dst;
+ op->regmask_dst |= op->regmask_src;
+ goto check_align;
+
+ case OP_JECXZ:
+ op->operand_cnt = 1;
+ op->regmask_src = 1 << xCX;
+ op->operand[0].type = OPT_REG;
+ op->operand[0].reg = xCX;
+ op->operand[0].lmod = OPLM_DWORD;
+ 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;
+
+ case OP_PUSH:
+ if (op->operand[0].lmod == OPLM_UNSPEC
+ && (op->operand[0].type == OPT_CONST
+ || op->operand[0].type == OPT_OFFSET
+ || op->operand[0].type == OPT_LABEL))
+ op->operand[0].lmod = OPLM_DWORD;
+ break;
+
+ // alignment
+ case OP_MOV:
+ check_align:
+ if (op->operand[0].type == OPT_REG && op->operand[1].type == OPT_REG
+ && op->operand[0].lmod == op->operand[1].lmod
+ && op->operand[0].reg == op->operand[1].reg
+ && IS(op->operand[0].name, op->operand[1].name)) // ah, al..
+ {
+ op->flags |= OPF_RMD;
+ op->regmask_src = op->regmask_dst = 0;
+ }
+ break;
+
+ case OP_LEA:
+ if (op->operand[0].type == OPT_REG
+ && op->operand[1].type == OPT_REGMEM)
+ {
+ char buf[16];
+ snprintf(buf, sizeof(buf), "%s+0", op->operand[0].name);
+ if (IS(buf, op->operand[1].name))
+ op->flags |= OPF_RMD;
+ }
+ break;
+
+ case OP_CALL:
+ // trashed regs must be explicitly detected later
+ op->regmask_dst = 0;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static const char *op_name(struct parsed_op *po)
+{
+ static char buf[16];
+ char *p;
+ int i;
+
+ if (po->op == OP_JCC || po->op == OP_SCC) {
+ p = buf;
+ *p++ = (po->op == OP_JCC) ? 'j' : 's';
+ if (po->pfo_inv)
+ *p++ = 'n';
+ strcpy(p, parsed_flag_op_names[po->pfo]);
+ return buf;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(op_table); i++)
+ if (op_table[i].op == po->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));
+ 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];
+}
+
+// cast1 is the "final" cast
+static const char *simplify_cast(const char *cast1, const char *cast2)
+{
+ static char buf[256];
+
+ if (cast1[0] == 0)
+ return cast2;
+ if (cast2[0] == 0)
+ return cast1;
+ if (IS(cast1, cast2))
+ return cast1;
+ if (IS(cast1, "(s8)") && IS(cast2, "(u8)"))
+ return cast1;
+ if (IS(cast1, "(s16)") && IS(cast2, "(u16)"))
+ return cast1;
+ if (IS(cast1, "(u8)") && IS_START(cast2, "*(u8 *)"))
+ return cast2;
+ if (IS(cast1, "(u16)") && IS_START(cast2, "*(u16 *)"))
+ return cast2;
+ if (strchr(cast1, '*') && IS_START(cast2, "(u32)"))
+ return cast1;
+
+ snprintf(buf, sizeof(buf), "%s%s", cast1, cast2);
+ return buf;
+}
+
+static const char *simplify_cast_num(const char *cast, unsigned int val)
+{
+ if (IS(cast, "(u8)") && val < 0x100)
+ return "";
+ if (IS(cast, "(s8)") && val < 0x80)
+ return "";
+ if (IS(cast, "(u16)") && val < 0x10000)
+ return "";
+ if (IS(cast, "(s16)") && val < 0x8000)
+ return "";
+ if (IS(cast, "(s32)") && val < 0x80000000)
+ return "";
+
+ return cast;
+}
+
+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 int is_stack_access(struct parsed_op *po,
+ const struct parsed_opr *popr)
+{
+ return (parse_stack_el(popr->name, NULL)
+ || (g_bp_frame && !(po->flags & OPF_EBP_S)
+ && IS_START(popr->name, "ebp")));
+}
+
+static void parse_stack_access(struct parsed_op *po,
+ const char *name, char *ofs_reg, int *offset_out,
+ int *stack_ra_out, const char **bp_arg_out, int is_lea)
+{
+ const char *bp_arg = "";
+ const char *p = NULL;
+ struct parsed_equ *eq;
+ char *endp = NULL;
+ int stack_ra = 0;
+ int offset = 0;
+
+ ofs_reg[0] = 0;
+
+ if (IS_START(name, "ebp-")
+ || (IS_START(name, "ebp+") && '0' <= name[4] && name[4] <= '9'))
+ {
+ p = name + 4;
+ if (IS_START(p, "0x"))
+ p += 2;
+ offset = strtoul(p, &endp, 16);
+ if (name[3] == '-')
+ offset = -offset;
+ if (*endp != 0)
+ ferr(po, "ebp- parse of '%s' failed\n", name);
+ }
+ else {
+ 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;
+
+ // yes it sometimes LEAs ra for compares..
+ if (!is_lea && ofs_reg[0] == 0
+ && stack_ra <= offset && offset < stack_ra + 4)
+ {
+ ferr(po, "reference to ra? %d %d\n", offset, stack_ra);
+ }
+
+ *offset_out = offset;
+ *stack_ra_out = stack_ra;
+ if (bp_arg_out)
+ *bp_arg_out = bp_arg;
+}
+
+static void stack_frame_access(struct parsed_op *po,
+ struct parsed_opr *popr, char *buf, size_t buf_size,
+ const char *name, const char *cast, int is_src, int is_lea)
+{
+ enum opr_lenmod tmp_lmod = OPLM_UNSPEC;
+ const char *prefix = "";
+ const char *bp_arg = NULL;
+ char ofs_reg[16] = { 0, };
+ int i, arg_i, arg_s;
+ int unaligned = 0;
+ int stack_ra = 0;
+ int offset = 0;
+ int sf_ofs;
+ int lim;
+
+ if (po->flags & OPF_EBP_S)
+ ferr(po, "stack_frame_access while ebp is scratch\n");
+
+ parse_stack_access(po, name, ofs_reg, &offset,
+ &stack_ra, &bp_arg, is_lea);
+
+ if (offset > stack_ra)
+ {
+ arg_i = (offset - stack_ra - 4) / 4;
+ if (arg_i < 0 || arg_i >= g_func_pp->argc_stack)
+ {
+ if (g_func_pp->is_vararg
+ && arg_i == g_func_pp->argc_stack && is_lea)
+ {
+ // should be va_list
+ if (cast[0] == 0)
+ cast = "(u32)";
+ snprintf(buf, buf_size, "%sap", cast);
+ return;
+ }
+ ferr(po, "offset %d (%s,%d) doesn't map to any arg\n",
+ offset, bp_arg, arg_i);
+ }
+ if (ofs_reg[0] != 0)
+ ferr(po, "offset reg on arg access?\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);
+
+ popr->is_ptr = g_func_pp->arg[i].type.is_ptr;
+
+ switch (popr->lmod)
+ {
+ case OPLM_BYTE:
+ if (is_lea)
+ ferr(po, "lea/byte to arg?\n");
+ if (is_src && (offset & 3) == 0)
+ snprintf(buf, buf_size, "%sa%d",
+ simplify_cast(cast, "(u8)"), i + 1);
+ else
+ snprintf(buf, buf_size, "%sBYTE%d(a%d)",
+ cast, offset & 3, i + 1);
+ break;
+
+ case OPLM_WORD:
+ if (is_lea)
+ ferr(po, "lea/word to arg?\n");
+ if (offset & 1) {
+ unaligned = 1;
+ if (!is_src) {
+ if (offset & 2)
+ ferr(po, "problematic arg store\n");
+ snprintf(buf, buf_size, "%s((char *)&a%d + 1)",
+ simplify_cast(cast, "*(u16 *)"), i + 1);
+ }
+ else
+ ferr(po, "unaligned arg word load\n");
+ }
+ else if (is_src && (offset & 2) == 0)
+ snprintf(buf, buf_size, "%sa%d",
+ simplify_cast(cast, "(u16)"), i + 1);
+ else
+ snprintf(buf, buf_size, "%s%sWORD(a%d)",
+ cast, (offset & 2) ? "HI" : "LO", i + 1);
+ break;
+
+ case OPLM_DWORD:
+ if (cast[0])
+ prefix = cast;
+ else if (is_src)
+ prefix = "(u32)";
+
+ if (offset & 3) {
+ unaligned = 1;
+ if (is_lea)
+ snprintf(buf, buf_size, "(u32)&a%d + %d",
+ i + 1, offset & 3);
+ else if (!is_src)
+ ferr(po, "unaligned arg store\n");
+ else {
+ // mov edx, [ebp+arg_4+2]; movsx ecx, dx
+ snprintf(buf, buf_size, "%s(a%d >> %d)",
+ prefix, i + 1, (offset & 3) * 8);
+ }
+ }
+ else {
+ snprintf(buf, buf_size, "%s%sa%d",
+ prefix, is_lea ? "&" : "", i + 1);
+ }
+ break;
+
+ default:
+ ferr(po, "bp_arg bad lmod: %d\n", popr->lmod);
+ }
+
+ if (unaligned)
+ snprintf(g_comment, sizeof(g_comment), "%s unaligned", bp_arg);
+
+ // common problem
+ guess_lmod_from_c_type(&tmp_lmod, &g_func_pp->arg[i].type);
+ if (tmp_lmod != OPLM_DWORD
+ && (unaligned || (!is_src && tmp_lmod < popr->lmod)))
+ {
+ ferr(po, "bp_arg arg%d/w offset %d and type '%s' is too small\n",
+ i + 1, offset, g_func_pp->arg[i].type.name);
+ }
+ // can't check this because msvc likes to reuse
+ // arg space for scratch..
+ //if (popr->is_ptr && popr->lmod != OPLM_DWORD)
+ // ferr(po, "bp_arg arg%d: non-dword ptr access\n", i + 1);
+ }
+ else
+ {
+ if (g_stack_fsz == 0)
+ ferr(po, "stack var access without stackframe\n");
+ g_stack_frame_used = 1;
+
+ sf_ofs = g_stack_fsz + offset;
+ lim = (ofs_reg[0] != 0) ? -4 : 0;
+ if (offset > 0 || sf_ofs < lim)
+ ferr(po, "bp_stack offset %d/%d\n", offset, g_stack_fsz);
+
+ if (is_lea)
+ prefix = "(u32)&";
+ else
+ prefix = cast;
+
+ switch (popr->lmod)
+ {
+ case OPLM_BYTE:
+ snprintf(buf, buf_size, "%ssf.b[%d%s%s]",
+ prefix, sf_ofs, ofs_reg[0] ? "+" : "", ofs_reg);
+ break;
+
+ case OPLM_WORD:
+ if ((sf_ofs & 1) || ofs_reg[0] != 0) {
+ // known unaligned or possibly unaligned
+ strcat(g_comment, " unaligned");
+ if (prefix[0] == 0)
+ prefix = "*(u16 *)&";
+ snprintf(buf, buf_size, "%ssf.b[%d%s%s]",
+ prefix, sf_ofs, ofs_reg[0] ? "+" : "", ofs_reg);
+ break;
+ }
+ snprintf(buf, buf_size, "%ssf.w[%d]", prefix, sf_ofs / 2);
+ break;
+
+ case OPLM_DWORD:
+ if ((sf_ofs & 3) || ofs_reg[0] != 0) {
+ // known unaligned or possibly unaligned
+ strcat(g_comment, " unaligned");
+ if (prefix[0] == 0)
+ prefix = "*(u32 *)&";
+ snprintf(buf, buf_size, "%ssf.b[%d%s%s]",
+ prefix, sf_ofs, ofs_reg[0] ? "+" : "", ofs_reg);
+ break;
+ }
+ snprintf(buf, buf_size, "%ssf.d[%d]", prefix, sf_ofs / 4);
+ break;
+
+ default:
+ ferr(po, "bp_stack bad lmod: %d\n", popr->lmod);
+ }
+ }
+}
+
+static void check_func_pp(struct parsed_op *po,
+ const struct parsed_proto *pp, const char *pfx)
+{
+ enum opr_lenmod tmp_lmod;
+ char buf[256];
+ int ret, i;
+
+ if (pp->argc_reg != 0) {
+ if (/*!g_allow_regfunc &&*/ !pp->is_fastcall) {
+ pp_print(buf, sizeof(buf), pp);
+ ferr(po, "%s: unexpected reg arg in icall: %s\n", pfx, buf);
+ }
+ if (pp->argc_stack > 0 && pp->argc_reg != 2)
+ ferr(po, "%s: %d reg arg(s) with %d stack arg(s)\n",
+ pfx, pp->argc_reg, pp->argc_stack);
+ }
+
+ // fptrs must use 32bit args, callsite might have no information and
+ // lack a cast to smaller types, which results in incorrectly masked
+ // args passed (callee may assume masked args, it does on ARM)
+ if (!pp->is_oslib) {
+ for (i = 0; i < pp->argc; i++) {
+ ret = guess_lmod_from_c_type(&tmp_lmod, &pp->arg[i].type);
+ if (ret && tmp_lmod != OPLM_DWORD)
+ ferr(po, "reference to %s with arg%d '%s'\n", pp->name,
+ i + 1, pp->arg[i].type.name);
+ }
+ }
+}
+
+static const char *check_label_read_ref(struct parsed_op *po,
+ const char *name)
+{
+ const struct parsed_proto *pp;
+
+ pp = proto_parse(g_fhdr, name, 0);
+ if (pp == NULL)
+ ferr(po, "proto_parse failed for ref '%s'\n", name);
+
+ if (pp->is_func)
+ check_func_pp(po, pp, "ref");
+
+ return pp->name;
+}
+
+static char *out_src_opr(char *buf, size_t buf_size,
+ struct parsed_op *po, struct parsed_opr *popr, const char *cast,
+ int is_lea)
+{
+ char tmp1[256], tmp2[256];
+ char expr[256];
+ const char *name;
+ char *p;
+ int ret;
+
+ if (cast == NULL)
+ cast = "";
+
+ 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%s", cast, opr_reg_p(po, popr));
+ break;
+ case OPLM_WORD:
+ snprintf(buf, buf_size, "%s%s",
+ simplify_cast(cast, "(u16)"), opr_reg_p(po, popr));
+ break;
+ case OPLM_BYTE:
+ if (popr->name[1] == 'h') // XXX..
+ snprintf(buf, buf_size, "%s(%s >> 8)",
+ simplify_cast(cast, "(u8)"), opr_reg_p(po, popr));
+ else
+ snprintf(buf, buf_size, "%s%s",
+ simplify_cast(cast, "(u8)"), opr_reg_p(po, popr));
+ break;
+ default:
+ ferr(po, "invalid src lmod: %d\n", popr->lmod);
+ }
+ break;
+
+ case OPT_REGMEM:
+ if (is_stack_access(po, popr)) {
+ stack_frame_access(po, popr, buf, buf_size,
+ popr->name, cast, 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);
+ if (tmp1[0] == '(') {
+ // (off_4FFF50+3)[eax]
+ p = strchr(tmp1 + 1, ')');
+ if (p == NULL || p[1] != 0)
+ ferr(po, "parse failure (2) for '%s'\n", expr);
+ *p = 0;
+ memmove(tmp1, tmp1 + 1, strlen(tmp1));
+ }
+ 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)",
+ simplify_cast(cast, lmod_cast_u_ptr(po, popr->lmod)), expr);
+ break;
+
+ case OPT_LABEL:
+ name = check_label_read_ref(po, popr->name);
+ if (cast[0] == 0 && popr->is_ptr)
+ cast = "(u32)";
+
+ if (is_lea)
+ snprintf(buf, buf_size, "(u32)&%s", name);
+ else if (popr->size_lt)
+ snprintf(buf, buf_size, "%s%s%s%s", cast,
+ lmod_cast_u_ptr(po, popr->lmod),
+ popr->is_array ? "" : "&", name);
+ else
+ snprintf(buf, buf_size, "%s%s%s", cast, name,
+ popr->is_array ? "[0]" : "");
+ break;
+
+ case OPT_OFFSET:
+ name = check_label_read_ref(po, popr->name);
+ if (cast[0] == 0)
+ cast = "(u32)";
+ if (is_lea)
+ ferr(po, "lea an offset?\n");
+ snprintf(buf, buf_size, "%s&%s", cast, name);
+ break;
+
+ case OPT_CONST:
+ if (is_lea)
+ ferr(po, "lea from const?\n");
+
+ printf_number(tmp1, sizeof(tmp1), popr->val);
+ if (popr->val == 0 && strchr(cast, '*'))
+ snprintf(buf, buf_size, "NULL");
+ else
+ snprintf(buf, buf_size, "%s%s",
+ simplify_cast_num(cast, popr->val), tmp1);
+ break;
+
+ default:
+ ferr(po, "invalid src type: %d\n", popr->type);
+ }
+
+ return buf;
+}
+
+// note: may set is_ptr (we find that out late for ebp frame..)
+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 (is_stack_access(po, popr)) {
+ stack_frame_access(po, popr, buf, buf_size,
+ popr->name, "", 0, 0);
+ break;
+ }
+
+ return out_src_opr(buf, buf_size, po, popr, NULL, 0);
+
+ case OPT_LABEL:
+ if (popr->size_mismatch)
+ snprintf(buf, buf_size, "%s%s%s",
+ lmod_cast_u_ptr(po, popr->lmod),
+ popr->is_array ? "" : "&", popr->name);
+ else
+ snprintf(buf, buf_size, "%s%s", popr->name,
+ popr->is_array ? "[0]" : "");
+ break;
+
+ default:
+ ferr(po, "invalid dst type: %d\n", popr->type);
+ }
+
+ return buf;
+}
+
+static char *out_src_opr_u32(char *buf, size_t buf_size,
+ struct parsed_op *po, struct parsed_opr *popr)
+{
+ return out_src_opr(buf, buf_size, po, popr, NULL, 0);
+}
+
+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)
+{
+ const char *cast, *scast, *cast_use;
+ char buf1[256], buf2[256];
+ enum opr_lenmod lmod;
+
+ if (po->operand[0].lmod != po->operand[1].lmod)
+ ferr(po, "%s: lmod mismatch: %d %d\n", __func__,
+ po->operand[0].lmod, po->operand[1].lmod);
+ lmod = po->operand[0].lmod;
+
+ cast = lmod_cast_u(po, lmod);
+ scast = lmod_cast_s(po, lmod);
+
+ switch (pfo) {
+ case PFO_C:
+ case PFO_Z:
+ case PFO_BE: // !a
+ cast_use = cast;
+ break;
+
+ case PFO_S:
+ case PFO_L: // !ge
+ case PFO_LE:
+ cast_use = scast;
+ break;
+
+ default:
+ ferr(po, "%s: unhandled parsed_flag_op: %d\n", __func__, pfo);
+ }
+
+ out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], cast_use, 0);
+ out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], cast_use, 0);
+
+ switch (pfo) {
+ case PFO_C:
+ // note: must be unsigned compare
+ snprintf(buf, buf_size, "(%s %s %s)",
+ buf1, is_inv ? ">=" : "<", buf2);
+ break;
+
+ case PFO_Z:
+ snprintf(buf, buf_size, "(%s %s %s)",
+ buf1, is_inv ? "!=" : "==", buf2);
+ break;
+
+ case PFO_BE: // !a
+ // note: must be unsigned compare
+ snprintf(buf, buf_size, "(%s %s %s)",
+ buf1, is_inv ? ">" : "<=", buf2);
+
+ // annoying case
+ if (is_inv && lmod == OPLM_BYTE
+ && po->operand[1].type == OPT_CONST
+ && po->operand[1].val == 0xff)
+ {
+ snprintf(g_comment, sizeof(g_comment), "if %s", buf);
+ snprintf(buf, buf_size, "(0)");
+ }
+ break;
+
+ // note: must be signed compare
+ case PFO_S:
+ snprintf(buf, buf_size, "(%s(%s - %s) %s 0)",
+ scast, buf1, buf2, is_inv ? ">=" : "<");
+ break;
+
+ case PFO_L: // !ge
+ snprintf(buf, buf_size, "(%s %s %s)",
+ buf1, is_inv ? ">=" : "<", buf2);
+ break;
+
+ case PFO_LE:
+ snprintf(buf, buf_size, "(%s %s %s)",
+ buf1, is_inv ? ">" : "<=", buf2);
+ break;
+
+ default:
+ break;
+ }
+}
+
+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_u32(buf3, sizeof(buf3), po, &po->operand[0]);
+ }
+ else {
+ out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]);
+ out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]);
+ 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_cmp_for_cc(buf, buf_size, po, pfo, is_inv);
+ }
+ 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) {
+ if (popr1->type_from_var) {
+ popr1->size_mismatch = 1;
+ if (popr1->lmod < popr2->lmod)
+ popr1->size_lt = 1;
+ popr1->lmod = popr2->lmod;
+ }
+ else if (popr2->type_from_var) {
+ popr2->size_mismatch = 1;
+ if (popr2->lmod < popr1->lmod)
+ popr2->size_lt = 1;
+ popr2->lmod = popr1->lmod;
+ }
+ else
+ 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 op_set_clear_flag(struct parsed_op *po,
+ enum op_flags flag_set, enum op_flags flag_clear)
+{
+ po->flags |= flag_set;
+ po->flags &= ~flag_clear;
+}
+
+// last op in stream - unconditional branch or ret
+#define LAST_OP(_i) ((ops[_i].flags & OPF_TAIL) \
+ || ((ops[_i].flags & (OPF_JMP|OPF_CJMP|OPF_RMD)) == OPF_JMP \
+ && ops[_i].op != OP_CALL))
+
+static int scan_for_pop(int i, int opcnt, const char *reg,
+ int magic, int depth, int *maxdepth, int do_flags)
+{
+ const struct parsed_proto *pp;
+ 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) {
+ if (po->op == OP_CALL) {
+ pp = proto_parse(g_fhdr, po->operand[0].name, 0);
+ if (pp != NULL && pp->is_noreturn)
+ // no stack cleanup for noreturn
+ return ret;
+ }
+ return -1; // deadend
+ }
+
+ if ((po->flags & OPF_RMD)
+ || (po->op == OP_PUSH && po->argnum != 0)) // arg push
+ continue;
+
+ if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+ if (po->btj != NULL) {
+ // jumptable
+ for (j = 0; j < po->btj->count; 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
+ }
+ return ret;
+ }
+
+ if (po->bt_i < 0) {
+ ferr(po, "dead branch\n");
+ return -1;
+ }
+
+ if (po->flags & OPF_CJMP) {
+ 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 && !(po->flags & OPF_FARG)) {
+ depth++;
+ if (depth > *maxdepth)
+ *maxdepth = depth;
+ if (do_flags)
+ op_set_clear_flag(po, OPF_RSAVE, OPF_RMD);
+ }
+ else if (po->op == OP_POP) {
+ if (depth == 0) {
+ if (do_flags)
+ op_set_clear_flag(po, OPF_RMD, OPF_RSAVE);
+ return 1;
+ }
+ else {
+ depth--;
+ if (depth < 0) // should not happen
+ ferr(po, "fail with depth\n");
+ if (do_flags)
+ op_set_clear_flag(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;
+}
+
+static void scan_propagate_df(int i, int opcnt)
+{
+ struct parsed_op *po = &ops[i];
+ int j;
+
+ for (; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->flags & OPF_DF)
+ return; // already resolved
+ po->flags |= OPF_DF;
+
+ if (po->op == OP_CALL)
+ ferr(po, "call with DF set?\n");
+
+ if (po->flags & OPF_JMP) {
+ if (po->btj != NULL) {
+ // jumptable
+ for (j = 0; j < po->btj->count; j++)
+ scan_propagate_df(po->btj->d[j].bt_i, opcnt);
+ return;
+ }
+
+ if (po->bt_i < 0) {
+ ferr(po, "dead branch\n");
+ return;
+ }
+
+ if (po->flags & OPF_CJMP)
+ scan_propagate_df(po->bt_i, opcnt);
+ else
+ i = po->bt_i - 1;
+ continue;
+ }
+
+ if (po->flags & OPF_TAIL)
+ break;
+
+ if (po->op == OP_CLD) {
+ po->flags |= OPF_RMD;
+ return;
+ }
+ }
+
+ ferr(po, "missing DF clear?\n");
+}
+
+// is operand 'opr' modified by parsed_op 'po'?
+static int is_opr_modified(const struct parsed_opr *opr,
+ const struct parsed_op *po)
+{
+ int mask;
+
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+ return 0;
+
+ if (opr->type == OPT_REG) {
+ if (po->op == OP_CALL) {
+ mask = (1 << xAX) | (1 << xCX) | (1 << xDX);
+ if ((1 << opr->reg) & mask)
+ return 1;
+ else
+ return 0;
+ }
+
+ if (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 c_mode)
+{
+ int mask;
+ int i;
+
+ if ((po->flags & OPF_RMD) || !(po->flags & OPF_DATA))
+ return 0;
+
+ if (po_test->operand_cnt == 1 && po_test->operand[0].type == OPT_CONST)
+ return 0;
+
+ if ((po_test->regmask_src | po_test->regmask_dst) & po->regmask_dst)
+ return 1;
+
+ // in reality, it can wreck any register, but in decompiled C
+ // version it can only overwrite eax or edx:eax
+ mask = (1 << xAX) | (1 << xDX);
+ if (!c_mode)
+ mask |= 1 << xCX;
+
+ if (po->op == OP_CALL
+ && ((po_test->regmask_src | po_test->regmask_dst) & mask))
+ 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,
+ int c_mode)
+{
+ if (po_test->operand_cnt == 1 && po_test->operand[0].type == OPT_CONST)
+ return -1;
+
+ for (; i < opcnt; i++) {
+ if (is_any_opr_modified(po_test, &ops[i], c_mode))
+ 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, int magic, int *branched,
+ int *setters, int *setter_cnt)
+{
+ struct label_ref *lr;
+ int ret;
+
+ while (i >= 0) {
+ if (ops[i].cc_scratch == magic) {
+ ferr(&ops[i], "%s looped\n", __func__);
+ return -1;
+ }
+ ops[i].cc_scratch = magic;
+
+ if (g_labels[i][0] != 0) {
+ *branched = 1;
+
+ lr = &g_label_refs[i];
+ for (; lr->next; lr = lr->next) {
+ ret = scan_for_flag_set(lr->i, magic,
+ branched, setters, setter_cnt);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (i > 0 && LAST_OP(i - 1)) {
+ i = lr->i;
+ continue;
+ }
+ ret = scan_for_flag_set(lr->i, magic,
+ branched, setters, setter_cnt);
+ if (ret < 0)
+ return ret;
+ }
+ i--;
+
+ if (ops[i].flags & OPF_FLAGS) {
+ setters[*setter_cnt] = i;
+ (*setter_cnt)++;
+ return 0;
+ }
+
+ if ((ops[i].flags & (OPF_JMP|OPF_CJMP)) == OPF_JMP)
+ return -1;
+ }
+
+ return -1;
+}
+
+// scan back for cdq, if anything modifies edx, fail
+static int scan_for_cdq_edx(int i)
+{
+ while (i >= 0) {
+ if (g_labels[i][0] != 0) {
+ if (g_label_refs[i].next != NULL)
+ return -1;
+ if (i > 0 && LAST_OP(i - 1)) {
+ i = g_label_refs[i].i;
+ continue;
+ }
+ return -1;
+ }
+ i--;
+
+ if (ops[i].op == OP_CDQ)
+ return i;
+
+ if (ops[i].regmask_dst & (1 << xDX))
+ return -1;
+ }
+
+ return -1;
+}
+
+static int scan_for_reg_clear(int i, int reg)
+{
+ while (i >= 0) {
+ if (g_labels[i][0] != 0) {
+ if (g_label_refs[i].next != NULL)
+ return -1;
+ if (i > 0 && LAST_OP(i - 1)) {
+ i = g_label_refs[i].i;
+ continue;
+ }
+ return -1;
+ }
+ 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;
+ }
+
+ return -1;
+}
+
+// scan for positive, constant esp adjust
+static int scan_for_esp_adjust(int i, int opcnt, int *adj,
+ int *multipath)
+{
+ struct parsed_op *po;
+ int first_pop = -1;
+
+ *adj = *multipath = 0;
+
+ for (; i < opcnt; i++) {
+ po = &ops[i];
+
+ if (g_labels[i][0] != 0)
+ *multipath = 1;
+
+ if (po->op == OP_ADD && po->operand[0].reg == xSP) {
+ if (po->operand[1].type != OPT_CONST)
+ ferr(&ops[i], "non-const esp adjust?\n");
+ *adj += po->operand[1].val;
+ if (*adj & 3)
+ ferr(&ops[i], "unaligned esp adjust: %x\n", *adj);
+ return i;
+ }
+ else if (po->op == OP_PUSH && !(po->flags & OPF_RMD)) {
+ //if (first_pop == -1)
+ // first_pop = -2; // none
+ *adj -= lmod_bytes(po, po->operand[0].lmod);
+ }
+ else if (po->op == OP_POP && !(po->flags & OPF_RMD)) {
+ // seems like msvc only uses 'pop ecx' for stack realignment..
+ if (po->operand[0].type != OPT_REG || po->operand[0].reg != xCX)
+ break;
+ if (first_pop == -1 && *adj >= 0)
+ first_pop = i;
+ *adj += lmod_bytes(po, po->operand[0].lmod);
+ }
+ else if (po->flags & (OPF_JMP|OPF_TAIL)) {
+ if (po->op == OP_JMP && po->btj == NULL) {
+ i = po->bt_i - 1;
+ continue;
+ }
+ if (po->op != OP_CALL)
+ break;
+ if (po->operand[0].type != OPT_LABEL)
+ break;
+ if (po->pp != NULL && po->pp->is_stdcall)
+ break;
+ }
+ }
+
+ if (first_pop >= 0) {
+ // probably 'pop ecx' was used..
+ return first_pop;
+ }
+
+ return -1;
+}
+
+static void scan_fwd_set_flags(int i, int opcnt, int magic, int flags)
+{
+ struct parsed_op *po;
+ int j;
+
+ if (i < 0)
+ ferr(ops, "%s: followed bad branch?\n", __func__);
+
+ for (; i < opcnt; i++) {
+ po = &ops[i];
+ if (po->cc_scratch == magic)
+ return;
+ po->cc_scratch = magic;
+ po->flags |= flags;
+
+ if ((po->flags & OPF_JMP) && po->op != OP_CALL) {
+ if (po->btj != NULL) {
+ // jumptable
+ for (j = 0; j < po->btj->count; j++)
+ scan_fwd_set_flags(po->btj->d[j].bt_i, opcnt, magic, flags);
+ return;
+ }
+
+ scan_fwd_set_flags(po->bt_i, opcnt, magic, flags);
+ if (!(po->flags & OPF_CJMP))
+ return;
+ }
+ if (po->flags & OPF_TAIL)
+ return;
+ }
+}
+
+static const struct parsed_proto *try_recover_pp(
+ struct parsed_op *po, const struct parsed_opr *opr, int *search_instead)
+{
+ const struct parsed_proto *pp = NULL;
+ char buf[256];
+ char *p;
+
+ // maybe an arg of g_func?
+ if (opr->type == OPT_REGMEM && is_stack_access(po, opr))
+ {
+ char ofs_reg[16] = { 0, };
+ int arg, arg_s, arg_i;
+ int stack_ra = 0;
+ int offset = 0;
+
+ parse_stack_access(po, opr->name, ofs_reg,
+ &offset, &stack_ra, NULL, 0);
+ if (ofs_reg[0] != 0)
+ ferr(po, "offset reg on arg access?\n");
+ if (offset <= stack_ra) {
+ // search who set the stack var instead
+ if (search_instead != NULL)
+ *search_instead = 1;
+ return NULL;
+ }
+
+ arg_i = (offset - stack_ra - 4) / 4;
+ for (arg = arg_s = 0; arg < g_func_pp->argc; arg++) {
+ if (g_func_pp->arg[arg].reg != NULL)
+ continue;
+ if (arg_s == arg_i)
+ break;
+ arg_s++;
+ }
+ if (arg == g_func_pp->argc)
+ ferr(po, "stack arg %d not in prototype?\n", arg_i);
+
+ pp = g_func_pp->arg[arg].fptr;
+ if (pp == NULL)
+ ferr(po, "icall sa: arg%d is not a fptr?\n", arg + 1);
+ check_func_pp(po, pp, "icall arg");
+ }
+ else if (opr->type == OPT_REGMEM && strchr(opr->name + 1, '[')) {
+ // label[index]
+ p = strchr(opr->name + 1, '[');
+ memcpy(buf, opr->name, p - opr->name);
+ buf[p - opr->name] = 0;
+ pp = proto_parse(g_fhdr, buf, 0);
+ }
+ else if (opr->type == OPT_OFFSET || opr->type == OPT_LABEL) {
+ pp = proto_parse(g_fhdr, opr->name, 0);
+ if (pp == NULL)
+ ferr(po, "proto_parse failed for icall from '%s'\n", opr->name);
+ check_func_pp(po, pp, "reg-fptr ref");
+ }
+
+ return pp;
+}
+
+static void scan_for_call_type(int i, const struct parsed_opr *opr,
+ int magic, const struct parsed_proto **pp_found, int *multi)
+{
+ const struct parsed_proto *pp = NULL;
+ struct parsed_op *po;
+ struct label_ref *lr;
+
+ ops[i].cc_scratch = magic;
+
+ while (1) {
+ if (g_labels[i][0] != 0) {
+ lr = &g_label_refs[i];
+ for (; lr != NULL; lr = lr->next)
+ scan_for_call_type(lr->i, opr, magic, pp_found, multi);
+ if (i > 0 && LAST_OP(i - 1))
+ return;
+ }
+
+ i--;
+ if (i < 0)
+ break;
+
+ if (ops[i].cc_scratch == magic)
+ return;
+ ops[i].cc_scratch = magic;
+
+ if (!(ops[i].flags & OPF_DATA))
+ continue;
+ if (!is_opr_modified(opr, &ops[i]))
+ continue;
+ if (ops[i].op != OP_MOV && ops[i].op != OP_LEA) {
+ // most probably trashed by some processing
+ *pp_found = NULL;
+ return;
+ }
+
+ opr = &ops[i].operand[1];
+ if (opr->type != OPT_REG)
+ break;
+ }
+
+ po = (i >= 0) ? &ops[i] : ops;
+
+ if (i < 0) {
+ // reached the top - can only be an arg-reg
+ if (opr->type != OPT_REG)
+ return;
+
+ for (i = 0; i < g_func_pp->argc; i++) {
+ if (g_func_pp->arg[i].reg == NULL)
+ continue;
+ if (IS(opr->name, g_func_pp->arg[i].reg))
+ break;
+ }
+ if (i == g_func_pp->argc)
+ return;
+ pp = g_func_pp->arg[i].fptr;
+ if (pp == NULL)
+ ferr(po, "icall: arg%d (%s) is not a fptr?\n",
+ i + 1, g_func_pp->arg[i].reg);
+ check_func_pp(po, pp, "icall reg-arg");
+ }
+ else
+ pp = try_recover_pp(po, opr, NULL);
+
+ if (*pp_found != NULL && pp != NULL && *pp_found != pp) {
+ if (!IS((*pp_found)->ret_type.name, pp->ret_type.name)
+ || (*pp_found)->is_stdcall != pp->is_stdcall
+ || (*pp_found)->is_fptr != pp->is_fptr
+ || (*pp_found)->argc != pp->argc
+ || (*pp_found)->argc_reg != pp->argc_reg
+ || (*pp_found)->argc_stack != pp->argc_stack)
+ {
+ ferr(po, "icall: parsed_proto mismatch\n");
+ }
+ *multi = 1;
+ }
+ if (pp != NULL)
+ *pp_found = pp;
+}
+
+static const struct parsed_proto *resolve_icall(int i, int opcnt,
+ int *multi_src)
+{
+ const struct parsed_proto *pp = NULL;
+ int search_advice = 0;
+
+ *multi_src = 0;
+
+ switch (ops[i].operand[0].type) {
+ case OPT_REGMEM:
+ case OPT_LABEL:
+ case OPT_OFFSET:
+ pp = try_recover_pp(&ops[i], &ops[i].operand[0], &search_advice);
+ if (!search_advice)
+ break;
+ // fallthrough
+ default:
+ scan_for_call_type(i, &ops[i].operand[0], i + opcnt * 9, &pp,
+ multi_src);
+ break;
+ }
+
+ return pp;
+}
+
+// find an instruction that changed opr before i op
+// *op_i must be set to -1
+static int resolve_origin(int i, const struct parsed_opr *opr,
+ int magic, int *op_i)
+{
+ struct label_ref *lr;
+ int ret = 0;
+
+ ops[i].cc_scratch = magic;
+
+ while (1) {
+ if (g_labels[i][0] != 0) {
+ lr = &g_label_refs[i];
+ for (; lr != NULL; lr = lr->next)
+ ret |= resolve_origin(lr->i, opr, magic, op_i);
+ if (i > 0 && LAST_OP(i - 1))
+ return ret;
+ }
+
+ i--;
+ if (i < 0)
+ return -1;
+
+ if (ops[i].cc_scratch == magic)
+ return 0;
+ ops[i].cc_scratch = magic;
+
+ if (!(ops[i].flags & OPF_DATA))
+ continue;
+ if (!is_opr_modified(opr, &ops[i]))
+ continue;
+
+ if (*op_i >= 0) {
+ if (*op_i == i)
+ return 1;
+ // XXX: could check if the other op does the same
+ return -1;
+ }
+
+ *op_i = i;
+ return 1;
+ }
+}
+
+static int try_resolve_const(int i, const struct parsed_opr *opr,
+ int magic, unsigned int *val)
+{
+ int s_i = -1;
+ int ret = 0;
+
+ ret = resolve_origin(i, opr, magic, &s_i);
+ if (ret == 1) {
+ i = s_i;
+ if (ops[i].op != OP_MOV && ops[i].operand[1].type != OPT_CONST)
+ return -1;
+
+ *val = ops[i].operand[1].val;
+ return 1;
+ }
+
+ return -1;
+}
+
+static int collect_call_args_r(struct parsed_op *po, int i,
+ struct parsed_proto *pp, int *regmask, int *save_arg_vars, int arg,
+ int magic, int need_op_saving, int may_reuse)
+{
+ struct parsed_proto *pp_tmp;
+ struct label_ref *lr;
+ int need_to_save_current;
+ int save_args;
+ int ret = 0;
+ char buf[32];
+ int j, k;
+
+ if (i < 0) {
+ ferr(po, "dead label encountered\n");
+ return -1;
+ }
+
+ for (; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+ magic = (magic & 0xffffff) | (arg << 24);
+
+ for (j = i; j >= 0 && (arg < pp->argc || pp->is_unresolved); )
+ {
+ if (((ops[j].cc_scratch ^ magic) & 0xffffff) == 0) {
+ if (ops[j].cc_scratch != magic) {
+ ferr(&ops[j], "arg collect hit same path with diff args for %s\n",
+ pp->name);
+ return -1;
+ }
+ // ok: have already been here
+ return 0;
+ }
+ ops[j].cc_scratch = magic;
+
+ if (g_labels[j][0] != 0 && g_label_refs[j].i != -1) {
+ lr = &g_label_refs[j];
+ if (lr->next != NULL)
+ need_op_saving = 1;
+ for (; lr->next; lr = lr->next) {
+ if ((ops[lr->i].flags & (OPF_JMP|OPF_CJMP)) != OPF_JMP)
+ may_reuse = 1;
+ ret = collect_call_args_r(po, lr->i, pp, regmask, save_arg_vars,
+ arg, magic, need_op_saving, may_reuse);
+ if (ret < 0)
+ return ret;
+ }
+
+ if ((ops[lr->i].flags & (OPF_JMP|OPF_CJMP)) != OPF_JMP)
+ may_reuse = 1;
+ if (j > 0 && LAST_OP(j - 1)) {
+ // follow last branch in reverse
+ j = lr->i;
+ continue;
+ }
+ need_op_saving = 1;
+ ret = collect_call_args_r(po, lr->i, pp, regmask, save_arg_vars,
+ arg, magic, need_op_saving, may_reuse);
+ if (ret < 0)
+ return ret;
+ }
+ j--;
+
+ if (ops[j].op == OP_CALL)
+ {
+ if (pp->is_unresolved)
+ break;
+
+ pp_tmp = ops[j].pp;
+ if (pp_tmp == NULL)
+ ferr(po, "arg collect hit unparsed call '%s'\n",
+ ops[j].operand[0].name);
+ if (may_reuse && pp_tmp->argc_stack > 0)
+ ferr(po, "arg collect %d/%d hit '%s' with %d stack args\n",
+ arg, pp->argc, opr_name(&ops[j], 0), pp_tmp->argc_stack);
+ }
+ else if (ops[j].op == OP_ADD && ops[j].operand[0].reg == xSP) {
+ if (pp->is_unresolved)
+ break;
+
+ ferr(po, "arg collect %d/%d hit esp adjust\n",
+ arg, pp->argc);
+ }
+ else if (ops[j].op == OP_POP) {
+ if (pp->is_unresolved)
+ break;
+
+ ferr(po, "arg collect %d/%d hit pop\n", arg, pp->argc);
+ }
+ else if (ops[j].flags & OPF_CJMP)
+ {
+ if (pp->is_unresolved)
+ break;
+
+ may_reuse = 1;
+ }
+ else if (ops[j].op == OP_PUSH && !(ops[j].flags & OPF_FARG))
+ {
+ if (pp->is_unresolved && (ops[j].flags & OPF_RMD))
+ break;
+
+ pp->arg[arg].datap = &ops[j];
+ need_to_save_current = 0;
+ save_args = 0;
+ if (!need_op_saving) {
+ ret = scan_for_mod(&ops[j], j + 1, i, 1);
+ need_to_save_current = (ret >= 0);
+ }
+ if (need_op_saving || need_to_save_current) {
+ // mark this push as one that needs operand saving
+ ops[j].flags &= ~OPF_RMD;
+ if (ops[j].argnum == 0) {
+ ops[j].argnum = arg + 1;
+ save_args |= 1 << arg;
+ }
+ else if (ops[j].argnum < arg + 1)
+ ferr(&ops[j], "argnum conflict (%d<%d) for '%s'\n",
+ ops[j].argnum, arg + 1, pp->name);
+ }
+ else if (ops[j].argnum == 0)
+ ops[j].flags |= OPF_RMD;
+
+ // some PUSHes are reused by different calls on other branches,
+ // but that can't happen if we didn't branch, so they
+ // can be removed from future searches (handles nested calls)
+ if (!may_reuse)
+ ops[j].flags |= OPF_FARG;
+
+ ops[j].flags &= ~OPF_RSAVE;
+
+ // check for __VALIST
+ if (!pp->is_unresolved && g_func_pp->is_vararg
+ && IS(pp->arg[arg].type.name, "__VALIST"))
+ {
+ snprintf(buf, sizeof(buf), "arg_%X",
+ g_func_pp->argc_stack * 4);
+ k = -1;
+ ret = resolve_origin(j, &ops[j].operand[0], magic + 1, &k);
+ if (ret == 1 && k >= 0 && ops[k].op == OP_LEA
+ && strstr(ops[k].operand[1].name, buf))
+ {
+ ops[k].flags |= OPF_RMD;
+ ops[j].flags |= OPF_RMD | OPF_VAPUSH;
+ save_args &= ~(1 << arg);
+ }
+ }
+
+ *save_arg_vars |= save_args;
+
+ // tracking reg usage
+ if (!(ops[j].flags & OPF_VAPUSH)
+ && ops[j].operand[0].type == OPT_REG)
+ {
+ *regmask |= 1 << ops[j].operand[0].reg;
+ }
+
+ arg++;
+ if (!pp->is_unresolved) {
+ // next arg
+ for (; arg < pp->argc; arg++)
+ if (pp->arg[arg].reg == NULL)
+ break;
+ }
+ magic = (magic & 0xffffff) | (arg << 24);
+ }
+ }
+
+ if (arg < pp->argc) {
+ ferr(po, "arg collect failed for '%s': %d/%d\n",
+ pp->name, arg, pp->argc);
+ return -1;
+ }
+
+ return arg;
+}
+
+static int collect_call_args(struct parsed_op *po, int i,
+ struct parsed_proto *pp, int *regmask, int *save_arg_vars,
+ int magic)
+{
+ int ret;
+ int a;
+
+ ret = collect_call_args_r(po, i, pp, regmask, save_arg_vars,
+ 0, magic, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ if (pp->is_unresolved) {
+ pp->argc += ret;
+ pp->argc_stack += ret;
+ for (a = 0; a < pp->argc; a++)
+ if (pp->arg[a].type.name == NULL)
+ pp->arg[a].type.name = strdup("int");
+ }
+
+ return ret;
+}
+
+// early check for tail call or branch back
+static int is_like_tailjmp(int j)
+{
+ if (!(ops[j].flags & OPF_JMP))
+ return 0;
+
+ if (ops[j].op == OP_JMP && !ops[j].operand[0].had_ds)
+ // probably local branch back..
+ return 1;
+ if (ops[j].op == OP_CALL)
+ // probably noreturn call..
+ return 1;
+
+ return 0;
+}
+
+static void pp_insert_reg_arg(struct parsed_proto *pp, const char *reg)
+{
+ int i;
+
+ for (i = 0; i < pp->argc; i++)
+ if (pp->arg[i].reg == NULL)
+ break;
+
+ if (pp->argc_stack)
+ memmove(&pp->arg[i + 1], &pp->arg[i],
+ sizeof(pp->arg[0]) * pp->argc_stack);
+ memset(&pp->arg[i], 0, sizeof(pp->arg[i]));
+ pp->arg[i].reg = strdup(reg);
+ pp->arg[i].type.name = strdup("int");
+ pp->argc++;
+ pp->argc_reg++;
+}
+
+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_new->next = lr->next;
+ lr->next = lr_new;
+}
+
+static void output_std_flags(FILE *fout, struct parsed_op *po,
+ int *pfomask, const char *dst_opr_text)
+{
+ if (*pfomask & (1 << PFO_Z)) {
+ fprintf(fout, "\n cond_z = (%s%s == 0);",
+ lmod_cast_u(po, po->operand[0].lmod), dst_opr_text);
+ *pfomask &= ~(1 << PFO_Z);
+ }
+ if (*pfomask & (1 << PFO_S)) {
+ fprintf(fout, "\n cond_s = (%s%s < 0);",
+ lmod_cast_s(po, po->operand[0].lmod), dst_opr_text);
+ *pfomask &= ~(1 << PFO_S);
+ }
+}
+
+static void output_pp_attrs(FILE *fout, const struct parsed_proto *pp,
+ int is_noreturn)
+{
+ if (pp->is_fastcall)
+ fprintf(fout, "__fastcall ");
+ else if (pp->is_stdcall && pp->argc_reg == 0)
+ fprintf(fout, "__stdcall ");
+ if (pp->is_noreturn || is_noreturn)
+ fprintf(fout, "noreturn ");
+}
+
+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], cast[64];
+ const struct parsed_proto *pp_c;
+ struct parsed_proto *pp, *pp_tmp;
+ struct parsed_data *pd;
+ const char *tmpname;
+ unsigned int uval;
+ int save_arg_vars = 0;
+ int cond_vars = 0;
+ int need_tmp_var = 0;
+ int need_tmp64 = 0;
+ int had_decl = 0;
+ int label_pending = 0;
+ int regmask_save = 0;