abcdd41591cb4bb63f551e2359a07ff394079619
[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_compatible_func(pp_sym, pp)) {
310     pp_print(fp_sym, sizeof(fp_sym), pp_sym);
311     anote("entry: %s\n", fp_var);
312     anote("label: %s\n", fp_sym);
313     awarn("^ mismatch\n");
314   }
315
316   return pp;
317 }
318
319 static void output_decorated_pp(FILE *fout,
320   const struct parsed_proto *pp)
321 {
322   if (pp->name[0] != '_')
323     fprintf(fout, pp->is_fastcall ? "@" : "_");
324   fprintf(fout, "%s", pp->name);
325   if (pp->is_stdcall && pp->argc > 0)
326     fprintf(fout, "@%d", pp->argc * 4);
327 }
328
329 static int align_value(int src_val)
330 {
331   if (src_val <= 0) {
332     awarn("bad align: %d\n", src_val);
333     src_val = 1;
334   }
335   if (!g_arm_mode)
336     return src_val;
337
338   return __builtin_ffs(src_val) - 1;
339 }
340
341 static int cmpstringp(const void *p1, const void *p2)
342 {
343   return strcmp(*(char * const *)p1, *(char * const *)p2);
344 }
345
346 /* XXX: maybe move to external file? */
347 static const char *unwanted_syms[] = {
348   "aRuntimeError",
349   "aTlossError",
350   "aSingError",
351   "aDomainError",
352   "aR6029ThisAppli",
353   "aR6028UnableToI",
354   "aR6027NotEnough",
355   "aR6026NotEnough",
356   "aR6025PureVirtu",
357   "aR6024NotEnough",
358   "aR6019UnableToO",
359   "aR6018Unexpecte",
360   "aR6017Unexpecte",
361   "aR6016NotEnough",
362   "aAbnormalProgra",
363   "aR6009NotEnough",
364   "aR6008NotEnough",
365   "aR6002FloatingP",
366   "aMicrosoftVisua",
367   "aRuntimeErrorPr",
368   "aThisApplicatio",
369   "aMicrosoftFindF",
370   "aMicrosoftOffic",
371 };
372
373 static int is_unwanted_sym(const char *sym)
374 {
375   return bsearch(&sym, unwanted_syms, ARRAY_SIZE(unwanted_syms),
376     sizeof(unwanted_syms[0]), cmpstringp) != NULL;
377 }
378
379 int main(int argc, char *argv[])
380 {
381   FILE *fout, *fasm, *fhdr = NULL, *frlist;
382   const struct parsed_proto *pp;
383   int no_decorations = 0;
384   int in_export_table = 0;
385   int rm_labels_lines = 0;
386   char comment_char = '#';
387   char words[20][256];
388   char word[256];
389   char line[256];
390   char last_sym[32];
391   unsigned long val;
392   unsigned long cnt;
393   uint64_t val64;
394   const char *sym;
395   enum dx_type type;
396   char **pub_syms;
397   int pub_sym_cnt = 0;
398   int pub_sym_alloc;
399   char **rlist;
400   int rlist_cnt = 0;
401   int rlist_alloc;
402   int header_mode = 0;
403   int is_ro = 0;
404   int is_label;
405   int is_bss;
406   int wordc;
407   int first;
408   int arg_out;
409   int arg = 1;
410   int len;
411   int w, i;
412   char *p;
413   char *p2;
414
415   if (argc < 4) {
416     // -nd: no symbol decorations
417     printf("usage:\n%s [-nd] [-i] [-a] <.s> <.asm> <hdrf> [rlist]*\n"
418            "%s -hdr <.h> <.asm>\n",
419       argv[0], argv[0]);
420     return 1;
421   }
422
423   for (arg = 1; arg < argc; arg++) {
424     if (IS(argv[arg], "-nd"))
425       no_decorations = 1;
426     else if (IS(argv[arg], "-i"))
427       g_cconv_novalidate = 1;
428     else if (IS(argv[arg], "-a")) {
429       comment_char = '@';
430       g_arm_mode = 1;
431     }
432     else if (IS(argv[arg], "-hdr"))
433       header_mode = 1;
434     else
435       break;
436   }
437
438   arg_out = arg++;
439
440   asmfn = argv[arg++];
441   fasm = fopen(asmfn, "r");
442   my_assert_not(fasm, NULL);
443
444   if (!header_mode) {
445     hdrfn = argv[arg++];
446     fhdr = fopen(hdrfn, "r");
447     my_assert_not(fhdr, NULL);
448   }
449
450   fout = fopen(argv[arg_out], "w");
451   my_assert_not(fout, NULL);
452
453   pub_sym_alloc = 64;
454   pub_syms = malloc(pub_sym_alloc * sizeof(pub_syms[0]));
455   my_assert_not(pub_syms, NULL);
456
457   rlist_alloc = 64;
458   rlist = malloc(rlist_alloc * sizeof(rlist[0]));
459   my_assert_not(rlist, NULL);
460
461   for (; arg < argc; arg++) {
462     frlist = fopen(argv[arg], "r");
463     my_assert_not(frlist, NULL);
464
465     while (my_fgets(line, sizeof(line), frlist)) {
466       p = sskip(line);
467       if (*p == 0 || *p == ';' || *p == '#')
468         continue;
469
470       p = next_word(words[0], sizeof(words[0]), p);
471       if (words[0][0] == 0)
472         continue;
473
474       if (rlist_cnt >= rlist_alloc) {
475         rlist_alloc = rlist_alloc * 2 + 64;
476         rlist = realloc(rlist, rlist_alloc * sizeof(rlist[0]));
477         my_assert_not(rlist, NULL);
478       }
479       rlist[rlist_cnt++] = strdup(words[0]);
480     }
481
482     fclose(frlist);
483     frlist = NULL;
484   }
485
486   if (rlist_cnt > 0)
487     qsort(rlist, rlist_cnt, sizeof(rlist[0]), cmpstringp);
488
489   qsort(unwanted_syms, ARRAY_SIZE(unwanted_syms),
490     sizeof(unwanted_syms[0]), cmpstringp);
491
492   last_sym[0] = 0;
493
494   while (1) {
495     next_section(fasm, line);
496     if (feof(fasm))
497       break;
498     if (IS(line + 1, "text"))
499       continue;
500
501     if (IS(line + 1, "rdata")) {
502       is_ro = 1;
503       if (!header_mode)
504         fprintf(fout, "\n.section .rodata\n");
505     }
506     else if (IS(line + 1, "data")) {
507       is_ro = 0;
508       if (!header_mode)
509         fprintf(fout, "\n.data\n");
510     }
511     else
512       aerr("unhandled section: '%s'\n", line);
513
514     if (!header_mode)
515       fprintf(fout, ".align %d\n", align_value(4));
516
517     while (my_fgets(line, sizeof(line), fasm))
518     {
519       sym = NULL;
520       asmln++;
521
522       p = sskip(line);
523       if (*p == 0)
524         continue;
525
526       if (*p == ';') {
527         if (IS_START(p, ";org") && sscanf(p + 5, "%Xh", &i) == 1) {
528           // ;org is only seen at section start, so assume . addr 0
529           i &= 0xfff;
530           if (i != 0 && !header_mode)
531             fprintf(fout, "\t\t  .skip 0x%x\n", i);
532         }
533         else if (IS_START(p, "; Export Address"))
534           in_export_table = 1;
535         else if (IS_START(p, "; Export"))
536           in_export_table = 0;
537         continue;
538       }
539
540       for (wordc = 0; wordc < ARRAY_SIZE(words); wordc++) {
541         p = sskip(next_word_s(words[wordc], sizeof(words[0]), p));
542         if (*p == 0 || *p == ';') {
543           wordc++;
544           break;
545         }
546         if (*p == ',') {
547           p = sskip(p + 1);
548         }
549       }
550
551       if (*p == ';') {
552         p = sskip(p + 1);
553         if (IS_START(p, "sctclrtype"))
554           g_func_sym_pp = NULL;
555       }
556
557       if (wordc == 2 && IS(words[1], "ends"))
558         break;
559       if (wordc <= 2 && IS(words[0], "end"))
560         break;
561       if (wordc < 2)
562         aerr("unhandled: '%s'\n", words[0]);
563
564       // don't cares
565       if (IS(words[0], "assume"))
566         continue;
567
568       if (IS(words[0], "align")) {
569         if (header_mode)
570           continue;
571
572         val = parse_number(words[1], 0);
573         fprintf(fout, "\t\t  .align %d", align_value(val));
574         goto fin;
575       }
576
577       if (IS(words[0], "public")) {
578         // skip, sym should appear in header anyway
579         continue;
580       }
581
582       w = 1;
583       type = parse_dx_directive(words[0]);
584       if (type == DXT_UNSPEC) {
585         type = parse_dx_directive(words[1]);
586         sym = words[0];
587         w = 2;
588       }
589       if (type == DXT_UNSPEC)
590         aerr("unhandled decl: '%s %s'\n", words[0], words[1]);
591
592       if (sym != NULL)
593       {
594         if (header_mode) {
595           int is_str = 0;
596
597           fprintf(fout, "extern ");
598           if (is_ro)
599             fprintf(fout, "const ");
600
601           switch (type) {
602           case DXT_BYTE:
603             for (i = w; i < wordc; i++)
604               if (words[i][0] == '\'')
605                 is_str = 1;
606             if (is_str)
607               fprintf(fout, "char     %s[];\n", sym);
608             else
609               fprintf(fout, "uint8_t  %s;\n", sym);
610             break;
611
612           case DXT_WORD:
613             fprintf(fout, "uint16_t %s;\n", sym);
614             break;
615
616           case DXT_DWORD:
617             fprintf(fout, "uint32_t %s;\n", sym);
618             break;
619
620           default:
621             fprintf(fout, "_UNKNOWN %s;\n", sym);
622             break;
623           }
624
625           continue;
626         }
627
628         snprintf(last_sym, sizeof(last_sym), "%s", sym);
629         if (IS_START(sym, "__IMPORT_DESCRIPTOR_"))
630           rm_labels_lines = 5;
631
632         pp = proto_parse(fhdr, sym, 1);
633         if (pp != NULL) {
634           g_func_sym_pp = NULL;
635
636           // public/global name
637           if (pub_sym_cnt >= pub_sym_alloc) {
638             pub_sym_alloc *= 2;
639             pub_syms = realloc(pub_syms, pub_sym_alloc * sizeof(pub_syms[0]));
640             my_assert_not(pub_syms, NULL);
641           }
642           pub_syms[pub_sym_cnt++] = strdup(sym);
643         }
644
645         len = strlen(sym);
646         fprintf(fout, "%s%s:", no_decorations ? "" : "_", sym);
647
648         len += 2;
649         if (len < 8)
650           fprintf(fout, "\t");
651         if (len < 16)
652           fprintf(fout, "\t");
653         if (len <= 16)
654           fprintf(fout, "  ");
655         else
656           fprintf(fout, " ");
657       }
658       else {
659         if (header_mode)
660           continue;
661
662         fprintf(fout, "\t\t  ");
663       }
664
665       // fill out some unwanted strings with zeroes..
666       if (type == DXT_BYTE && words[w][0] == '\''
667         && is_unwanted_sym(last_sym))
668       {
669         len = 0;
670         for (; w < wordc; w++) {
671           if (words[w][0] == '\'') {
672             p = words[w] + 1;
673             for (; *p && *p != '\''; p++)
674               len++;
675           }
676           else {
677             // assume encoded byte
678             len++;
679           }
680         }
681         fprintf(fout, ".skip %d", len);
682         goto fin;
683       }
684       else if (type == DXT_BYTE
685         && (words[w][0] == '\''
686             || (w + 1 < wordc && words[w + 1][0] == '\'')))
687       {
688         // string; use asciz for most common case
689         if (w == wordc - 2 && IS(words[w + 1], "0")) {
690           fprintf(fout, ".asciz \"");
691           wordc--;
692         }
693         else
694           fprintf(fout, ".ascii \"");
695
696         for (; w < wordc; w++) {
697           if (words[w][0] == '\'') {
698             p = words[w] + 1;
699             p2 = strchr(p, '\'');
700             if (p2 == NULL)
701               aerr("unterminated string? '%s'\n", p);
702             memcpy(word, p, p2 - p);
703             word[p2 - p] = 0;
704             fprintf(fout, "%s", escape_string(word));
705           }
706           else {
707             val = parse_number(words[w], 0);
708             if (val & ~0xff)
709               aerr("bad string trailing byte?\n");
710             // unfortunately \xHH is unusable - gas interprets
711             // things like \x27b as 0x7b, so have to use octal here
712             fprintf(fout, "\\%03lo", val);
713           }
714         }
715         fprintf(fout, "\"");
716         goto fin;
717       }
718
719       if (w == wordc - 2) {
720         if (IS_START(words[w + 1], "dup(")) {
721           cnt = parse_number(words[w], 0);
722           p = words[w + 1] + 4;
723           p2 = strchr(p, ')');
724           if (p2 == NULL)
725             aerr("bad dup?\n");
726           memmove(word, p, p2 - p);
727           word[p2 - p] = 0;
728
729           val = 0;
730           if (!IS(word, "?"))
731             val = parse_number(word, 0);
732
733           fprintf(fout, ".fill 0x%02lx,%d,0x%02lx",
734             cnt, type_size(type), val);
735           goto fin;
736         }
737       }
738
739       if (type == DXT_DWORD && words[w][0] == '\''
740         && words[w][5] == '\'' && strlen(words[w]) == 6)
741       {
742         if (w != wordc - 1)
743           aerr("TODO\n");
744
745         p = words[w];
746         val = (p[1] << 24) | (p[2] << 16) | (p[3] << 8) | p[4];
747         fprintf(fout, ".long 0x%lx", val);
748         snprintf(g_comment, sizeof(g_comment), "%s", words[w]);
749         goto fin;
750       }
751
752       if (type >= DXT_DWORD && strchr(words[w], '.'))
753       {
754         if (w != wordc - 1)
755           aerr("TODO\n");
756
757         if (g_arm_mode && type == DXT_TEN) {
758           fprintf(fout, ".fill 10");
759           snprintf(g_comment, sizeof(g_comment), "%s %s",
760             type_name_float(type), words[w]);
761         }
762         else
763           fprintf(fout, "%s %s", type_name_float(type), words[w]);
764         goto fin;
765       }
766
767       first = 1;
768       fprintf(fout, "%s ", type_name(type));
769       for (; w < wordc; w++)
770       {
771         if (!first)
772           fprintf(fout, ", ");
773
774         is_label = is_bss = 0;
775         if (w <= wordc - 2 && IS(words[w], "offset")) {
776           is_label = 1;
777           w++;
778         }
779         else if (IS(words[w], "?")) {
780           is_bss = 1;
781         }
782         else if (type == DXT_DWORD
783                  && !('0' <= words[w][0] && words[w][0] <= '9'))
784         {
785           // assume label
786           is_label = 1;
787         }
788
789         if (is_bss) {
790           fprintf(fout, "0");
791         }
792         else if (is_label) {
793           p = words[w];
794           if (IS_START(p, "loc_") || IS_START(p, "__imp")
795              || strchr(p, '?') || strchr(p, '@')
796              || rm_labels_lines > 0
797              || bsearch(&p, rlist, rlist_cnt, sizeof(rlist[0]),
798                   cmpstringp))
799           {
800             fprintf(fout, "0");
801             snprintf(g_comment, sizeof(g_comment), "%s", p);
802           }
803           else {
804             pp = check_var(fhdr, sym, p, in_export_table);
805             if (pp == NULL) {
806               fprintf(fout, "%s%s",
807                 (no_decorations || p[0] == '_') ? "" : "_", p);
808             }
809             else {
810               if (no_decorations)
811                 fprintf(fout, "%s", pp->name);
812               else
813                 output_decorated_pp(fout, pp);
814             }
815           }
816         }
817         else {
818           val64 = parse_number(words[w], 1);
819           if (val64 < 10)
820             fprintf(fout, "%d", (int)val64);
821           else
822             fprintf(fout, "0x%" PRIx64, val64);
823         }
824
825         first = 0;
826       }
827
828 fin:
829       if (rm_labels_lines > 0)
830         rm_labels_lines--;
831
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