/* libdeps plugin for the GNU linker. Copyright (C) 2020-2023 Free Software Foundation, Inc. This file is part of the GNU Binutils. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sysdep.h" #include "bfd.h" #if BFD_SUPPORTS_PLUGINS #include "plugin-api.h" #include /* For isspace. */ extern enum ld_plugin_status onload (struct ld_plugin_tv *tv); /* Helper for calling plugin api message function. */ #define TV_MESSAGE if (tv_message) (*tv_message) /* Function pointers to cache hooks passed at onload time. */ static ld_plugin_register_claim_file tv_register_claim_file = 0; static ld_plugin_register_all_symbols_read tv_register_all_symbols_read = 0; static ld_plugin_register_cleanup tv_register_cleanup = 0; static ld_plugin_message tv_message = 0; static ld_plugin_add_input_library tv_add_input_library = 0; static ld_plugin_set_extra_library_path tv_set_extra_library_path = 0; /* Handle/record information received in a transfer vector entry. */ static enum ld_plugin_status parse_tv_tag (struct ld_plugin_tv *tv) { #define SETVAR(x) x = tv->tv_u.x switch (tv->tv_tag) { case LDPT_REGISTER_CLAIM_FILE_HOOK: SETVAR(tv_register_claim_file); break; case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK: SETVAR(tv_register_all_symbols_read); break; case LDPT_REGISTER_CLEANUP_HOOK: SETVAR(tv_register_cleanup); break; case LDPT_MESSAGE: SETVAR(tv_message); break; case LDPT_ADD_INPUT_LIBRARY: SETVAR(tv_add_input_library); break; case LDPT_SET_EXTRA_LIBRARY_PATH: SETVAR(tv_set_extra_library_path); break; default: break; } #undef SETVAR return LDPS_OK; } /* Defs for archive parsing. */ #define ARMAGSIZE 8 typedef struct arhdr { char ar_name[16]; char ar_date[12]; char ar_uid[6]; char ar_gid[6]; char ar_mode[8]; char ar_size[10]; char ar_fmag[2]; } arhdr; typedef struct linerec { struct linerec *next; char line[]; } linerec; #define LIBDEPS "__.LIBDEP/ " static linerec *line_head, **line_tail = &line_head; static enum ld_plugin_status get_libdeps (int fd) { arhdr ah; int len; unsigned long mlen; size_t amt; linerec *lr; enum ld_plugin_status rc = LDPS_NO_SYMS; lseek (fd, ARMAGSIZE, SEEK_SET); for (;;) { len = read (fd, (void *) &ah, sizeof (ah)); if (len != sizeof (ah)) break; mlen = strtoul (ah.ar_size, NULL, 10); if (!mlen || strncmp (ah.ar_name, LIBDEPS, sizeof (LIBDEPS)-1)) { lseek (fd, mlen, SEEK_CUR); continue; } amt = mlen + sizeof (linerec); if (amt <= mlen) return LDPS_ERR; lr = malloc (amt); if (!lr) return LDPS_ERR; lr->next = NULL; len = read (fd, lr->line, mlen); lr->line[mlen-1] = '\0'; *line_tail = lr; line_tail = &lr->next; rc = LDPS_OK; break; } return rc; } /* Turn a string into an argvec. */ static char ** str2vec (char *in) { char **res; char *s, *first, *end; char *sq, *dq; int i; end = in + strlen (in); s = in; while (isspace ((unsigned char) *s)) s++; first = s; i = 1; while ((s = strchr (s, ' '))) { s++; i++; } res = (char **)malloc ((i+1) * sizeof (char *)); if (!res) return res; i = 0; sq = NULL; dq = NULL; res[0] = first; for (s = first; *s; s++) { if (*s == '\\') { memmove (s, s+1, end-s-1); end--; } if (isspace ((unsigned char) *s)) { if (sq || dq) continue; *s++ = '\0'; while (isspace ((unsigned char) *s)) s++; if (*s) res[++i] = s; } if (*s == '\'' && !dq) { if (sq) { memmove (sq, sq+1, s-sq-1); memmove (s-2, s+1, end-s-1); end -= 2; s--; sq = NULL; } else { sq = s; } } if (*s == '"' && !sq) { if (dq) { memmove (dq, dq+1, s-dq-1); memmove (s-2, s+1, end-s-1); end -= 2; s--; dq = NULL; } else { dq = s; } } } res[++i] = NULL; return res; } static char *prevfile; /* Standard plugin API registerable hook. */ static enum ld_plugin_status onclaim_file (const struct ld_plugin_input_file *file, int *claimed) { enum ld_plugin_status rv; *claimed = 0; /* If we've already seen this file, ignore it. */ if (prevfile && !strcmp (file->name, prevfile)) return LDPS_OK; /* If it's not an archive member, ignore it. */ if (!file->offset) return LDPS_OK; if (prevfile) free (prevfile); prevfile = strdup (file->name); if (!prevfile) return LDPS_ERR; /* This hook only gets called on actual object files. * We have to examine the archive ourselves, to find * our LIBDEPS member. */ rv = get_libdeps (file->fd); if (rv == LDPS_ERR) return rv; if (rv == LDPS_OK) { linerec *lr = (linerec *)line_tail; /* Inform the user/testsuite. */ TV_MESSAGE (LDPL_INFO, "got deps for library %s: %s", file->name, lr->line); fflush (NULL); } return LDPS_OK; } /* Standard plugin API registerable hook. */ static enum ld_plugin_status onall_symbols_read (void) { linerec *lr; char **vec; enum ld_plugin_status rv = LDPS_OK; while ((lr = line_head)) { line_head = lr->next; vec = str2vec (lr->line); if (vec) { int i; for (i = 0; vec[i]; i++) { if (vec[i][0] != '-') { TV_MESSAGE (LDPL_WARNING, "ignoring libdep argument %s", vec[i]); fflush (NULL); continue; } if (vec[i][1] == 'l') rv = tv_add_input_library (vec[i]+2); else if (vec[i][1] == 'L') rv = tv_set_extra_library_path (vec[i]+2); else { TV_MESSAGE (LDPL_WARNING, "ignoring libdep argument %s", vec[i]); fflush (NULL); } if (rv != LDPS_OK) break; } free (vec); } free (lr); } line_tail = NULL; return rv; } /* Standard plugin API registerable hook. */ static enum ld_plugin_status oncleanup (void) { if (prevfile) { free (prevfile); prevfile = NULL; } if (line_head) { linerec *lr; while ((lr = line_head)) { line_head = lr->next; free (lr); } line_tail = NULL; } return LDPS_OK; } /* Standard plugin API entry point. */ enum ld_plugin_status onload (struct ld_plugin_tv *tv) { enum ld_plugin_status rv; /* This plugin requires a valid tv array. */ if (!tv) return LDPS_ERR; /* First entry should always be LDPT_MESSAGE, letting us get hold of it easily so we can send output straight away. */ if (tv[0].tv_tag == LDPT_MESSAGE) tv_message = tv[0].tv_u.tv_message; do if ((rv = parse_tv_tag (tv)) != LDPS_OK) return rv; while ((tv++)->tv_tag != LDPT_NULL); /* Register hooks. */ if (tv_register_claim_file && tv_register_all_symbols_read && tv_register_cleanup) { (*tv_register_claim_file) (onclaim_file); (*tv_register_all_symbols_read) (onall_symbols_read); (*tv_register_cleanup) (oncleanup); } fflush (NULL); return LDPS_OK; } #endif /* BFD_SUPPORTS_PLUGINS */