handle some masm vs some_other_msvc issues
[ia32rtools.git] / plugin / saveasm.cpp
1 /*
2  * ia32rtools
3  * (C) notaz, 2013,2014
4  *
5  * This work is licensed under the terms of 3-clause BSD license.
6  * See COPYING file in the top-level directory.
7  */
8
9 #define NO_OBSOLETE_FUNCS
10 #include <ida.hpp>
11 #include <idp.hpp>
12 #include <bytes.hpp>
13 #include <loader.hpp>
14 #include <kernwin.hpp>
15
16 #include <name.hpp>
17 #include <frame.hpp>
18 #include <struct.hpp>
19 #include <offset.hpp>
20 #include <auto.hpp>
21 #include <intel.hpp>
22
23 #define IS_START(w, y) !strncmp(w, y, strlen(y))
24 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
25
26 // non-local branch targets
27 static ea_t *nonlocal_bt;
28 static int nonlocal_bt_alloc;
29 static int nonlocal_bt_cnt;
30
31 //--------------------------------------------------------------------------
32 static int idaapi init(void)
33 {
34   return PLUGIN_OK;
35 }
36
37 //--------------------------------------------------------------------------
38 static void idaapi term(void)
39 {
40   if (nonlocal_bt != NULL) {
41     free(nonlocal_bt);
42     nonlocal_bt = NULL;
43   }
44   nonlocal_bt_alloc = 0;
45 }
46
47 //--------------------------------------------------------------------------
48
49 static const char *reserved_names[] = {
50   "name",
51   "type",
52   "offset",
53   "aam",
54   "text",
55   "size",
56   "c",
57 };
58
59 static int is_name_reserved(const char *name)
60 {
61   int i;
62   for (i = 0; i < ARRAY_SIZE(reserved_names); i++)
63     if (strcasecmp(name, reserved_names[i]) == 0)
64       return 1;
65
66   return 0;
67 }
68
69 static int nonlocal_bt_cmp(const void *p1, const void *p2)
70 {
71   const ea_t *e1 = (const ea_t *)p1, *e2 = (const ea_t *)p2;
72   return *e1 - *e2;
73 }
74
75 static void nonlocal_add(ea_t ea)
76 {
77   if (nonlocal_bt_cnt >= nonlocal_bt_alloc) {
78     nonlocal_bt_alloc += nonlocal_bt_alloc * 2 + 64;
79     nonlocal_bt = (ea_t *)realloc(nonlocal_bt,
80       nonlocal_bt_alloc * sizeof(nonlocal_bt[0]));
81     if (nonlocal_bt == NULL) {
82       msg("OOM\n");
83       return;
84     }
85   }
86   nonlocal_bt[nonlocal_bt_cnt++] = ea;
87 }
88
89 // is instruction a (un)conditional jump (not call)?
90 static int is_insn_jmp(uint16 itype)
91 {
92   return itype == NN_jmp || (NN_ja <= itype && itype <= NN_jz);
93 }
94
95 static void do_def_line(char *buf, size_t buf_size, const char *line,
96   ea_t ea)
97 {
98   ea_t *ea_ret;
99   char *p;
100   int len;
101
102   tag_remove(line, buf, buf_size); // remove color codes
103   len = strlen(buf);
104   if (len < 9) {
105     buf[0] = 0;
106     return;
107   }
108   memmove(buf, buf + 9, len - 9 + 1); // rm address
109
110   p = buf;
111   while (*p && *p != ' ' && *p != ':')
112     p++;
113   if (*p == ':') {
114     ea_ret = (ea_t *)bsearch(&ea, nonlocal_bt, nonlocal_bt_cnt,
115       sizeof(nonlocal_bt[0]), nonlocal_bt_cmp);
116     if (ea_ret != 0) {
117       if (p[1] != ' ')
118         msg("no trailing blank in '%s'\n", buf);
119       else
120         p[1] = ':';
121     }
122   }
123 }
124
125 static void idaapi run(int /*arg*/)
126 {
127   // isEnabled(ea) // address belongs to disassembly
128   // ea_t ea = get_screen_ea();
129   // foo = DecodeInstruction(ScreenEA());
130   FILE *fout = NULL;
131   int fout_line = 0;
132   char buf[MAXSTR];
133   char buf2[MAXSTR];
134   const char *name;
135   struc_t *frame;
136   func_t *func;
137   ea_t ui_ea_block = 0, ea_size;
138   ea_t tmp_ea, target_ea;
139   ea_t ea;
140   flags_t ea_flags;
141   uval_t idx;
142   int i, o, m, n;
143   int ret;
144   char *p;
145
146   nonlocal_bt_cnt = 0;
147
148   // get rid of structs, masm doesn't understand them
149   idx = get_first_struc_idx();
150   while (idx != BADNODE) {
151     tid_t tid = get_struc_by_idx(idx);
152     struc_t *struc = get_struc(tid);
153     get_struc_name(tid, buf, sizeof(buf));
154     msg("removing struct '%s'\n", buf);
155     //del_struc_members(struc, 0, get_max_offset(struc));
156     del_struc(struc);
157
158     idx = get_first_struc_idx();
159   }
160
161   // 1st pass: walk through all funcs
162   func = get_func(inf.minEA);
163   while (func != NULL)
164   {
165     func_tail_iterator_t fti(func);
166     if (!fti.main()) {
167       msg("%x: func_tail_iterator_t main failed\n", ea);
168       return;
169     }
170     const area_t &f_area = fti.chunk();
171     ea = f_area.startEA;
172
173     // rename global syms which conflict with frame member names
174     frame = get_frame(func);
175     if (frame != NULL)
176     {
177       for (m = 0; m < (int)frame->memqty; m++)
178       {
179         ret = get_member_name(frame->members[m].id, buf, sizeof(buf));
180         if (ret <= 0) {
181           msg("%x: member has no name?\n", ea);
182           return;
183         }
184         if (buf[0] == ' ') // what's this?
185           continue;
186         if (IS_START(buf, "arg_") || IS_START(buf, "var_"))
187           continue;
188
189         // check for dupe names
190         int m1, dupe = 0;
191         for (m1 = 0; m1 < m; m1++) {
192           get_member_name(frame->members[m1].id, buf2, sizeof(buf2));
193           if (stricmp(buf, buf2) == 0)
194             dupe = 1;
195         }
196
197         if (is_name_reserved(buf) || dupe) {
198           msg("%x: renaming '%s'\n", ea, buf);
199           qstrncat(buf, "_", sizeof(buf));
200           ret = set_member_name(frame, frame->members[m].soff, buf);
201           if (!ret) {
202             msg("%x: renaming failed\n", ea);
203             return;
204           }
205         }
206
207         tmp_ea = get_name_ea(ea, buf);
208         if (tmp_ea == 0 || tmp_ea == ~0)
209           continue;
210
211         msg("%x: from %x: renaming '%s'\n", tmp_ea, ea, buf);
212         qstrncat(buf, "_g", sizeof(buf));
213         set_name(tmp_ea, buf);
214       }
215     }
216
217     func = get_next_func(ea);
218   }
219
220   // 2nd pass over whole .text and .(ro)data segments
221   for (ea = inf.minEA; ea != BADADDR; ea = next_head(ea, inf.maxEA))
222   {
223     segment_t *seg = getseg(ea);
224     if (!seg)
225       break;
226     if (seg->type == SEG_XTRN)
227       continue;
228     if (seg->type != SEG_CODE && seg->type != SEG_DATA)
229       break;
230
231     ea_flags = get_flags_novalue(ea);
232     func = get_func(ea);
233     if (isCode(ea_flags))
234     {
235       if (!decode_insn(ea)) {
236         msg("%x: decode_insn() failed\n", ea);
237         continue;
238       }
239
240       // masm doesn't understand IDA's float/xmm types
241       if (cmd.itype == NN_fld || cmd.itype == NN_fst
242         || cmd.itype == NN_movapd || cmd.itype == NN_movlpd)
243       {
244         for (o = 0; o < UA_MAXOP; o++) {
245           if (cmd.Operands[o].type == o_void)
246             break;
247
248           if (cmd.Operands[o].type == o_mem) {
249             tmp_ea = cmd.Operands[o].addr;
250             flags_t tmp_ea_flags = get_flags_novalue(tmp_ea);
251             if (!isUnknown(tmp_ea_flags)) {
252               buf[0] = 0;
253               get_name(ea, tmp_ea, buf, sizeof(buf));
254               msg("%x: undefining %x '%s'\n", ea, tmp_ea, buf);
255               do_unknown(tmp_ea, DOUNK_EXPAND);
256             }
257           }
258         }
259       }
260       else if (cmd.itype == NN_lea) {
261         // detect code alignment
262         if (cmd.Operands[0].reg == cmd.Operands[1].reg
263           && cmd.Operands[1].type == o_displ
264           && cmd.Operands[1].addr == 0)
265         {
266           tmp_ea = next_head(ea, inf.maxEA);
267           if ((tmp_ea & 0x03) == 0) {
268             n = calc_max_align(tmp_ea);
269             if (n > 4) // masm doesn't like more..
270               n = 4;
271             msg("%x: align %d\n", ea, 1 << n);
272             do_unknown(ea, DOUNK_SIMPLE);
273             doAlign(ea, tmp_ea - ea, n);
274           }
275         }
276         else if (!isDefArg1(ea_flags)
277           && cmd.Operands[1].type == o_mem // why o_mem?
278           && cmd.Operands[1].dtyp == dt_dword)
279         {
280           if (inf.minEA <= cmd.Operands[1].addr
281             && cmd.Operands[1].addr < inf.maxEA)
282           {
283             // lea to segments, like ds:58D6A8h[edx*8]
284             msg("%x: lea offset to %x\n", ea, cmd.Operands[1].addr);
285             op_offset(ea, 1, REF_OFF32);
286           }
287           else
288           {
289             // ds:0[eax*8] -> [eax*8+0]
290             msg("%x: dropping ds: for %x\n", ea, cmd.Operands[1].addr);
291             op_hex(ea, 1);
292           }
293         }
294       }
295
296       // find non-local branches
297       if (is_insn_jmp(cmd.itype) && cmd.Operands[0].type == o_near)
298       {
299         target_ea = cmd.Operands[0].addr;
300         if (func == NULL)
301           nonlocal_add(target_ea);
302         else {
303           ret = get_func_chunknum(func, target_ea);
304           if (ret != 0) {
305             // a jump to another func or chunk
306             // check if it lands on func start
307             if (!isFunc(get_flags_novalue(target_ea)))
308               nonlocal_add(target_ea);
309           }
310         }
311       }
312     }
313     else { // not code
314       int do_undef = 0;
315       ea_size = get_item_size(ea);
316
317       if (func == NULL && isOff0(ea_flags)) {
318         for (tmp_ea = 0; tmp_ea < ea_size; tmp_ea += 4)
319           nonlocal_add(get_long(ea + tmp_ea));
320       }
321
322       // IDA vs masm float/mmx/xmm type incompatibility
323       if (isDouble(ea_flags) || isTbyt(ea_flags)
324        || isPackReal(ea_flags))
325       {
326         do_undef = 1;
327       }
328       else if (isOwrd(ea_flags)) {
329         buf[0] = 0;
330         get_name(BADADDR, ea, buf, sizeof(buf));
331         if (IS_START(buf, "xmm"))
332           do_undef = 1;
333       }
334       // masm doesn't understand IDA's unicode
335       else if (isASCII(ea_flags) && ea_size >= 4
336         && (get_long(ea) & 0xff00ff00) == 0) // lame..
337       {
338         do_undef = 1;
339       }
340       // masm doesn't understand large aligns
341       else if (isAlign(ea_flags) && ea_size > 0x10) {
342         msg("%x: undefining align %d\n", ea, ea_size);
343         do_unknown(ea, DOUNK_EXPAND);
344       }
345
346       if (do_undef) {
347         buf[0] = 0;
348         get_name(BADADDR, ea, buf, sizeof(buf));
349         msg("%x: undefining '%s'\n", ea, buf);
350         do_unknown(ea, DOUNK_EXPAND);
351       }
352     }
353   }
354
355   // check namelist for reserved names
356   n = get_nlist_size();
357   for (i = 0; i < n; i++) {
358     ea = get_nlist_ea(i);
359     name = get_nlist_name(i);
360     if (name == NULL) {
361       msg("%x: null name?\n", ea);
362       continue;
363     }
364
365     // rename vars with '?@' (funcs are ok)
366     int change_qat = 0;
367     ea_flags = get_flags_novalue(ea);
368     if (!isCode(ea_flags) && strpbrk(name, "?@"))
369       change_qat = 1;
370
371     if (change_qat || is_name_reserved(name)) {
372       msg("%x: renaming name '%s'\n", ea, name);
373       qsnprintf(buf, sizeof(buf), "%s_g", name);
374
375       if (change_qat) {
376         for (p = buf; *p != 0; p++) {
377           if (*p == '?' || *p == '@') {
378             qsnprintf(buf2, sizeof(buf2), "%02x", (unsigned char)*p);
379             memmove(p + 1, p, strlen(p) + 1);
380             memcpy(p, buf2, 2);
381           }
382         }
383       }
384
385       set_name(ea, buf);
386     }
387   }
388
389   if (nonlocal_bt_cnt > 1) {
390     qsort(nonlocal_bt, nonlocal_bt_cnt,
391       sizeof(nonlocal_bt[0]), nonlocal_bt_cmp);
392   }
393
394   char *fname = askfile_c(1, NULL, "Save asm file");
395   if (fname == NULL)
396     return;
397   fout = qfopen(fname, "w");
398   if (fout == NULL) {
399     msg("couldn't open '%s'\n", fname);
400     return;
401   }
402
403   show_wait_box("Saving..");
404
405   // deal with the beginning
406   ea = inf.minEA;
407   int flags = 0; // calc_default_idaplace_flags();
408   linearray_t ln(&flags);
409   idaplace_t pl;
410   pl.ea = ea;
411   pl.lnnum = 0;
412   ln.set_place(&pl);
413   n = ln.get_linecnt();
414   for (i = 0; i < n - 1; i++) {
415     do_def_line(buf, sizeof(buf), ln.down(), ea);
416     if (strstr(buf, "include"))
417       continue;
418
419     fout_line++;
420     qfprintf(fout, "%s\n", buf);
421     p = strstr(buf, ".mmx");
422     if (p != NULL) {
423       memcpy(p, ".xmm", 4);
424       fout_line++;
425       qfprintf(fout, "%s\n", buf);
426       continue;
427     }
428     p = strstr(buf, ".model");
429     if (p != NULL) {
430       qstrncpy(p, "include imports.inc", sizeof(buf) - (p - buf));
431       fout_line++;
432       qfprintf(fout, "\n%s\n", buf);
433       i++;
434       break;
435     }
436   }
437   pl.lnnum = i;
438
439   for (;;)
440   {
441     int drop_large = 0, do_rva = 0, set_scale = 0, jmp_near = 0;
442     int word_imm = 0, dword_imm = 0, do_pushf = 0, do_nops = 0;
443
444     if ((ea >> 14) != ui_ea_block) {
445       ui_ea_block = ea >> 14;
446       showAddr(ea);
447       if (wasBreak())
448         break;
449     }
450
451     segment_t *seg = getseg(ea);
452     if (!seg || (seg->type != SEG_CODE && seg->type != SEG_DATA))
453       goto pass;
454
455     ea_flags = get_flags_novalue(ea);
456     if (isCode(ea_flags))
457     {
458       if (!decode_insn(ea))
459         goto pass;
460
461       if (is_insn_jmp(cmd.itype) && cmd.Operands[0].type == o_near
462         && cmd.Operands[0].dtyp == dt_dword)
463       {
464         jmp_near = 1;
465       }
466       else if ((cmd.itype == NN_pushf || cmd.itype == NN_popf)
467         && natop())
468       {
469         do_pushf = 1;
470       }
471
472       for (o = 0; o < UA_MAXOP; o++) {
473         const op_t &opr = cmd.Operands[o];
474         if (opr.type == o_void)
475           break;
476
477         // correct?
478         if (opr.type == o_mem && opr.specval_shorts.high == 0x21)
479           drop_large = 1;
480         if (opr.hasSIB && x86_scale(opr) == 0
481           && x86_index(opr) != INDEX_NONE)
482         {
483           set_scale = 1;
484         }
485         // annoying alignment variant..
486         if (opr.type == o_imm && opr.dtyp == dt_dword
487           && (opr.value < 0x80 || opr.value > 0xffffff80)
488           && cmd.size >= opr.offb + 4)
489         {
490           if (get_long(ea + opr.offb) == opr.value)
491             dword_imm = 1;
492         }
493         else if (opr.type == o_imm && opr.dtyp == dt_word
494           && (opr.value < 0x80 || opr.value > 0xff80)
495           && cmd.size >= opr.offb + 2)
496         {
497           if (get_word(ea + opr.offb) == (ushort)opr.value)
498             word_imm = 1;
499         }
500         else if (opr.type == o_displ && opr.addr == 0
501           && opr.offb != 0 && opr.hasSIB && opr.sib == 0x24)
502         {
503           // uses [esp+0] with 0 encoded into op
504           do_nops++;
505         }
506       }
507     }
508     else { // not code
509       if (isOff0(ea_flags))
510         do_rva = 1;
511     }
512
513 pass:
514     n = ln.get_linecnt();
515     for (i = pl.lnnum; i < n; i++) {
516       do_def_line(buf, sizeof(buf), ln.down(), ea);
517
518       char *fw;
519       for (fw = buf; *fw != 0 && *fw == ' '; )
520         fw++;
521
522       // patches..
523       if (drop_large) {
524         p = strstr(fw, "large ");
525         if (p != NULL)
526           memmove(p, p + 6, strlen(p + 6) + 1);
527       }
528       while (do_rva) {
529         p = strstr(fw, " rva ");
530         if (p == NULL)
531           break;
532         memmove(p + 4 + 3, p + 4, strlen(p + 4) + 1);
533         memcpy(p + 1, "offset", 6);
534       }
535       if (set_scale) {
536         p = strchr(fw, '[');
537         if (p != NULL)
538           p = strchr(p, '+');
539         if (p != NULL && p[1] == 'e') {
540           p += 4;
541           // scale is 1, must specify it explicitly so that
542           // masm chooses the right scaled reg
543           memmove(p + 2, p, strlen(p) + 1);
544           memcpy(p, "*1", 2);
545         }
546       }
547       else if (jmp_near) {
548         p = strchr(fw, 'j');
549         while (p && *p != ' ')
550           p++;
551         while (p && *p == ' ')
552           p++;
553         if (p != NULL) {
554           memmove(p + 9, p, strlen(p) + 1);
555           memcpy(p, "near ptr ", 9);
556         }
557       }
558       if (word_imm) {
559         p = strstr(fw, ", ");
560         if (p != NULL && '0' <= p[2] && p[2] <= '9') {
561           p += 2;
562           memmove(p + 9, p, strlen(p) + 1);
563           memcpy(p, "word ptr ", 9);
564         }
565       }
566       else if (dword_imm) {
567         p = strstr(fw, ", ");
568         if (p != NULL && '0' <= p[2] && p[2] <= '9') {
569           p += 2;
570           memmove(p + 10, p, strlen(p) + 1);
571           memcpy(p, "dword ptr ", 10);
572         }
573       }
574       else if (do_pushf) {
575         p = strstr(fw, "pushf");
576         if (p == NULL)
577           p = strstr(fw, "popf");
578         if (p != NULL) {
579           p = strchr(p, 'f') + 1;
580           memmove(p + 1, p, strlen(p) + 1);
581           *p = 'd';
582         }
583       }
584
585       if (fw[0] == 'a' && IS_START(fw, "assume cs")) {
586         // "assume cs" causes problems with ext syms
587         memmove(fw + 1, fw, strlen(fw) + 1);
588         *fw = ';';
589       }
590       else if (fw[0] == 'e' && IS_START(fw, "end") && fw[3] == ' ') {
591         fout_line++;
592         qfprintf(fout, "include public.inc\n\n");
593
594         // kill entry point
595         fw[3] = 0;
596       }
597
598       fout_line++;
599       qfprintf(fout, "%s\n", buf);
600     }
601
602     while (do_nops-- > 0)
603       qfprintf(fout, "                nop ; adj\n");
604
605     // note: next_head skips some undefined stuff
606     ea = next_not_tail(ea); // correct?
607     if (ea == BADADDR)
608       break;
609
610     pl.ea = ea;
611     pl.lnnum = 0;
612     ln.set_place(&pl);
613   }
614
615   if (fout != NULL)
616     qfclose(fout);
617   if (fname != NULL)
618     qfree(fname);
619
620   hide_wait_box();
621   msg("%d lines saved.\n", fout_line);
622 }
623
624 //--------------------------------------------------------------------------
625
626 static const char comment[] = "Generate disassembly lines for one address";
627 static const char help[] = "Generate asm file\n";
628 static const char wanted_name[] = "Save asm";
629 static const char wanted_hotkey[] = "Ctrl-F6";
630
631 //--------------------------------------------------------------------------
632 //
633 //      PLUGIN DESCRIPTION BLOCK
634 //
635 //--------------------------------------------------------------------------
636 plugin_t PLUGIN =
637 {
638   IDP_INTERFACE_VERSION,
639   0,                    // plugin flags
640   init,                 // initialize
641   term,                 // terminate. this pointer may be NULL.
642   run,                  // invoke plugin
643   comment,              // long comment about the plugin
644                         // it could appear in the status line
645                         // or as a hint
646   help,                 // multiline help about the plugin
647   wanted_name,          // the preferred short name of the plugin
648   wanted_hotkey         // the preferred hotkey to run the plugin
649 };
650
651 // vim:ts=2:shiftwidth=2:expandtab