yet more APIs
[ia32rtools.git] / tools / protoparse.h
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 struct parsed_proto;
10
11 struct parsed_type {
12         char *name;
13         unsigned int is_array:1;
14         unsigned int is_ptr:1;
15         unsigned int is_struct:1; // split for args
16         unsigned int is_retreg:1; // register to return to caller
17         unsigned int is_va_list:1;
18 };
19
20 struct parsed_proto_arg {
21         char *reg;
22         struct parsed_type type;
23         struct parsed_proto *pp; // fptr or struct
24         void *datap;
25         unsigned int is_saved:1; // not set here, for tool use
26 };
27
28 struct parsed_proto {
29         char name[256];
30         union {
31                 struct parsed_type ret_type;
32                 struct parsed_type type;
33         };
34         struct parsed_proto_arg arg[16];
35         int argc;
36         int argc_stack;
37         int argc_reg;
38         unsigned int is_func:1;
39         unsigned int is_stdcall:1;
40         unsigned int is_fastcall:1;
41         unsigned int is_vararg:1;     // vararg func
42         unsigned int is_fptr:1;
43         unsigned int is_noreturn:1;
44         unsigned int is_unresolved:1;
45         unsigned int is_userstack:1;
46         unsigned int is_include:1;    // not from top-level header
47         unsigned int is_osinc:1;      // OS/system library func
48         unsigned int is_cinc:1;       // crt library func
49         unsigned int is_arg:1;        // declared in some func arg
50         unsigned int has_structarg:1;
51         unsigned int has_retreg:1;
52 };
53
54 struct parsed_struct {
55         char name[256];
56         struct {
57                 int offset;
58                 struct parsed_proto pp;
59         } members[64];
60         int member_count;
61 };
62
63 static const char *hdrfn;
64 static int hdrfline = 0;
65
66 static void pp_copy_arg(struct parsed_proto_arg *d,
67         const struct parsed_proto_arg *s);
68
69 static int b_pp_c_handler(char *proto, const char *fname,
70         int is_include, int is_osinc, int is_cinc);
71 static int struct_handler(FILE *fhdr, char *proto, int *line);
72
73 static int do_protostrs(FILE *fhdr, const char *fname, int is_include)
74 {
75         const char *finc_name;
76         const char *hdrfn_saved;
77         char protostr[256];
78         char path[256];
79         char fname_inc[256];
80         int is_osinc;
81         int is_cinc;
82         FILE *finc;
83         int line = 0;
84         int ret;
85         char *p;
86
87         hdrfn_saved = hdrfn;
88         hdrfn = fname;
89
90         is_cinc = strstr(fname, "stdc.hlist") != NULL;
91         is_osinc = is_cinc || strstr(fname, "win32.hlist") != NULL;
92
93         while (fgets(protostr, sizeof(protostr), fhdr))
94         {
95                 line++;
96                 if (strncmp(protostr, "//#include ", 11) == 0) {
97                         finc_name = protostr + 11;
98                         p = strpbrk(finc_name, "\r\n ");
99                         if (p != NULL)
100                                 *p = 0;
101
102                         path[0] = 0;
103                         p = strrchr(hdrfn_saved, '/');
104                         if (p) {
105                                 memcpy(path, hdrfn_saved,
106                                         p - hdrfn_saved + 1);
107                                 path[p - hdrfn_saved + 1] = 0;
108                         }
109                         snprintf(fname_inc, sizeof(fname_inc), "%s%s", 
110                                 path, finc_name);
111                         finc = fopen(fname_inc, "r");
112                         if (finc == NULL) {
113                                 printf("%s:%d: can't open '%s'\n",
114                                         fname_inc, line, finc_name);
115                                 continue;
116                         }
117                         ret = do_protostrs(finc, finc_name, 1);
118                         fclose(finc);
119                         if (ret < 0)
120                                 break;
121                         continue;
122                 }
123                 if (strncmp(sskip(protostr), "//", 2) == 0)
124                         continue;
125
126                 p = protostr + strlen(protostr);
127                 for (p--; p >= protostr && my_isblank(*p); --p)
128                         *p = 0;
129                 if (p < protostr)
130                         continue;
131
132                 hdrfline = line;
133
134                 if (!strncmp(protostr, "struct", 6)
135                     && strchr(protostr, '{') != NULL)
136                         ret = struct_handler(fhdr, protostr, &line);
137                 else
138                         ret = b_pp_c_handler(protostr, hdrfn,
139                                 is_include, is_osinc, is_cinc);
140                 if (ret < 0)
141                         break;
142         }
143
144         hdrfn = hdrfn_saved;
145
146         if (feof(fhdr))
147                 return 0;
148
149         return -1;
150 }
151
152 static int get_regparm(char *dst, size_t dlen, char *p, int *retreg)
153 {
154         int i = 0, o;
155
156         *retreg = 0;
157
158         if (*p != '<')
159                 return 0;
160
161         i++;
162         if (p[i] == '*') {
163                 *retreg = 1;
164                 i++;
165         }
166
167         for (o = 0; o < dlen; i++) {
168                 if (p[i] == 0)
169                         return 0;
170                 if (p[i] == '>')
171                         break;
172                 dst[o++] = p[i];
173         }
174         dst[o] = 0;
175         return i + 1;
176 }
177
178 // hmh..
179 static const char *known_type_mod[] = {
180         "const",
181         "signed",
182         "unsigned",
183         "enum",
184         "CONST",
185         "volatile",
186 };
187
188 static const char *known_ptr_types[] = {
189         "FARPROC",
190         "WNDPROC",
191         "LINECALLBACK",
192         "HACCEL",
193         "HANDLE",
194         "HBITMAP",
195         "HBRUSH",
196         "HCALL",
197         "HCURSOR",
198         "HDC",
199         "HFONT",
200         "HGDIOBJ",
201         "HGLOBAL",
202         "HHOOK",
203         "HICON",
204         "HINSTANCE",
205         "HIMC", // DWORD in mingw, ptr in wine..
206         "HLINE",
207         "HLINEAPP",
208         "HLOCAL",
209         "HMODULE",
210         "HPALETTE",
211         "HRGN",
212         "HRSRC",
213         "HKEY",
214         "HMENU",
215         "HWAVEOUT",
216         "HWND",
217         "PBYTE",
218         "PCRITICAL_SECTION",
219         "PDWORD",
220         "PFILETIME",
221         "PLARGE_INTEGER",
222         "PHANDLE",
223         "PHKEY",
224         "PLONG",
225         "PMEMORY_BASIC_INFORMATION",
226         "PUINT",
227         "PVOID",
228         "PCVOID",
229         "PWORD",
230         "REFCLSID",
231         "REFIID",
232         "HOOKPROC",
233         "DLGPROC",
234         "TIMERPROC",
235         "WNDENUMPROC",
236         "va_list",
237         "__VALIST",
238 };
239
240 static const char *ignored_keywords[] = {
241         "extern",
242         "WINBASEAPI",
243         "WINUSERAPI",
244         "WINGDIAPI",
245         "WINADVAPI",
246 };
247
248 static int typecmp(const char *n, const char *t)
249 {
250         for (; *t != 0; n++, t++) {
251                 while (n[0] == ' ' && (n[1] == ' ' || n[1] == '*'))
252                         n++;
253                 while (t[0] == ' ' && (t[1] == ' ' || t[1] == '*'))
254                         t++;
255                 if (*n != *t)
256                         return *n - *t;
257         }
258
259         return 0;
260 }
261
262 static const char *skip_type_mod(const char *n)
263 {
264         int len;
265         int i;
266
267         for (i = 0; i < ARRAY_SIZE(known_type_mod); i++) {
268                 len = strlen(known_type_mod[i]);
269                 if (strncmp(n, known_type_mod[i], len) != 0)
270                         continue;
271                 if (!my_isblank(n[len]))
272                         continue;
273
274                 n += len;
275                 while (my_isblank(*n))
276                         n++;
277                 i = 0;
278         }
279
280         return n;
281 }
282
283 static int check_type(const char *name, struct parsed_type *type)
284 {
285         const char *n, *n1;
286         int ret = -1;
287         int i;
288
289         n = skip_type_mod(name);
290
291         if (!strncmp(n, "struct", 6) && my_isblank(n[6])) {
292                 type->is_struct = 1;
293
294                 n += 6;
295                 while (my_isblank(*n))
296                         n++;
297         }
298
299         for (i = 0; i < ARRAY_SIZE(known_ptr_types); i++) {
300                 if (typecmp(n, known_ptr_types[i]))
301                         continue;
302
303                 type->is_ptr = 1;
304                 break;
305         }
306
307         if (n[0] == 'L' && n[1] == 'P' && strncmp(n, "LPARAM", 6))
308                 type->is_ptr = 1;
309
310         // assume single word
311         while (!my_isblank(*n) && !my_issep(*n))
312                 n++;
313
314         while (1) {
315                 n1 = n;
316                 while (my_isblank(*n))
317                         n++;
318                 if (*n == '*') {
319                         type->is_ptr = 1;
320                         n++;
321                         continue;
322                 }
323                 break;
324         }
325
326         ret = n1 - name;
327         type->name = strndup(name, ret);
328         if (IS(type->name, "__VALIST") || IS(type->name, "va_list"))
329                 type->is_va_list = 1;
330         if (IS(type->name, "VOID"))
331                 memcpy(type->name, "void", 4);
332
333         return ret;
334 }
335
336 /* args are always expanded to 32bit */
337 static const char *map_reg(const char *reg)
338 {
339         const char *regs_f[] = { "eax", "ebx", "ecx", "edx", "esi", "edi" };
340         const char *regs_w[] = { "ax",  "bx",  "cx",  "dx",  "si",  "di" };
341         const char *regs_b[] = { "al",  "bl",  "cl",  "dl" };
342         int i;
343
344         for (i = 0; i < ARRAY_SIZE(regs_w); i++)
345                 if (IS(reg, regs_w[i]))
346                         return regs_f[i];
347
348         for (i = 0; i < ARRAY_SIZE(regs_b); i++)
349                 if (IS(reg, regs_b[i]))
350                         return regs_f[i];
351
352         return reg;
353 }
354
355 static int check_struct_arg(struct parsed_proto_arg *arg)
356 {
357         if (IS(arg->type.name, "POINT"))
358                 return 2 - 1;
359
360         return 0;
361 }
362
363 static int parse_protostr(char *protostr, struct parsed_proto *pp);
364
365 static int parse_arg(char **p_, struct parsed_proto_arg *arg, int xarg)
366 {
367         char buf[256];
368         char *p = *p_;
369         char *pe;
370         int ret;
371
372         arg->pp = calloc(1, sizeof(*arg->pp));
373         my_assert_not(arg->pp, NULL);
374         arg->pp->is_arg = 1;
375
376         pe = p;
377         while (1) {
378                 pe = strpbrk(pe, ",()");
379                 if (pe == NULL)
380                         return -1;
381                 if (*pe == ',' || *pe == ')')
382                         break;
383                 pe = strchr(pe, ')');
384                 if (pe == NULL)
385                         return -1;
386                 pe++;
387         }
388
389         if (pe - p > sizeof(buf) - 1)
390                 return -1;
391         memcpy(buf, p, pe - p);
392         buf[pe - p] = 0;
393
394         ret = parse_protostr(buf, arg->pp);
395         if (ret < 0)
396                 return -1;
397
398         // we don't use actual names right now...
399         snprintf(arg->pp->name, sizeof(arg->pp->name), "a%d", xarg);
400
401         if (!arg->type.is_struct)
402                 // we'll treat it as void * for non-calls
403                 arg->type.name = strdup("void *");
404         arg->type.is_ptr = 1;
405
406         p += ret;
407         *p_ = p;
408         return 0;
409 }
410
411 static int parse_protostr(char *protostr, struct parsed_proto *pp)
412 {
413         struct parsed_proto_arg *arg;
414         char regparm[16];
415         char buf[256];
416         char cconv[32];
417         int is_retreg;
418         char *p, *p1;
419         int xarg = 0;
420         int i, l;
421         int ret;
422
423         p = sskip(protostr);
424         if (p[0] == '/' && p[1] == '/') {
425                 printf("%s:%d: commented out?\n", hdrfn, hdrfline);
426                 p = sskip(p + 2);
427         }
428
429         // allow start of line comment
430         if (p[0] == '/' && p[1] == '*') {
431                 p = strstr(p + 2, "*/");
432                 if (p == NULL) {
433                         printf("%s:%d: multiline comments unsupported\n",
434                                 hdrfn, hdrfline);
435                         return -1;
436                 }
437                 p = sskip(p + 2);
438         }
439
440         // we need remaining hints in comments, so strip / *
441         for (p1 = p; p1[0] != 0 && p1[1] != 0; p1++) {
442                 if ((p1[0] == '/' && p1[1] == '*')
443                  || (p1[0] == '*' && p1[1] == '/'))
444                         p1[0] = p1[1] = ' ';
445         }
446
447         if (!strncmp(p, "DECLSPEC_NORETURN ", 18)) {
448                 pp->is_noreturn = 1;
449                 p = sskip(p + 18);
450         }
451
452         for (i = 0; i < ARRAY_SIZE(ignored_keywords); i++) {
453                 l = strlen(ignored_keywords[i]);
454                 if (!strncmp(p, ignored_keywords[i], l) && my_isblank(p[l]))
455                         p = sskip(p + l + 1);
456         }
457
458         ret = check_type(p, &pp->ret_type);
459         if (ret <= 0) {
460                 printf("%s:%d:%zd: unhandled return in '%s'\n",
461                         hdrfn, hdrfline, (p - protostr) + 1, protostr);
462                 return -1;
463         }
464         p = sskip(p + ret);
465
466         if (!strncmp(p, "noreturn ", 9)) {
467                 pp->is_noreturn = 1;
468                 p = sskip(p + 9);
469         }
470
471         if (!strchr(p, ')')) {
472                 p = next_idt(buf, sizeof(buf), p);
473                 p = sskip(p);
474                 if (!pp->is_arg && buf[0] == 0) {
475                         printf("%s:%d:%zd: var name is missing\n",
476                                 hdrfn, hdrfline, (p - protostr) + 1);
477                         return -1;
478                 }
479                 strcpy(pp->name, buf);
480
481                 p1 = strchr(p, ']');
482                 if (p1 != NULL) {
483                         p = p1 + 1;
484                         pp->ret_type.is_array = 1;
485                 }
486                 return p - protostr;
487         }
488
489         pp->is_func = 1;
490
491         if (*p == '(') {
492                 pp->is_fptr = 1;
493                 p = sskip(p + 1);
494         }
495
496         p = next_word(cconv, sizeof(cconv), p);
497         p = sskip(p);
498         if (cconv[0] == 0) {
499                 printf("%s:%d:%zd: cconv missing\n",
500                         hdrfn, hdrfline, (p - protostr) + 1);
501                 return -1;
502         }
503         if      (IS(cconv, "__cdecl"))
504                 pp->is_stdcall = 0;
505         else if (IS(cconv, "__stdcall"))
506                 pp->is_stdcall = 1;
507         else if (IS(cconv, "__fastcall")) {
508                 pp->is_fastcall = 1;
509                 pp->is_stdcall = 1; // sort of..
510         }
511         else if (IS(cconv, "__thiscall"))
512                 pp->is_stdcall = 1;
513         else if (IS(cconv, "__userpurge"))
514                 pp->is_stdcall = 1; // IDA
515         else if (IS(cconv, "__usercall"))
516                 pp->is_stdcall = 0; // IDA
517         else if (IS(cconv, "__userstack")) {
518                 pp->is_stdcall = 0; // custom
519                 pp->is_userstack = 1;
520         }
521         else if (IS(cconv, "WINAPI") || IS(cconv, "PASCAL"))
522                 pp->is_stdcall = 1;
523         else {
524                 printf("%s:%d:%zd: unhandled cconv: '%s'\n",
525                         hdrfn, hdrfline, (p - protostr) + 1, cconv);
526                 return -1;
527         }
528
529         if (pp->is_fptr) {
530                 if (*p != '*') {
531                         printf("%s:%d:%zd: '*' expected\n",
532                                 hdrfn, hdrfline, (p - protostr) + 1);
533                         return -1;
534                 }
535                 p++;
536                 // XXX: skipping extra asterisks, for now
537                 while (*p == '*')
538                         p++;
539                 p = sskip(p);
540         }
541
542         p = next_idt(buf, sizeof(buf), p);
543         p = sskip(p);
544         if (buf[0] == 0) {
545                 //printf("%s:%d:%zd: func name missing\n",
546                 //      hdrfn, hdrfline, (p - protostr) + 1);
547                 //return -1;
548         }
549         strcpy(pp->name, buf);
550
551         ret = get_regparm(regparm, sizeof(regparm), p, &is_retreg);
552         if (ret > 0) {
553                 if (!IS(regparm, "eax") && !IS(regparm, "ax")
554                  && !IS(regparm, "al") && !IS(regparm, "edx:eax"))
555                 {
556                         printf("%s:%d:%zd: bad regparm: %s\n",
557                                 hdrfn, hdrfline, (p - protostr) + 1, regparm);
558                         return -1;
559                 }
560                 p += ret;
561                 p = sskip(p);
562         }
563
564         if (pp->is_fptr) {
565                 if (*p == '[') {
566                         // not really ret_type is array, but ohwell
567                         pp->ret_type.is_array = 1;
568                         p = strchr(p + 1, ']');
569                         if (p == NULL) {
570                                 printf("%s:%d:%zd: ']' expected\n",
571                                  hdrfn, hdrfline, (p - protostr) + 1);
572                                 return -1;
573                         }
574                         p = sskip(p + 1);
575                 }
576                 if (*p != ')') {
577                         printf("%s:%d:%zd: ')' expected\n",
578                                 hdrfn, hdrfline, (p - protostr) + 1);
579                         return -1;
580                 }
581                 p = sskip(p + 1);
582         }
583
584         if (*p != '(') {
585                 printf("%s:%d:%zd: '(' expected, got '%c'\n",
586                                 hdrfn, hdrfline, (p - protostr) + 1, *p);
587                 return -1;
588         }
589         p++;
590
591         // check for x(void)
592         p = sskip(p);
593         if ((!strncmp(p, "void", 4) || !strncmp(p, "VOID", 4))
594            && *sskip(p + 4) == ')')
595                 p += 4;
596
597         while (1) {
598                 p = sskip(p);
599                 if (*p == ')') {
600                         p++;
601                         break;
602                 }
603                 if (xarg > 0) {
604                         if (*p != ',') {
605                                 printf("%s:%d:%zd: ',' expected\n",
606                                  hdrfn, hdrfline, (p - protostr) + 1);
607                                 return -1;
608                         }
609                         p = sskip(p + 1);
610                 }
611
612                 if (!strncmp(p, "...", 3)) {
613                         pp->is_vararg = 1;
614                         p = sskip(p + 3);
615                         if (*p == ')') {
616                                 p++;
617                                 break;
618                         }
619                         printf("%s:%d:%zd: ')' expected\n",
620                                 hdrfn, hdrfline, (p - protostr) + 1);
621                         return -1;
622                 }
623
624                 arg = &pp->arg[xarg];
625                 xarg++;
626
627                 p1 = p;
628                 ret = check_type(p, &arg->type);
629                 if (ret <= 0) {
630                         printf("%s:%d:%zd: unhandled type for arg%d\n",
631                                 hdrfn, hdrfline, (p - protostr) + 1, xarg);
632                         return -1;
633                 }
634                 p = sskip(p + ret);
635
636                 if (*p == '(' || arg->type.is_struct) {
637                         // func ptr or struct
638                         ret = parse_arg(&p1, arg, xarg);
639                         if (ret < 0) {
640                                 printf("%s:%d:%zd: funcarg parse failed\n",
641                                         hdrfn, hdrfline, p1 - protostr);
642                                 return -1;
643                         }
644                         p = p1;
645                 }
646
647                 p = next_idt(buf, sizeof(buf), p);
648                 p = sskip(p);
649 #if 0
650                 if (buf[0] == 0) {
651                         printf("%s:%d:%zd: idt missing for arg%d\n",
652                                 hdrfn, hdrfline, (p - protostr) + 1, xarg);
653                         return -1;
654                 }
655 #endif
656                 arg->reg = NULL;
657
658                 ret = get_regparm(regparm, sizeof(regparm), p, &is_retreg);
659                 if (ret > 0) {
660                         p += ret;
661                         p = sskip(p);
662
663                         arg->reg = strdup(map_reg(regparm));
664                         arg->type.is_retreg = is_retreg;
665                         pp->has_retreg |= is_retreg;
666                 }
667
668                 if (strstr(arg->type.name, "int64")
669                     || IS(arg->type.name, "double"))
670                 {
671                         // hack..
672                         free(arg->type.name);
673                         arg->type.name = strdup("int");
674                         pp_copy_arg(&pp->arg[xarg], arg);
675                         xarg++;
676                 }
677
678                 ret = check_struct_arg(arg);
679                 if (ret > 0) {
680                         pp->has_structarg = 1;
681                         arg->type.is_struct = 1;
682                         free(arg->type.name);
683                         arg->type.name = strdup("int");
684                         for (l = 0; l < ret; l++) {
685                                 pp_copy_arg(&pp->arg[xarg], arg);
686                                 xarg++;
687                         }
688                 }
689         }
690
691         if (xarg > 0 && (IS(cconv, "__fastcall") || IS(cconv, "__thiscall"))) {
692                 if (pp->arg[0].reg != NULL) {
693                         printf("%s:%d: %s with arg1 spec %s?\n",
694                                 hdrfn, hdrfline, cconv, pp->arg[0].reg);
695                 }
696                 pp->arg[0].reg = strdup("ecx");
697         }
698
699         if (xarg > 1 && IS(cconv, "__fastcall")) {
700                 if (pp->arg[1].reg != NULL) {
701                         printf("%s:%d: %s with arg2 spec %s?\n",
702                                 hdrfn, hdrfline, cconv, pp->arg[1].reg);
703                 }
704                 pp->arg[1].reg = strdup("edx");
705         }
706
707         pp->argc = xarg;
708
709         for (i = 0; i < pp->argc; i++) {
710                 if (pp->arg[i].reg == NULL)
711                         pp->argc_stack++;
712                 else
713                         pp->argc_reg++;
714         }
715
716         if (pp->argc == 1 && pp->arg[0].reg != NULL
717             && IS(pp->arg[0].reg, "ecx"))
718         {
719                 pp->is_fastcall = 1;
720         }
721         else if (pp->argc_reg == 2
722           && pp->arg[0].reg != NULL && IS(pp->arg[0].reg, "ecx")
723           && pp->arg[1].reg != NULL && IS(pp->arg[1].reg, "edx"))
724         {
725                 pp->is_fastcall = 1;
726         }
727
728         if (pp->is_vararg && (pp->is_stdcall || pp->is_fastcall)) {
729                 printf("%s:%d: vararg %s?\n", hdrfn, hdrfline, cconv);
730                 return -1;
731         }
732
733         return p - protostr;
734 }
735
736 static int pp_name_cmp(const void *p1, const void *p2)
737 {
738         const struct parsed_proto *pp1 = p1, *pp2 = p2;
739         return strcmp(pp1->name, pp2->name);
740 }
741
742 static int ps_name_cmp(const void *p1, const void *p2)
743 {
744         const struct parsed_struct *ps1 = p1, *ps2 = p2;
745         return strcmp(ps1->name, ps2->name);
746 }
747
748 // parsed struct cache
749 static struct parsed_struct *ps_cache;
750 static int ps_cache_size;
751 static int ps_cache_alloc;
752
753 static int struct_handler(FILE *fhdr, char *proto, int *line)
754 {
755         struct parsed_struct *ps;
756         char lstr[256], *p;
757         int offset = 0;
758         int m = 0;
759         int ret;
760
761         if (ps_cache_size >= ps_cache_alloc) {
762                 ps_cache_alloc = ps_cache_alloc * 2 + 64;
763                 ps_cache = realloc(ps_cache, ps_cache_alloc
764                                 * sizeof(ps_cache[0]));
765                 my_assert_not(ps_cache, NULL);
766                 memset(ps_cache + ps_cache_size, 0,
767                         (ps_cache_alloc - ps_cache_size)
768                          * sizeof(ps_cache[0]));
769         }
770
771         ps = &ps_cache[ps_cache_size++];
772         ret = sscanf(proto, "struct %255s {", ps->name);
773         if (ret != 1) {
774                 printf("%s:%d: struct parse failed\n", hdrfn, *line);
775                 return -1;
776         }
777
778         while (fgets(lstr, sizeof(lstr), fhdr))
779         {
780                 (*line)++;
781
782                 p = sskip(lstr);
783                 if (p[0] == '/' && p[1] == '/')
784                         continue;
785                 if (p[0] == '}')
786                         break;
787
788                 if (m >= ARRAY_SIZE(ps->members)) {
789                         printf("%s:%d: too many struct members\n",
790                                 hdrfn, *line);
791                         return -1;
792                 }
793
794                 hdrfline = *line;
795                 ret = parse_protostr(p, &ps->members[m].pp);
796                 if (ret < 0) {
797                         printf("%s:%d: struct member #%d/%02x "
798                                 "doesn't parse\n", hdrfn, *line,
799                                 m, offset);
800                         return -1;
801                 }
802                 ps->members[m].offset = offset;
803                 offset += 4;
804                 m++;
805         }
806
807         ps->member_count = m;
808
809         return 0;
810 }
811
812 // parsed proto cache
813 static struct parsed_proto *pp_cache;
814 static int pp_cache_size;
815 static int pp_cache_alloc;
816
817 static int b_pp_c_handler(char *proto, const char *fname,
818         int is_include, int is_osinc, int is_cinc)
819 {
820         int ret;
821
822         if (pp_cache_size >= pp_cache_alloc) {
823                 pp_cache_alloc = pp_cache_alloc * 2 + 64;
824                 pp_cache = realloc(pp_cache, pp_cache_alloc
825                                 * sizeof(pp_cache[0]));
826                 my_assert_not(pp_cache, NULL);
827                 memset(pp_cache + pp_cache_size, 0,
828                         (pp_cache_alloc - pp_cache_size)
829                          * sizeof(pp_cache[0]));
830         }
831
832         ret = parse_protostr(proto, &pp_cache[pp_cache_size]);
833         if (ret < 0)
834                 return -1;
835
836         pp_cache[pp_cache_size].is_include = is_include;
837         pp_cache[pp_cache_size].is_osinc = is_osinc;
838         pp_cache[pp_cache_size].is_cinc = is_cinc;
839         pp_cache_size++;
840         return 0;
841 }
842
843 static void build_caches(FILE *fhdr)
844 {
845         long pos;
846         int ret;
847
848         pos = ftell(fhdr);
849         rewind(fhdr);
850
851         ret = do_protostrs(fhdr, hdrfn, 0);
852         if (ret < 0)
853                 exit(1);
854
855         qsort(pp_cache, pp_cache_size, sizeof(pp_cache[0]), pp_name_cmp);
856         qsort(ps_cache, ps_cache_size, sizeof(ps_cache[0]), ps_name_cmp);
857         fseek(fhdr, pos, SEEK_SET);
858 }
859
860 static const struct parsed_proto *proto_parse(FILE *fhdr, const char *sym,
861         int quiet)
862 {
863         const struct parsed_proto *pp_ret;
864         struct parsed_proto pp_search;
865         char *p;
866
867         if (pp_cache == NULL)
868                 build_caches(fhdr);
869
870         if (sym[0] == '_') // && strncmp(fname, "stdc", 4) == 0)
871                 sym++;
872
873         strcpy(pp_search.name, sym);
874         p = strchr(pp_search.name, '@');
875         if (p != NULL)
876                 *p = 0;
877
878         pp_ret = bsearch(&pp_search, pp_cache, pp_cache_size,
879                         sizeof(pp_cache[0]), pp_name_cmp);
880         if (pp_ret == NULL && !quiet)
881                 printf("%s: sym '%s' is missing\n", hdrfn, sym);
882
883         return pp_ret;
884 }
885
886 static const struct parsed_proto *proto_lookup_struct(FILE *fhdr,
887         const char *type, int offset)
888 {
889         struct parsed_struct ps_search, *ps;
890         int m;
891
892         if (pp_cache == NULL)
893                 build_caches(fhdr);
894         if (ps_cache_size == 0)
895                 return NULL;
896
897         while (my_isblank(*type))
898                 type++;
899         if (!strncmp(type, "struct", 6) && my_isblank(type[6]))
900                 type += 7;
901
902         if (sscanf(type, "%255s", ps_search.name) != 1)
903                 return NULL;
904
905         ps = bsearch(&ps_search, ps_cache, ps_cache_size,
906                         sizeof(ps_cache[0]), ps_name_cmp);
907         if (ps == NULL) {
908                 printf("%s: struct '%s' is missing\n",
909                         hdrfn, ps_search.name);
910                 return NULL;
911         }
912
913         for (m = 0; m < ps->member_count; m++) {
914                 if (ps->members[m].offset == offset)
915                         return &ps->members[m].pp;
916         }
917
918         return NULL;
919 }
920
921 static void pp_copy_arg(struct parsed_proto_arg *d,
922         const struct parsed_proto_arg *s)
923 {
924         memcpy(d, s, sizeof(*d));
925
926         if (s->reg != NULL) {
927                 d->reg = strdup(s->reg);
928                 my_assert_not(d->reg, NULL);
929         }
930         if (s->type.name != NULL) {
931                 d->type.name = strdup(s->type.name);
932                 my_assert_not(d->type.name, NULL);
933         }
934         if (s->pp != NULL) {
935                 d->pp = malloc(sizeof(*d->pp));
936                 my_assert_not(d->pp, NULL);
937                 memcpy(d->pp, s->pp, sizeof(*d->pp));
938         }
939 }
940
941 struct parsed_proto *proto_clone(const struct parsed_proto *pp_c)
942 {
943         struct parsed_proto *pp;
944         int i;
945
946         pp = malloc(sizeof(*pp));
947         my_assert_not(pp, NULL);
948         memcpy(pp, pp_c, sizeof(*pp)); // lazy..
949
950         // do the actual deep copy..
951         for (i = 0; i < pp_c->argc; i++)
952                 pp_copy_arg(&pp->arg[i], &pp_c->arg[i]);
953         if (pp_c->ret_type.name != NULL)
954                 pp->ret_type.name = strdup(pp_c->ret_type.name);
955
956         return pp;
957 }
958
959
960 static inline int pp_cmp_func(const struct parsed_proto *pp1,
961   const struct parsed_proto *pp2)
962 {
963   int i;
964
965   if (pp1->argc != pp2->argc || pp1->argc_reg != pp2->argc_reg)
966     return 1;
967   else {
968     for (i = 0; i < pp1->argc; i++) {
969       if ((pp1->arg[i].reg != NULL) != (pp2->arg[i].reg != NULL))
970         return 1;
971
972       if ((pp1->arg[i].reg != NULL)
973         && !IS(pp1->arg[i].reg, pp2->arg[i].reg))
974       {
975         return 1;
976       }
977     }
978   }
979
980   return 0;
981 }
982
983 static inline void pp_print(char *buf, size_t buf_size,
984   const struct parsed_proto *pp)
985 {
986   size_t l;
987   int i;
988
989   snprintf(buf, buf_size, "%s %s(", pp->ret_type.name, pp->name);
990   l = strlen(buf);
991
992   for (i = 0; i < pp->argc_reg; i++) {
993     snprintf(buf + l, buf_size - l, "%s%s",
994       i == 0 ? "" : ", ", pp->arg[i].reg);
995     l = strlen(buf);
996   }
997   if (pp->argc_stack > 0) {
998     snprintf(buf + l, buf_size - l, "%s{%d stack}",
999       i == 0 ? "" : ", ", pp->argc_stack);
1000     l = strlen(buf);
1001   }
1002   snprintf(buf + l, buf_size - l, ")");
1003 }
1004
1005 static inline void proto_release(struct parsed_proto *pp)
1006 {
1007         int i;
1008
1009         for (i = 0; i < pp->argc; i++) {
1010                 if (pp->arg[i].reg != NULL)
1011                         free(pp->arg[i].reg);
1012                 if (pp->arg[i].type.name != NULL)
1013                         free(pp->arg[i].type.name);
1014                 if (pp->arg[i].pp != NULL)
1015                         free(pp->arg[i].pp);
1016         }
1017         if (pp->ret_type.name != NULL)
1018                 free(pp->ret_type.name);
1019         free(pp);
1020
1021         (void)proto_lookup_struct;
1022 }