translate: handle arg push for multiple funcs better
[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             // ..but base float is ok..
252             int is_flt = isDwrd(tmp_ea_flags) || isFloat(tmp_ea_flags);
253             if (!is_flt && !isUnknown(tmp_ea_flags))
254             {
255               buf[0] = 0;
256               get_name(ea, tmp_ea, buf, sizeof(buf));
257               msg("%x: undefining %x '%s'\n", ea, tmp_ea, buf);
258               do_unknown(tmp_ea, DOUNK_EXPAND);
259             }
260           }
261         }
262       }
263       else if (cmd.itype == NN_lea) {
264         // detect code alignment
265         if (cmd.Operands[0].reg == cmd.Operands[1].reg
266           && cmd.Operands[1].type == o_displ
267           && cmd.Operands[1].addr == 0)
268         {
269           tmp_ea = next_head(ea, inf.maxEA);
270           if ((tmp_ea & 0x03) == 0) {
271             n = calc_max_align(tmp_ea);
272             if (n > 4) // masm doesn't like more..
273               n = 4;
274             msg("%x: align %d\n", ea, 1 << n);
275             do_unknown(ea, DOUNK_SIMPLE);
276             doAlign(ea, tmp_ea - ea, n);
277           }
278         }
279         else if (!isDefArg1(ea_flags)
280           && cmd.Operands[1].type == o_mem // why o_mem?
281           && cmd.Operands[1].dtyp == dt_dword)
282         {
283           if (inf.minEA <= cmd.Operands[1].addr
284             && cmd.Operands[1].addr < inf.maxEA)
285           {
286             // lea to segments, like ds:58D6A8h[edx*8]
287             msg("%x: lea offset to %x\n", ea, cmd.Operands[1].addr);
288             op_offset(ea, 1, REF_OFF32);
289           }
290           else
291           {
292             // ds:0[eax*8] -> [eax*8+0]
293             msg("%x: dropping ds: for %x\n", ea, cmd.Operands[1].addr);
294             op_hex(ea, 1);
295           }
296         }
297       }
298
299       // find non-local branches
300       if (is_insn_jmp(cmd.itype) && cmd.Operands[0].type == o_near)
301       {
302         target_ea = cmd.Operands[0].addr;
303         if (func == NULL)
304           nonlocal_add(target_ea);
305         else {
306           ret = get_func_chunknum(func, target_ea);
307           if (ret != 0) {
308             // a jump to another func or chunk
309             // check if it lands on func start
310             if (!isFunc(get_flags_novalue(target_ea)))
311               nonlocal_add(target_ea);
312           }
313         }
314       }
315     }
316     else { // not code
317       int do_undef = 0;
318       ea_size = get_item_size(ea);
319
320       if (func == NULL && isOff0(ea_flags)) {
321         for (tmp_ea = 0; tmp_ea < ea_size; tmp_ea += 4)
322           nonlocal_add(get_long(ea + tmp_ea));
323       }
324
325       // IDA vs masm float/mmx/xmm type incompatibility
326       if (isDouble(ea_flags) || isTbyt(ea_flags)
327        || isPackReal(ea_flags))
328       {
329         do_undef = 1;
330       }
331       else if (isOwrd(ea_flags)) {
332         buf[0] = 0;
333         get_name(BADADDR, ea, buf, sizeof(buf));
334         if (IS_START(buf, "xmm"))
335           do_undef = 1;
336       }
337       // masm doesn't understand IDA's unicode
338       else if (isASCII(ea_flags) && ea_size >= 4
339         && (get_long(ea) & 0xff00ff00) == 0) // lame..
340       {
341         do_undef = 1;
342       }
343       // masm doesn't understand large aligns
344       else if (isAlign(ea_flags) && ea_size > 0x10) {
345         msg("%x: undefining align %d\n", ea, ea_size);
346         do_unknown(ea, DOUNK_EXPAND);
347       }
348
349       if (do_undef) {
350         buf[0] = 0;
351         get_name(BADADDR, ea, buf, sizeof(buf));
352         msg("%x: undefining '%s'\n", ea, buf);
353         do_unknown(ea, DOUNK_EXPAND);
354       }
355     }
356   }
357
358   // check namelist for reserved names
359   n = get_nlist_size();
360   for (i = 0; i < n; i++) {
361     ea = get_nlist_ea(i);
362     name = get_nlist_name(i);
363     if (name == NULL) {
364       msg("%x: null name?\n", ea);
365       continue;
366     }
367
368     // rename vars with '?@' (funcs are ok)
369     int change_qat = 0;
370     ea_flags = get_flags_novalue(ea);
371     if (!isCode(ea_flags) && strpbrk(name, "?@"))
372       change_qat = 1;
373
374     if (change_qat || is_name_reserved(name)) {
375       msg("%x: renaming name '%s'\n", ea, name);
376       qsnprintf(buf, sizeof(buf), "%s_g", name);
377
378       if (change_qat) {
379         for (p = buf; *p != 0; p++) {
380           if (*p == '?' || *p == '@') {
381             qsnprintf(buf2, sizeof(buf2), "%02x", (unsigned char)*p);
382             memmove(p + 1, p, strlen(p) + 1);
383             memcpy(p, buf2, 2);
384           }
385         }
386       }
387
388       set_name(ea, buf);
389     }
390   }
391
392   if (nonlocal_bt_cnt > 1) {
393     qsort(nonlocal_bt, nonlocal_bt_cnt,
394       sizeof(nonlocal_bt[0]), nonlocal_bt_cmp);
395   }
396
397   char *fname = askfile_c(1, NULL, "Save asm file");
398   if (fname == NULL)
399     return;
400   fout = qfopen(fname, "w");
401   if (fout == NULL) {
402     msg("couldn't open '%s'\n", fname);
403     return;
404   }
405
406   show_wait_box("Saving..");
407
408   // deal with the beginning
409   ea = inf.minEA;
410   int flags = 0; // calc_default_idaplace_flags();
411   linearray_t ln(&flags);
412   idaplace_t pl;
413   pl.ea = ea;
414   pl.lnnum = 0;
415   ln.set_place(&pl);
416   n = ln.get_linecnt();
417   for (i = 0; i < n - 1; i++) {
418     do_def_line(buf, sizeof(buf), ln.down(), ea);
419     if (strstr(buf, "include"))
420       continue;
421
422     fout_line++;
423     qfprintf(fout, "%s\n", buf);
424     p = strstr(buf, ".mmx");
425     if (p != NULL) {
426       memcpy(p, ".xmm", 4);
427       fout_line++;
428       qfprintf(fout, "%s\n", buf);
429       continue;
430     }
431     p = strstr(buf, ".model");
432     if (p != NULL) {
433       qstrncpy(p, "include imports.inc", sizeof(buf) - (p - buf));
434       fout_line++;
435       qfprintf(fout, "\n%s\n", buf);
436       i++;
437       break;
438     }
439   }
440   pl.lnnum = i;
441
442   for (;;)
443   {
444     int drop_large = 0, do_rva = 0, set_scale = 0, jmp_near = 0;
445     int word_imm = 0, dword_imm = 0, do_pushf = 0, do_nops = 0;
446
447     if ((ea >> 14) != ui_ea_block) {
448       ui_ea_block = ea >> 14;
449       showAddr(ea);
450       if (wasBreak())
451         break;
452     }
453
454     segment_t *seg = getseg(ea);
455     if (!seg || (seg->type != SEG_CODE && seg->type != SEG_DATA))
456       goto pass;
457
458     ea_flags = get_flags_novalue(ea);
459     if (isCode(ea_flags))
460     {
461       if (!decode_insn(ea))
462         goto pass;
463
464       if (is_insn_jmp(cmd.itype) && cmd.Operands[0].type == o_near
465         && cmd.Operands[0].dtyp == dt_dword)
466       {
467         jmp_near = 1;
468       }
469       else if ((cmd.itype == NN_pushf || cmd.itype == NN_popf)
470         && natop())
471       {
472         do_pushf = 1;
473       }
474
475       for (o = 0; o < UA_MAXOP; o++) {
476         const op_t &opr = cmd.Operands[o];
477         if (opr.type == o_void)
478           break;
479
480         // correct?
481         if (opr.type == o_mem && opr.specval_shorts.high == 0x21)
482           drop_large = 1;
483         if (opr.hasSIB && x86_scale(opr) == 0
484           && x86_index(opr) != INDEX_NONE)
485         {
486           set_scale = 1;
487         }
488         // annoying alignment variant..
489         if (opr.type == o_imm && opr.dtyp == dt_dword
490           && (opr.value < 0x80 || opr.value > 0xffffff80)
491           && cmd.size >= opr.offb + 4)
492         {
493           if (get_long(ea + opr.offb) == opr.value)
494             dword_imm = 1;
495         }
496         else if (opr.type == o_imm && opr.dtyp == dt_word
497           && (opr.value < 0x80 || opr.value > 0xff80)
498           && cmd.size >= opr.offb + 2)
499         {
500           if (get_word(ea + opr.offb) == (ushort)opr.value)
501             word_imm = 1;
502         }
503         else if (opr.type == o_displ && opr.addr == 0
504           && opr.offb != 0 && opr.hasSIB && opr.sib == 0x24)
505         {
506           // uses [esp+0] with 0 encoded into op
507           do_nops++;
508         }
509       }
510     }
511     else { // not code
512       if (isOff0(ea_flags))
513         do_rva = 1;
514     }
515
516 pass:
517     n = ln.get_linecnt();
518     for (i = pl.lnnum; i < n; i++) {
519       do_def_line(buf, sizeof(buf), ln.down(), ea);
520
521       char *fw;
522       for (fw = buf; *fw != 0 && *fw == ' '; )
523         fw++;
524
525       // patches..
526       if (drop_large) {
527         p = strstr(fw, "large ");
528         if (p != NULL)
529           memmove(p, p + 6, strlen(p + 6) + 1);
530       }
531       while (do_rva) {
532         p = strstr(fw, " rva ");
533         if (p == NULL)
534           break;
535         memmove(p + 4 + 3, p + 4, strlen(p + 4) + 1);
536         memcpy(p + 1, "offset", 6);
537       }
538       if (set_scale) {
539         p = strchr(fw, '[');
540         if (p != NULL)
541           p = strchr(p, '+');
542         if (p != NULL && p[1] == 'e') {
543           p += 4;
544           // scale is 1, must specify it explicitly so that
545           // masm chooses the right scaled reg
546           memmove(p + 2, p, strlen(p) + 1);
547           memcpy(p, "*1", 2);
548         }
549       }
550       else if (jmp_near) {
551         p = NULL;
552         if (fw != buf && fw[0] == 'j')
553           p = fw;
554         while (p && *p != ' ')
555           p++;
556         while (p && *p == ' ')
557           p++;
558         if (p != NULL) {
559           memmove(p + 9, p, strlen(p) + 1);
560           memcpy(p, "near ptr ", 9);
561           jmp_near = 0;
562         }
563       }
564       if (word_imm) {
565         p = strstr(fw, ", ");
566         if (p != NULL && '0' <= p[2] && p[2] <= '9') {
567           p += 2;
568           memmove(p + 9, p, strlen(p) + 1);
569           memcpy(p, "word ptr ", 9);
570         }
571       }
572       else if (dword_imm) {
573         p = strstr(fw, ", ");
574         if (p != NULL && '0' <= p[2] && p[2] <= '9') {
575           p += 2;
576           memmove(p + 10, p, strlen(p) + 1);
577           memcpy(p, "dword ptr ", 10);
578         }
579       }
580       else if (do_pushf) {
581         p = strstr(fw, "pushf");
582         if (p == NULL)
583           p = strstr(fw, "popf");
584         if (p != NULL) {
585           p = strchr(p, 'f') + 1;
586           memmove(p + 1, p, strlen(p) + 1);
587           *p = 'd';
588         }
589       }
590
591       if (fw[0] == 'a' && IS_START(fw, "assume cs")) {
592         // "assume cs" causes problems with ext syms
593         memmove(fw + 1, fw, strlen(fw) + 1);
594         *fw = ';';
595       }
596       else if (fw[0] == 'e' && IS_START(fw, "end") && fw[3] == ' ') {
597         fout_line++;
598         qfprintf(fout, "include public.inc\n\n");
599
600         // kill entry point
601         fw[3] = 0;
602       }
603
604       fout_line++;
605       qfprintf(fout, "%s\n", buf);
606     }
607
608     while (do_nops-- > 0)
609       qfprintf(fout, "                nop ; adj\n");
610
611     // note: next_head skips some undefined stuff
612     ea = next_not_tail(ea); // correct?
613     if (ea == BADADDR)
614       break;
615
616     pl.ea = ea;
617     pl.lnnum = 0;
618     ln.set_place(&pl);
619   }
620
621   if (fout != NULL)
622     qfclose(fout);
623   if (fname != NULL)
624     qfree(fname);
625
626   hide_wait_box();
627   msg("%d lines saved.\n", fout_line);
628 }
629
630 //--------------------------------------------------------------------------
631
632 static const char comment[] = "Generate disassembly lines for one address";
633 static const char help[] = "Generate asm file\n";
634 static const char wanted_name[] = "Save asm";
635 static const char wanted_hotkey[] = "Ctrl-F6";
636
637 //--------------------------------------------------------------------------
638 //
639 //      PLUGIN DESCRIPTION BLOCK
640 //
641 //--------------------------------------------------------------------------
642 plugin_t PLUGIN =
643 {
644   IDP_INTERFACE_VERSION,
645   0,                    // plugin flags
646   init,                 // initialize
647   term,                 // terminate. this pointer may be NULL.
648   run,                  // invoke plugin
649   comment,              // long comment about the plugin
650                         // it could appear in the status line
651                         // or as a hint
652   help,                 // multiline help about the plugin
653   wanted_name,          // the preferred short name of the plugin
654   wanted_hotkey         // the preferred hotkey to run the plugin
655 };
656
657 // vim:ts=2:shiftwidth=2:expandtab