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