1/* Test plugin for the GNU linker.
2   Copyright 2010 Free Software Foundation, Inc.
3
4   This file is part of the GNU Binutils.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
19   MA 02110-1301, USA.  */
20
21#include "sysdep.h"
22#include "bfd.h"
23#include "plugin-api.h"
24/* For ARRAY_SIZE macro only - we don't link the library itself.  */
25#include "libiberty.h"
26
27extern enum ld_plugin_status onload (struct ld_plugin_tv *tv);
28static enum ld_plugin_status onclaim_file (const struct ld_plugin_input_file *file,
29				int *claimed);
30static enum ld_plugin_status onall_symbols_read (void);
31static enum ld_plugin_status oncleanup (void);
32
33/* Helper for calling plugin api message function.  */
34#define TV_MESSAGE if (tv_message) (*tv_message)
35
36/* Struct for recording files to claim / files claimed.  */
37typedef struct claim_file
38{
39  struct claim_file *next;
40  struct ld_plugin_input_file file;
41  bfd_boolean claimed;
42  struct ld_plugin_symbol *symbols;
43  int n_syms_allocated;
44  int n_syms_used;
45} claim_file_t;
46
47/* Types of things that can be added at all symbols read time.  */
48typedef enum addfile_enum
49{
50  ADD_FILE,
51  ADD_LIB,
52  ADD_DIR
53} addfile_enum_t;
54
55/* Struct for recording files to add to final link.  */
56typedef struct add_file
57{
58  struct add_file *next;
59  const char *name;
60  addfile_enum_t type;
61} add_file_t;
62
63/* Helper macro for defining array of transfer vector tags and names.  */
64#define ADDENTRY(tag) { tag, #tag }
65
66/* Struct for looking up human-readable versions of tag names.  */
67typedef struct tag_name
68{
69  enum ld_plugin_tag tag;
70  const char *name;
71} tag_name_t;
72
73/* Array of all known tags and their names.  */
74static const tag_name_t tag_names[] =
75{
76  ADDENTRY(LDPT_NULL),
77  ADDENTRY(LDPT_API_VERSION),
78  ADDENTRY(LDPT_GOLD_VERSION),
79  ADDENTRY(LDPT_LINKER_OUTPUT),
80  ADDENTRY(LDPT_OPTION),
81  ADDENTRY(LDPT_REGISTER_CLAIM_FILE_HOOK),
82  ADDENTRY(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK),
83  ADDENTRY(LDPT_REGISTER_CLEANUP_HOOK),
84  ADDENTRY(LDPT_ADD_SYMBOLS),
85  ADDENTRY(LDPT_GET_SYMBOLS),
86  ADDENTRY(LDPT_ADD_INPUT_FILE),
87  ADDENTRY(LDPT_MESSAGE),
88  ADDENTRY(LDPT_GET_INPUT_FILE),
89  ADDENTRY(LDPT_RELEASE_INPUT_FILE),
90  ADDENTRY(LDPT_ADD_INPUT_LIBRARY),
91  ADDENTRY(LDPT_OUTPUT_NAME),
92  ADDENTRY(LDPT_SET_EXTRA_LIBRARY_PATH),
93  ADDENTRY(LDPT_GNU_LD_VERSION)
94};
95
96/* Function pointers to cache hooks passed at onload time.  */
97static ld_plugin_register_claim_file tv_register_claim_file = 0;
98static ld_plugin_register_all_symbols_read tv_register_all_symbols_read = 0;
99static ld_plugin_register_cleanup tv_register_cleanup = 0;
100static ld_plugin_add_symbols tv_add_symbols = 0;
101static ld_plugin_get_symbols tv_get_symbols = 0;
102static ld_plugin_add_input_file tv_add_input_file = 0;
103static ld_plugin_message tv_message = 0;
104static ld_plugin_get_input_file tv_get_input_file = 0;
105static ld_plugin_release_input_file tv_release_input_file = 0;
106static ld_plugin_add_input_library tv_add_input_library = 0;
107static ld_plugin_set_extra_library_path tv_set_extra_library_path = 0;
108
109/* Other cached info from the transfer vector.  */
110static enum ld_plugin_output_file_type linker_output;
111static const char *output_name;
112
113/* Behaviour control flags set by plugin options.  */
114static enum ld_plugin_status onload_ret = LDPS_OK;
115static enum ld_plugin_status claim_file_ret = LDPS_OK;
116static enum ld_plugin_status all_symbols_read_ret = LDPS_OK;
117static enum ld_plugin_status cleanup_ret = LDPS_OK;
118static bfd_boolean register_claimfile_hook = FALSE;
119static bfd_boolean register_allsymbolsread_hook = FALSE;
120static bfd_boolean register_cleanup_hook = FALSE;
121static bfd_boolean dumpresolutions = FALSE;
122
123/* The master list of all claimable/claimed files.  */
124static claim_file_t *claimfiles_list = NULL;
125
126/* We keep a tail pointer for easy linking on the end.  */
127static claim_file_t **claimfiles_tail_chain_ptr = &claimfiles_list;
128
129/* The last claimed file added to the list, for receiving syms.  */
130static claim_file_t *last_claimfile = NULL;
131
132/* The master list of all files to add to the final link.  */
133static add_file_t *addfiles_list = NULL;
134
135/* We keep a tail pointer for easy linking on the end.  */
136static add_file_t **addfiles_tail_chain_ptr = &addfiles_list;
137
138/* Add a new claimfile on the end of the chain.  */
139static enum ld_plugin_status
140record_claim_file (const char *file)
141{
142  claim_file_t *newfile;
143
144  newfile = malloc (sizeof *newfile);
145  if (!newfile)
146    return LDPS_ERR;
147  memset (newfile, 0, sizeof *newfile);
148  /* Only setup for now is remembering the name to look for.  */
149  newfile->file.name = file;
150  /* Chain it on the end of the list.  */
151  *claimfiles_tail_chain_ptr = newfile;
152  claimfiles_tail_chain_ptr = &newfile->next;
153  /* Record it as active for receiving symbols to register.  */
154  last_claimfile = newfile;
155  return LDPS_OK;
156}
157
158/* Add a new addfile on the end of the chain.  */
159static enum ld_plugin_status
160record_add_file (const char *file, addfile_enum_t type)
161{
162  add_file_t *newfile;
163
164  newfile = malloc (sizeof *newfile);
165  if (!newfile)
166    return LDPS_ERR;
167  newfile->next = NULL;
168  newfile->name = file;
169  newfile->type = type;;
170  /* Chain it on the end of the list.  */
171  *addfiles_tail_chain_ptr = newfile;
172  addfiles_tail_chain_ptr = &newfile->next;
173  return LDPS_OK;
174}
175
176/* Parse a command-line argument string into a symbol definition.
177   Symbol-strings follow the colon-separated format:
178	NAME:VERSION:def:vis:size:COMDATKEY
179   where the fields in capitals are strings and those in lower
180   case are integers.  We don't allow to specify a resolution as
181   doing so is not meaningful when calling the add symbols hook.  */
182static enum ld_plugin_status
183parse_symdefstr (const char *str, struct ld_plugin_symbol *sym)
184{
185  int n;
186  long long size;
187  const char *colon1, *colon2, *colon5;
188
189  /* Locate the colons separating the first two strings.  */
190  colon1 = strchr (str, ':');
191  if (!colon1)
192    return LDPS_ERR;
193  colon2 = strchr (colon1+1, ':');
194  if (!colon2)
195    return LDPS_ERR;
196  /* Name must not be empty (version may be).  */
197  if (colon1 == str)
198    return LDPS_ERR;
199
200  /* The fifth colon and trailing comdat key string are optional,
201     but the intermediate ones must all be present.  */
202  colon5 = strchr (colon2+1, ':');	/* Actually only third so far.  */
203  if (!colon5)
204    return LDPS_ERR;
205  colon5 = strchr (colon5+1, ':');	/* Hopefully fourth now.  */
206  if (!colon5)
207    return LDPS_ERR;
208  colon5 = strchr (colon5+1, ':');	/* Optional fifth now.  */
209
210  /* Finally we'll use sscanf to parse the numeric fields, then
211     we'll split out the strings which we need to allocate separate
212     storage for anyway so that we can add nul termination.  */
213  n = sscanf (colon2 + 1, "%i:%i:%lli", &sym->def, &sym->visibility, &size);
214  if (n != 3)
215    return LDPS_ERR;
216
217  /* Parsed successfully, so allocate strings and fill out fields.  */
218  sym->size = size;
219  sym->resolution = LDPR_UNKNOWN;
220  sym->name = malloc (colon1 - str + 1);
221  if (!sym->name)
222    return LDPS_ERR;
223  memcpy (sym->name, str, colon1 - str);
224  sym->name[colon1 - str] = '\0';
225  if (colon2 > (colon1 + 1))
226    {
227      sym->version = malloc (colon2 - colon1);
228      if (!sym->version)
229	return LDPS_ERR;
230      memcpy (sym->version, colon1 + 1, colon2 - (colon1 + 1));
231      sym->version[colon2 - (colon1 + 1)] = '\0';
232    }
233  else
234    sym->version = NULL;
235  if (colon5 && colon5[1])
236    {
237      sym->comdat_key = malloc (strlen (colon5 + 1) + 1);
238      if (!sym->comdat_key)
239	return LDPS_ERR;
240      strcpy (sym->comdat_key, colon5 + 1);
241    }
242  else
243    sym->comdat_key = 0;
244  return LDPS_OK;
245}
246
247/* Record a symbol to be added for the last-added claimfile.  */
248static enum ld_plugin_status
249record_claimed_file_symbol (const char *symdefstr)
250{
251  struct ld_plugin_symbol sym;
252
253  /* Can't add symbols except as belonging to claimed files.  */
254  if (!last_claimfile)
255    return LDPS_ERR;
256
257  /* If string doesn't parse correctly, give an error.  */
258  if (parse_symdefstr (symdefstr, &sym) != LDPS_OK)
259    return LDPS_ERR;
260
261  /* Check for enough space, resize array if needed, and add it.  */
262  if (last_claimfile->n_syms_allocated == last_claimfile->n_syms_used)
263    {
264      int new_n_syms = last_claimfile->n_syms_allocated
265			? 2 * last_claimfile->n_syms_allocated
266			: 10;
267      last_claimfile->symbols = realloc (last_claimfile->symbols,
268			new_n_syms * sizeof *last_claimfile->symbols);
269      if (!last_claimfile->symbols)
270	return LDPS_ERR;
271      last_claimfile->n_syms_allocated = new_n_syms;
272    }
273  last_claimfile->symbols[last_claimfile->n_syms_used++] = sym;
274
275  return LDPS_OK;
276}
277
278/* Records the status to return from one of the registered hooks.  */
279static enum ld_plugin_status
280set_ret_val (const char *whichval, enum ld_plugin_status retval)
281{
282  if (!strcmp ("onload", whichval))
283    onload_ret = retval;
284  else if (!strcmp ("claimfile", whichval))
285    claim_file_ret = retval;
286  else if (!strcmp ("allsymbolsread", whichval))
287    all_symbols_read_ret = retval;
288  else if (!strcmp ("cleanup", whichval))
289    cleanup_ret = retval;
290  else
291    return LDPS_ERR;
292  return LDPS_OK;
293}
294
295/* Records hooks which should be registered.  */
296static enum ld_plugin_status
297set_register_hook (const char *whichhook, bfd_boolean yesno)
298{
299  if (!strcmp ("claimfile", whichhook))
300    register_claimfile_hook = yesno;
301  else if (!strcmp ("allsymbolsread", whichhook))
302    register_allsymbolsread_hook = yesno;
303  else if (!strcmp ("cleanup", whichhook))
304    register_cleanup_hook = yesno;
305  else
306    return LDPS_ERR;
307  return LDPS_OK;
308}
309
310/* Determine type of plugin option and pass to individual parsers.  */
311static enum ld_plugin_status
312parse_option (const char *opt)
313{
314  if (!strncmp ("fail", opt, 4))
315    return set_ret_val (opt + 4, LDPS_ERR);
316  else if (!strncmp ("pass", opt, 4))
317    return set_ret_val (opt + 4, LDPS_OK);
318  else if (!strncmp ("register", opt, 8))
319    return set_register_hook (opt + 8, TRUE);
320  else if (!strncmp ("noregister", opt, 10))
321    return set_register_hook (opt + 10, FALSE);
322  else if (!strncmp ("claim:", opt, 6))
323    return record_claim_file (opt + 6);
324  else if (!strncmp ("sym:", opt, 4))
325    return record_claimed_file_symbol (opt + 4);
326  else if (!strncmp ("add:", opt, 4))
327    return record_add_file (opt + 4, ADD_FILE);
328  else if (!strncmp ("lib:", opt, 4))
329    return record_add_file (opt + 4, ADD_LIB);
330  else if (!strncmp ("dir:", opt, 4))
331    return record_add_file (opt + 4, ADD_DIR);
332  else if (!strcmp ("dumpresolutions", opt))
333    dumpresolutions = TRUE;
334  else
335    return LDPS_ERR;
336  return LDPS_OK;
337}
338
339/* Output contents of transfer vector array entry in human-readable form.  */
340static void
341dump_tv_tag (size_t n, struct ld_plugin_tv *tv)
342{
343  size_t tag;
344  char unknownbuf[40];
345  const char *name;
346
347  for (tag = 0; tag < ARRAY_SIZE (tag_names); tag++)
348    if (tag_names[tag].tag == tv->tv_tag)
349      break;
350  sprintf (unknownbuf, "unknown tag #%d", tv->tv_tag);
351  name = (tag < ARRAY_SIZE (tag_names)) ? tag_names[tag].name : unknownbuf;
352  switch (tv->tv_tag)
353    {
354      case LDPT_OPTION:
355      case LDPT_OUTPUT_NAME:
356	TV_MESSAGE (LDPL_INFO, "tv[%d]: %s '%s'", n, name,
357		    tv->tv_u.tv_string);
358        break;
359      case LDPT_REGISTER_CLAIM_FILE_HOOK:
360      case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
361      case LDPT_REGISTER_CLEANUP_HOOK:
362      case LDPT_ADD_SYMBOLS:
363      case LDPT_GET_SYMBOLS:
364      case LDPT_ADD_INPUT_FILE:
365      case LDPT_MESSAGE:
366      case LDPT_GET_INPUT_FILE:
367      case LDPT_RELEASE_INPUT_FILE:
368      case LDPT_ADD_INPUT_LIBRARY:
369      case LDPT_SET_EXTRA_LIBRARY_PATH:
370	TV_MESSAGE (LDPL_INFO, "tv[%d]: %s func@0x%p", n, name,
371		    (void *)(tv->tv_u.tv_message));
372        break;
373      case LDPT_NULL:
374      case LDPT_API_VERSION:
375      case LDPT_GOLD_VERSION:
376      case LDPT_LINKER_OUTPUT:
377      case LDPT_GNU_LD_VERSION:
378      default:
379	TV_MESSAGE (LDPL_INFO, "tv[%d]: %s value %W (%d)", n, name,
380		    (bfd_vma)tv->tv_u.tv_val, tv->tv_u.tv_val);
381	break;
382    }
383}
384
385/* Handle/record information received in a transfer vector entry.  */
386static enum ld_plugin_status
387parse_tv_tag (struct ld_plugin_tv *tv)
388{
389#define SETVAR(x) x = tv->tv_u.x
390  switch (tv->tv_tag)
391    {
392      case LDPT_OPTION:
393	return parse_option (tv->tv_u.tv_string);
394      case LDPT_NULL:
395      case LDPT_GOLD_VERSION:
396      case LDPT_GNU_LD_VERSION:
397      case LDPT_API_VERSION:
398      default:
399	break;
400      case LDPT_OUTPUT_NAME:
401	output_name = tv->tv_u.tv_string;
402	break;
403      case LDPT_LINKER_OUTPUT:
404	linker_output = tv->tv_u.tv_val;
405	break;
406      case LDPT_REGISTER_CLAIM_FILE_HOOK:
407	SETVAR(tv_register_claim_file);
408	break;
409      case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK:
410	SETVAR(tv_register_all_symbols_read);
411	break;
412      case LDPT_REGISTER_CLEANUP_HOOK:
413	SETVAR(tv_register_cleanup);
414	break;
415      case LDPT_ADD_SYMBOLS:
416	SETVAR(tv_add_symbols);
417	break;
418      case LDPT_GET_SYMBOLS:
419	SETVAR(tv_get_symbols);
420	break;
421      case LDPT_ADD_INPUT_FILE:
422	SETVAR(tv_add_input_file);
423	break;
424      case LDPT_MESSAGE:
425	SETVAR(tv_message);
426	break;
427      case LDPT_GET_INPUT_FILE:
428	SETVAR(tv_get_input_file);
429	break;
430      case LDPT_RELEASE_INPUT_FILE:
431	SETVAR(tv_release_input_file);
432	break;
433      case LDPT_ADD_INPUT_LIBRARY:
434	SETVAR(tv_add_input_library);
435	break;
436      case LDPT_SET_EXTRA_LIBRARY_PATH:
437	SETVAR(tv_set_extra_library_path);
438	break;
439    }
440#undef SETVAR
441  return LDPS_OK;
442}
443
444/* Record any useful information in transfer vector entry and display
445   it in human-readable form using the plugin API message() callback.  */
446enum ld_plugin_status
447parse_and_dump_tv_tag (size_t n, struct ld_plugin_tv *tv)
448{
449  enum ld_plugin_status rv = parse_tv_tag (tv);
450  dump_tv_tag (n, tv);
451  return rv;
452}
453
454/* Standard plugin API entry point.  */
455enum ld_plugin_status
456onload (struct ld_plugin_tv *tv)
457{
458  size_t n = 0;
459  enum ld_plugin_status rv;
460
461  /* This plugin does nothing but dump the tv array.  It would
462     be an error if this function was called without one.  */
463  if (!tv)
464    return LDPS_ERR;
465
466  /* First entry should always be LDPT_MESSAGE, letting us get
467     hold of it easily so we can send output straight away.  */
468  if (tv[0].tv_tag == LDPT_MESSAGE)
469    tv_message = tv[0].tv_u.tv_message;
470
471  fflush (NULL);
472  TV_MESSAGE (LDPL_INFO, "Hello from testplugin.");
473
474  do
475    if ((rv = parse_and_dump_tv_tag (n++, tv)) != LDPS_OK)
476      return rv;
477  while ((tv++)->tv_tag != LDPT_NULL);
478
479  /* Register hooks only if instructed by options.  */
480  if (register_claimfile_hook)
481    {
482      if (!tv_register_claim_file)
483	{
484	  TV_MESSAGE (LDPL_FATAL, "No register_claim_file hook");
485	  fflush (NULL);
486	  return LDPS_ERR;
487	}
488      (*tv_register_claim_file) (onclaim_file);
489    }
490  if (register_allsymbolsread_hook)
491    {
492      if (!tv_register_all_symbols_read)
493	{
494	  TV_MESSAGE (LDPL_FATAL, "No register_all_symbols_read hook");
495	  fflush (NULL);
496	  return LDPS_ERR;
497	}
498      (*tv_register_all_symbols_read) (onall_symbols_read);
499    }
500  if (register_cleanup_hook)
501    {
502      if (!tv_register_cleanup)
503	{
504	  TV_MESSAGE (LDPL_FATAL, "No register_cleanup hook");
505	  fflush (NULL);
506	  return LDPS_ERR;
507	}
508      (*tv_register_cleanup) (oncleanup);
509    }
510  fflush (NULL);
511  return onload_ret;
512}
513
514/* Standard plugin API registerable hook.  */
515static enum ld_plugin_status
516onclaim_file (const struct ld_plugin_input_file *file, int *claimed)
517{
518  /* Let's see if we want to claim this file.  */
519  claim_file_t *claimfile = claimfiles_list;
520  while (claimfile)
521    {
522      if (!strcmp (file->name, claimfile->file.name))
523	break;
524      claimfile = claimfile->next;
525    }
526
527  /* Inform the user/testsuite.  */
528  TV_MESSAGE (LDPL_INFO, "hook called: claim_file %s [@%ld/%ld] %s",
529	      file->name, (long)file->offset, (long)file->filesize,
530	      claimfile ? "CLAIMED" : "not claimed");
531  fflush (NULL);
532
533  /* If we decided to claim it, record that fact, and add any symbols
534     that were defined for it by plugin options.  */
535  *claimed = (claimfile != 0);
536  if (claimfile)
537    {
538      claimfile->claimed = TRUE;
539      claimfile->file = *file;
540      if (claimfile->n_syms_used && !tv_add_symbols)
541	return LDPS_ERR;
542      else if (claimfile->n_syms_used)
543	return (*tv_add_symbols) (claimfile->file.handle,
544				claimfile->n_syms_used, claimfile->symbols);
545    }
546
547  return claim_file_ret;
548}
549
550/* Standard plugin API registerable hook.  */
551static enum ld_plugin_status
552onall_symbols_read (void)
553{
554  static const char *resolutions[] =
555    {
556      "LDPR_UNKNOWN",
557      "LDPR_UNDEF",
558      "LDPR_PREVAILING_DEF",
559      "LDPR_PREVAILING_DEF_IRONLY",
560      "LDPR_PREEMPTED_REG",
561      "LDPR_PREEMPTED_IR",
562      "LDPR_RESOLVED_IR",
563      "LDPR_RESOLVED_EXEC",
564      "LDPR_RESOLVED_DYN",
565    };
566  claim_file_t *claimfile = dumpresolutions ? claimfiles_list : NULL;
567  add_file_t *addfile = addfiles_list;
568  TV_MESSAGE (LDPL_INFO, "hook called: all symbols read.");
569  for ( ; claimfile; claimfile = claimfile->next)
570    {
571      enum ld_plugin_status rv;
572      int n;
573      if (claimfile->n_syms_used && !tv_get_symbols)
574	return LDPS_ERR;
575      else if (!claimfile->n_syms_used)
576        continue;
577      rv = tv_get_symbols (claimfile->file.handle, claimfile->n_syms_used,
578				claimfile->symbols);
579      if (rv != LDPS_OK)
580	return rv;
581      for (n = 0; n < claimfile->n_syms_used; n++)
582	TV_MESSAGE (LDPL_INFO, "Sym: '%s%s%s' Resolution: %s",
583		    claimfile->symbols[n].name,
584		    claimfile->symbols[n].version ? "@" : "",
585		    (claimfile->symbols[n].version
586		     ? claimfile->symbols[n].version : ""),
587		    resolutions[claimfile->symbols[n].resolution]);
588    }
589  for ( ; addfile ; addfile = addfile->next)
590    {
591      enum ld_plugin_status rv;
592      if (addfile->type == ADD_LIB && tv_add_input_library)
593	rv = (*tv_add_input_library) (addfile->name);
594      else if (addfile->type == ADD_FILE && tv_add_input_file)
595	rv = (*tv_add_input_file) (addfile->name);
596      else if (addfile->type == ADD_DIR && tv_set_extra_library_path)
597	rv = (*tv_set_extra_library_path) (addfile->name);
598      else
599	rv = LDPS_ERR;
600      if (rv != LDPS_OK)
601	return rv;
602    }
603  fflush (NULL);
604  return all_symbols_read_ret;
605}
606
607/* Standard plugin API registerable hook.  */
608static enum ld_plugin_status
609oncleanup (void)
610{
611  TV_MESSAGE (LDPL_INFO, "hook called: cleanup.");
612  fflush (NULL);
613  return cleanup_ret;
614}
615