+static int resolve_origin(int i, const struct parsed_opr *opr,
+ int magic, int *op_i, int *is_caller);
+static void set_label(int i, const char *name);
+
+static void eliminate_seh_writes(int opcnt)
+{
+ const struct parsed_opr *opr;
+ char ofs_reg[16];
+ int offset;
+ int i;
+
+ // assume all sf writes above g_seh_size to be seh related
+ // (probably unsafe but oh well)
+ for (i = 0; i < opcnt; i++) {
+ if (ops[i].op != OP_MOV)
+ continue;
+ opr = &ops[i].operand[0];
+ if (opr->type != OPT_REGMEM)
+ continue;
+ if (!is_stack_access(&ops[i], opr))
+ continue;
+
+ offset = 0;
+ parse_stack_access(&ops[i], opr->name, ofs_reg, &offset,
+ NULL, NULL, 0);
+ if (offset < 0 && offset >= -g_seh_size)
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ }
+}
+
+static void eliminate_seh_finally(int opcnt)
+{
+ const char *target_name = NULL;
+ const char *return_name = NULL;
+ int exits[MAX_EXITS];
+ int exit_count = 0;
+ int call_i = -1;
+ int target_i = -1;
+ int return_i = -1;
+ int tgend_i = -1;
+ int i;
+
+ for (i = 0; i < opcnt; i++) {
+ if (ops[i].op != OP_CALL)
+ continue;
+ if (!IS_START(opr_name(&ops[i], 0), "loc_"))
+ continue;
+ if (target_name != NULL)
+ ferr(&ops[i], "multiple finally calls? (last was %s)\n",
+ target_name);
+ target_name = opr_name(&ops[i], 0);
+ call_i = i;
+
+ if (g_labels[i + 1] == NULL)
+ set_label(i + 1, "seh_fin_done");
+ return_name = g_labels[i + 1];
+ return_i = i + 1;
+ }
+
+ if (call_i == -1)
+ // no finally block
+ return;
+
+ // find finally code (bt_i is not set because it's call)
+ for (i = 0; i < opcnt; i++) {
+ if (g_labels[i] == NULL)
+ continue;
+ if (!IS(g_labels[i], target_name))
+ continue;
+
+ ferr_assert(&ops[i], target_i == -1);
+ target_i = i;
+ }
+ ferr_assert(&ops[0], target_i != -1);
+
+ find_reachable_exits(target_i, opcnt, target_i + opcnt * 24,
+ exits, &exit_count);
+ ferr_assert(&ops[target_i], exit_count == 1);
+ ferr_assert(&ops[target_i], ops[exits[0]].op == OP_RET);
+ tgend_i = exits[0];
+
+ // convert to jumps, link
+ ops[call_i].op = OP_JMP;
+ ops[call_i].bt_i = target_i;
+ add_label_ref(&g_label_refs[target_i], call_i);
+
+ ops[tgend_i].op = OP_JMP;
+ ops[tgend_i].flags &= ~OPF_TAIL;
+ ops[tgend_i].flags |= OPF_JMP;
+ ops[tgend_i].bt_i = return_i;
+ ops[tgend_i].operand_cnt = 1;
+ ops[tgend_i].operand[0].type = OPT_LABEL;
+ snprintf(ops[tgend_i].operand[0].name, NAMELEN, "%s", return_name);
+ add_label_ref(&g_label_refs[return_i], tgend_i);
+
+ // rm seh finally entry code
+ for (i = target_i - 1; i >= 0; i--) {
+ if (g_labels[i] != NULL && g_label_refs[i].i != -1)
+ return;
+ if (ops[i].flags & OPF_CJMP)
+ return;
+ if (ops[i].flags & (OPF_JMP | OPF_TAIL))
+ break;
+ }
+ for (i = target_i - 1; i >= 0; i--) {
+ if (ops[i].flags & (OPF_JMP | OPF_TAIL))
+ break;
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ }
+}
+
+static void eliminate_seh(int opcnt)
+{
+ int i, j, k, ret;
+
+ for (i = 0; i < opcnt; i++) {
+ if (ops[i].op != OP_MOV)
+ continue;
+ if (ops[i].operand[0].segment != SEG_FS)
+ continue;
+ if (!IS(opr_name(&ops[i], 0), "0"))
+ continue;
+
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ if (ops[i].operand[1].reg == xSP) {
+ for (j = i - 1; j >= 0; j--) {
+ if (ops[j].op != OP_PUSH)
+ continue;
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ g_seh_size += 4;
+ if (ops[j].operand[0].val == ~0)
+ break;
+ if (ops[j].operand[0].type == OPT_REG) {
+ k = -1;
+ ret = resolve_origin(j, &ops[j].operand[0],
+ j + opcnt * 22, &k, NULL);
+ if (ret == 1)
+ ops[k].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ }
+ }
+ if (j < 0)
+ ferr(ops, "missing seh terminator\n");
+ }
+ else {
+ k = -1;
+ ret = resolve_origin(i, &ops[i].operand[1],
+ i + opcnt * 23, &k, NULL);
+ if (ret == 1)
+ ops[k].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ }
+ }
+
+ eliminate_seh_writes(opcnt);
+ eliminate_seh_finally(opcnt);
+}
+
+static void eliminate_seh_calls(int opcnt)
+{
+ int epilog_found = 0;
+ int i;
+
+ g_bp_frame = 1;
+ g_seh_size = 0x10;
+
+ i = 0;
+ ferr_assert(&ops[i], ops[i].op == OP_PUSH
+ && ops[i].operand[0].type == OPT_CONST);
+ g_stack_fsz = g_seh_size + ops[i].operand[0].val;
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+
+ i++;
+ ferr_assert(&ops[i], ops[i].op == OP_PUSH
+ && ops[i].operand[0].type == OPT_OFFSET);
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+
+ i++;
+ ferr_assert(&ops[i], ops[i].op == OP_CALL
+ && IS(opr_name(&ops[i], 0), "__SEH_prolog"));
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+
+ for (i++; i < opcnt; i++) {
+ if (ops[i].op != OP_CALL)
+ continue;
+ if (!IS(opr_name(&ops[i], 0), "__SEH_epilog"))
+ continue;
+
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ epilog_found = 1;
+ }
+ ferr_assert(ops, epilog_found);
+
+ eliminate_seh_writes(opcnt);
+ eliminate_seh_finally(opcnt);
+}
+
+// check for prologue of many pushes and epilogue with pops
+static void check_simple_sequence(int opcnt, int *fsz)
+{
+ int found = 0;
+ int seq_len;
+ int seq_p;
+ int seq[4];
+ int reg;
+ int i, j;
+
+ for (i = 0; i < opcnt && i < ARRAY_SIZE(seq); i++) {
+ if (ops[i].op != OP_PUSH || ops[i].operand[0].type != OPT_REG)
+ break;
+ reg = ops[i].operand[0].reg;
+ if (reg != xBX && reg != xSI && reg != xDI && reg != xBP)
+ break;
+ for (j = 0; j < i; j++)
+ if (seq[j] == reg)
+ break;
+ if (j != i)
+ // probably something else is going on here
+ break;
+ seq[i] = reg;
+ }
+ seq_len = i;
+ if (seq_len == 0)
+ return;
+
+ for (; i < opcnt && seq_len > 0; i++) {
+ if (!(ops[i].flags & OPF_TAIL))
+ continue;
+
+ for (j = i - 1, seq_p = 0; j >= 0 && seq_p < seq_len; j--) {
+ if (ops[j].op != OP_POP || ops[j].operand[0].type != OPT_REG)
+ break;
+ if (ops[j].operand[0].reg != seq[seq_p])
+ break;
+ seq_p++;
+ }
+ found = seq_len = seq_p;
+ }
+ if (!found)
+ return;
+
+ for (i = 0; i < seq_len; i++)
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+
+ for (; i < opcnt && seq_len > 0; i++) {
+ if (!(ops[i].flags & OPF_TAIL))
+ continue;
+
+ for (j = i - 1, seq_p = 0; j >= 0 && seq_p < seq_len; j--) {
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ seq_p++;
+ }
+ }
+
+ // unlike pushes after sub esp,
+ // IDA treats pushes like this as part of var area
+ *fsz += seq_len * 4;
+}
+
+static int scan_prologue(int i, int opcnt, int *ecx_push, int *esp_sub)
+{
+ const char *name;
+ int j, len, ret;
+
+ for (; i < opcnt; i++)
+ if (!(ops[i].flags & OPF_DONE))
+ break;
+
+ while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ g_stack_fsz += 4;
+ (*ecx_push)++;
+ i++;
+ }
+
+ for (; i < opcnt; i++) {
+ if (i > 0 && g_labels[i] != NULL)
+ break;
+ if (ops[i].flags & (OPF_JMP|OPF_TAIL))
+ break;
+ if (ops[i].flags & OPF_DONE)
+ continue;
+ if (ops[i].op == OP_PUSH)
+ break;
+ if (ops[i].op == OP_SUB && ops[i].operand[0].reg == xSP
+ && ops[i].operand[1].type == OPT_CONST)
+ {
+ g_stack_fsz += opr_const(&ops[i], 1);
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ i++;
+ *esp_sub = 1;
+ break;
+ }
+ if (ops[i].op == OP_LEA && ops[i].operand[0].reg == xSP
+ && ops[i].operand[1].type == OPT_REGMEM
+ && IS_START(ops[i].operand[1].name, "esp-"))
+ {
+ name = ops[i].operand[1].name;
+ ret = sscanf(name, "esp-%x%n", &j, &len);
+ ferr_assert(&ops[i], ret == 1 && len == strlen(name));
+ g_stack_fsz += j;
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ i++;
+ *esp_sub = 1;
+ break;
+ }
+ if (ops[i].op == OP_MOV && ops[i].operand[0].reg == xAX
+ && ops[i].operand[1].type == OPT_CONST)
+ {
+ for (j = i + 1; j < opcnt; j++)
+ if (!(ops[j].flags & OPF_DONE))
+ break;
+ if (ops[j].op == OP_CALL
+ && IS(opr_name(&ops[j], 0), "__alloca_probe"))
+ {
+ g_stack_fsz += opr_const(&ops[i], 1);
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ i = j + 1;
+ *esp_sub = 1;
+ break;
+ }
+ }
+ }
+
+ return i;
+}
+
+static void scan_prologue_epilogue(int opcnt, int *stack_align)
+{
+ int ecx_push = 0, esp_sub = 0, pusha = 0;
+ int sandard_epilogue;
+ int found, ret, len;
+ int push_fsz = 0;
+ int i, j, l;
+
+ if (g_seh_found == 2) {
+ eliminate_seh_calls(opcnt);
+ return;
+ }
+ if (g_seh_found) {
+ eliminate_seh(opcnt);
+ // ida treats seh as part of sf
+ g_stack_fsz = g_seh_size;
+ esp_sub = 1;
+ }
+
+ if (ops[0].op == OP_PUSH && IS(opr_name(&ops[0], 0), "ebp")
+ && ops[1].op == OP_MOV
+ && IS(opr_name(&ops[1], 0), "ebp")
+ && IS(opr_name(&ops[1], 1), "esp"))
+ {
+ g_bp_frame = 1;
+ ops[0].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ ops[1].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+
+ for (i = 2; i < opcnt; i++)
+ if (!(ops[i].flags & OPF_DONE))
+ break;
+
+ if (ops[i].op == OP_PUSHA) {
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ pusha = 1;
+ i++;
+ }
+
+ if (ops[i].op == OP_AND && ops[i].operand[0].reg == xSP
+ && ops[i].operand[1].type == OPT_CONST)
+ {
+ l = ops[i].operand[1].val;
+ j = ffs(l) - 1;
+ if (j == -1 || (l >> j) != -1)
+ ferr(&ops[i], "unhandled esp align: %x\n", l);
+ if (stack_align != NULL)
+ *stack_align = 1 << j;
+ ops[i].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ i++;
+ }
+
+ i = scan_prologue(i, opcnt, &ecx_push, &esp_sub);
+
+ found = 0;
+ do {
+ for (; i < opcnt; i++)
+ if (ops[i].flags & OPF_TAIL)
+ break;
+ j = i - 1;
+ if (i == opcnt && (ops[j].flags & OPF_JMP)) {
+ if (ops[j].bt_i != -1 || ops[j].btj != NULL)
+ break;
+ i--;
+ j--;
+ }
+
+ sandard_epilogue = 0;
+ if (ops[j].op == OP_POP && IS(opr_name(&ops[j], 0), "ebp"))
+ {
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ // the standard epilogue is sometimes even used without a sf
+ if (ops[j - 1].op == OP_MOV
+ && IS(opr_name(&ops[j - 1], 0), "esp")
+ && IS(opr_name(&ops[j - 1], 1), "ebp"))
+ sandard_epilogue = 1;
+ }
+ else if (ops[j].op == OP_LEAVE)
+ {
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ sandard_epilogue = 1;
+ }
+ else if (ops[i].op == OP_CALL && ops[i].pp != NULL
+ && ops[i].pp->is_noreturn)
+ {
+ // on noreturn, msvc sometimes cleans stack, sometimes not
+ i++;
+ found = 1;
+ continue;
+ }
+ else if (!(g_ida_func_attr & IDAFA_NORETURN))
+ ferr(&ops[j], "'pop ebp' expected\n");
+
+ if (g_stack_fsz != 0 || sandard_epilogue) {
+ if (ops[j].op == OP_LEAVE)
+ j--;
+ else if (sandard_epilogue) // mov esp, ebp
+ {
+ ops[j - 1].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ j -= 2;
+ }
+ else if (!(g_ida_func_attr & IDAFA_NORETURN))
+ {
+ ferr(&ops[j], "esp restore expected\n");
+ }
+
+ if (ecx_push && j >= 0 && ops[j].op == OP_POP
+ && IS(opr_name(&ops[j], 0), "ecx"))
+ {
+ ferr(&ops[j], "unexpected ecx pop\n");
+ }
+ }
+
+ if (pusha) {
+ if (ops[j].op == OP_POPA)
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ else
+ ferr(&ops[j], "popa expected\n");
+ }
+
+ found = 1;
+ i++;
+ } while (i < opcnt);
+
+ if (!found)
+ ferr(ops, "missing ebp epilogue\n");
+ return;
+ }
+
+ // non-bp frame
+ check_simple_sequence(opcnt, &push_fsz);
+ i = scan_prologue(0, opcnt, &ecx_push, &esp_sub);
+
+ if (ecx_push && !esp_sub) {
+ // could actually be args for a call..
+ for (; i < opcnt; i++)
+ if (ops[i].op != OP_PUSH)
+ break;
+
+ if (ops[i].op == OP_CALL && ops[i].operand[0].type == OPT_LABEL) {
+ const struct parsed_proto *pp;
+ pp = proto_parse(g_fhdr, opr_name(&ops[i], 0), 1);
+ j = pp ? pp->argc_stack : 0;
+ while (i > 0 && j > 0) {
+ i--;
+ if (ops[i].op == OP_PUSH) {
+ ops[i].flags &= ~(OPF_RMD | OPF_DONE | OPF_NOREGS);
+ j--;
+ }
+ }
+ if (j != 0)
+ ferr(&ops[i], "unhandled prologue\n");
+
+ // recheck
+ i = ecx_push = 0;
+ g_stack_fsz = g_seh_size;
+ while (ops[i].op == OP_PUSH && IS(opr_name(&ops[i], 0), "ecx")) {
+ if (!(ops[i].flags & OPF_RMD))
+ break;
+ g_stack_fsz += 4;
+ ecx_push++;
+ i++;
+ }
+ }
+ }
+
+ found = 0;
+ if (ecx_push || esp_sub)
+ {
+ g_sp_frame = 1;
+
+ do {
+ for (; i < opcnt; i++)
+ if (ops[i].flags & OPF_TAIL)
+ break;
+
+ j = i - 1;
+ if (i == opcnt && (ops[j].flags & OPF_JMP)) {
+ if (ops[j].bt_i != -1 || ops[j].btj != NULL)
+ break;
+ i--;
+ j--;
+ }
+ else if (i < opcnt && (ops[i].flags & OPF_ATAIL)) {
+ // skip arg updates for arg-reuse tailcall
+ for (; j >= 0; j--) {
+ if (ops[j].op != OP_MOV)
+ break;
+ if (ops[j].operand[0].type == OPT_REGMEM
+ && strstr(ops[j].operand[0].name, "arg_") != NULL)
+ continue;
+ if (ops[j].operand[0].type == OPT_REG)
+ continue; // assume arg-reg mov
+ break;
+ }
+ }
+
+ for (; j >= 0; j--) {
+ if ((ops[j].flags & (OPF_RMD | OPF_DONE | OPF_NOREGS)) !=
+ (OPF_RMD | OPF_DONE | OPF_NOREGS))
+ break;
+ }
+
+ if (ecx_push > 0 && !esp_sub) {
+ for (l = 0; l < ecx_push && j >= 0; l++) {
+ if (ops[j].op == OP_POP && IS(opr_name(&ops[j], 0), "ecx"))
+ /* pop ecx */;
+ else if (ops[j].op == OP_ADD
+ && IS(opr_name(&ops[j], 0), "esp")
+ && ops[j].operand[1].type == OPT_CONST)
+ {
+ /* add esp, N */
+ l += ops[j].operand[1].val / 4 - 1;
+ }
+ else
+ break;
+
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ j--;
+ }
+ if (l != ecx_push) {
+ if (i < opcnt && ops[i].op == OP_CALL
+ && ops[i].pp != NULL && ops[i].pp->is_noreturn)
+ {
+ // noreturn tailcall with no epilogue
+ i++;
+ found = 1;
+ continue;
+ }
+ ferr(&ops[j], "epilogue scan failed\n");
+ }
+
+ found = 1;
+ }
+
+ if (esp_sub) {
+ if (ops[j].op == OP_ADD
+ && IS(opr_name(&ops[j], 0), "esp")
+ && ops[j].operand[1].type == OPT_CONST)
+ {
+ if (ops[j].operand[1].val < g_stack_fsz)
+ ferr(&ops[j], "esp adj is too low (need %d)\n", g_stack_fsz);
+
+ ops[j].operand[1].val -= g_stack_fsz; // for stack arg scanner
+ if (ops[j].operand[1].val == 0)
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ found = 1;
+ }
+ else if (ops[j].op == OP_LEA && ops[j].operand[0].reg == xSP
+ && ops[j].operand[1].type == OPT_REGMEM
+ && IS_START(ops[j].operand[1].name, "esp+"))
+ {
+ const char *name = ops[j].operand[1].name;
+ ret = sscanf(name, "esp+%x%n", &l, &len);
+ ferr_assert(&ops[j], ret == 1 && len == strlen(name));
+ ferr_assert(&ops[j], l <= g_stack_fsz);
+ ops[j].flags |= OPF_RMD | OPF_DONE | OPF_NOREGS;
+ found = 1;
+ }
+ else if (i < opcnt && ops[i].op == OP_CALL
+ && ops[i].pp != NULL && ops[i].pp->is_noreturn)
+ {
+ // noreturn tailcall with no epilogue
+ found = 1;
+ }
+ else
+ ferr(&ops[j], "'add esp' expected\n");
+ }
+
+ i++;
+ } while (i < opcnt);
+
+ if (!found)
+ ferr(ops, "missing esp epilogue\n");
+ }
+
+ if (g_stack_fsz != 0)
+ // see check_simple_sequence
+ g_stack_fsz += push_fsz;