some func ptr arg type checking
[ia32rtools.git] / tools / translate.c
index e3d8f5b..c697f32 100644 (file)
@@ -1,3 +1,11 @@
+/*
+ * ia32rtools
+ * (C) notaz, 2013,2014
+ *
+ * This work is licensed under the terms of 3-clause BSD license.
+ * See COPYING file in the top-level directory.
+ */
+
 #define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
@@ -46,6 +54,7 @@ enum op_flags {
   OPF_ATAIL  = (1 << 14), /* tail call with reused arg frame */
   OPF_32BIT  = (1 << 15), /* 32bit division */
   OPF_LOCK   = (1 << 16), /* op has lock prefix */
+  OPF_VAPUSH = (1 << 17), /* vararg ptr push (as call arg) */
 };
 
 enum op_op {
@@ -128,9 +137,10 @@ struct parsed_opr {
   unsigned int size_mismatch:1; // type override differs from C
   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[256];
+  char name[112];
 };
 
 struct parsed_op {
@@ -148,8 +158,9 @@ struct parsed_op {
   int cc_scratch;         // scratch storage during analysis
   int bt_i;               // branch target for branches
   struct parsed_data *btj;// branch targets for jumptables
-  struct parsed_proto *pp;
+  struct parsed_proto *pp;// parsed_proto for OP_CALL
   void *datap;
+  int asmln;
 };
 
 // datap:
@@ -211,13 +222,13 @@ static int g_stack_fsz;
 static int g_ida_func_attr;
 static int g_allow_regfunc;
 #define ferr(op_, fmt, ...) do { \
-  printf("error:%s:#%zd: '%s': " fmt, g_func, (op_) - ops, \
+  printf("%s:%d: error: [%s] '%s': " fmt, asmfn, (op_)->asmln, g_func, \
     dump_op(op_), ##__VA_ARGS__); \
   fcloseall(); \
   exit(1); \
 } while (0)
 #define fnote(op_, fmt, ...) \
-  printf("error:%s:#%zd: '%s': " fmt, g_func, (op_) - ops, \
+  printf("%s:%d: note: [%s] '%s': " fmt, asmfn, (op_)->asmln, g_func, \
     dump_op(op_), ##__VA_ARGS__)
 
 #define MAX_REGS 8
@@ -462,16 +473,18 @@ static int guess_lmod_from_c_type(enum opr_lenmod *lmod,
   static const char *dword_types[] = {
     "int", "_DWORD", "UINT_PTR", "DWORD",
     "WPARAM", "LPARAM", "UINT", "__int32",
-    "LONG", "HIMC",
+    "LONG", "HIMC", "BOOL",
   };
   static const char *word_types[] = {
-    "uint16_t", "int16_t", "_WORD",
+    "uint16_t", "int16_t", "_WORD", "WORD",
     "unsigned __int16", "__int16",
   };
   static const char *byte_types[] = {
     "uint8_t", "int8_t", "char",
     "unsigned __int8", "__int8", "BYTE", "_BYTE",
     "CHAR", "_UNKNOWN",
+    // structures.. deal the same as with _UNKNOWN for now
+    "CRITICAL_SECTION",
   };
   const char *n;
   int i;
@@ -507,6 +520,24 @@ static int guess_lmod_from_c_type(enum opr_lenmod *lmod,
   return 0;
 }
 
+static char *default_cast_to(char *buf, size_t buf_size,
+  struct parsed_opr *opr)
+{
+  buf[0] = 0;
+
+  if (!opr->is_ptr)
+    return buf;
+  if (opr->pp == NULL || opr->pp->type.name == NULL
+    || opr->pp->is_fptr)
+  {
+    snprintf(buf, buf_size, "%s", "(void *)");
+    return buf;
+  }
+
+  snprintf(buf, buf_size, "(%s)", opr->pp->type.name);
+  return buf;
+}
+
 static enum opr_type lmod_from_directive(const char *d)
 {
   if (IS(d, "dd"))
@@ -605,8 +636,10 @@ static int parse_operand(struct parsed_opr *opr,
   if (wordc_in == 2) {
     if (IS(words[w], "offset")) {
       opr->type = OPT_OFFSET;
+      opr->lmod = OPLM_DWORD;
       strcpy(opr->name, words[w + 1]);
-      return wordc;
+      pp = proto_parse(g_fhdr, opr->name, 1);
+      goto do_label;
     }
     if (IS(words[w], "(offset")) {
       p = strchr(words[w + 1], ')');
@@ -699,6 +732,7 @@ do_label:
     }
     opr->is_array = pp->type.is_array;
   }
+  opr->pp = pp;
 
   if (opr->lmod == OPLM_UNSPEC)
     guess_lmod_from_name(opr);
@@ -884,6 +918,7 @@ static void parse_op(struct parsed_op *op, char words[16][256], int wordc)
   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;
@@ -1526,7 +1561,9 @@ static void stack_frame_access(struct parsed_op *po,
 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) {
@@ -1537,6 +1574,18 @@ static void check_func_pp(struct parsed_op *po,
       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,
@@ -2548,8 +2597,10 @@ static const struct parsed_proto *resolve_icall(int i, int opcnt,
   return pp;
 }
 
-static int try_resolve_const(int i, const struct parsed_opr *opr,
-  int magic, unsigned int *val)
+// 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;
@@ -2560,7 +2611,7 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
     if (g_labels[i][0] != 0) {
       lr = &g_label_refs[i];
       for (; lr != NULL; lr = lr->next)
-        ret |= try_resolve_const(lr->i, opr, magic, val);
+        ret |= resolve_origin(lr->i, opr, magic, op_i);
       if (i > 0 && LAST_OP(i - 1))
         return ret;
     }
@@ -2577,12 +2628,36 @@ static int try_resolve_const(int i, const struct parsed_opr *opr,
       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,
@@ -2592,8 +2667,10 @@ static int collect_call_args_r(struct parsed_op *po, int i,
   struct parsed_proto *pp_tmp;
   struct label_ref *lr;
   int need_to_save_current;
+  int save_args;
   int ret = 0;
-  int j;
+  char buf[32];
+  int j, k;
 
   if (i < 0) {
     ferr(po, "dead label encountered\n");
@@ -2686,6 +2763,7 @@ static int collect_call_args_r(struct parsed_op *po, int i,
 
       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);
@@ -2695,7 +2773,7 @@ static int collect_call_args_r(struct parsed_op *po, int i,
         ops[j].flags &= ~OPF_RMD;
         if (ops[j].argnum == 0) {
           ops[j].argnum = arg + 1;
-          *save_arg_vars |= 1 << arg;
+          save_args |= 1 << arg;
         }
         else if (ops[j].argnum < arg + 1)
           ferr(&ops[j], "argnum conflict (%d<%d) for '%s'\n",
@@ -2712,6 +2790,32 @@ static int collect_call_args_r(struct parsed_op *po, int i,
 
       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
@@ -2720,10 +2824,6 @@ static int collect_call_args_r(struct parsed_op *po, int i,
             break;
       }
       magic = (magic & 0xffffff) | (arg << 24);
-
-      // tracking reg usage
-      if (ops[j].operand[0].type == OPT_REG)
-        *regmask |= 1 << ops[j].operand[0].reg;
     }
   }
 
@@ -2871,39 +2971,7 @@ 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);
 
-  fprintf(fout, "%s ", g_func_pp->ret_type.name);
-  output_pp_attrs(fout, g_func_pp, g_ida_func_attr & IDAFA_NORETURN);
-  fprintf(fout, "%s(", g_func_pp->name);
-
   for (i = 0; i < g_func_pp->argc; i++) {
-    if (i > 0)
-      fprintf(fout, ", ");
-    if (g_func_pp->arg[i].fptr != NULL) {
-      // func pointer..
-      pp = g_func_pp->arg[i].fptr;
-      fprintf(fout, "%s (", pp->ret_type.name);
-      output_pp_attrs(fout, pp, 0);
-      fprintf(fout, "*a%d)(", i + 1);
-      for (j = 0; j < pp->argc; j++) {
-        if (j > 0)
-          fprintf(fout, ", ");
-        if (pp->arg[j].fptr)
-          ferr(ops, "nested fptr\n");
-        fprintf(fout, "%s", pp->arg[j].type.name);
-      }
-      if (pp->is_vararg) {
-        if (j > 0)
-          fprintf(fout, ", ");
-        fprintf(fout, "...");
-      }
-      fprintf(fout, ")");
-    }
-    else if (g_func_pp->arg[i].type.is_retreg) {
-      fprintf(fout, "u32 *r_%s", g_func_pp->arg[i].reg);
-    }
-    else {
-      fprintf(fout, "%s a%d", g_func_pp->arg[i].type.name, i + 1);
-    }
     if (g_func_pp->arg[i].reg != NULL) {
       reg = char_array_i(regs_r32,
               ARRAY_SIZE(regs_r32), g_func_pp->arg[i].reg);
@@ -2912,13 +2980,6 @@ static void gen_func(FILE *fout, FILE *fhdr, const char *funcn, int opcnt)
       regmask_arg |= 1 << reg;
     }
   }
-  if (g_func_pp->is_vararg) {
-    if (i > 0)
-      fprintf(fout, ", ");
-    fprintf(fout, "...");
-  }
-
-  fprintf(fout, ")\n{\n");
 
   // pass1:
   // - handle ebp/esp frame, remove ops related to it
@@ -3185,6 +3246,8 @@ tailcall:
         // indirect call
         pp_c = resolve_icall(i, opcnt, &l);
         if (pp_c != NULL) {
+          if (!pp_c->is_func && !pp_c->is_fptr)
+            ferr(po, "call to non-func: %s\n", pp_c->name);
           pp = proto_clone(pp_c);
           my_assert_not(pp, NULL);
           if (l)
@@ -3408,8 +3471,12 @@ tailcall:
             pfomask = 1 << po->pfo;
           }
 
-          if (tmp_op->op == OP_ADD && po->pfo == PFO_C)
-            need_tmp64 = 1;
+          if (tmp_op->op == OP_ADD && po->pfo == PFO_C) {
+            propagate_lmod(tmp_op, &tmp_op->operand[0],
+              &tmp_op->operand[1]);
+            if (tmp_op->operand[0].lmod == OPLM_DWORD)
+              need_tmp64 = 1;
+          }
         }
         if (pfomask) {
           tmp_op->pfomask |= pfomask;
@@ -3430,7 +3497,8 @@ tailcall:
     else if (po->op == OP_MUL
       || (po->op == OP_IMUL && po->operand_cnt == 1))
     {
-      need_tmp64 = 1;
+      if (po->operand[0].lmod == OPLM_DWORD)
+        need_tmp64 = 1;
     }
     else if (po->op == OP_CALL) {
       pp = po->pp;
@@ -3492,38 +3560,24 @@ tailcall:
           }
         }
       }
-
-      // declare indirect funcs
-      if (pp->is_fptr && !(pp->name[0] != 0 && pp->is_arg)) {
-        if (pp->name[0] != 0) {
-          memmove(pp->name + 2, pp->name, strlen(pp->name) + 1);
-          memcpy(pp->name, "i_", 2);
-
-          // might be declared already
-          found = 0;
-          for (j = 0; j < i; j++) {
-            if (ops[j].op == OP_CALL && (pp_tmp = ops[j].pp)) {
-              if (pp_tmp->is_fptr && IS(pp->name, pp_tmp->name)) {
-                found = 1;
-                break;
-              }
-            }
-          }
-          if (found)
-            continue;
-        }
-        else
-          snprintf(pp->name, sizeof(pp->name), "icall%d", i);
-
-        fprintf(fout, "  %s (", pp->ret_type.name);
-        output_pp_attrs(fout, pp, 0);
-        fprintf(fout, "*%s)(", pp->name);
-        for (j = 0; j < pp->argc; j++) {
-          if (j > 0)
-            fprintf(fout, ", ");
-          fprintf(fout, "%s a%d", pp->arg[j].type.name, j + 1);
+    }
+    else if (po->op == OP_MOV && po->operand[0].pp != NULL
+      && po->operand[1].pp != NULL)
+    {
+      // <var> = offset <something>
+      if ((po->operand[1].pp->is_func || po->operand[1].pp->is_fptr)
+        && !IS_START(po->operand[1].name, "off_"))
+      {
+        if (!po->operand[0].pp->is_fptr)
+          ferr(po, "%s not declared as fptr when it should be\n",
+            po->operand[0].name);
+        if (pp_cmp_func(po->operand[0].pp, po->operand[1].pp)) {
+          pp_print(buf1, sizeof(buf1), po->operand[0].pp);
+          pp_print(buf2, sizeof(buf2), po->operand[1].pp);
+          fnote(po, "var:  %s\n", buf1);
+          fnote(po, "func: %s\n", buf2);
+          ferr(po, "^ mismatch\n");
         }
-        fprintf(fout, ");\n");
       }
     }
     else if (po->op == OP_RET && !IS(g_func_pp->ret_type.name, "void"))
@@ -3562,6 +3616,102 @@ tailcall:
     }
   }
 
+  // output starts here
+
+  // define userstack size
+  if (g_func_pp->is_userstack) {
+    fprintf(fout, "#ifndef US_SZ_%s\n", g_func_pp->name);
+    fprintf(fout, "#define US_SZ_%s USERSTACK_SIZE\n", g_func_pp->name);
+    fprintf(fout, "#endif\n");
+  }
+
+  // the function itself
+  fprintf(fout, "%s ", g_func_pp->ret_type.name);
+  output_pp_attrs(fout, g_func_pp, g_ida_func_attr & IDAFA_NORETURN);
+  fprintf(fout, "%s(", g_func_pp->name);
+
+  for (i = 0; i < g_func_pp->argc; i++) {
+    if (i > 0)
+      fprintf(fout, ", ");
+    if (g_func_pp->arg[i].fptr != NULL) {
+      // func pointer..
+      pp = g_func_pp->arg[i].fptr;
+      fprintf(fout, "%s (", pp->ret_type.name);
+      output_pp_attrs(fout, pp, 0);
+      fprintf(fout, "*a%d)(", i + 1);
+      for (j = 0; j < pp->argc; j++) {
+        if (j > 0)
+          fprintf(fout, ", ");
+        if (pp->arg[j].fptr)
+          ferr(ops, "nested fptr\n");
+        fprintf(fout, "%s", pp->arg[j].type.name);
+      }
+      if (pp->is_vararg) {
+        if (j > 0)
+          fprintf(fout, ", ");
+        fprintf(fout, "...");
+      }
+      fprintf(fout, ")");
+    }
+    else if (g_func_pp->arg[i].type.is_retreg) {
+      fprintf(fout, "u32 *r_%s", g_func_pp->arg[i].reg);
+    }
+    else {
+      fprintf(fout, "%s a%d", g_func_pp->arg[i].type.name, i + 1);
+    }
+  }
+  if (g_func_pp->is_vararg) {
+    if (i > 0)
+      fprintf(fout, ", ");
+    fprintf(fout, "...");
+  }
+
+  fprintf(fout, ")\n{\n");
+
+  // declare indirect functions
+  for (i = 0; i < opcnt; i++) {
+    po = &ops[i];
+    if (po->flags & OPF_RMD)
+      continue;
+
+    if (po->op == OP_CALL) {
+      pp = po->pp;
+      if (pp == NULL)
+        ferr(po, "NULL pp\n");
+
+      if (pp->is_fptr && !(pp->name[0] != 0 && pp->is_arg)) {
+        if (pp->name[0] != 0) {
+          memmove(pp->name + 2, pp->name, strlen(pp->name) + 1);
+          memcpy(pp->name, "i_", 2);
+
+          // might be declared already
+          found = 0;
+          for (j = 0; j < i; j++) {
+            if (ops[j].op == OP_CALL && (pp_tmp = ops[j].pp)) {
+              if (pp_tmp->is_fptr && IS(pp->name, pp_tmp->name)) {
+                found = 1;
+                break;
+              }
+            }
+          }
+          if (found)
+            continue;
+        }
+        else
+          snprintf(pp->name, sizeof(pp->name), "icall%d", i);
+
+        fprintf(fout, "  %s (", pp->ret_type.name);
+        output_pp_attrs(fout, pp, 0);
+        fprintf(fout, "*%s)(", pp->name);
+        for (j = 0; j < pp->argc; j++) {
+          if (j > 0)
+            fprintf(fout, ", ");
+          fprintf(fout, "%s a%d", pp->arg[j].type.name, j + 1);
+        }
+        fprintf(fout, ");\n");
+      }
+    }
+  }
 
   // output LUTs/jumptables
   for (i = 0; i < g_func_pd_cnt; i++) {
@@ -3598,8 +3748,8 @@ tailcall:
   }
 
   if (g_func_pp->is_userstack) {
-    fprintf(fout, "  u32 fake_sf[1024];\n");
-    fprintf(fout, "  u32 *esp = &fake_sf[1024];\n");
+    fprintf(fout, "  u32 fake_sf[US_SZ_%s / 4];\n", g_func_pp->name);
+    fprintf(fout, "  u32 *esp = &fake_sf[sizeof(fake_sf) / 4];\n");
     had_decl = 1;
   }
 
@@ -3807,9 +3957,10 @@ tailcall:
         assert_operand_cnt(2);
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
         out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]);
+        default_cast_to(buf3, sizeof(buf3), &po->operand[0]);
         fprintf(fout, "  %s = %s;", buf1,
             out_src_opr(buf2, sizeof(buf2), po, &po->operand[1],
-              po->operand[0].is_ptr ? "(void *)" : "", 0));
+              buf3, 0));
         break;
 
       case OP_LEA:
@@ -3853,9 +4004,11 @@ tailcall:
           out_src_opr(buf1, sizeof(buf1), po, &po->operand[0], "", 0));
         fprintf(fout, " %s = %s;",
           out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]),
-          out_src_opr(buf2, sizeof(buf2), po, &po->operand[1], "", 0));
-        fprintf(fout, " %s = tmp;",
-          out_dst_opr(buf1, sizeof(buf1), po, &po->operand[1]));
+          out_src_opr(buf2, sizeof(buf2), po, &po->operand[1],
+            default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0));
+        fprintf(fout, " %s = %stmp;",
+          out_dst_opr(buf1, sizeof(buf1), po, &po->operand[1]),
+          default_cast_to(buf3, sizeof(buf3), &po->operand[1]));
         snprintf(g_comment, sizeof(g_comment), "xchg");
         break;
 
@@ -3880,7 +4033,7 @@ tailcall:
           ferr(po, "TODO\n");
         }
         else {
-          fprintf(fout, "  eax = %sedi; esi %c= %d;",
+          fprintf(fout, "  eax = %sesi; esi %c= %d;",
             lmod_cast_u_ptr(po, po->operand[0].lmod),
             (po->flags & OPF_DF) ? '-' : '+',
             lmod_bytes(po, po->operand[0].lmod));
@@ -3939,11 +4092,11 @@ tailcall:
           if (pfomask & (1 << PFO_C)) {
             // ugh..
             fprintf(fout,
-            "    cond_c = %sedi < %sesi;\n", buf1, buf1);
+            "    cond_c = %sesi < %sedi;\n", buf1, buf1);
             pfomask &= ~(1 << PFO_C);
           }
           fprintf(fout,
-            "    cond_z = (%sedi == %sesi); edi %c= %d, esi %c= %d;\n",
+            "    cond_z = (%sesi == %sedi); esi %c= %d, edi %c= %d;\n",
               buf1, buf1, l, j, l, j);
           fprintf(fout,
             "    if (cond_z %s 0) break;\n",
@@ -3955,7 +4108,7 @@ tailcall:
         }
         else {
           fprintf(fout,
-            "  cond_z = (%sedi = %sesi); edi %c= %d; esi %c= %d;",
+            "  cond_z = (%sesi == %sedi); esi %c= %d; edi %c= %d;",
             buf1, buf1, l, j, l, j);
           strcpy(g_comment, "cmps");
         }
@@ -3986,7 +4139,7 @@ tailcall:
             (po->flags & OPF_REPZ) ? "e" : "ne");
         }
         else {
-          fprintf(fout, "  cond_z = (%seax = %sedi); edi %c= %d;",
+          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);
           strcpy(g_comment, "scas");
@@ -4131,13 +4284,22 @@ tailcall:
         assert_operand_cnt(2);
         propagate_lmod(po, &po->operand[0], &po->operand[1]);
         if (pfomask & (1 << PFO_C)) {
-          fprintf(fout, "  tmp64 = (u64)%s + %s;\n",
-            out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]),
-            out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]));
-          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");
+          out_src_opr_u32(buf1, sizeof(buf1), po, &po->operand[0]);
+          out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[1]);
+          if (po->operand[0].lmod == OPLM_DWORD) {
+            fprintf(fout, "  tmp64 = (u64)%s + %s;\n", buf1, buf2);
+            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");
+          }
+          else {
+            fprintf(fout, "  cond_c = ((u32)%s + %s) >> %d;\n",
+              buf1, buf2, lmod_bytes(po, po->operand[0].lmod) * 8);
+            fprintf(fout, "  %s += %s;",
+              out_dst_opr(buf1, sizeof(buf1), po, &po->operand[0]),
+              buf2);
+          }
           pfomask &= ~(1 << PFO_C);
           output_std_flags(fout, po, &pfomask, buf1);
           last_arith_dst = &po->operand[0];
@@ -4230,11 +4392,24 @@ tailcall:
         // fallthrough
       case OP_MUL:
         assert_operand_cnt(1);
-        strcpy(buf1, po->op == OP_IMUL ? "(s64)(s32)" : "(u64)");
-        fprintf(fout, "  tmp64 = %seax * %s%s;\n", buf1, buf1,
-          out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[0]));
-        fprintf(fout, "  edx = tmp64 >> 32;\n");
-        fprintf(fout, "  eax = tmp64;");
+        switch (po->operand[0].lmod) {
+        case OPLM_DWORD:
+          strcpy(buf1, po->op == OP_IMUL ? "(s64)(s32)" : "(u64)");
+          fprintf(fout, "  tmp64 = %seax * %s%s;\n", buf1, buf1,
+            out_src_opr_u32(buf2, sizeof(buf2), po, &po->operand[0]));
+          fprintf(fout, "  edx = tmp64 >> 32;\n");
+          fprintf(fout, "  eax = tmp64;");
+          break;
+        case OPLM_BYTE:
+          strcpy(buf1, po->op == OP_IMUL ? "(s16)(s8)" : "(u16)(u8)");
+          fprintf(fout, "  LOWORD(eax) = %seax * %s;", buf1,
+            out_src_opr(buf2, sizeof(buf2), po, &po->operand[0],
+              buf1, 0));
+          break;
+        default:
+          ferr(po, "TODO: unhandled mul type\n");
+          break;
+        }
         last_arith_dst = NULL;
         delayed_flag_op = NULL;
         break;
@@ -4342,10 +4517,14 @@ tailcall:
           fprintf(fout, " {\n");
         }
 
-        if (pp->is_fptr && !pp->is_arg)
+        if (pp->is_fptr && !pp->is_arg) {
           fprintf(fout, "%s%s = %s;\n", buf3, pp->name,
             out_src_opr(buf1, sizeof(buf1), po, &po->operand[0],
               "(void *)", 0));
+          if (pp->is_unresolved)
+            fprintf(fout, "%sunresolved_call(\"%s:%d\", %s);\n",
+              buf3, asmfn, po->asmln, pp->name);
+        }
 
         fprintf(fout, "%s", buf3);
         if (strstr(pp->ret_type.name, "int64")) {
@@ -4424,7 +4603,11 @@ tailcall:
             tmp_op = pp->arg[arg].datap;
             if (tmp_op == NULL)
               ferr(po, "parsed_op missing for arg%d\n", arg);
-            if (tmp_op->argnum != 0) {
+
+            if (tmp_op->flags & OPF_VAPUSH) {
+              fprintf(fout, "ap");
+            }
+            else if (tmp_op->argnum != 0) {
               fprintf(fout, "%ss_a%d", cast, tmp_op->argnum);
             }
             else {
@@ -4443,7 +4626,7 @@ tailcall:
         }
 
         if (pp->is_unresolved) {
-          snprintf(buf2, sizeof(buf2), " unresoved %dreg",
+          snprintf(buf2, sizeof(buf2), " unresolved %dreg",
             pp->argc_reg);
           strcat(g_comment, buf2);
         }
@@ -4544,7 +4727,7 @@ tailcall:
           fprintf(fout, "  %s = %s;", buf1,
             out_src_opr(buf2, sizeof(buf2),
               tmp_op, &tmp_op->operand[0],
-              po->operand[0].is_ptr ? "(void *)" : "", 0));
+              default_cast_to(buf3, sizeof(buf3), &po->operand[0]), 0));
           break;
         }
         else if (g_func_pp->is_userstack) {
@@ -4745,6 +4928,9 @@ static void scan_ahead(FILE *fasm)
 
         add_func_chunk(fasm, words[0], asmln);
       }
+      else if (IS_START(p, "; sctend"))
+        break;
+
       continue;
     } // *p == ';'
 
@@ -4790,6 +4976,7 @@ int main(int argc, char *argv[])
   int eq_alloc;
   int verbose = 0;
   int multi_seg = 0;
+  int end = 0;
   int arg_out;
   int arg;
   int pi = 0;
@@ -5020,6 +5207,11 @@ parse_words:
       if (IS_START(p, "; sctproto:")) {
         sctproto = strdup(p + 11);
       }
+      else if (IS_START(p, "; sctend")) {
+        end = 1;
+        if (!pending_endp)
+          break;
+      }
     }
 
     if (wordc == 0) {
@@ -5040,7 +5232,7 @@ parse_words:
 do_pending_endp:
     // do delayed endp processing to collect switch jumptables
     if (pending_endp) {
-      if (in_func && !skip_func && wordc >= 2
+      if (in_func && !skip_func && !end && wordc >= 2
           && ((words[0][0] == 'd' && words[0][2] == 0)
               || (words[1][0] == 'd' && words[1][2] == 0)))
       {
@@ -5123,6 +5315,9 @@ do_pending_endp:
       }
       g_func_pd_cnt = 0;
       pd = NULL;
+
+      if (end)
+        break;
       if (wordc == 0)
         continue;
     }
@@ -5187,8 +5382,12 @@ do_pending_endp:
     }
 
     if (wordc == 2 && IS(words[1], "ends")) {
-      if (!multi_seg)
+      if (!multi_seg) {
+        end = 1;
+        if (pending_endp)
+          goto do_pending_endp;
         break;
+      }
 
       // scan for next text segment
       while (fgets(line, sizeof(line), fasm)) {
@@ -5257,7 +5456,7 @@ do_pending_endp:
     parse_op(&ops[pi], words, wordc);
 
     if (sctproto != NULL) {
-      if (ops[pi].op == OP_CALL)
+      if (ops[pi].op == OP_CALL || ops[pi].op == OP_JMP)
         ops[pi].datap = sctproto;
       sctproto = NULL;
     }