handle stuff, add funcs
[ia32rtools.git] / tools / cvt_data.c
1 /*
2  * ia32rtools
3  * (C) notaz, 2013-2015
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 _GNU_SOURCE
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdint.h>
14 #include <inttypes.h>
15
16 #include "my_assert.h"
17 #include "my_str.h"
18 #include "common.h"
19
20 #include "protoparse.h"
21
22 static const char *asmfn;
23 static int asmln;
24
25 static const struct parsed_proto *g_func_sym_pp;
26 static char g_comment[256];
27 static int g_warn_cnt;
28 static int g_cconv_novalidate;
29 static int g_arm_mode;
30
31 // note: must be in ascending order
32 enum dx_type {
33   DXT_UNSPEC,
34   DXT_BYTE,
35   DXT_WORD,
36   DXT_DWORD,
37   DXT_QUAD,
38   DXT_TEN,
39 };
40
41 #define anote(fmt, ...) \
42         printf("%s:%d: note: " fmt, asmfn, asmln, ##__VA_ARGS__)
43 #define awarn(fmt, ...) do { \
44         printf("%s:%d: warning: " fmt, asmfn, asmln, ##__VA_ARGS__); \
45   if (++g_warn_cnt == 10) { \
46     fcloseall(); \
47           exit(1); \
48   } \
49 } while (0)
50 #define aerr(fmt, ...) do { \
51         printf("%s:%d: error: " fmt, asmfn, asmln, ##__VA_ARGS__); \
52   fcloseall(); \
53         exit(1); \
54 } while (0)
55
56 #include "masm_tools.h"
57
58 static char *next_word_s(char *w, size_t wsize, char *s)
59 {
60   int quote = 0;
61         size_t i;
62
63         s = sskip(s);
64
65         for (i = 0; i < wsize - 1; i++) {
66     if (s[i] == '\'')
67       quote ^= 1;
68                 if (s[i] == 0 || (!quote && (my_isblank(s[i]) || s[i] == ',')))
69                         break;
70                 w[i] = s[i];
71         }
72         w[i] = 0;
73
74         if (s[i] != 0 && !my_isblank(s[i]) && s[i] != ',')
75                 printf("warning: '%s' truncated\n", w);
76
77         return s + i;
78 }
79
80 static void next_section(FILE *fasm, char *name)
81 {
82   char words[2][256];
83   char line[256];
84   int wordc;
85   char *p;
86
87   name[0] = 0;
88
89   while (my_fgets(line, sizeof(line), fasm))
90   {
91     wordc = 0;
92     asmln++;
93
94     p = sskip(line);
95     if (*p == 0)
96       continue;
97
98     if (*p == ';')
99       continue;
100
101     for (wordc = 0; wordc < ARRAY_SIZE(words); wordc++) {
102       p = sskip(next_word(words[wordc], sizeof(words[0]), p));
103       if (*p == 0 || *p == ';') {
104         wordc++;
105         break;
106       }
107     }
108
109     if (wordc < 2)
110       continue;
111
112     if (!IS(words[1], "segment"))
113       continue;
114
115     strcpy(name, words[0]);
116     break;
117   }
118 }
119
120 static enum dx_type parse_dx_directive(const char *name)
121 {
122   if (IS(name, "dd"))
123     return DXT_DWORD;
124   if (IS(name, "dw"))
125     return DXT_WORD;
126   if (IS(name, "db"))
127     return DXT_BYTE;
128   if (IS(name, "dq"))
129     return DXT_QUAD;
130   if (IS(name, "dt"))
131     return DXT_TEN;
132
133   return DXT_UNSPEC;
134 }
135
136 static const char *type_name(enum dx_type type)
137 {
138   switch (type) {
139   case DXT_BYTE:
140     return ".byte";
141   case DXT_WORD:
142     return ".hword";
143   case DXT_DWORD:
144     return ".long";
145   case DXT_QUAD:
146     return ".quad";
147   case DXT_TEN:
148     return ".tfloat";
149   case DXT_UNSPEC:
150     break;
151   }
152   return "<bad>";
153 }
154
155 static const char *type_name_float(enum dx_type type)
156 {
157   switch (type) {
158   case DXT_DWORD:
159     return ".float";
160   case DXT_QUAD:
161     return ".double";
162   case DXT_TEN:
163     return ".tfloat";
164   default:
165     break;
166   }
167   return "<bad_float>";
168 }
169
170 static int type_size(enum dx_type type)
171 {
172   switch (type) {
173   case DXT_BYTE:
174     return 1;
175   case DXT_WORD:
176     return 2;
177   case DXT_DWORD:
178     return 4;
179   case DXT_QUAD:
180     return 8;
181   case DXT_TEN:
182     return 10;
183   case DXT_UNSPEC:
184     break;
185   }
186   return -1;
187 }
188
189 static char *escape_string(char *s)
190 {
191   char buf[256];
192   char *t = buf;
193
194   for (; *s != 0; s++) {
195     if (*s == '"') {
196       strcpy(t, "\\\"");
197       t += strlen(t);
198       continue;
199     }
200     if (*s == '\\') {
201       strcpy(t, "\\\\");
202       t += strlen(t);
203       continue;
204     }
205     *t++ = *s;
206   }
207   *t++ = *s;
208   if (t - buf > sizeof(buf))
209     aerr("string is too long\n");
210   return strcpy(s, buf);
211 }
212
213 static void sprint_pp_short(const struct parsed_proto *pp, char *buf,
214   size_t buf_size)
215 {
216   char *p = buf;
217   size_t l;
218   int i;
219
220   if (pp->ret_type.is_ptr)
221     *p++ = 'p';
222   else if (IS(pp->ret_type.name, "void"))
223     *p++ = 'v';
224   else
225     *p++ = 'i';
226   *p++ = '(';
227   l = 2;
228
229   for (i = 0; i < pp->argc; i++) {
230     if (pp->arg[i].reg != NULL)
231       snprintf(buf + l, buf_size - l, "%s%s",
232         i == 0 ? "" : ",", pp->arg[i].reg);
233     else
234       snprintf(buf + l, buf_size - l, "%sa%d",
235         i == 0 ? "" : ",", i + 1);
236     l = strlen(buf);
237   }
238   snprintf(buf + l, buf_size - l, ")");
239 }
240
241 static const struct parsed_proto *check_var(FILE *fhdr,
242   const char *sym, const char *varname, int is_export)
243 {
244   const struct parsed_proto *pp, *pp_sym;
245   char fp_sym[256], fp_var[256], *p;
246   int i;
247
248   pp = proto_parse(fhdr, varname, 1);
249   if (pp == NULL) {
250     if (IS_START(varname, "sub_"))
251       awarn("sub_ sym missing proto: '%s'\n", varname);
252     return NULL;
253   }
254
255   if (is_export)
256     return NULL;
257   if (!pp->is_func && !pp->is_fptr)
258     return NULL;
259
260   pp_print(fp_var, sizeof(fp_var), pp);
261
262   if (pp->argc_reg == 0)
263     goto check_sym;
264   if (pp->argc_reg == 1 && pp->argc_stack == 0
265     && IS(pp->arg[0].reg, "ecx"))
266   {
267     goto check_sym;
268   }
269   if (!g_cconv_novalidate
270     && (pp->argc_reg != 2
271        || !IS(pp->arg[0].reg, "ecx")
272        || !IS(pp->arg[1].reg, "edx")))
273   {
274     awarn("unhandled reg call: %s\n", fp_var);
275   }
276
277 check_sym:
278   // fptrs must use 32bit args, callsite might have no information and
279   // lack a cast to smaller types, which results in incorrectly masked
280   // args passed (callee may assume masked args, it does on ARM)
281   for (i = 0; i < pp->argc; i++) {
282     if (pp->arg[i].type.is_ptr)
283       continue;
284     p = pp->arg[i].type.name;
285     if (strstr(p, "int8") || strstr(p, "int16")
286       || strstr(p, "char") || strstr(p, "short"))
287     {
288       awarn("reference to %s with arg%d '%s'\n", pp->name, i + 1, p);
289     }
290   }
291
292   sprint_pp_short(pp, g_comment, sizeof(g_comment));
293
294   if (sym != NULL) {
295     g_func_sym_pp = NULL;
296     pp_sym = proto_parse(fhdr, sym, 1);
297     if (pp_sym == NULL)
298       return pp;
299     if (!pp_sym->is_fptr)
300       aerr("func ptr data, but label '%s' !is_fptr\n", pp_sym->name);
301     g_func_sym_pp = pp_sym;
302   }
303   else {
304     pp_sym = g_func_sym_pp;
305     if (pp_sym == NULL)
306       return pp;
307   }
308
309   if (pp_cmp_func(pp, pp_sym)) {
310     if (pp_sym->argc_stack == 0 && pp_sym->is_fastcall
311         && pp->argc_stack == 0
312         && (pp->is_fastcall || pp->argc_reg == 0)
313         && pp_sym->argc_reg > pp->argc_reg)
314       ; /* fascall compatible func doesn't use all args -> ok */
315     else {
316       pp_print(fp_sym, sizeof(fp_sym), pp_sym);
317       anote("var: %s\n", fp_var);
318       anote("sym: %s\n", fp_sym);
319       awarn("^ mismatch\n");
320     }
321   }
322
323   return pp;
324 }
325
326 static void output_decorated_pp(FILE *fout,
327   const struct parsed_proto *pp)
328 {
329   if (pp->name[0] != '_')
330     fprintf(fout, pp->is_fastcall ? "@" : "_");
331   fprintf(fout, "%s", pp->name);
332   if (pp->is_stdcall && pp->argc > 0)
333     fprintf(fout, "@%d", pp->argc * 4);
334 }
335
336 static int align_value(int src_val)
337 {
338   if (src_val <= 0) {
339     awarn("bad align: %d\n", src_val);
340     src_val = 1;
341   }
342   if (!g_arm_mode)
343     return src_val;
344
345   return __builtin_ffs(src_val) - 1;
346 }
347
348 static int cmpstringp(const void *p1, const void *p2)
349 {
350   return strcmp(*(char * const *)p1, *(char * const *)p2);
351 }
352
353 /* XXX: maybe move to external file? */
354 static const char *unwanted_syms[] = {
355   "aRuntimeError",
356   "aTlossError",
357   "aSingError",
358   "aDomainError",
359   "aR6029ThisAppli",
360   "aR6028UnableToI",
361   "aR6027NotEnough",
362   "aR6026NotEnough",
363   "aR6025PureVirtu",
364   "aR6024NotEnough",
365   "aR6019UnableToO",
366   "aR6018Unexpecte",
367   "aR6017Unexpecte",
368   "aR6016NotEnough",
369   "aAbnormalProgra",
370   "aR6009NotEnough",
371   "aR6008NotEnough",
372   "aR6002FloatingP",
373   "aMicrosoftVisua",
374   "aRuntimeErrorPr",
375   "aThisApplicatio",
376   "aMicrosoftFindF",
377   "aMicrosoftOffic",
378 };
379
380 static int is_unwanted_sym(const char *sym)
381 {
382   return bsearch(&sym, unwanted_syms, ARRAY_SIZE(unwanted_syms),
383     sizeof(unwanted_syms[0]), cmpstringp) != NULL;
384 }
385
386 int main(int argc, char *argv[])
387 {
388   FILE *fout, *fasm, *fhdr = NULL, *frlist;
389   const struct parsed_proto *pp;
390   int no_decorations = 0;
391   int in_export_table = 0;
392   char comment_char = '#';
393   char words[20][256];
394   char word[256];
395   char line[256];
396   char last_sym[32];
397   unsigned long val;
398   unsigned long cnt;
399   uint64_t val64;
400   const char *sym;
401   enum dx_type type;
402   char **pub_syms;
403   int pub_sym_cnt = 0;
404   int pub_sym_alloc;
405   char **rlist;
406   int rlist_cnt = 0;
407   int rlist_alloc;
408   int header_mode = 0;
409   int is_ro = 0;
410   int is_label;
411   int is_bss;
412   int wordc;
413   int first;
414   int arg_out;
415   int arg = 1;
416   int len;
417   int w, i;
418   char *p;
419   char *p2;
420
421   if (argc < 4) {
422     // -nd: no symbol decorations
423     printf("usage:\n%s [-nd] [-i] [-a] <.s> <.asm> <hdrf> [rlist]*\n"
424            "%s -hdr <.h> <.asm>\n",
425       argv[0], argv[0]);
426     return 1;
427   }
428
429   for (arg = 1; arg < argc; arg++) {
430     if (IS(argv[arg], "-nd"))
431       no_decorations = 1;
432     else if (IS(argv[arg], "-i"))
433       g_cconv_novalidate = 1;
434     else if (IS(argv[arg], "-a")) {
435       comment_char = '@';
436       g_arm_mode = 1;
437     }
438     else if (IS(argv[arg], "-hdr"))
439       header_mode = 1;
440     else
441       break;
442   }
443
444   arg_out = arg++;
445
446   asmfn = argv[arg++];
447   fasm = fopen(asmfn, "r");
448   my_assert_not(fasm, NULL);
449
450   if (!header_mode) {
451     hdrfn = argv[arg++];
452     fhdr = fopen(hdrfn, "r");
453     my_assert_not(fhdr, NULL);
454   }
455
456   fout = fopen(argv[arg_out], "w");
457   my_assert_not(fout, NULL);
458
459   pub_sym_alloc = 64;
460   pub_syms = malloc(pub_sym_alloc * sizeof(pub_syms[0]));
461   my_assert_not(pub_syms, NULL);
462
463   rlist_alloc = 64;
464   rlist = malloc(rlist_alloc * sizeof(rlist[0]));
465   my_assert_not(rlist, NULL);
466
467   for (; arg < argc; arg++) {
468     frlist = fopen(argv[arg], "r");
469     my_assert_not(frlist, NULL);
470
471     while (my_fgets(line, sizeof(line), frlist)) {
472       p = sskip(line);
473       if (*p == 0 || *p == ';' || *p == '#')
474         continue;
475
476       p = next_word(words[0], sizeof(words[0]), p);
477       if (words[0][0] == 0)
478         continue;
479
480       if (rlist_cnt >= rlist_alloc) {
481         rlist_alloc = rlist_alloc * 2 + 64;
482         rlist = realloc(rlist, rlist_alloc * sizeof(rlist[0]));
483         my_assert_not(rlist, NULL);
484       }
485       rlist[rlist_cnt++] = strdup(words[0]);
486     }
487
488     fclose(frlist);
489     frlist = NULL;
490   }
491
492   if (rlist_cnt > 0)
493     qsort(rlist, rlist_cnt, sizeof(rlist[0]), cmpstringp);
494
495   qsort(unwanted_syms, ARRAY_SIZE(unwanted_syms),
496     sizeof(unwanted_syms[0]), cmpstringp);
497
498   last_sym[0] = 0;
499
500   while (1) {
501     next_section(fasm, line);
502     if (feof(fasm))
503       break;
504     if (IS(line + 1, "text"))
505       continue;
506
507     if (IS(line + 1, "rdata")) {
508       is_ro = 1;
509       if (!header_mode)
510         fprintf(fout, "\n.section .rodata\n");
511     }
512     else if (IS(line + 1, "data")) {
513       is_ro = 0;
514       if (!header_mode)
515         fprintf(fout, "\n.data\n");
516     }
517     else
518       aerr("unhandled section: '%s'\n", line);
519
520     if (!header_mode)
521       fprintf(fout, ".align %d\n", align_value(4));
522
523     while (my_fgets(line, sizeof(line), fasm))
524     {
525       sym = NULL;
526       asmln++;
527
528       p = sskip(line);
529       if (*p == 0)
530         continue;
531
532       if (*p == ';') {
533         if (IS_START(p, ";org") && sscanf(p + 5, "%Xh", &i) == 1) {
534           // ;org is only seen at section start, so assume . addr 0
535           i &= 0xfff;
536           if (i != 0 && !header_mode)
537             fprintf(fout, "\t\t  .skip 0x%x\n", i);
538         }
539         else if (IS_START(p, "; Export Address"))
540           in_export_table = 1;
541         else if (IS_START(p, "; Export"))
542           in_export_table = 0;
543         continue;
544       }
545
546       for (wordc = 0; wordc < ARRAY_SIZE(words); wordc++) {
547         p = sskip(next_word_s(words[wordc], sizeof(words[0]), p));
548         if (*p == 0 || *p == ';') {
549           wordc++;
550           break;
551         }
552         if (*p == ',') {
553           p = sskip(p + 1);
554         }
555       }
556
557       if (*p == ';') {
558         p = sskip(p + 1);
559         if (IS_START(p, "sctclrtype"))
560           g_func_sym_pp = NULL;
561       }
562
563       if (wordc == 2 && IS(words[1], "ends"))
564         break;
565       if (wordc <= 2 && IS(words[0], "end"))
566         break;
567       if (wordc < 2)
568         aerr("unhandled: '%s'\n", words[0]);
569
570       // don't cares
571       if (IS(words[0], "assume"))
572         continue;
573
574       if (IS(words[0], "align")) {
575         if (header_mode)
576           continue;
577
578         val = parse_number(words[1], 0);
579         fprintf(fout, "\t\t  .align %d", align_value(val));
580         goto fin;
581       }
582
583       if (IS(words[0], "public")) {
584         // skip, sym should appear in header anyway
585         continue;
586       }
587
588       w = 1;
589       type = parse_dx_directive(words[0]);
590       if (type == DXT_UNSPEC) {
591         type = parse_dx_directive(words[1]);
592         sym = words[0];
593         w = 2;
594       }
595       if (type == DXT_UNSPEC)
596         aerr("unhandled decl: '%s %s'\n", words[0], words[1]);
597
598       if (sym != NULL)
599       {
600         if (header_mode) {
601           int is_str = 0;
602
603           fprintf(fout, "extern ");
604           if (is_ro)
605             fprintf(fout, "const ");
606
607           switch (type) {
608           case DXT_BYTE:
609             for (i = w; i < wordc; i++)
610               if (words[i][0] == '\'')
611                 is_str = 1;
612             if (is_str)
613               fprintf(fout, "char     %s[];\n", sym);
614             else
615               fprintf(fout, "uint8_t  %s;\n", sym);
616             break;
617
618           case DXT_WORD:
619             fprintf(fout, "uint16_t %s;\n", sym);
620             break;
621
622           case DXT_DWORD:
623             fprintf(fout, "uint32_t %s;\n", sym);
624             break;
625
626           default:
627             fprintf(fout, "_UNKNOWN %s;\n", sym);
628             break;
629           }
630
631           continue;
632         }
633
634         snprintf(last_sym, sizeof(last_sym), "%s", sym);
635
636         pp = proto_parse(fhdr, sym, 1);
637         if (pp != NULL) {
638           g_func_sym_pp = NULL;
639
640           // public/global name
641           if (pub_sym_cnt >= pub_sym_alloc) {
642             pub_sym_alloc *= 2;
643             pub_syms = realloc(pub_syms, pub_sym_alloc * sizeof(pub_syms[0]));
644             my_assert_not(pub_syms, NULL);
645           }
646           pub_syms[pub_sym_cnt++] = strdup(sym);
647         }
648
649         len = strlen(sym);
650         fprintf(fout, "%s%s:", no_decorations ? "" : "_", sym);
651
652         len += 2;
653         if (len < 8)
654           fprintf(fout, "\t");
655         if (len < 16)
656           fprintf(fout, "\t");
657         if (len <= 16)
658           fprintf(fout, "  ");
659         else
660           fprintf(fout, " ");
661       }
662       else {
663         if (header_mode)
664           continue;
665
666         fprintf(fout, "\t\t  ");
667       }
668
669       // fill out some unwanted strings with zeroes..
670       if (type == DXT_BYTE && words[w][0] == '\''
671         && is_unwanted_sym(last_sym))
672       {
673         len = 0;
674         for (; w < wordc; w++) {
675           if (words[w][0] == '\'') {
676             p = words[w] + 1;
677             for (; *p && *p != '\''; p++)
678               len++;
679           }
680           else {
681             // assume encoded byte
682             len++;
683           }
684         }
685         fprintf(fout, ".skip %d", len);
686         goto fin;
687       }
688       else if (type == DXT_BYTE
689         && (words[w][0] == '\''
690             || (w + 1 < wordc && words[w + 1][0] == '\'')))
691       {
692         // string; use asciz for most common case
693         if (w == wordc - 2 && IS(words[w + 1], "0")) {
694           fprintf(fout, ".asciz \"");
695           wordc--;
696         }
697         else
698           fprintf(fout, ".ascii \"");
699
700         for (; w < wordc; w++) {
701           if (words[w][0] == '\'') {
702             p = words[w] + 1;
703             p2 = strchr(p, '\'');
704             if (p2 == NULL)
705               aerr("unterminated string? '%s'\n", p);
706             memcpy(word, p, p2 - p);
707             word[p2 - p] = 0;
708             fprintf(fout, "%s", escape_string(word));
709           }
710           else {
711             val = parse_number(words[w], 0);
712             if (val & ~0xff)
713               aerr("bad string trailing byte?\n");
714             // unfortunately \xHH is unusable - gas interprets
715             // things like \x27b as 0x7b, so have to use octal here
716             fprintf(fout, "\\%03lo", val);
717           }
718         }
719         fprintf(fout, "\"");
720         goto fin;
721       }
722
723       if (w == wordc - 2) {
724         if (IS_START(words[w + 1], "dup(")) {
725           cnt = parse_number(words[w], 0);
726           p = words[w + 1] + 4;
727           p2 = strchr(p, ')');
728           if (p2 == NULL)
729             aerr("bad dup?\n");
730           memmove(word, p, p2 - p);
731           word[p2 - p] = 0;
732
733           val = 0;
734           if (!IS(word, "?"))
735             val = parse_number(word, 0);
736
737           fprintf(fout, ".fill 0x%02lx,%d,0x%02lx",
738             cnt, type_size(type), val);
739           goto fin;
740         }
741       }
742
743       if (type == DXT_DWORD && words[w][0] == '\''
744         && words[w][5] == '\'' && strlen(words[w]) == 6)
745       {
746         if (w != wordc - 1)
747           aerr("TODO\n");
748
749         p = words[w];
750         val = (p[1] << 24) | (p[2] << 16) | (p[3] << 8) | p[4];
751         fprintf(fout, ".long 0x%lx", val);
752         snprintf(g_comment, sizeof(g_comment), "%s", words[w]);
753         goto fin;
754       }
755
756       if (type >= DXT_DWORD && strchr(words[w], '.'))
757       {
758         if (w != wordc - 1)
759           aerr("TODO\n");
760
761         if (g_arm_mode && type == DXT_TEN) {
762           fprintf(fout, ".fill 10");
763           snprintf(g_comment, sizeof(g_comment), "%s %s",
764             type_name_float(type), words[w]);
765         }
766         else
767           fprintf(fout, "%s %s", type_name_float(type), words[w]);
768         goto fin;
769       }
770
771       first = 1;
772       fprintf(fout, "%s ", type_name(type));
773       for (; w < wordc; w++)
774       {
775         if (!first)
776           fprintf(fout, ", ");
777
778         is_label = is_bss = 0;
779         if (w <= wordc - 2 && IS(words[w], "offset")) {
780           is_label = 1;
781           w++;
782         }
783         else if (IS(words[w], "?")) {
784           is_bss = 1;
785         }
786         else if (type == DXT_DWORD
787                  && !('0' <= words[w][0] && words[w][0] <= '9'))
788         {
789           // assume label
790           is_label = 1;
791         }
792
793         if (is_bss) {
794           fprintf(fout, "0");
795         }
796         else if (is_label) {
797           p = words[w];
798           if (IS_START(p, "loc_") || IS_START(p, "__imp")
799              || strchr(p, '?') || strchr(p, '@')
800              || bsearch(&p, rlist, rlist_cnt, sizeof(rlist[0]),
801                   cmpstringp))
802           {
803             fprintf(fout, "0");
804             snprintf(g_comment, sizeof(g_comment), "%s", p);
805           }
806           else {
807             pp = check_var(fhdr, sym, p, in_export_table);
808             if (pp == NULL) {
809               fprintf(fout, "%s%s",
810                 (no_decorations || p[0] == '_') ? "" : "_", p);
811             }
812             else {
813               if (no_decorations)
814                 fprintf(fout, "%s", pp->name);
815               else
816                 output_decorated_pp(fout, pp);
817             }
818           }
819         }
820         else {
821           val64 = parse_number(words[w], 1);
822           if (val64 < 10)
823             fprintf(fout, "%d", (int)val64);
824           else
825             fprintf(fout, "0x%" PRIx64, val64);
826         }
827
828         first = 0;
829       }
830
831 fin:
832       if (g_comment[0] != 0) {
833         fprintf(fout, "\t\t%c %s", comment_char, g_comment);
834         g_comment[0] = 0;
835       }
836       fprintf(fout, "\n");
837     }
838   }
839
840   fprintf(fout, "\n");
841
842   // dump public syms
843   for (i = 0; i < pub_sym_cnt; i++)
844     fprintf(fout, ".global %s%s\n",
845       no_decorations ? "" : "_", pub_syms[i]);
846
847   fclose(fout);
848   fclose(fasm);
849   if (fhdr != NULL)
850     fclose(fhdr);
851
852   return 0;
853 }
854
855 // vim:ts=2:shiftwidth=2:expandtab