1/* test_plugin.c -- simple linker plugin test
2
3   Copyright 2008, 2009 Free Software Foundation, Inc.
4   Written by Cary Coutant <ccoutant@google.com>.
5
6   This file is part of gold.
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 3 of the License, or
11   (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
21   MA 02110-1301, USA.  */
22
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include "plugin-api.h"
31
32struct claimed_file
33{
34  const char* name;
35  void* handle;
36  int nsyms;
37  struct ld_plugin_symbol* syms;
38  struct claimed_file* next;
39};
40
41struct sym_info
42{
43  int size;
44  char* type;
45  char* bind;
46  char* vis;
47  char* sect;
48  char* name;
49};
50
51static struct claimed_file* first_claimed_file = NULL;
52static struct claimed_file* last_claimed_file = NULL;
53
54static ld_plugin_register_claim_file register_claim_file_hook = NULL;
55static ld_plugin_register_all_symbols_read register_all_symbols_read_hook = NULL;
56static ld_plugin_register_cleanup register_cleanup_hook = NULL;
57static ld_plugin_add_symbols add_symbols = NULL;
58static ld_plugin_get_symbols get_symbols = NULL;
59static ld_plugin_add_input_file add_input_file = NULL;
60static ld_plugin_message message = NULL;
61static ld_plugin_get_input_file get_input_file = NULL;
62static ld_plugin_release_input_file release_input_file = NULL;
63
64#define MAXOPTS 10
65
66static const char *opts[MAXOPTS];
67static int nopts = 0;
68
69enum ld_plugin_status onload(struct ld_plugin_tv *tv);
70enum ld_plugin_status claim_file_hook(const struct ld_plugin_input_file *file,
71                                      int *claimed);
72enum ld_plugin_status all_symbols_read_hook(void);
73enum ld_plugin_status cleanup_hook(void);
74
75static void parse_readelf_line(char*, struct sym_info*);
76
77enum ld_plugin_status
78onload(struct ld_plugin_tv *tv)
79{
80  struct ld_plugin_tv *entry;
81  int api_version = 0;
82  int gold_version = 0;
83  int i;
84
85  for (entry = tv; entry->tv_tag != LDPT_NULL; ++entry)
86    {
87      switch (entry->tv_tag)
88        {
89        case LDPT_API_VERSION:
90          api_version = entry->tv_u.tv_val;
91          break;
92        case LDPT_GOLD_VERSION:
93          gold_version = entry->tv_u.tv_val;
94          break;
95        case LDPT_LINKER_OUTPUT:
96          break;
97        case LDPT_OPTION:
98          if (nopts < MAXOPTS)
99            opts[nopts++] = entry->tv_u.tv_string;
100          break;
101        case LDPT_REGISTER_CLAIM_FILE_HOOK:
102          register_claim_file_hook = entry->tv_u.tv_register_claim_file;
103          break;
104        case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
105          register_all_symbols_read_hook =
106            entry->tv_u.tv_register_all_symbols_read;
107          break;
108        case LDPT_REGISTER_CLEANUP_HOOK:
109          register_cleanup_hook = entry->tv_u.tv_register_cleanup;
110          break;
111        case LDPT_ADD_SYMBOLS:
112          add_symbols = entry->tv_u.tv_add_symbols;
113          break;
114        case LDPT_GET_SYMBOLS:
115          get_symbols = entry->tv_u.tv_get_symbols;
116          break;
117        case LDPT_ADD_INPUT_FILE:
118          add_input_file = entry->tv_u.tv_add_input_file;
119          break;
120        case LDPT_MESSAGE:
121          message = entry->tv_u.tv_message;
122          break;
123        case LDPT_GET_INPUT_FILE:
124          get_input_file = entry->tv_u.tv_get_input_file;
125          break;
126        case LDPT_RELEASE_INPUT_FILE:
127          release_input_file = entry->tv_u.tv_release_input_file;
128          break;
129        default:
130          break;
131        }
132    }
133
134  if (message == NULL)
135    {
136      fprintf(stderr, "tv_message interface missing\n");
137      return LDPS_ERR;
138    }
139
140  if (register_claim_file_hook == NULL)
141    {
142      fprintf(stderr, "tv_register_claim_file_hook interface missing\n");
143      return LDPS_ERR;
144    }
145
146  if (register_all_symbols_read_hook == NULL)
147    {
148      fprintf(stderr, "tv_register_all_symbols_read_hook interface missing\n");
149      return LDPS_ERR;
150    }
151
152  if (register_cleanup_hook == NULL)
153    {
154      fprintf(stderr, "tv_register_cleanup_hook interface missing\n");
155      return LDPS_ERR;
156    }
157
158  (*message)(LDPL_INFO, "API version:   %d", api_version);
159  (*message)(LDPL_INFO, "gold version:  %d", gold_version);
160
161  for (i = 0; i < nopts; ++i)
162    (*message)(LDPL_INFO, "option: %s", opts[i]);
163
164  if ((*register_claim_file_hook)(claim_file_hook) != LDPS_OK)
165    {
166      (*message)(LDPL_ERROR, "error registering claim file hook");
167      return LDPS_ERR;
168    }
169
170  if ((*register_all_symbols_read_hook)(all_symbols_read_hook) != LDPS_OK)
171    {
172      (*message)(LDPL_ERROR, "error registering all symbols read hook");
173      return LDPS_ERR;
174    }
175
176  if ((*register_cleanup_hook)(cleanup_hook) != LDPS_OK)
177    {
178      (*message)(LDPL_ERROR, "error registering cleanup hook");
179      return LDPS_ERR;
180    }
181
182  return LDPS_OK;
183}
184
185enum ld_plugin_status
186claim_file_hook (const struct ld_plugin_input_file* file, int* claimed)
187{
188  int len;
189  off_t end_offset;
190  char buf[160];
191  struct claimed_file* claimed_file;
192  struct ld_plugin_symbol* syms;
193  int nsyms = 0;
194  int maxsyms = 0;
195  FILE* irfile;
196  struct sym_info info;
197  int weak;
198  int def;
199  int vis;
200  int is_comdat;
201  int i;
202
203  (*message)(LDPL_INFO,
204             "%s: claim file hook called (offset = %ld, size = %ld)",
205             file->name, (long)file->offset, (long)file->filesize);
206
207  /* Look for the beginning of output from readelf -s.  */
208  irfile = fdopen(file->fd, "r");
209  (void)fseek(irfile, file->offset, SEEK_SET);
210  end_offset = file->offset + file->filesize;
211  len = fread(buf, 1, 13, irfile);
212  if (len < 13 || strncmp(buf, "\nSymbol table", 13) != 0)
213    return LDPS_OK;
214
215  /* Skip the two header lines.  */
216  (void) fgets(buf, sizeof(buf), irfile);
217  (void) fgets(buf, sizeof(buf), irfile);
218
219  if (add_symbols == NULL)
220    {
221      fprintf(stderr, "tv_add_symbols interface missing\n");
222      return LDPS_ERR;
223    }
224
225  /* Parse the output from readelf. The columns are:
226     Index Value Size Type Binding Visibility Section Name.  */
227  syms = (struct ld_plugin_symbol*)malloc(sizeof(struct ld_plugin_symbol) * 8);
228  if (syms == NULL)
229    return LDPS_ERR;
230  maxsyms = 8;
231  while (ftell(irfile) < end_offset
232         && fgets(buf, sizeof(buf), irfile) != NULL)
233    {
234      parse_readelf_line(buf, &info);
235
236      /* Ignore local symbols.  */
237      if (strncmp(info.bind, "LOCAL", 5) == 0)
238        continue;
239
240      weak = strncmp(info.bind, "WEAK", 4) == 0;
241      if (strncmp(info.sect, "UND", 3) == 0)
242        def = weak ? LDPK_WEAKUNDEF : LDPK_UNDEF;
243      else if (strncmp(info.sect, "COM", 3) == 0)
244        def = LDPK_COMMON;
245      else
246        def = weak ? LDPK_WEAKDEF : LDPK_DEF;
247
248      if (strncmp(info.vis, "INTERNAL", 8) == 0)
249        vis = LDPV_INTERNAL;
250      else if (strncmp(info.vis, "HIDDEN", 6) == 0)
251        vis = LDPV_HIDDEN;
252      else if (strncmp(info.vis, "PROTECTED", 9) == 0)
253        vis = LDPV_PROTECTED;
254      else
255        vis = LDPV_DEFAULT;
256
257      /* If the symbol is listed in the options list, special-case
258         it as a comdat symbol.  */
259      is_comdat = 0;
260      for (i = 0; i < nopts; ++i)
261        {
262          if (info.name != NULL && strcmp(info.name, opts[i]) == 0)
263            {
264              is_comdat = 1;
265              break;
266            }
267        }
268
269      if (nsyms >= maxsyms)
270        {
271          syms = (struct ld_plugin_symbol*)
272            realloc(syms, sizeof(struct ld_plugin_symbol) * maxsyms * 2);
273          if (syms == NULL)
274            return LDPS_ERR;
275          maxsyms *= 2;
276        }
277
278      if (info.name == NULL)
279        syms[nsyms].name = NULL;
280      else
281        {
282          len = strlen(info.name);
283          syms[nsyms].name = malloc(len + 1);
284          strncpy(syms[nsyms].name, info.name, len + 1);
285        }
286      syms[nsyms].version = NULL;
287      syms[nsyms].def = def;
288      syms[nsyms].visibility = vis;
289      syms[nsyms].size = info.size;
290      syms[nsyms].comdat_key = is_comdat ? syms[nsyms].name : NULL;
291      syms[nsyms].resolution = LDPR_UNKNOWN;
292      ++nsyms;
293    }
294
295  claimed_file = (struct claimed_file*) malloc(sizeof(struct claimed_file));
296  if (claimed_file == NULL)
297    return LDPS_ERR;
298
299  claimed_file->name = file->name;
300  claimed_file->handle = file->handle;
301  claimed_file->nsyms = nsyms;
302  claimed_file->syms = syms;
303  claimed_file->next = NULL;
304  if (last_claimed_file == NULL)
305    first_claimed_file = claimed_file;
306  else
307    last_claimed_file->next = claimed_file;
308  last_claimed_file = claimed_file;
309
310  (*message)(LDPL_INFO, "%s: claiming file, adding %d symbols",
311             file->name, nsyms);
312
313  if (nsyms > 0)
314    (*add_symbols)(file->handle, nsyms, syms);
315
316  *claimed = 1;
317  return LDPS_OK;
318}
319
320enum ld_plugin_status
321all_symbols_read_hook(void)
322{
323  int i;
324  const char* res;
325  struct claimed_file* claimed_file;
326  struct ld_plugin_input_file file;
327  FILE* irfile;
328  off_t end_offset;
329  struct sym_info info;
330  int len;
331  char buf[160];
332  char* p;
333  const char* filename;
334
335  (*message)(LDPL_INFO, "all symbols read hook called");
336
337  if (get_symbols == NULL)
338    {
339      fprintf(stderr, "tv_get_symbols interface missing\n");
340      return LDPS_ERR;
341    }
342
343  for (claimed_file = first_claimed_file;
344       claimed_file != NULL;
345       claimed_file = claimed_file->next)
346    {
347      (*get_symbols)(claimed_file->handle, claimed_file->nsyms,
348                     claimed_file->syms);
349
350      for (i = 0; i < claimed_file->nsyms; ++i)
351        {
352          switch (claimed_file->syms[i].resolution)
353            {
354            case LDPR_UNKNOWN:
355              res = "UNKNOWN";
356              break;
357            case LDPR_UNDEF:
358              res = "UNDEF";
359              break;
360            case LDPR_PREVAILING_DEF:
361              res = "PREVAILING_DEF_REG";
362              break;
363            case LDPR_PREVAILING_DEF_IRONLY:
364              res = "PREVAILING_DEF_IRONLY";
365              break;
366            case LDPR_PREEMPTED_REG:
367              res = "PREEMPTED_REG";
368              break;
369            case LDPR_PREEMPTED_IR:
370              res = "PREEMPTED_IR";
371              break;
372            case LDPR_RESOLVED_IR:
373              res = "RESOLVED_IR";
374              break;
375            case LDPR_RESOLVED_EXEC:
376              res = "RESOLVED_EXEC";
377              break;
378            case LDPR_RESOLVED_DYN:
379              res = "RESOLVED_DYN";
380              break;
381            default:
382              res = "?";
383              break;
384            }
385          (*message)(LDPL_INFO, "%s: %s: %s", claimed_file->name,
386                     claimed_file->syms[i].name, res);
387        }
388    }
389
390  if (add_input_file == NULL)
391    {
392      fprintf(stderr, "tv_add_input_file interface missing\n");
393      return LDPS_ERR;
394    }
395  if (get_input_file == NULL)
396    {
397      fprintf(stderr, "tv_get_input_file interface missing\n");
398      return LDPS_ERR;
399    }
400  if (release_input_file == NULL)
401    {
402      fprintf(stderr, "tv_release_input_file interface missing\n");
403      return LDPS_ERR;
404    }
405
406  for (claimed_file = first_claimed_file;
407       claimed_file != NULL;
408       claimed_file = claimed_file->next)
409    {
410      (*get_input_file) (claimed_file->handle, &file);
411
412      /* Look for the beginning of output from readelf -s.  */
413      irfile = fdopen(file.fd, "r");
414      (void)fseek(irfile, file.offset, SEEK_SET);
415      end_offset = file.offset + file.filesize;
416      len = fread(buf, 1, 13, irfile);
417      if (len < 13 || strncmp(buf, "\nSymbol table", 13) != 0)
418        {
419          fprintf(stderr, "%s: can't re-read original input file\n",
420                  claimed_file->name);
421          return LDPS_ERR;
422        }
423
424      /* Skip the two header lines.  */
425      (void) fgets(buf, sizeof(buf), irfile);
426      (void) fgets(buf, sizeof(buf), irfile);
427
428      filename = NULL;
429      while (ftell(irfile) < end_offset
430             && fgets(buf, sizeof(buf), irfile) != NULL)
431        {
432          parse_readelf_line(buf, &info);
433
434          /* Look for file name.  */
435          if (strncmp(info.type, "FILE", 4) == 0)
436            {
437              len = strlen(info.name);
438              p = malloc(len + 1);
439              strncpy(p, info.name, len + 1);
440              filename = p;
441              break;
442            }
443        }
444
445      (*release_input_file) (claimed_file->handle);
446
447      if (filename == NULL)
448        filename = claimed_file->name;
449
450      if (claimed_file->nsyms == 0)
451        continue;
452
453      if (strlen(filename) >= sizeof(buf))
454        {
455          (*message)(LDPL_FATAL, "%s: filename too long", filename);
456          return LDPS_ERR;
457        }
458      strcpy(buf, filename);
459      p = strrchr(buf, '.');
460      if (p == NULL
461          || (strcmp(p, ".syms") != 0
462              && strcmp(p, ".c") != 0
463              && strcmp(p, ".cc") != 0))
464        {
465          (*message)(LDPL_FATAL, "%s: filename has unknown suffix",
466                     filename);
467          return LDPS_ERR;
468        }
469      p[1] = 'o';
470      p[2] = '\0';
471      (*message)(LDPL_INFO, "%s: adding new input file", buf);
472      (*add_input_file)(buf);
473    }
474
475  return LDPS_OK;
476}
477
478enum ld_plugin_status
479cleanup_hook(void)
480{
481  (*message)(LDPL_INFO, "cleanup hook called");
482  return LDPS_OK;
483}
484
485static void
486parse_readelf_line(char* p, struct sym_info* info)
487{
488  int len;
489
490  p += strspn(p, " ");
491
492  /* Index field.  */
493  p += strcspn(p, " ");
494  p += strspn(p, " ");
495
496  /* Value field.  */
497  p += strcspn(p, " ");
498  p += strspn(p, " ");
499
500  /* Size field.  */
501  info->size = atoi(p);
502  p += strcspn(p, " ");
503  p += strspn(p, " ");
504
505  /* Type field.  */
506  info->type = p;
507  p += strcspn(p, " ");
508  p += strspn(p, " ");
509
510  /* Binding field.  */
511  info->bind = p;
512  p += strcspn(p, " ");
513  p += strspn(p, " ");
514
515  /* Visibility field.  */
516  info->vis = p;
517  p += strcspn(p, " ");
518  p += strspn(p, " ");
519
520  /* Section field.  */
521  info->sect = p;
522  p += strcspn(p, " ");
523  p += strspn(p, " ");
524
525  /* Name field.  */
526  /* FIXME:  Look for version.  */
527  len = strlen(p);
528  if (len == 0)
529    p = NULL;
530  else if (p[len-1] == '\n')
531    p[--len] = '\0';
532  info->name = p;
533}
534