X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=tools%2Ftranslate.c;h=dadf7a915098cf64c3d2c4acebca801228046f30;hb=d4a985bd88473515445c6f9f0ae51be9895b9d60;hp=78ff9d234ea8dfbbccfe4a4a74319052e6b0d855;hpb=04abc5d640b0c379928b434a9c0df83d5f650788;p=ia32rtools.git diff --git a/tools/translate.c b/tools/translate.c index 78ff9d2..dadf7a9 100644 --- a/tools/translate.c +++ b/tools/translate.c @@ -59,6 +59,10 @@ enum op_flags { OPF_VAPUSH = (1 << 18), /* vararg ptr push (as call arg) */ OPF_DONE = (1 << 19), /* already fully handled by analysis */ OPF_PPUSH = (1 << 20), /* part of complex push-pop graph */ + OPF_NOREGS = (1 << 21), /* don't track regs of this op */ + OPF_FPUSH = (1 << 22), /* pushes x87 stack */ + OPF_FPOP = (1 << 23), /* pops x87 stack */ + OPF_FSHIFT = (1 << 24), /* x87 stack shift is actually needed */ }; enum op_op { @@ -115,11 +119,29 @@ enum op_op { OP_LOOP, OP_JCC, OP_SCC, - // x87 - // mmx - OP_EMMS, - // undefined - OP_UD2, + // x87 + OP_FLD, + OP_FILD, + OP_FLDc, + OP_FST, + OP_FADD, + OP_FDIV, + OP_FMUL, + OP_FSUB, + OP_FDIVR, + OP_FSUBR, + OP_FIADD, + OP_FIDIV, + OP_FIMUL, + OP_FISUB, + OP_FIDIVR, + OP_FISUBR, + // mmx + OP_EMMS, + // pseudo-ops for lib calls + OPP_FTOL, + // undefined + OP_UD2, }; enum opr_type { @@ -140,12 +162,18 @@ enum opr_lenmod { OPLM_QWORD, }; +#define MAX_EXITS 128 + #define MAX_OPERANDS 3 #define NAMELEN 112 +#define OPR_INIT(type_, lmod_, reg_) \ + { type_, lmod_, reg_, } + struct parsed_opr { enum opr_type type; enum opr_lenmod lmod; + int reg; unsigned int is_ptr:1; // pointer in C unsigned int is_array:1; // array in C unsigned int type_from_var:1; // .. in header, sometimes wrong @@ -153,7 +181,6 @@ struct parsed_opr { unsigned int size_lt:1; // type override is larger than C unsigned int had_ds:1; // had ds: prefix const struct parsed_proto *pp; // for OPT_LABEL - int reg; unsigned int val; char name[NAMELEN]; }; @@ -221,6 +248,16 @@ enum ida_func_attr { IDAFA_FPD = (1 << 5), }; +enum x87_const { + X87_CONST_1 = 1, + X87_CONST_2T, + X87_CONST_2E, + X87_CONST_PI, + X87_CONST_LG2, + X87_CONST_LN2, + X87_CONST_Z, +}; + // note: limited to 32k due to p_argnext #define MAX_OPS 4096 #define MAX_ARG_GRP 2 @@ -233,6 +270,7 @@ static struct label_ref g_label_refs[MAX_OPS]; static const struct parsed_proto *g_func_pp; static struct parsed_data *g_func_pd; static int g_func_pd_cnt; +static int g_func_lmods; static char g_func[256]; static char g_comment[256]; static int g_bp_frame; @@ -256,20 +294,33 @@ static int g_header_mode; dump_op(op_), ##__VA_ARGS__) #define ferr_assert(op_, cond) do { \ - if (!(cond)) ferr(op_, "assertion '%s' failed on ln :%d\n", #cond, \ - __LINE__); \ + if (!(cond)) ferr(op_, "assertion '%s' failed\n", #cond); \ } while (0) const char *regs_r32[] = { "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp", // not r32, but list here for easy parsing and printing "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7", + "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" }; const char *regs_r16[] = { "ax", "bx", "cx", "dx", "si", "di", "bp", "sp" }; const char *regs_r8l[] = { "al", "bl", "cl", "dl" }; const char *regs_r8h[] = { "ah", "bh", "ch", "dh" }; -enum x86_regs { xUNSPEC = -1, xAX, xBX, xCX, xDX, xSI, xDI, xBP, xSP }; +enum x86_regs { + xUNSPEC = -1, + xAX, xBX, xCX, xDX, + xSI, xDI, xBP, xSP, + xMM0, xMM1, xMM2, xMM3, // mmx + xMM4, xMM5, xMM6, xMM7, + xST0, xST1, xST2, xST3, // x87 + xST4, xST5, xST6, xST7, +}; + +#define mxAX (1 << xAX) +#define mxDX (1 << xDX) +#define mxST0 (1 << xST0) +#define mxST1 (1 << xST1) // possible basic comparison types (without inversion) enum parsed_flag_op { @@ -748,6 +799,9 @@ static int parse_operand(struct parsed_opr *opr, equ_find(NULL, parse_stack_el(opr->name, NULL, 1), &i); if (eq) opr->lmod = eq->lmod; + + // might be unaligned access + g_func_lmods |= 1 << OPLM_BYTE; } return wordc; } @@ -956,9 +1010,35 @@ static const struct { { "setg", OP_SCC, 1, 1, OPF_DATA|OPF_CC, PFO_LE, 1 }, { "setnle", OP_SCC, 1, 1, OPF_DATA|OPF_CC, PFO_LE, 1 }, // x87 + { "fld", OP_FLD, 1, 1, OPF_FPUSH }, + { "fild", OP_FILD, 1, 1, OPF_FPUSH }, + { "fld1", OP_FLDc, 0, 0, OPF_FPUSH }, + { "fldz", OP_FLDc, 0, 0, OPF_FPUSH }, + { "fstp", OP_FST, 1, 1, OPF_FPOP }, + { "fst", OP_FST, 1, 1, 0 }, + { "fadd", OP_FADD, 0, 2, 0 }, + { "faddp", OP_FADD, 0, 2, OPF_FPOP }, + { "fdiv", OP_FDIV, 0, 2, 0 }, + { "fdivp", OP_FDIV, 0, 2, OPF_FPOP }, + { "fmul", OP_FMUL, 0, 2, 0 }, + { "fmulp", OP_FMUL, 0, 2, OPF_FPOP }, + { "fsub", OP_FSUB, 0, 2, 0 }, + { "fsubp", OP_FSUB, 0, 2, OPF_FPOP }, + { "fdivr", OP_FDIVR, 0, 2, 0 }, + { "fdivrp", OP_FDIVR, 0, 2, OPF_FPOP }, + { "fsubr", OP_FSUBR, 0, 2, 0 }, + { "fsubrp", OP_FSUBR, 0, 2, OPF_FPOP }, + { "fiadd", OP_FIADD, 1, 1, 0 }, + { "fidiv", OP_FIDIV, 1, 1, 0 }, + { "fimul", OP_FIMUL, 1, 1, 0 }, + { "fisub", OP_FISUB, 1, 1, 0 }, + { "fidivr", OP_FIDIVR, 1, 1, 0 }, + { "fisubr", OP_FISUBR, 1, 1, 0 }, // mmx - { "emms", OP_EMMS, 0, 0, OPF_DATA }, - { "movq", OP_MOV, 2, 2, OPF_DATA }, + { "emms", OP_EMMS, 0, 0, OPF_DATA }, + { "movq", OP_MOV, 2, 2, OPF_DATA }, + // pseudo-ops for lib calls + { "_ftol", OPP_FTOL }, // must be last { "ud2", OP_UD2 }, }; @@ -972,7 +1052,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) int op_w = 0; int opr = 0; int w = 0; - int i; + int i, j; for (i = 0; i < ARRAY_SIZE(pref_table); i++) { if (IS(words[w], pref_table[i].name)) { @@ -1023,6 +1103,9 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) else op->regmask_src |= regmask; op->regmask_src |= regmask_ind; + + if (op->operand[opr].lmod != OPLM_UNSPEC) + g_func_lmods |= 1 << op->operand[opr].lmod; } if (w < wordc) @@ -1082,7 +1165,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) op->operand_cnt = 2; setup_reg_opr(&op->operand[0], xAX, OPLM_BYTE, &op->regmask_src); op->regmask_dst = op->regmask_src; - setup_reg_opr(&op->operand[1], xDX, OPLM_DWORD, &op->regmask_src); + setup_reg_opr(&op->operand[1], xBX, OPLM_DWORD, &op->regmask_src); break; case OP_CDQ: @@ -1094,37 +1177,42 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) 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); + j = 0; + op->regmask_src = 0; + setup_reg_opr(&op->operand[j++], op->op == OP_LODS ? xSI : xDI, + OPLM_DWORD, &op->regmask_src); op->regmask_dst = op->regmask_src; - setup_reg_opr(&op->operand[2], xAX, OPLM_DWORD, + setup_reg_opr(&op->operand[j++], xAX, lmod, op->op == OP_LODS ? &op->regmask_dst : &op->regmask_src); + if (op->flags & OPF_REP) { + setup_reg_opr(&op->operand[j++], xCX, OPLM_DWORD, &op->regmask_src); + op->regmask_dst |= 1 << xCX; + } + op->operand_cnt = j; 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); + j = 0; + op->regmask_src = 0; + // note: lmod is not correct, don't have where to place it + setup_reg_opr(&op->operand[j++], xDI, lmod, &op->regmask_src); + setup_reg_opr(&op->operand[j++], xSI, OPLM_DWORD, &op->regmask_src); + if (op->flags & OPF_REP) + setup_reg_opr(&op->operand[j++], xCX, OPLM_DWORD, &op->regmask_src); + op->operand_cnt = j; op->regmask_dst = op->regmask_src; break; @@ -1140,6 +1228,11 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) break; case OP_IMUL: + if (op->operand_cnt == 2) { + if (op->operand[0].type != OPT_REG) + aerr("reg expected\n"); + op->regmask_src |= 1 << op->operand[0].reg; + } if (op->operand_cnt != 1) break; // fallthrough @@ -1171,6 +1264,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) op->operand[1].lmod = OPLM_BYTE; break; + case OP_SHLD: case OP_SHRD: op->regmask_src |= op->regmask_dst; if (op->operand[2].lmod == OPLM_UNSPEC) @@ -1195,7 +1289,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) && op->operand[0].reg == op->operand[1].reg && IS(op->operand[0].name, op->operand[1].name)) // ! ah, al.. { - op->flags |= OPF_RMD | OPF_DONE; + op->flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; op->regmask_src = op->regmask_dst = 0; } break; @@ -1207,7 +1301,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) char buf[16]; snprintf(buf, sizeof(buf), "%s+0", op->operand[0].name); if (IS(buf, op->operand[1].name)) - op->flags |= OPF_RMD | OPF_DONE; + op->flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; } break; @@ -1221,9 +1315,73 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc) op->regmask_src = 1 << xBP; break; + case OP_FLD: + case OP_FILD: + op->regmask_dst |= mxST0; + break; + + case OP_FLDc: + op->regmask_dst |= mxST0; + if (IS(words[op_w] + 3, "1")) + op->operand[0].val = X87_CONST_1; + else if (IS(words[op_w] + 3, "z")) + op->operand[0].val = X87_CONST_Z; + else + aerr("TODO\n"); + break; + + case OP_FST: + op->regmask_src |= mxST0; + break; + + case OP_FADD: + case OP_FDIV: + case OP_FMUL: + case OP_FSUB: + case OP_FDIVR: + case OP_FSUBR: + op->regmask_src |= mxST0; + if (op->operand_cnt == 2) + op->regmask_src |= op->regmask_dst; + else if (op->operand_cnt == 1) { + memcpy(&op->operand[1], &op->operand[0], sizeof(op->operand[1])); + op->operand[0].type = OPT_REG; + op->operand[0].lmod = OPLM_QWORD; + op->operand[0].reg = xST0; + op->regmask_dst |= mxST0; + } + else + // IDA doesn't use this + aerr("no operands?\n"); + break; + + case OP_FIADD: + case OP_FIDIV: + case OP_FIMUL: + case OP_FISUB: + case OP_FIDIVR: + case OP_FISUBR: + op->regmask_src |= mxST0; + op->regmask_dst |= mxST0; + break; + default: break; } + + if (op->operand[0].type == OPT_REG + && op->operand[1].type == OPT_CONST) + { + struct parsed_opr *op1 = &op->operand[1]; + if ((op->op == OP_AND && op1->val == 0) + || (op->op == OP_OR + && (op1->val == ~0 + || (op->operand[0].lmod == OPLM_WORD && op1->val == 0xffff) + || (op->operand[0].lmod == OPLM_BYTE && op1->val == 0xff)))) + { + op->regmask_src = 0; + } + } } static const char *op_name(struct parsed_op *po) @@ -1391,10 +1549,45 @@ static const char *opr_reg_p(struct parsed_op *po, struct parsed_opr *popr) return regs_r32[popr->reg]; } +static int check_simple_cast(const char *cast, int *bits, int *is_signed) +{ + if (IS_START(cast, "(s8)") || IS_START(cast, "(u8)")) + *bits = 8; + else if (IS_START(cast, "(s16)") || IS_START(cast, "(u16)")) + *bits = 16; + else if (IS_START(cast, "(s32)") || IS_START(cast, "(u32)")) + *bits = 32; + else if (IS_START(cast, "(s64)") || IS_START(cast, "(u64)")) + *bits = 64; + else + return -1; + + *is_signed = cast[1] == 's' ? 1 : 0; + return 0; +} + +static int check_deref_cast(const char *cast, int *bits) +{ + if (IS_START(cast, "*(u8 *)")) + *bits = 8; + else if (IS_START(cast, "*(u16 *)")) + *bits = 16; + else if (IS_START(cast, "*(u32 *)")) + *bits = 32; + else if (IS_START(cast, "*(u64 *)")) + *bits = 64; + else + return -1; + + return 0; +} + // cast1 is the "final" cast static const char *simplify_cast(const char *cast1, const char *cast2) { static char buf[256]; + int bits1, bits2; + int s1, s2; if (cast1[0] == 0) return cast2; @@ -1402,14 +1595,22 @@ static const char *simplify_cast(const char *cast1, const char *cast2) 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 (check_simple_cast(cast1, &bits1, &s1) == 0 + && check_simple_cast(cast2, &bits2, &s2) == 0) + { + if (bits1 <= bits2) + return cast1; + } + if (check_simple_cast(cast1, &bits1, &s1) == 0 + && check_deref_cast(cast2, &bits2) == 0) + { + if (bits1 == bits2) { + snprintf(buf, sizeof(buf), "*(%c%d *)", s1 ? 's' : 'u', bits1); + return buf; + } + } + if (strchr(cast1, '*') && IS_START(cast2, "(u32)")) return cast1; @@ -1713,6 +1914,14 @@ static int stack_frame_access(struct parsed_op *po, snprintf(buf, buf_size, "%ssf.d[%d]", prefix, sf_ofs / 4); break; + case OPLM_QWORD: + ferr_assert(po, !(sf_ofs & 7)); + ferr_assert(po, ofs_reg[0] == 0); + // float callers set is_lea + ferr_assert(po, is_lea); + snprintf(buf, buf_size, "%ssf.q[%d]", prefix, sf_ofs / 8); + break; + default: ferr(po, "bp_stack bad lmod: %d\n", popr->lmod); } @@ -1947,6 +2156,52 @@ static char *out_src_opr_u32(char *buf, size_t buf_size, return out_src_opr(buf, buf_size, po, popr, NULL, 0); } +static char *out_src_opr_float(char *buf, size_t buf_size, + struct parsed_op *po, struct parsed_opr *popr) +{ + const char *cast = NULL; + char tmp[256]; + + switch (popr->type) { + case OPT_REG: + if (popr->reg < xST0 || popr->reg > xST7) + ferr(po, "bad reg: %d\n", popr->reg); + + snprintf(buf, buf_size, "f_st%d", popr->reg - xST0); + break; + + case OPT_REGMEM: + case OPT_LABEL: + case OPT_OFFSET: + switch (popr->lmod) { + case OPLM_QWORD: + cast = "double"; + break; + case OPLM_DWORD: + cast = "float"; + break; + default: + ferr(po, "unhandled lmod: %d\n", popr->lmod); + break; + } + out_src_opr(tmp, sizeof(tmp), po, popr, "", 1); + snprintf(buf, buf_size, "*((%s *)%s)", cast, tmp); + break; + + default: + ferr(po, "invalid float type: %d\n", popr->type); + } + + return buf; +} + +static char *out_dst_opr_float(char *buf, size_t buf_size, + struct parsed_op *po, struct parsed_opr *popr) +{ + // same? + return out_src_opr_float(buf, buf_size, po, popr); +} + 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) @@ -2146,13 +2401,6 @@ static const char *op_to_c(struct parsed_op *po) } } -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 \ @@ -2162,32 +2410,32 @@ static void op_set_clear_flag(struct parsed_op *po, if ((i) < 0) \ ferr(po, "bad " #i ": %d\n", i) -static int scan_for_pop(int i, int opcnt, const char *reg, - int magic, int depth, int *maxdepth, int do_flags) +// note: this skips over calls and rm'd stuff assuming they're handled +// so it's intended to use at one of final passes +static int scan_for_pop(int i, int opcnt, int magic, int reg, + int depth, int flags_set) { - const struct parsed_proto *pp; struct parsed_op *po; + int relevant; int ret = 0; int j; for (; i < opcnt; i++) { po = &ops[i]; if (po->cc_scratch == magic) - break; // already checked + return ret; // 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, g_quiet_pp); - if (pp != NULL && pp->is_noreturn) - // no stack cleanup for noreturn - return ret; + if (po->pp != NULL && po->pp->is_noreturn) + // assume no stack cleanup for noreturn + return 1; } return -1; // deadend } - if ((po->flags & (OPF_RMD|OPF_DONE)) - || (po->op == OP_PUSH && po->p_argnum != 0)) // arg push + if (po->flags & (OPF_RMD|OPF_DONE|OPF_FARG)) continue; if ((po->flags & OPF_JMP) && po->op != OP_CALL) { @@ -2195,8 +2443,8 @@ static int scan_for_pop(int i, int opcnt, const char *reg, // jumptable for (j = 0; j < po->btj->count; j++) { check_i(po, po->btj->d[j].bt_i); - ret |= scan_for_pop(po->btj->d[j].bt_i, opcnt, reg, magic, - depth, maxdepth, do_flags); + ret |= scan_for_pop(po->btj->d[j].bt_i, opcnt, magic, reg, + depth, flags_set); if (ret < 0) return ret; // dead end } @@ -2205,8 +2453,8 @@ static int scan_for_pop(int i, int opcnt, const char *reg, check_i(po, po->bt_i); if (po->flags & OPF_CJMP) { - ret |= scan_for_pop(po->bt_i, opcnt, reg, magic, - depth, maxdepth, do_flags); + ret |= scan_for_pop(po->bt_i, opcnt, magic, reg, + depth, flags_set); if (ret < 0) return ret; // dead end } @@ -2216,166 +2464,397 @@ static int scan_for_pop(int i, int opcnt, const char *reg, continue; } + relevant = 0; if ((po->op == OP_POP || po->op == OP_PUSH) - && po->operand[0].type == OPT_REG - && IS(po->operand[0].name, reg)) + && po->operand[0].type == OPT_REG && po->operand[0].reg == reg) { - if (po->op == OP_PUSH && !(po->flags & OPF_FARGNR)) { - 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); - } + relevant = 1; + } + + if (po->op == OP_PUSH) { + depth++; + } + else if (po->op == OP_POP) { + if (relevant && depth == 0) { + po->flags |= flags_set; + return 1; } + depth--; } } - return ret; + return -1; } -// 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) +// scan for 'reg' pop backwards starting from i +// intended to use for register restore search, so other reg +// references are considered an error +static int scan_for_rsave_pop_reg(int i, int magic, int reg, int set_flags) { - int found = 0; - int j; - - for (; i < opcnt; i++) { - if (!(ops[i].flags & OPF_TAIL)) - continue; + struct parsed_op *po; + struct label_ref *lr; + int ret = 0; - for (j = i - 1; j >= 0; j--) { - if (ops[j].flags & (OPF_RMD|OPF_DONE)) - continue; - if (ops[j].flags & OPF_JMP) - return -1; + ops[i].cc_scratch = magic; - if (ops[j].op == OP_POP && ops[j].datap == NULL - && ops[j].operand[0].type == OPT_REG - && IS(ops[j].operand[0].name, reg)) - { - found = 1; - ops[j].flags |= flag_set; - break; + while (1) + { + if (g_labels[i] != NULL) { + lr = &g_label_refs[i]; + for (; lr != NULL; lr = lr->next) { + check_i(&ops[i], lr->i); + ret |= scan_for_rsave_pop_reg(lr->i, magic, reg, set_flags); + if (ret < 0) + return ret; } + if (i > 0 && LAST_OP(i - 1)) + return ret; + } + + i--; + if (i < 0) + break; + + if (ops[i].cc_scratch == magic) + return ret; + ops[i].cc_scratch = magic; - if (g_labels[j] != NULL) + po = &ops[i]; + if (po->op == OP_POP && po->operand[0].reg == reg) { + if (po->flags & (OPF_RMD|OPF_DONE)) return -1; + + po->flags |= set_flags; + return 1; } + + // this also covers the case where we reach corresponding push + if ((po->regmask_dst | po->regmask_src) & (1 << reg)) + return -1; } - return found ? 0 : -1; + // nothing interesting on this path + return 0; } -static void scan_for_pop_const(int i, int opcnt, int *regmask_pp) +static void find_reachable_exits(int i, int opcnt, int magic, + int *exits, int *exit_count) { struct parsed_op *po; - int is_multipath = 0; int j; - for (j = i + 1; j < opcnt; j++) { - po = &ops[j]; - - if (po->op == OP_JMP && po->btj == NULL) { - ferr_assert(po, po->bt_i >= 0); - j = po->bt_i - 1; - continue; - } + for (; i < opcnt; i++) + { + po = &ops[i]; + if (po->cc_scratch == magic) + return; + po->cc_scratch = magic; - if ((po->flags & (OPF_JMP|OPF_TAIL|OPF_RSAVE)) - || po->op == OP_PUSH) - { - break; + if (po->flags & OPF_TAIL) { + ferr_assert(po, *exit_count < MAX_EXITS); + exits[*exit_count] = i; + (*exit_count)++; + return; } - if (g_labels[j] != NULL) - is_multipath = 1; + if ((po->flags & OPF_JMP) && po->op != OP_CALL) { + if (po->flags & OPF_RMD) + continue; - if (po->op == OP_POP && !(po->flags & OPF_RMD)) - { - is_multipath |= !!(po->flags & OPF_PPUSH); - if (is_multipath) { - ops[i].flags |= OPF_PPUSH | OPF_DONE; - ops[i].datap = po; - po->flags |= OPF_PPUSH | OPF_DONE; - *regmask_pp |= 1 << po->operand[0].reg; - } - else { - ops[i].flags |= OPF_RMD | OPF_DONE; - po->flags |= OPF_DONE; - po->datap = &ops[i]; + if (po->btj != NULL) { + for (j = 0; j < po->btj->count; j++) { + check_i(po, po->btj->d[j].bt_i); + find_reachable_exits(po->btj->d[j].bt_i, opcnt, magic, + exits, exit_count); + } + return; } - break; + + check_i(po, po->bt_i); + if (po->flags & OPF_CJMP) + find_reachable_exits(po->bt_i, opcnt, magic, exits, exit_count); + else + i = po->bt_i - 1; + continue; } } } -static void scan_propagate_df(int i, int opcnt) +// scan for 'reg' pop backwards starting from exits (all paths) +static int scan_for_pop_ret(int i, int opcnt, int reg, int set_flags) { - struct parsed_op *po = &ops[i]; + static int exits[MAX_EXITS]; + static int exit_count; + int j, ret; + + if (!set_flags) { + exit_count = 0; + find_reachable_exits(i, opcnt, i + opcnt * 15, exits, + &exit_count); + ferr_assert(&ops[i], exit_count > 0); + } + + for (j = 0; j < exit_count; j++) { + ret = scan_for_rsave_pop_reg(exits[j], i + opcnt * 16 + set_flags, + reg, set_flags); + if (ret == -1) + return -1; + } + + return 1; +} + +// scan for one or more pop of push +static int scan_for_pop_const_r(int i, int opcnt, int magic, + int push_i, int is_probe) +{ + struct parsed_op *po; + struct label_ref *lr; + int ret = 0; int j; - for (; i < opcnt; i++) { + 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->cc_scratch == magic) + return ret; // already checked + po->cc_scratch = magic; if (po->flags & OPF_JMP) { + if (po->flags & OPF_RMD) + continue; + if (po->op == OP_CALL) + return -1; + if (po->btj != NULL) { - // jumptable for (j = 0; j < po->btj->count; j++) { check_i(po, po->btj->d[j].bt_i); - scan_propagate_df(po->btj->d[j].bt_i, opcnt); + ret |= scan_for_pop_const_r(po->btj->d[j].bt_i, opcnt, magic, + push_i, is_probe); + if (ret < 0) + return ret; } - return; + return ret; } check_i(po, po->bt_i); - if (po->flags & OPF_CJMP) - scan_propagate_df(po->bt_i, opcnt); - else + if (po->flags & OPF_CJMP) { + ret |= scan_for_pop_const_r(po->bt_i, opcnt, magic, push_i, + is_probe); + if (ret < 0) + return ret; + } + else { i = po->bt_i - 1; + } continue; } - if (po->flags & OPF_TAIL) - break; + if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_PUSH) + return -1; - if (po->op == OP_CLD) { - po->flags |= OPF_RMD | OPF_DONE; - return; + if (g_labels[i] != NULL) { + // all refs must be visited + lr = &g_label_refs[i]; + for (; lr != NULL; lr = lr->next) { + check_i(po, lr->i); + if (ops[lr->i].cc_scratch != magic) + return -1; + } + if (i > 0 && !LAST_OP(i - 1) && ops[i - 1].cc_scratch != magic) + return -1; + } + + if (po->op == OP_POP) + { + if (po->flags & (OPF_RMD|OPF_DONE)) + return -1; + + if (!is_probe) { + po->flags |= OPF_DONE; + po->datap = &ops[push_i]; + } + return 1; } } - ferr(po, "missing DF clear?\n"); + return -1; } -// is operand 'opr' referenced by parsed_op 'po'? -static int is_opr_referenced(const struct parsed_opr *opr, - const struct parsed_op *po) +static void scan_for_pop_const(int i, int opcnt, int magic) { - int i, mask; + int ret; - if (opr->type == OPT_REG) { - mask = po->regmask_dst | po->regmask_src; + ret = scan_for_pop_const_r(i + 1, opcnt, magic, i, 1); + if (ret == 1) { + ops[i].flags |= OPF_RMD | OPF_DONE; + scan_for_pop_const_r(i + 1, opcnt, magic + 1, i, 0); + } +} + +// check if all branch targets within a marked path are also marked +// note: the path checked must not be empty or end with a branch +static int check_path_branches(int opcnt, int magic) +{ + struct parsed_op *po; + int i, j; + + for (i = 0; i < opcnt; i++) { + po = &ops[i]; + if (po->cc_scratch != magic) + continue; + + if (po->flags & OPF_JMP) { + if ((po->flags & OPF_RMD) || po->op == OP_CALL) + continue; + + if (po->btj != NULL) { + for (j = 0; j < po->btj->count; j++) { + check_i(po, po->btj->d[j].bt_i); + if (ops[po->btj->d[j].bt_i].cc_scratch != magic) + return 0; + } + } + + check_i(po, po->bt_i); + if (ops[po->bt_i].cc_scratch != magic) + return 0; + if ((po->flags & OPF_CJMP) && ops[i + 1].cc_scratch != magic) + return 0; + } + } + + return 1; +} + +// scan for multiple pushes for given pop +static int scan_pushes_for_pop_r(int i, int magic, int pop_i, + int is_probe) +{ + int reg = ops[pop_i].operand[0].reg; + struct parsed_op *po; + struct label_ref *lr; + int ret = 0; + + ops[i].cc_scratch = magic; + + while (1) + { + if (g_labels[i] != NULL) { + lr = &g_label_refs[i]; + for (; lr != NULL; lr = lr->next) { + check_i(&ops[i], lr->i); + ret |= scan_pushes_for_pop_r(lr->i, magic, pop_i, is_probe); + if (ret < 0) + return ret; + } + if (i > 0 && LAST_OP(i - 1)) + return ret; + } + + i--; + if (i < 0) + break; + + if (ops[i].cc_scratch == magic) + return ret; + ops[i].cc_scratch = magic; + + po = &ops[i]; + if (po->op == OP_CALL) + return -1; + if ((po->flags & (OPF_TAIL|OPF_RSAVE)) || po->op == OP_POP) + return -1; + + if (po->op == OP_PUSH) + { + if (po->datap != NULL) + return -1; + if (po->operand[0].type == OPT_REG && po->operand[0].reg == reg) + // leave this case for reg save/restore handlers + return -1; + + if (!is_probe) { + po->flags |= OPF_PPUSH | OPF_DONE; + po->datap = &ops[pop_i]; + } + return 1; + } + } + + return -1; +} + +static void scan_pushes_for_pop(int i, int opcnt, int *regmask_pp) +{ + int magic = i + opcnt * 14; + int ret; + + ret = scan_pushes_for_pop_r(i, magic, i, 1); + if (ret == 1) { + ret = check_path_branches(opcnt, magic); + if (ret == 1) { + ops[i].flags |= OPF_PPUSH | OPF_DONE; + *regmask_pp |= 1 << ops[i].operand[0].reg; + scan_pushes_for_pop_r(i, magic + 1, i, 0); + } + } +} + +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++) { + check_i(po, po->btj->d[j].bt_i); + scan_propagate_df(po->btj->d[j].bt_i, opcnt); + } + return; + } + + if (po->flags & OPF_RMD) + continue; + check_i(po, po->bt_i); + 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 | OPF_DONE; + return; + } + } + + ferr(po, "missing DF clear?\n"); +} + +// is operand 'opr' referenced by parsed_op 'po'? +static int is_opr_referenced(const struct parsed_opr *opr, + const struct parsed_op *po) +{ + int i, mask; + + if (opr->type == OPT_REG) { + mask = po->regmask_dst | po->regmask_src; if (po->op == OP_CALL) mask |= (1 << xAX) | (1 << xCX) | (1 << xDX); if ((1 << opr->reg) & mask) @@ -2395,14 +2874,8 @@ static int is_opr_referenced(const struct parsed_opr *opr, static int is_opr_read(const struct parsed_opr *opr, const struct parsed_op *po) { - int mask; - if (opr->type == OPT_REG) { - mask = po->regmask_src; - if (po->op == OP_CALL) - // assume worst case - mask |= (1 << xAX) | (1 << xCX) | (1 << xDX); - if ((1 << opr->reg) & mask) + if (po->regmask_src & (1 << opr->reg)) return 1; else return 0; @@ -2418,24 +2891,20 @@ static int is_opr_modified(const struct parsed_opr *opr, { int mask; - if (!(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) + mask = po->regmask_dst; + mask |= (1 << xAX) | (1 << xCX) | (1 << xDX); // ? + if (mask & (1 << opr->reg)) return 1; else return 0; } - if (po->operand[0].type == OPT_REG) { - if (po->regmask_dst & (1 << opr->reg)) - return 1; - else - return 0; - } + if (po->regmask_dst & (1 << opr->reg)) + return 1; + else + return 0; } return IS(po->operand[0].name, opr->name); @@ -2673,7 +3142,7 @@ static int scan_for_esp_adjust(int i, int opcnt, if (do_update && *adj >= 0) { po->flags |= OPF_RMD; if (!*is_multipath) - po->flags |= OPF_DONE; + po->flags |= OPF_DONE | OPF_NOREGS; } *adj += lmod_bytes(po, po->operand[0].lmod); @@ -2960,14 +3429,58 @@ static void clear_labels(int count) } } +static int get_pp_arg_regmask_src(const struct parsed_proto *pp) +{ + int regmask = 0; + int i, reg; + + for (i = 0; i < pp->argc; i++) { + if (pp->arg[i].reg != NULL) { + reg = char_array_i(regs_r32, + ARRAY_SIZE(regs_r32), pp->arg[i].reg); + if (reg < 0) + ferr(ops, "arg '%s' of func '%s' is not a reg?\n", + pp->arg[i].reg, pp->name); + regmask |= 1 << reg; + } + } + + return regmask; +} + +static int get_pp_arg_regmask_dst(const struct parsed_proto *pp) +{ + if (strstr(pp->ret_type.name, "int64")) + return (1 << xAX) | (1 << xDX); + if (IS(pp->ret_type.name, "float") + || IS(pp->ret_type.name, "double")) + { + return mxST0; + } + if (strcasecmp(pp->ret_type.name, "void") == 0) + return 0; + + return mxAX; +} + static void resolve_branches_parse_calls(int opcnt) { + static const struct { + const char *name; + enum op_op op; + unsigned int flags; + unsigned int regmask_src; + unsigned int regmask_dst; + } pseudo_ops[] = { + { "__ftol", OPP_FTOL, OPF_FPOP, mxST0, mxAX | mxDX }, + }; const struct parsed_proto *pp_c; struct parsed_proto *pp; struct parsed_data *pd; struct parsed_op *po; const char *tmpname; - int i, l, ret; + int i, l; + int ret; for (i = 0; i < opcnt; i++) { @@ -2982,6 +3495,23 @@ static void resolve_branches_parse_calls(int opcnt) tmpname = opr_name(po, 0); if (IS_START(tmpname, "loc_")) ferr(po, "call to loc_*\n"); + + // convert some calls to pseudo-ops + for (l = 0; l < ARRAY_SIZE(pseudo_ops); l++) { + if (!IS(tmpname, pseudo_ops[l].name)) + continue; + + po->op = pseudo_ops[l].op; + po->operand_cnt = 0; + po->regmask_src = pseudo_ops[l].regmask_src; + po->regmask_dst = pseudo_ops[l].regmask_dst; + po->flags = pseudo_ops[l].flags; + po->flags |= po->regmask_dst ? OPF_DATA : 0; + break; + } + if (l < ARRAY_SIZE(pseudo_ops)) + continue; + pp_c = proto_parse(g_fhdr, tmpname, g_header_mode); if (!g_header_mode && pp_c == NULL) ferr(po, "proto_parse failed for call '%s'\n", tmpname); @@ -3069,13 +3599,13 @@ static void scan_prologue_epilogue(int opcnt) && IS(opr_name(&ops[1], 1), "esp")) { g_bp_frame = 1; - ops[0].flags |= OPF_RMD | OPF_DONE; - ops[1].flags |= OPF_RMD | OPF_DONE; + ops[0].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; + ops[1].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; 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 | OPF_DONE; + ops[2].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; i++; } else { @@ -3083,7 +3613,7 @@ static void scan_prologue_epilogue(int opcnt) i = 2; while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) { g_stack_fsz += 4; - ops[i].flags |= OPF_RMD | OPF_DONE; + ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; ecx_push++; i++; } @@ -3094,9 +3624,9 @@ static void scan_prologue_epilogue(int opcnt) && IS(opr_name(&ops[i + 1], 0), "__alloca_probe")) { g_stack_fsz += ops[i].operand[1].val; - ops[i].flags |= OPF_RMD | OPF_DONE; + ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; i++; - ops[i].flags |= OPF_RMD | OPF_DONE; + ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; i++; } } @@ -3117,7 +3647,7 @@ static void scan_prologue_epilogue(int opcnt) if ((ops[j].op == OP_POP && IS(opr_name(&ops[j], 0), "ebp")) || ops[j].op == OP_LEAVE) { - ops[j].flags |= OPF_RMD | OPF_DONE; + ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; } else if (ops[i].op == OP_CALL && ops[i].pp != NULL && ops[i].pp->is_noreturn) @@ -3138,7 +3668,7 @@ static void scan_prologue_epilogue(int opcnt) && IS(opr_name(&ops[j - 1], 0), "esp") && IS(opr_name(&ops[j - 1], 1), "ebp")) { - ops[j - 1].flags |= OPF_RMD | OPF_DONE; + ops[j - 1].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; j -= 2; } else if (!(g_ida_func_attr & IDAFA_NORETURN)) @@ -3165,7 +3695,7 @@ static void scan_prologue_epilogue(int opcnt) // non-bp frame i = 0; while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) { - ops[i].flags |= OPF_RMD | OPF_DONE; + ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; g_stack_fsz += 4; ecx_push++; i++; @@ -3178,7 +3708,7 @@ static void scan_prologue_epilogue(int opcnt) && ops[i].operand[1].type == OPT_CONST) { g_stack_fsz = ops[i].operand[1].val; - ops[i].flags |= OPF_RMD | OPF_DONE; + ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; esp_sub = 1; break; } @@ -3197,7 +3727,7 @@ static void scan_prologue_epilogue(int opcnt) while (i > 0 && j > 0) { i--; if (ops[i].op == OP_PUSH) { - ops[i].flags &= ~(OPF_RMD | OPF_DONE); + ops[i].flags &= ~(OPF_RMD | OPF_DONE | OPF_NOREGS); j--; } } @@ -3248,7 +3778,7 @@ static void scan_prologue_epilogue(int opcnt) else ferr(&ops[j], "'pop ecx' expected\n"); - ops[j].flags |= OPF_RMD | OPF_DONE; + ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; j--; } if (l != ecx_push) @@ -3264,7 +3794,7 @@ static void scan_prologue_epilogue(int opcnt) || ops[j].operand[1].val != g_stack_fsz) ferr(&ops[j], "'add esp' expected\n"); - ops[j].flags |= OPF_RMD | OPF_DONE; + ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS; ops[j].operand[1].val = 0; // hack for stack arg scanner found = 1; } @@ -3402,9 +3932,9 @@ static int resolve_last_ref(int i, const struct parsed_opr *opr, } // find next instruction that reads opr -// if multiple results are found - fail // *op_i must be set to -1 by the caller -// returns 1 if found, *op_i is then set to referencer insn +// on return, *op_i is set to first referencer insn +// returns 1 if exactly 1 referencer is found static int find_next_read(int i, int opcnt, const struct parsed_opr *opr, int magic, int *op_i) { @@ -3414,7 +3944,7 @@ static int find_next_read(int i, int opcnt, for (; i < opcnt; i++) { if (ops[i].cc_scratch == magic) - return 0; + return ret; ops[i].cc_scratch = magic; po = &ops[i]; @@ -3433,21 +3963,26 @@ static int find_next_read(int i, int opcnt, continue; check_i(po, po->bt_i); if (po->flags & OPF_CJMP) { - ret = find_next_read(po->bt_i, opcnt, opr, magic, op_i); + ret |= find_next_read(po->bt_i, opcnt, opr, magic, op_i); if (ret < 0) return ret; } - - i = po->bt_i - 1; + else + i = po->bt_i - 1; continue; } if (!is_opr_read(opr, po)) { - if (is_opr_modified(opr, po)) + if (is_opr_modified(opr, po) + && (po->op == OP_CALL + || ((po->flags & OPF_DATA) + && po->operand[0].lmod == OPLM_DWORD))) + { // it's overwritten - return 0; + return ret; + } if (po->flags & OPF_TAIL) - return 0; + return ret; continue; } @@ -3553,16 +4088,16 @@ static struct parsed_proto *process_call(int i, int opcnt) && ops[call_i].operand[1].type == OPT_LABEL) { // no other source users? - ret = resolve_last_ref(i, &po->operand[0], opcnt * 10, + ret = resolve_last_ref(i, &po->operand[0], i + opcnt * 10, &ref_i); if (ret == 1 && call_i == ref_i) { // and nothing uses it after us? ref_i = -1; - ret = find_next_read(i + 1, opcnt, &po->operand[0], - opcnt * 11, &ref_i); - if (ret != 1) + find_next_read(i + 1, opcnt, &po->operand[0], + i + opcnt * 11, &ref_i); + if (ref_i == -1) // then also don't need the source mov - ops[call_i].flags |= OPF_RMD; + ops[call_i].flags |= OPF_RMD | OPF_NOREGS; } } break; @@ -3804,7 +4339,8 @@ static int collect_call_args_r(struct parsed_op *po, int i, if (pp->is_unresolved) break; - ferr(po, "arg collect %d/%d hit esp adjust of %d\n", + fnote(po, "(this call)\n"); + ferr(&ops[j], "arg collect %d/%d hit esp adjust of %d\n", arg, pp->argc, ops[j].operand[1].val); } else if (ops[j].op == OP_POP && !(ops[j].flags & OPF_DONE)) @@ -3812,7 +4348,8 @@ static int collect_call_args_r(struct parsed_op *po, int i, if (pp->is_unresolved) break; - ferr(po, "arg collect %d/%d hit pop\n", arg, pp->argc); + fnote(po, "(this call)\n"); + ferr(&ops[j], "arg collect %d/%d hit pop\n", arg, pp->argc); } else if (ops[j].flags & OPF_CJMP) { @@ -3887,18 +4424,22 @@ static int collect_call_args_r(struct parsed_op *po, int i, if (ret == 1 && k >= 0) { if (ops[k].op == OP_LEA) { + if (!g_func_pp->is_vararg) + ferr(&ops[k], "lea used, but %s is not vararg?\n", + g_func_pp->name); + snprintf(buf, sizeof(buf), "arg_%X", g_func_pp->argc_stack * 4); - if (!g_func_pp->is_vararg - || strstr(ops[k].operand[1].name, buf)) + if (strstr(ops[k].operand[1].name, buf) + || strstr(ops[k].operand[1].name, "arglist")) { - ops[k].flags |= OPF_RMD | OPF_DONE; - ops[j].flags |= OPF_RMD | OPF_VAPUSH; + ops[k].flags |= OPF_RMD | OPF_NOREGS | OPF_DONE; + ops[j].flags |= OPF_RMD | OPF_NOREGS | OPF_VAPUSH; save_args &= ~(1 << arg); reg = -1; } else - ferr(&ops[j], "lea va_list used, but no vararg?\n"); + ferr(&ops[k], "va_list arg detection failed\n"); } // check for va_list from g_func_pp arg too else if (ops[k].op == OP_MOV @@ -3999,6 +4540,194 @@ static int collect_call_args(struct parsed_op *po, int i, return ret; } +static void reg_use_pass(int i, int opcnt, unsigned char *cbits, + int regmask_now, int *regmask, + int regmask_save_now, int *regmask_save, + int *regmask_init, int regmask_arg) +{ + struct parsed_op *po; + unsigned int mask; + int already_saved; + int regmask_new; + int regmask_op; + int flags_set; + int ret, reg; + int j; + + for (; i < opcnt; i++) + { + po = &ops[i]; + if (cbits[i >> 3] & (1 << (i & 7))) + return; + cbits[i >> 3] |= (1 << (i & 7)); + + if ((po->flags & OPF_JMP) && po->op != OP_CALL) { + if (po->flags & (OPF_RMD|OPF_DONE)) + continue; + if (po->btj != NULL) { + for (j = 0; j < po->btj->count; j++) { + check_i(po, po->btj->d[j].bt_i); + reg_use_pass(po->btj->d[j].bt_i, opcnt, cbits, + regmask_now, regmask, regmask_save_now, regmask_save, + regmask_init, regmask_arg); + } + return; + } + + check_i(po, po->bt_i); + if (po->flags & OPF_CJMP) + reg_use_pass(po->bt_i, opcnt, cbits, + regmask_now, regmask, regmask_save_now, regmask_save, + regmask_init, regmask_arg); + else + i = po->bt_i - 1; + continue; + } + + if (po->op == OP_PUSH && !(po->flags & (OPF_FARG|OPF_DONE)) + && !g_func_pp->is_userstack + && po->operand[0].type == OPT_REG) + { + reg = po->operand[0].reg; + ferr_assert(po, reg >= 0); + + already_saved = 0; + flags_set = OPF_RSAVE | OPF_RMD | OPF_DONE; + if (regmask_now & (1 << reg)) { + already_saved = regmask_save_now & (1 << reg); + flags_set = OPF_RSAVE | OPF_DONE; + } + + ret = scan_for_pop(i + 1, opcnt, i + opcnt * 3, reg, 0, 0); + if (ret == 1) { + scan_for_pop(i + 1, opcnt, i + opcnt * 4, reg, 0, flags_set); + } + else { + ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, 0); + if (ret == 1) { + scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, + flags_set); + } + } + if (ret == 1) { + ferr_assert(po, !already_saved); + po->flags |= flags_set; + + if (regmask_now & (1 << reg)) { + regmask_save_now |= (1 << reg); + *regmask_save |= regmask_save_now; + } + continue; + } + } + else if (po->op == OP_POP && (po->flags & OPF_RSAVE)) { + reg = po->operand[0].reg; + ferr_assert(po, reg >= 0); + + if (regmask_save_now & (1 << reg)) + regmask_save_now &= ~(1 << reg); + else + regmask_now &= ~(1 << reg); + continue; + } + else if (po->op == OP_CALL) { + if ((po->regmask_dst & (1 << xAX)) + && !(po->regmask_dst & (1 << xDX))) + { + if (po->flags & OPF_TAIL) + // don't need eax, will do "return f();" or "f(); return;" + po->regmask_dst &= ~(1 << xAX); + else { + struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, xAX); + j = -1; + find_next_read(i + 1, opcnt, &opr, i + opcnt * 17, &j); + if (j == -1) + // not used + po->regmask_dst &= ~(1 << xAX); + } + } + } + + if (po->flags & OPF_NOREGS) + continue; + + if (po->flags & OPF_FPUSH) { + if (regmask_now & mxST1) + ferr(po, "TODO: FPUSH on active ST1\n"); + if (regmask_now & mxST0) + po->flags |= OPF_FSHIFT; + mask = mxST0 | mxST1; + regmask_now = (regmask_now & ~mask) | ((regmask_now & mxST0) << 1); + } + + // if incomplete register is used, clear it on init to avoid + // later use of uninitialized upper part in some situations + if ((po->flags & OPF_DATA) && po->operand[0].type == OPT_REG + && po->operand[0].lmod != OPLM_DWORD) + { + reg = po->operand[0].reg; + ferr_assert(po, reg >= 0); + + if (!(regmask_now & (1 << reg))) + *regmask_init |= 1 << reg; + } + + regmask_op = po->regmask_src | po->regmask_dst; + + regmask_new = po->regmask_src & ~regmask_now & ~regmask_arg; + regmask_new &= ~(1 << xSP); + if (g_bp_frame && !(po->flags & OPF_EBP_S)) + regmask_new &= ~(1 << xBP); + + if (po->op == OP_CALL) { + // allow fastcall calls from anywhere, calee may be also sitting + // in some fastcall table even when it's not using reg args + if (regmask_new & po->regmask_src & (1 << xCX)) { + *regmask_init |= (1 << xCX); + regmask_now |= (1 << xCX); + regmask_new &= ~(1 << xCX); + } + if (regmask_new & po->regmask_src & (1 << xDX)) { + *regmask_init |= (1 << xDX); + regmask_now |= (1 << xDX); + regmask_new &= ~(1 << xDX); + } + } + + if (regmask_new != 0) + fnote(po, "uninitialized reg mask: %x\n", regmask_new); + + if (regmask_op & (1 << xBP)) { + if (g_bp_frame && !(po->flags & OPF_EBP_S)) { + if (po->regmask_dst & (1 << xBP)) + // compiler decided to drop bp frame and use ebp as scratch + scan_fwd_set_flags(i + 1, opcnt, i + opcnt * 5, OPF_EBP_S); + else + regmask_op &= ~(1 << xBP); + } + } + + regmask_now |= regmask_op; + *regmask |= regmask_now; + + // released regs + if (po->flags & OPF_FPOP) { + mask = mxST0 | mxST1; + if (!(regmask_now & mask)) + ferr(po, "float pop on empty stack?\n"); + if (regmask_now & mxST1) + po->flags |= OPF_FSHIFT; + regmask_now = (regmask_now & ~mask) | ((regmask_now & mxST1) >> 1); + } + + if (po->flags & OPF_TAIL) { + if (regmask_now & (mxST0 | mxST1)) + ferr(po, "float regs on tail: %x\n", regmask_now); + return; + } + } +} + static void pp_insert_reg_arg(struct parsed_proto *pp, const char *reg) { int i; @@ -4095,25 +4824,6 @@ static void output_pp(FILE *fout, const struct parsed_proto *pp, fprintf(fout, ")"); } -static int get_pp_arg_regmask(const struct parsed_proto *pp) -{ - int regmask = 0; - int i, reg; - - for (i = 0; i < pp->argc; i++) { - if (pp->arg[i].reg != NULL) { - reg = char_array_i(regs_r32, - ARRAY_SIZE(regs_r32), pp->arg[i].reg); - if (reg < 0) - ferr(ops, "arg '%s' of func '%s' is not a reg?\n", - pp->arg[i].reg, pp->name); - regmask |= 1 << reg; - } - } - - return regmask; -} - static char *saved_arg_name(char *buf, size_t buf_size, int grp, int num) { char buf1[16]; @@ -4137,20 +4847,21 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) struct parsed_data *pd; unsigned int uval; int save_arg_vars[MAX_ARG_GRP] = { 0, }; + unsigned char cbits[MAX_OPS / 8]; 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; // regs saved/restored in this func - int regmask_arg = 0; // regs carrying function args (fastcall, etc) + int regmask_arg; // regs from this function args (fastcall, etc) + int regmask_ret; // regs needed on ret int regmask_now; // temp int regmask_init = 0; // regs that need zero initialization int regmask_pp = 0; // regs used in complex push-pop graph int regmask = 0; // used regs int pfomask = 0; int found = 0; - int depth = 0; int no_output; int i, j, l; int arg; @@ -4164,7 +4875,19 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) if (g_func_pp == NULL) ferr(ops, "proto_parse failed for '%s'\n", funcn); - regmask_arg = get_pp_arg_regmask(g_func_pp); + regmask_arg = get_pp_arg_regmask_src(g_func_pp); + regmask_ret = get_pp_arg_regmask_dst(g_func_pp); + + if (g_func_pp->has_retreg) { + for (arg = 0; arg < g_func_pp->argc; arg++) { + if (g_func_pp->arg[arg].type.is_retreg) { + reg = char_array_i(regs_r32, + ARRAY_SIZE(regs_r32), g_func_pp->arg[arg].reg); + ferr_assert(ops, reg >= 0); + regmask_ret |= 1 << reg; + } + } + } // pass1: // - resolve all branches @@ -4177,12 +4900,16 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) // pass3: // - remove dead labels + // - set regs needed at ret for (i = 0; i < opcnt; i++) { if (g_labels[i] != NULL && g_label_refs[i].i == -1) { free(g_labels[i]); g_labels[i] = NULL; } + + if (ops[i].op == OP_RET) + ops[i].regmask_src |= regmask_ret; } // pass4: @@ -4210,7 +4937,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) patch_esp_adjust(&ops[j], pp->argc_stack * 4); else { for (l = 0; l < pp->argc_stack; l++) - ops[j + l].flags |= OPF_DONE | OPF_RMD; + ops[j + l].flags |= OPF_DONE | OPF_RMD | OPF_NOREGS; } } @@ -4223,38 +4950,69 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) } // pass5: - // - process calls - // - handle push /pop pairs + // - process calls, stage 2 + // - handle some push/pop pairs + // - scan for STD/CLD, propagate DF for (i = 0; i < opcnt; i++) { po = &ops[i]; - if (po->flags & (OPF_RMD|OPF_DONE)) + if (po->flags & OPF_RMD) continue; - if (po->op == OP_CALL && !(po->flags & OPF_DONE)) + if (po->op == OP_CALL) { - pp = process_call(i, opcnt); + if (!(po->flags & OPF_DONE)) { + pp = process_call(i, opcnt); - if (!pp->is_unresolved && !(po->flags & OPF_ATAIL)) { - // since we know the args, collect them - collect_call_args(po, i, pp, ®mask, save_arg_vars, - i + opcnt * 2); + if (!pp->is_unresolved && !(po->flags & OPF_ATAIL)) { + // since we know the args, collect them + collect_call_args(po, i, pp, ®mask, save_arg_vars, + i + opcnt * 2); + } + // for unresolved, collect after other passes } + pp = po->pp; + ferr_assert(po, pp != NULL); + + po->regmask_src |= get_pp_arg_regmask_src(pp); + po->regmask_dst |= get_pp_arg_regmask_dst(pp); + + if (po->regmask_dst & mxST0) + po->flags |= OPF_FPUSH; + if (strstr(pp->ret_type.name, "int64")) need_tmp64 = 1; + + continue; } - else if (po->op == OP_PUSH && !(po->flags & OPF_FARG) + + if (po->flags & OPF_DONE) + continue; + + if (po->op == OP_PUSH && !(po->flags & OPF_FARG) && !(po->flags & OPF_RSAVE) && po->operand[0].type == OPT_CONST) - scan_for_pop_const(i, opcnt, ®mask_pp); + { + scan_for_pop_const(i, opcnt, i + opcnt * 12); + } + else if (po->op == OP_POP) + scan_pushes_for_pop(i, opcnt, ®mask_pp); + else if (po->op == OP_STD) { + po->flags |= OPF_DF | OPF_RMD | OPF_DONE; + scan_propagate_df(i + 1, opcnt); + } } // pass6: // - find POPs for PUSHes, rm both - // - scan for STD/CLD, propagate DF // - scan for all used registers + memset(cbits, 0, sizeof(cbits)); + reg_use_pass(0, opcnt, cbits, 0, ®mask, + 0, ®mask_save, ®mask_init, regmask_arg); + + // pass7: // - find flag set ops for their users - // - do unreselved calls + // - do unresolved calls // - declare indirect functions for (i = 0; i < opcnt; i++) { @@ -4262,69 +5020,6 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) if (po->flags & (OPF_RMD|OPF_DONE)) continue; - if (po->op == OP_PUSH && (po->flags & OPF_RSAVE)) { - reg = po->operand[0].reg; - if (!(regmask & (1 << reg))) - // not a reg save after all, rerun scan_for_pop - po->flags &= ~OPF_RSAVE; - else - regmask_save |= 1 << reg; - } - - if (po->op == OP_PUSH && !(po->flags & OPF_FARG) - && !(po->flags & OPF_RSAVE) && !g_func_pp->is_userstack) - { - if (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 * 3, 0, &depth, 0); - if (ret == 1) { - if (depth > 1) - ferr(po, "too much depth: %d\n", depth); - - po->flags |= OPF_RMD; - scan_for_pop(i + 1, opcnt, po->operand[0].name, - i + opcnt * 4, 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; - } - } - } - - if (po->op == OP_STD) { - po->flags |= OPF_DF | OPF_RMD | OPF_DONE; - scan_propagate_df(i + 1, opcnt); - } - - regmask_now = po->regmask_src | po->regmask_dst; - if (regmask_now & (1 << xBP)) { - if (g_bp_frame && !(po->flags & OPF_EBP_S)) { - if (po->regmask_dst & (1 << xBP)) - // compiler decided to drop bp frame and use ebp as scratch - scan_fwd_set_flags(i + 1, opcnt, i + opcnt * 5, OPF_EBP_S); - else - regmask_now &= ~(1 << xBP); - } - } - - regmask |= regmask_now; - if (po->flags & OPF_CC) { int setters[16], cnt = 0, branched = 0; @@ -4395,8 +5090,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) else if (po->op == OP_CALL) { // note: resolved non-reg calls are OPF_DONE already pp = po->pp; - if (pp == NULL) - ferr(po, "NULL pp\n"); + ferr_assert(po, pp != NULL); if (pp->is_unresolved) { int regmask_stack = 0; @@ -4440,19 +5134,6 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) if (pp->argc_stack > 0) pp->is_stdcall = 1; } - - for (arg = 0; arg < pp->argc; arg++) { - if (pp->arg[arg].reg != NULL) { - reg = char_array_i(regs_r32, - ARRAY_SIZE(regs_r32), pp->arg[arg].reg); - if (reg < 0) - ferr(ops, "arg '%s' is not a reg?\n", pp->arg[arg].reg); - if (!(regmask & (1 << reg))) { - regmask_init |= 1 << reg; - regmask |= 1 << reg; - } - } - } } else if (po->op == OP_MOV && po->operand[0].pp != NULL && po->operand[1].pp != NULL) @@ -4473,40 +5154,33 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) } } } - else if (po->op == OP_RET && !IS(g_func_pp->ret_type.name, "void")) - regmask |= 1 << xAX; else if (po->op == OP_DIV || po->op == OP_IDIV) { - // 32bit division is common, look for it - if (po->op == OP_DIV) - ret = scan_for_reg_clear(i, xDX); - else - ret = scan_for_cdq_edx(i); - if (ret >= 0) - po->flags |= OPF_32BIT; + if (po->operand[0].lmod == OPLM_DWORD) { + // 32bit division is common, look for it + if (po->op == OP_DIV) + ret = scan_for_reg_clear(i, xDX); + else + ret = scan_for_cdq_edx(i); + if (ret >= 0) + po->flags |= OPF_32BIT; + else + need_tmp64 = 1; + } else - need_tmp64 = 1; + need_tmp_var = 1; } else if (po->op == OP_CLD) po->flags |= OPF_RMD | OPF_DONE; - - if (po->op == OP_RCL || po->op == OP_RCR || po->op == OP_XCHG) { - need_tmp_var = 1; + else if (po->op == OPP_FTOL) { + struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, xDX); + j = -1; + find_next_read(i + 1, opcnt, &opr, i + opcnt * 18, &j); + if (j == -1) + po->flags |= OPF_32BIT; } - } - - // pass7: - // - confirm regmask_save, it might have been reduced - if (regmask_save != 0) - { - regmask_save = 0; - for (i = 0; i < opcnt; i++) { - po = &ops[i]; - if (po->flags & OPF_RMD) - continue; - if (po->op == OP_PUSH && (po->flags & OPF_RSAVE)) - regmask_save |= 1 << po->operand[0].reg; - } + if (po->op == OP_RCL || po->op == OP_RCR || po->op == OP_XCHG) + need_tmp_var = 1; } // output starts here @@ -4592,8 +5266,14 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) // declare stack frame, va_arg if (g_stack_fsz) { - fprintf(fout, " union { u32 d[%d]; u16 w[%d]; u8 b[%d]; } sf;\n", - (g_stack_fsz + 3) / 4, (g_stack_fsz + 1) / 2, g_stack_fsz); + fprintf(fout, " union { u32 d[%d];", (g_stack_fsz + 3) / 4); + if (g_func_lmods & (1 << OPLM_WORD)) + fprintf(fout, " u16 w[%d];", (g_stack_fsz + 1) / 2); + if (g_func_lmods & (1 << OPLM_BYTE)) + fprintf(fout, " u8 b[%d];", g_stack_fsz); + if (g_func_lmods & (1 << OPLM_QWORD)) + fprintf(fout, " double q[%d];", (g_stack_fsz + 7) / 8); + fprintf(fout, " } sf;\n"); had_decl = 1; } @@ -4646,6 +5326,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) } } } + // ... mmx if (regmask_now & 0xff00) { for (reg = 8; reg < 16; reg++) { if (regmask_now & (1 << reg)) { @@ -4657,6 +5338,18 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) } } } + // ... x87 + if (regmask_now & 0xff0000) { + for (reg = 16; reg < 24; reg++) { + if (regmask_now & (1 << reg)) { + fprintf(fout, " double f_st%d", reg - 16); + if (regmask_init & (1 << reg)) + fprintf(fout, " = 0"); + fprintf(fout, ";\n"); + had_decl = 1; + } + } + } if (regmask_save) { for (reg = 0; reg < 8; reg++) { @@ -4798,10 +5491,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) pfomask = po->pfomask; if (po->flags & (OPF_REPZ|OPF_REPNZ)) { - struct parsed_opr opr = {0,}; - opr.type = OPT_REG; - opr.reg = xCX; - opr.lmod = OPLM_DWORD; + struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, xCX); ret = try_resolve_const(i, &opr, opcnt * 7 + i, &uval); if (ret != 1 || uval == 0) { @@ -4910,45 +5600,48 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) break; case OP_LODS: - assert_operand_cnt(3); if (po->flags & OPF_REP) { + assert_operand_cnt(3); // hmh.. ferr(po, "TODO\n"); } else { - fprintf(fout, " eax = %sesi; esi %c= %d;", - lmod_cast_u_ptr(po, po->operand[0].lmod), + assert_operand_cnt(2); + fprintf(fout, " %s = %sesi; esi %c= %d;", + out_dst_opr(buf1, sizeof(buf1), po, &po->operand[1]), + lmod_cast_u_ptr(po, po->operand[1].lmod), (po->flags & OPF_DF) ? '-' : '+', - lmod_bytes(po, po->operand[0].lmod)); + lmod_bytes(po, po->operand[1].lmod)); strcpy(g_comment, "lods"); } break; case OP_STOS: - assert_operand_cnt(3); if (po->flags & OPF_REP) { + assert_operand_cnt(3); fprintf(fout, " for (; ecx != 0; ecx--, edi %c= %d)\n", (po->flags & OPF_DF) ? '-' : '+', - lmod_bytes(po, po->operand[0].lmod)); + lmod_bytes(po, po->operand[1].lmod)); fprintf(fout, " %sedi = eax;", - lmod_cast_u_ptr(po, po->operand[0].lmod)); + lmod_cast_u_ptr(po, po->operand[1].lmod)); strcpy(g_comment, "rep stos"); } else { + assert_operand_cnt(2); fprintf(fout, " %sedi = eax; edi %c= %d;", - lmod_cast_u_ptr(po, po->operand[0].lmod), + lmod_cast_u_ptr(po, po->operand[1].lmod), (po->flags & OPF_DF) ? '-' : '+', - lmod_bytes(po, po->operand[0].lmod)); + lmod_bytes(po, po->operand[1].lmod)); strcpy(g_comment, "stos"); } break; case OP_MOVS: - assert_operand_cnt(3); j = lmod_bytes(po, po->operand[0].lmod); strcpy(buf1, lmod_cast_u_ptr(po, po->operand[0].lmod)); l = (po->flags & OPF_DF) ? '-' : '+'; if (po->flags & OPF_REP) { + assert_operand_cnt(3); fprintf(fout, " for (; ecx != 0; ecx--, edi %c= %d, esi %c= %d)\n", l, j, l, j); @@ -4957,6 +5650,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) strcpy(g_comment, "rep movs"); } else { + assert_operand_cnt(2); fprintf(fout, " %sedi = %sesi; edi %c= %d; esi %c= %d;", buf1, buf1, l, j, l, j); strcpy(g_comment, "movs"); @@ -4965,11 +5659,11 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) case OP_CMPS: // repe ~ repeat while ZF=1 - assert_operand_cnt(3); j = lmod_bytes(po, po->operand[0].lmod); strcpy(buf1, lmod_cast_u_ptr(po, po->operand[0].lmod)); l = (po->flags & OPF_DF) ? '-' : '+'; if (po->flags & OPF_REP) { + assert_operand_cnt(3); fprintf(fout, " for (; ecx != 0; ecx--) {\n"); if (pfomask & (1 << PFO_C)) { @@ -4990,6 +5684,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) (po->flags & OPF_REPZ) ? "e" : "ne"); } else { + assert_operand_cnt(2); fprintf(fout, " cond_z = (%sesi == %sedi); esi %c= %d; edi %c= %d;", buf1, buf1, l, j, l, j); @@ -5003,16 +5698,16 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) case OP_SCAS: // only does ZF (for now) // repe ~ repeat while ZF=1 - assert_operand_cnt(3); - j = lmod_bytes(po, po->operand[0].lmod); + j = lmod_bytes(po, po->operand[1].lmod); l = (po->flags & OPF_DF) ? '-' : '+'; if (po->flags & OPF_REP) { + assert_operand_cnt(3); fprintf(fout, " for (; ecx != 0; ecx--) {\n"); fprintf(fout, " cond_z = (%seax == %sedi); edi %c= %d;\n", - lmod_cast_u(po, po->operand[0].lmod), - lmod_cast_u_ptr(po, po->operand[0].lmod), l, j); + lmod_cast_u(po, po->operand[1].lmod), + lmod_cast_u_ptr(po, po->operand[1].lmod), l, j); fprintf(fout, " if (cond_z %s 0) break;\n", (po->flags & OPF_REPZ) ? "==" : "!="); @@ -5022,9 +5717,10 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) (po->flags & OPF_REPZ) ? "e" : "ne"); } else { + assert_operand_cnt(2); fprintf(fout, " cond_z = (%seax == %sedi); edi %c= %d;", - lmod_cast_u(po, po->operand[0].lmod), - lmod_cast_u_ptr(po, po->operand[0].lmod), l, j); + lmod_cast_u(po, po->operand[1].lmod), + lmod_cast_u_ptr(po, po->operand[1].lmod), l, j); strcpy(g_comment, "scas"); } pfomask &= ~(1 << PFO_Z); @@ -5034,22 +5730,20 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) // arithmetic w/flags case OP_AND: - if (po->operand[1].type == OPT_CONST && !po->operand[1].val) { - // deal with complex dst clear - assert_operand_cnt(2); - fprintf(fout, " %s = %s;", - out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]), - out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], - default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0)); - output_std_flags(fout, po, &pfomask, buf1); - last_arith_dst = &po->operand[0]; - delayed_flag_op = NULL; - break; - } - // fallthrough + if (po->operand[1].type == OPT_CONST && !po->operand[1].val) + goto dualop_arith_const; + propagate_lmod(po, &po->operand[0], &po->operand[1]); + goto dualop_arith; + case OP_OR: propagate_lmod(po, &po->operand[0], &po->operand[1]); - // fallthrough + if (po->operand[1].type == OPT_CONST) { + j = lmod_bytes(po, po->operand[0].lmod); + if (((1ull << j * 8) - 1) == po->operand[1].val) + goto dualop_arith_const; + } + goto dualop_arith; + dualop_arith: assert_operand_cnt(2); fprintf(fout, " %s %s= %s;", @@ -5061,6 +5755,18 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) delayed_flag_op = NULL; break; + dualop_arith_const: + // and 0, or ~0 used instead mov + assert_operand_cnt(2); + fprintf(fout, " %s = %s;", + out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]), + out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], + default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0)); + output_std_flags(fout, po, &pfomask, buf1); + last_arith_dst = &po->operand[0]; + delayed_flag_op = NULL; + break; + case OP_SHL: case OP_SHR: assert_operand_cnt(2); @@ -5111,11 +5817,14 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) assert_operand_cnt(3); propagate_lmod(po, &po->operand[0], &po->operand[1]); l = lmod_bytes(po, po->operand[0].lmod) * 8; - out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]); - out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]); out_src_opr_u32(buf3, sizeof(buf3), po, &po->operand[2]); - if (po->operand[2].type != OPT_CONST) - ferr(po, "TODO: masking\n"); + if (po->operand[2].type != OPT_CONST) { + // no handling for "undefined" case, hopefully not needed + snprintf(buf2, sizeof(buf2), "(%s & 0x1f)", buf3); + strcpy(buf3, buf2); + } + out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]); + out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]); if (po->op == OP_SHLD) { fprintf(fout, " %s <<= %s; %s |= %s >> (%d - %s);", buf1, buf3, buf1, buf2, l, buf3); @@ -5215,7 +5924,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) fprintf(fout, " cond_c = tmp64 >> 32;\n"); fprintf(fout, " %s = (u32)tmp64;", out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0])); - strcat(g_comment, "add64"); + strcat(g_comment, " add64"); } else { fprintf(fout, " cond_c = ((u32)%s + %s) >> %d;\n", @@ -5281,7 +5990,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) output_std_flags(fout, po, &pfomask, buf1); last_arith_dst = &po->operand[0]; delayed_flag_op = NULL; - strcat(g_comment, "bsf"); + strcat(g_comment, " bsf"); break; case OP_DEC: @@ -5367,34 +6076,51 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) case OP_DIV: case OP_IDIV: assert_operand_cnt(1); - if (po->operand[0].lmod != OPLM_DWORD) - ferr(po, "unhandled lmod %d\n", po->operand[0].lmod); - out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]); - strcpy(buf2, lmod_cast(po, po->operand[0].lmod, + strcpy(cast, lmod_cast(po, po->operand[0].lmod, po->op == OP_IDIV)); switch (po->operand[0].lmod) { case OPLM_DWORD: if (po->flags & OPF_32BIT) - snprintf(buf3, sizeof(buf3), "%seax", buf2); + snprintf(buf2, sizeof(buf2), "%seax", cast); else { fprintf(fout, " tmp64 = ((u64)edx << 32) | eax;\n"); - snprintf(buf3, sizeof(buf3), "%stmp64", + snprintf(buf2, sizeof(buf2), "%stmp64", (po->op == OP_IDIV) ? "(s64)" : ""); } if (po->operand[0].type == OPT_REG && po->operand[0].reg == xDX) { - fprintf(fout, " eax = %s / %s%s;", buf3, buf2, buf1); - fprintf(fout, " edx = %s %% %s%s;\n", buf3, buf2, buf1); + fprintf(fout, " eax = %s / %s%s;\n", buf2, cast, buf1); + fprintf(fout, " edx = %s %% %s%s;", buf2, cast, buf1); + } + else { + fprintf(fout, " edx = %s %% %s%s;\n", buf2, cast, buf1); + fprintf(fout, " eax = %s / %s%s;", buf2, cast, buf1); + } + break; + case OPLM_WORD: + fprintf(fout, " tmp = (edx << 16) | (eax & 0xffff);\n"); + snprintf(buf2, sizeof(buf2), "%stmp", + (po->op == OP_IDIV) ? "(s32)" : ""); + if (po->operand[0].type == OPT_REG + && po->operand[0].reg == xDX) + { + fprintf(fout, " LOWORD(eax) = %s / %s%s;\n", + buf2, cast, buf1); + fprintf(fout, " LOWORD(edx) = %s %% %s%s;", + buf2, cast, buf1); } else { - fprintf(fout, " edx = %s %% %s%s;\n", buf3, buf2, buf1); - fprintf(fout, " eax = %s / %s%s;", buf3, buf2, buf1); + fprintf(fout, " LOWORD(edx) = %s %% %s%s;\n", + buf2, cast, buf1); + fprintf(fout, " LOWORD(eax) = %s / %s%s;", + buf2, cast, buf1); } + strcat(g_comment, " div16"); break; default: - ferr(po, "unhandled division type\n"); + ferr(po, "unhandled div lmod %d\n", po->operand[0].lmod); } last_arith_dst = NULL; delayed_flag_op = NULL; @@ -5431,13 +6157,13 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) case OP_JECXZ: fprintf(fout, " if (ecx == 0)\n"); fprintf(fout, " goto %s;", po->operand[0].name); - strcat(g_comment, "jecxz"); + strcat(g_comment, " jecxz"); break; case OP_LOOP: - fprintf(fout, " if (--ecx == 0)\n"); + fprintf(fout, " if (--ecx != 0)\n"); fprintf(fout, " goto %s;", po->operand[0].name); - strcat(g_comment, "loop"); + strcat(g_comment, " loop"); break; case OP_JMP: @@ -5490,17 +6216,22 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) } else if (!IS(pp->ret_type.name, "void")) { if (po->flags & OPF_TAIL) { - if (!IS(g_func_pp->ret_type.name, "void")) { + if (regmask_ret & mxAX) { fprintf(fout, "return "); if (g_func_pp->ret_type.is_ptr != pp->ret_type.is_ptr) fprintf(fout, "(%s)", g_func_pp->ret_type.name); } + else if (regmask_ret & mxST0) + ferr(po, "float tailcall\n"); } - else if (regmask & (1 << xAX)) { + else if (po->regmask_dst & mxAX) { fprintf(fout, "eax = "); if (pp->ret_type.is_ptr) fprintf(fout, "(u32)"); } + else if (po->regmask_dst & mxST0) { + fprintf(fout, "f_st0 = "); + } } if (pp->name[0] == 0) @@ -5598,21 +6329,22 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) ret = 0; else if (IS(pp->ret_type.name, "void")) ret = 1; - else if (IS(g_func_pp->ret_type.name, "void")) + else if (!(regmask_ret & (1 << xAX))) ret = 1; // else already handled as 'return f()' if (ret) { - if (!IS(g_func_pp->ret_type.name, "void")) { - ferr(po, "int func -> void func tailcall?\n"); - } - else { - fprintf(fout, "\n%sreturn;", buf3); - strcat(g_comment, " ^ tailcall"); - } + fprintf(fout, "\n%sreturn;", buf3); + strcat(g_comment, " ^ tailcall"); } else strcat(g_comment, " tailcall"); + + if ((regmask_ret & (1 << xAX)) + && IS(pp->ret_type.name, "void") && !pp->is_noreturn) + { + ferr(po, "int func -> void func tailcall?\n"); + } } if (pp->is_noreturn) strcat(g_comment, " noreturn"); @@ -5638,7 +6370,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) g_func_pp->arg[arg].reg, g_func_pp->arg[arg].reg); } - if (IS(g_func_pp->ret_type.name, "void")) { + if (!(regmask_ret & (1 << xAX))) { if (i != opcnt - 1 || label_pending) fprintf(fout, " return;"); } @@ -5691,7 +6423,7 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) break; } else if (po->flags & OPF_PPUSH) { - // push/pop graph + // push/pop graph / non-const ferr_assert(po, po->datap == NULL); fprintf(fout, " %s = pp_%s;", buf1, buf1); break; @@ -5717,9 +6449,123 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt) no_output = 1; break; + // x87 + case OP_FLD: + if (po->flags & OPF_FSHIFT) + fprintf(fout, " f_st1 = f_st0;\n"); + if (po->operand[0].type == OPT_REG + && po->operand[0].reg == xST0) + { + strcat(g_comment, " fld st"); + break; + } + fprintf(fout, " f_st0 = %s;", + out_src_opr_float(buf1, sizeof(buf1), po, &po->operand[0])); + strcat(g_comment, " fld"); + break; + + case OP_FILD: + if (po->flags & OPF_FSHIFT) + fprintf(fout, " f_st1 = f_st0;\n"); + fprintf(fout, " f_st0 = (double)%s;", + out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], + lmod_cast(po, po->operand[0].lmod, 1), 0)); + strcat(g_comment, " fild"); + break; + + case OP_FLDc: + if (po->flags & OPF_FSHIFT) + fprintf(fout, " f_st1 = f_st0;\n"); + fprintf(fout, " f_st0 = "); + switch (po->operand[0].val) { + case X87_CONST_1: fprintf(fout, "1.0;"); break; + case X87_CONST_Z: fprintf(fout, "0.0;"); break; + default: ferr(po, "TODO\n"); break; + } + break; + + case OP_FST: + if ((po->flags & OPF_FPOP) && po->operand[0].type == OPT_REG + && po->operand[0].reg == xST0) + { + no_output = 1; + break; + } + fprintf(fout, " %s = f_st0;", + out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0])); + if (po->flags & OPF_FSHIFT) + fprintf(fout, "\n f_st0 = f_st1;"); + strcat(g_comment, " fst"); + break; + + case OP_FADD: + case OP_FDIV: + case OP_FMUL: + case OP_FSUB: + switch (po->op) { + case OP_FADD: j = '+'; break; + case OP_FDIV: j = '/'; break; + case OP_FMUL: j = '*'; break; + case OP_FSUB: j = '-'; break; + default: j = 'x'; break; + } + if (po->flags & OPF_FSHIFT) { + fprintf(fout, " f_st0 = f_st1 %c f_st0;", j); + } + else { + fprintf(fout, " %s %c= %s;", + out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0]), + j, + out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1])); + } + break; + + case OP_FDIVR: + case OP_FSUBR: + if (po->flags & OPF_FSHIFT) + snprintf(buf1, sizeof(buf1), "f_st0"); + else + out_dst_opr_float(buf1, sizeof(buf1), po, &po->operand[0]); + fprintf(fout, " %s = %s %c %s;", buf1, + out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1]), + po->op == OP_FDIVR ? '/' : '-', + out_src_opr_float(buf3, sizeof(buf3), po, &po->operand[0])); + break; + + case OP_FIADD: + case OP_FIDIV: + case OP_FIMUL: + case OP_FISUB: + switch (po->op) { + case OP_FIADD: j = '+'; break; + case OP_FIDIV: j = '/'; break; + case OP_FIMUL: j = '*'; break; + case OP_FISUB: j = '-'; break; + default: j = 'x'; break; + } + fprintf(fout, " f_st0 %c= (double)%s;", j, + out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], + lmod_cast(po, po->operand[0].lmod, 1), 0)); + break; + + case OP_FIDIVR: + case OP_FISUBR: + fprintf(fout, " f_st0 = %s %c f_st0;", + out_src_opr_float(buf2, sizeof(buf2), po, &po->operand[1]), + po->op == OP_FIDIVR ? '/' : '-'); + break; + + case OPP_FTOL: + ferr_assert(po, po->flags & OPF_32BIT); + fprintf(fout, " eax = (s32)f_st0;"); + if (po->flags & OPF_FSHIFT) + fprintf(fout, "\n f_st0 = f_st1;"); + strcat(g_comment, " ftol"); + break; + // mmx case OP_EMMS: - strcpy(g_comment, "(emms)"); + strcpy(g_comment, " (emms)"); break; default: @@ -5912,7 +6758,6 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits, struct func_proto_dep *dep; struct parsed_op *po; int from_caller = 0; - int depth; int j, l; int reg; int ret; @@ -5955,8 +6800,7 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits, else if (po->op == OP_PUSH && po->operand[0].type == OPT_REG) { reg = po->operand[0].reg; - if (reg < 0) - ferr(po, "reg not set for push?\n"); + ferr_assert(po, reg >= 0); if (po->flags & OPF_RSAVE) { regmask_save |= 1 << reg; @@ -5965,14 +6809,11 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits, if (po->flags & OPF_DONE) continue; - depth = 0; - ret = scan_for_pop(i + 1, opcnt, - po->operand[0].name, i + opcnt * 2, 0, &depth, 0); + ret = scan_for_pop(i + 1, opcnt, i + opcnt * 2, reg, 0, 0); if (ret == 1) { regmask_save |= 1 << reg; po->flags |= OPF_RMD; - scan_for_pop(i + 1, opcnt, - po->operand[0].name, i + opcnt * 3, 0, &depth, 1); + scan_for_pop(i + 1, opcnt, i + opcnt * 3, reg, 0, OPF_RMD); continue; } } @@ -6003,9 +6844,7 @@ static void gen_hdr_dep_pass(int i, int opcnt, unsigned char *cbits, ret = 1; } else { - struct parsed_opr opr = { 0, }; - opr.type = OPT_REG; - opr.reg = xAX; + struct parsed_opr opr = OPR_INIT(OPT_REG, OPLM_DWORD, xAX); j = -1; from_caller = 0; ret = resolve_origin(i, &opr, i + opcnt * 4, &j, &from_caller); @@ -6117,7 +6956,7 @@ static void gen_hdr(const char *funcn, int opcnt) continue; if (po->op == OP_PUSH && po->operand[0].type == OPT_CONST) - scan_for_pop_const(i, opcnt, ®mask_dummy); + scan_for_pop_const(i, opcnt, i + opcnt * 13); } // pass5: @@ -6145,7 +6984,7 @@ static void gen_hdr(const char *funcn, int opcnt) patch_esp_adjust(&ops[j], pp->argc_stack * 4); else { for (l = 0; l < pp->argc_stack; l++) - ops[j + l].flags |= OPF_DONE | OPF_RMD; + ops[j + l].flags |= OPF_DONE | OPF_RMD | OPF_NOREGS; } } @@ -6163,16 +7002,17 @@ static void gen_hdr(const char *funcn, int opcnt) if (po->flags & (OPF_RMD|OPF_DONE)) continue; - if (po->op == OP_PUSH && po->operand[0].type == OPT_REG) + if (po->op == OP_PUSH && po->operand[0].type == OPT_REG + && po->operand[0].reg != xCX) { - ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, 0); - if (ret == 0) { + ret = scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, 0); + if (ret == 1) { // regmask_save |= 1 << po->operand[0].reg; // do it later po->flags |= OPF_RSAVE | OPF_RMD | OPF_DONE; - scan_for_pop_ret(i + 1, opcnt, po->operand[0].name, OPF_RMD); + scan_for_pop_ret(i + 1, opcnt, po->operand[0].reg, OPF_RMD); } } - else if (po->op == OP_CALL && !(po->flags & OPF_DONE)) + else if (po->op == OP_CALL) { pp = process_call(i, opcnt); @@ -6390,7 +7230,7 @@ static void output_hdr(FILE *fout) fp->pp = &pp_cache[i]; fp->argc_stack = fp->pp->argc_stack; fp->is_stdcall = fp->pp->is_stdcall; - fp->regmask_dep = get_pp_arg_regmask(fp->pp); + fp->regmask_dep = get_pp_arg_regmask_src(fp->pp); fp->has_ret = !IS(fp->pp->ret_type.name, "void"); } @@ -6934,8 +7774,8 @@ int main(int argc, char *argv[]) unsigned long addr = strtoul(p, NULL, 16); unsigned long f_addr = strtoul(g_func + 4, NULL, 16); if (addr > f_addr && !scanned_ahead) { - anote("scan_ahead caused by '%s', addr %lx\n", - g_func, addr); + //anote("scan_ahead caused by '%s', addr %lx\n", + // g_func, addr); scan_ahead(fasm); scanned_ahead = 1; func_chunks_sorted = 0; @@ -7080,6 +7920,7 @@ do_pending_endp: pd->d = NULL; } g_func_pd_cnt = 0; + g_func_lmods = 0; pd = NULL; if (end)