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