/* test_plugin.c -- simple linker plugin test Copyright (C) 2008-2021 Free Software Foundation, Inc. Written by Cary Coutant . This file is part of gold. 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "plugin-api.h" struct claimed_file { const char* name; void* handle; int nsyms; struct ld_plugin_symbol* syms; struct claimed_file* next; }; struct sym_info { int size; char* type; char* bind; char* vis; char* sect; char* name; char* ver; }; static struct claimed_file* first_claimed_file = NULL; static struct claimed_file* last_claimed_file = NULL; static ld_plugin_register_claim_file register_claim_file_hook = NULL; static ld_plugin_register_all_symbols_read register_all_symbols_read_hook = NULL; static ld_plugin_register_cleanup register_cleanup_hook = NULL; static ld_plugin_add_symbols add_symbols = NULL; static ld_plugin_get_symbols get_symbols = NULL; static ld_plugin_get_symbols get_symbols_v2 = NULL; static ld_plugin_get_symbols get_symbols_v3 = NULL; static ld_plugin_add_input_file add_input_file = NULL; static ld_plugin_message message = NULL; static ld_plugin_get_input_file get_input_file = NULL; static ld_plugin_release_input_file release_input_file = NULL; static ld_plugin_get_input_section_count get_input_section_count = NULL; static ld_plugin_get_input_section_type get_input_section_type = NULL; static ld_plugin_get_input_section_name get_input_section_name = NULL; static ld_plugin_get_input_section_contents get_input_section_contents = NULL; static ld_plugin_update_section_order update_section_order = NULL; static ld_plugin_allow_section_ordering allow_section_ordering = NULL; static ld_plugin_get_wrap_symbols get_wrap_symbols = NULL; #define MAXOPTS 10 static const char *opts[MAXOPTS]; static int nopts = 0; enum ld_plugin_status onload(struct ld_plugin_tv *tv); enum ld_plugin_status claim_file_hook(const struct ld_plugin_input_file *file, int *claimed); enum ld_plugin_status all_symbols_read_hook(void); enum ld_plugin_status cleanup_hook(void); static void parse_readelf_line(char*, struct sym_info*); enum ld_plugin_status onload(struct ld_plugin_tv *tv) { struct ld_plugin_tv *entry; int api_version = 0; int gold_version = 0; int i; for (entry = tv; entry->tv_tag != LDPT_NULL; ++entry) { switch (entry->tv_tag) { case LDPT_API_VERSION: api_version = entry->tv_u.tv_val; break; case LDPT_GOLD_VERSION: gold_version = entry->tv_u.tv_val; break; case LDPT_LINKER_OUTPUT: break; case LDPT_OPTION: if (nopts < MAXOPTS) opts[nopts++] = entry->tv_u.tv_string; break; case LDPT_REGISTER_CLAIM_FILE_HOOK: register_claim_file_hook = entry->tv_u.tv_register_claim_file; break; case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK: register_all_symbols_read_hook = entry->tv_u.tv_register_all_symbols_read; break; case LDPT_REGISTER_CLEANUP_HOOK: register_cleanup_hook = entry->tv_u.tv_register_cleanup; break; case LDPT_ADD_SYMBOLS: add_symbols = entry->tv_u.tv_add_symbols; break; case LDPT_GET_SYMBOLS: get_symbols = entry->tv_u.tv_get_symbols; break; case LDPT_GET_SYMBOLS_V2: get_symbols_v2 = entry->tv_u.tv_get_symbols; break; case LDPT_GET_SYMBOLS_V3: get_symbols_v3 = entry->tv_u.tv_get_symbols; break; case LDPT_ADD_INPUT_FILE: add_input_file = entry->tv_u.tv_add_input_file; break; case LDPT_MESSAGE: message = entry->tv_u.tv_message; break; case LDPT_GET_INPUT_FILE: get_input_file = entry->tv_u.tv_get_input_file; break; case LDPT_RELEASE_INPUT_FILE: release_input_file = entry->tv_u.tv_release_input_file; break; case LDPT_GET_INPUT_SECTION_COUNT: get_input_section_count = *entry->tv_u.tv_get_input_section_count; break; case LDPT_GET_INPUT_SECTION_TYPE: get_input_section_type = *entry->tv_u.tv_get_input_section_type; break; case LDPT_GET_INPUT_SECTION_NAME: get_input_section_name = *entry->tv_u.tv_get_input_section_name; break; case LDPT_GET_INPUT_SECTION_CONTENTS: get_input_section_contents = *entry->tv_u.tv_get_input_section_contents; break; case LDPT_UPDATE_SECTION_ORDER: update_section_order = *entry->tv_u.tv_update_section_order; break; case LDPT_ALLOW_SECTION_ORDERING: allow_section_ordering = *entry->tv_u.tv_allow_section_ordering; break; case LDPT_GET_WRAP_SYMBOLS: get_wrap_symbols = *entry->tv_u.tv_get_wrap_symbols; break; default: break; } } if (message == NULL) { fprintf(stderr, "tv_message interface missing\n"); return LDPS_ERR; } if (register_claim_file_hook == NULL) { fprintf(stderr, "tv_register_claim_file_hook interface missing\n"); return LDPS_ERR; } if (register_all_symbols_read_hook == NULL) { fprintf(stderr, "tv_register_all_symbols_read_hook interface missing\n"); return LDPS_ERR; } if (register_cleanup_hook == NULL) { fprintf(stderr, "tv_register_cleanup_hook interface missing\n"); return LDPS_ERR; } (*message)(LDPL_INFO, "API version: %d", api_version); (*message)(LDPL_INFO, "gold version: %d", gold_version); for (i = 0; i < nopts; ++i) (*message)(LDPL_INFO, "option: %s", opts[i]); if ((*register_claim_file_hook)(claim_file_hook) != LDPS_OK) { (*message)(LDPL_ERROR, "error registering claim file hook"); return LDPS_ERR; } if ((*register_all_symbols_read_hook)(all_symbols_read_hook) != LDPS_OK) { (*message)(LDPL_ERROR, "error registering all symbols read hook"); return LDPS_ERR; } if ((*register_cleanup_hook)(cleanup_hook) != LDPS_OK) { (*message)(LDPL_ERROR, "error registering cleanup hook"); return LDPS_ERR; } if (get_input_section_count == NULL) { fprintf(stderr, "tv_get_input_section_count interface missing\n"); return LDPS_ERR; } if (get_input_section_type == NULL) { fprintf(stderr, "tv_get_input_section_type interface missing\n"); return LDPS_ERR; } if (get_input_section_name == NULL) { fprintf(stderr, "tv_get_input_section_name interface missing\n"); return LDPS_ERR; } if (get_input_section_contents == NULL) { fprintf(stderr, "tv_get_input_section_contents interface missing\n"); return LDPS_ERR; } if (update_section_order == NULL) { fprintf(stderr, "tv_update_section_order interface missing\n"); return LDPS_ERR; } if (allow_section_ordering == NULL) { fprintf(stderr, "tv_allow_section_ordering interface missing\n"); return LDPS_ERR; } if (get_wrap_symbols == NULL) { fprintf(stderr, "tv_get_wrap_symbols interface missing\n"); return LDPS_ERR; } else { const char **wrap_symbols; uint64_t count = 0; if (get_wrap_symbols(&count, &wrap_symbols) == LDPS_OK) { (*message)(LDPL_INFO, "Number of wrap symbols = %lu", count); for (; count > 0; --count) (*message)(LDPL_INFO, "Wrap symbol %s", wrap_symbols[count - 1]); } else { fprintf(stderr, "tv_get_wrap_symbols interface call failed\n"); return LDPS_ERR; } } return LDPS_OK; } enum ld_plugin_status claim_file_hook (const struct ld_plugin_input_file* file, int* claimed) { int len; off_t end_offset; char buf[160]; struct claimed_file* claimed_file; struct ld_plugin_symbol* syms; int nsyms = 0; int maxsyms = 0; FILE* irfile; struct sym_info info; int weak; int def; int vis; int is_comdat; int i; int irfile_was_opened = 0; char syms_name[80]; (*message)(LDPL_INFO, "%s: claim file hook called (offset = %ld, size = %ld)", file->name, (long)file->offset, (long)file->filesize); /* Look for matching syms file for an archive member. */ if (file->offset == 0) snprintf(syms_name, sizeof(syms_name), "%s.syms", file->name); else snprintf(syms_name, sizeof(syms_name), "%s-%d.syms", file->name, (int)file->offset); irfile = fopen(syms_name, "r"); if (irfile != NULL) { irfile_was_opened = 1; end_offset = 1 << 20; } /* Otherwise, see if the file itself is a syms file. */ if (!irfile_was_opened) { irfile = fdopen(file->fd, "r"); (void)fseek(irfile, file->offset, SEEK_SET); end_offset = file->offset + file->filesize; } /* Look for the beginning of output from readelf -s. */ len = fread(buf, 1, 13, irfile); if (len < 13 || strncmp(buf, "\nSymbol table", 13) != 0) return LDPS_OK; /* Skip the two header lines. */ (void) fgets(buf, sizeof(buf), irfile); (void) fgets(buf, sizeof(buf), irfile); if (add_symbols == NULL) { fprintf(stderr, "tv_add_symbols interface missing\n"); return LDPS_ERR; } /* Parse the output from readelf. The columns are: Index Value Size Type Binding Visibility Section Name. */ syms = (struct ld_plugin_symbol*)malloc(sizeof(struct ld_plugin_symbol) * 8); if (syms == NULL) return LDPS_ERR; maxsyms = 8; while (ftell(irfile) < end_offset && fgets(buf, sizeof(buf), irfile) != NULL) { parse_readelf_line(buf, &info); /* Ignore local symbols. */ if (strncmp(info.bind, "LOCAL", 5) == 0) continue; weak = strncmp(info.bind, "WEAK", 4) == 0; if (strncmp(info.sect, "UND", 3) == 0) def = weak ? LDPK_WEAKUNDEF : LDPK_UNDEF; else if (strncmp(info.sect, "COM", 3) == 0) def = LDPK_COMMON; else def = weak ? LDPK_WEAKDEF : LDPK_DEF; if (strncmp(info.vis, "INTERNAL", 8) == 0) vis = LDPV_INTERNAL; else if (strncmp(info.vis, "HIDDEN", 6) == 0) vis = LDPV_HIDDEN; else if (strncmp(info.vis, "PROTECTED", 9) == 0) vis = LDPV_PROTECTED; else vis = LDPV_DEFAULT; /* If the symbol is listed in the options list, special-case it as a comdat symbol. */ is_comdat = 0; for (i = 0; i < nopts; ++i) { if (info.name != NULL && strcmp(info.name, opts[i]) == 0) { is_comdat = 1; break; } } if (nsyms >= maxsyms) { syms = (struct ld_plugin_symbol*) realloc(syms, sizeof(struct ld_plugin_symbol) * maxsyms * 2); if (syms == NULL) return LDPS_ERR; maxsyms *= 2; } if (info.name == NULL) syms[nsyms].name = NULL; else { len = strlen(info.name); syms[nsyms].name = malloc(len + 1); strncpy(syms[nsyms].name, info.name, len + 1); } if (info.ver == NULL) syms[nsyms].version = NULL; else { len = strlen(info.ver); syms[nsyms].version = malloc(len + 1); strncpy(syms[nsyms].version, info.ver, len + 1); } syms[nsyms].def = def; syms[nsyms].visibility = vis; syms[nsyms].size = info.size; syms[nsyms].comdat_key = is_comdat ? syms[nsyms].name : NULL; syms[nsyms].resolution = LDPR_UNKNOWN; ++nsyms; } claimed_file = (struct claimed_file*) malloc(sizeof(struct claimed_file)); if (claimed_file == NULL) return LDPS_ERR; claimed_file->name = file->name; claimed_file->handle = file->handle; claimed_file->nsyms = nsyms; claimed_file->syms = syms; claimed_file->next = NULL; if (last_claimed_file == NULL) first_claimed_file = claimed_file; else last_claimed_file->next = claimed_file; last_claimed_file = claimed_file; (*message)(LDPL_INFO, "%s: claiming file, adding %d symbols", file->name, nsyms); if (nsyms > 0) (*add_symbols)(file->handle, nsyms, syms); *claimed = 1; if (irfile_was_opened) fclose(irfile); return LDPS_OK; } enum ld_plugin_status all_symbols_read_hook(void) { int i; const char* res; struct claimed_file* claimed_file; struct ld_plugin_input_file file; FILE* irfile; off_t end_offset; struct sym_info info; int len; char buf[160]; char* p; const char* filename; (*message)(LDPL_INFO, "all symbols read hook called"); if (get_symbols_v3 == NULL) { fprintf(stderr, "tv_get_symbols (v3) interface missing\n"); return LDPS_ERR; } for (claimed_file = first_claimed_file; claimed_file != NULL; claimed_file = claimed_file->next) { enum ld_plugin_status status = (*get_symbols_v3)( claimed_file->handle, claimed_file->nsyms, claimed_file->syms); if (status == LDPS_NO_SYMS) { (*message)(LDPL_INFO, "%s: no symbols", claimed_file->name); continue; } for (i = 0; i < claimed_file->nsyms; ++i) { switch (claimed_file->syms[i].resolution) { case LDPR_UNKNOWN: res = "UNKNOWN"; break; case LDPR_UNDEF: res = "UNDEF"; break; case LDPR_PREVAILING_DEF: res = "PREVAILING_DEF_REG"; break; case LDPR_PREVAILING_DEF_IRONLY: res = "PREVAILING_DEF_IRONLY"; break; case LDPR_PREVAILING_DEF_IRONLY_EXP: res = "PREVAILING_DEF_IRONLY_EXP"; break; case LDPR_PREEMPTED_REG: res = "PREEMPTED_REG"; break; case LDPR_PREEMPTED_IR: res = "PREEMPTED_IR"; break; case LDPR_RESOLVED_IR: res = "RESOLVED_IR"; break; case LDPR_RESOLVED_EXEC: res = "RESOLVED_EXEC"; break; case LDPR_RESOLVED_DYN: res = "RESOLVED_DYN"; break; default: res = "?"; break; } (*message)(LDPL_INFO, "%s: %s: %s", claimed_file->name, claimed_file->syms[i].name, res); } } if (add_input_file == NULL) { fprintf(stderr, "tv_add_input_file interface missing\n"); return LDPS_ERR; } if (get_input_file == NULL) { fprintf(stderr, "tv_get_input_file interface missing\n"); return LDPS_ERR; } if (release_input_file == NULL) { fprintf(stderr, "tv_release_input_file interface missing\n"); return LDPS_ERR; } for (claimed_file = first_claimed_file; claimed_file != NULL; claimed_file = claimed_file->next) { int irfile_was_opened = 0; char syms_name[80]; (*get_input_file) (claimed_file->handle, &file); if (file.offset == 0) snprintf(syms_name, sizeof(syms_name), "%s.syms", file.name); else snprintf(syms_name, sizeof(syms_name), "%s-%d.syms", file.name, (int)file.offset); irfile = fopen(syms_name, "r"); if (irfile != NULL) { irfile_was_opened = 1; end_offset = 1 << 20; } if (!irfile_was_opened) { irfile = fdopen(file.fd, "r"); (void)fseek(irfile, file.offset, SEEK_SET); end_offset = file.offset + file.filesize; } /* Look for the beginning of output from readelf -s. */ len = fread(buf, 1, 13, irfile); if (len < 13 || strncmp(buf, "\nSymbol table", 13) != 0) { fprintf(stderr, "%s: can't re-read original input file\n", claimed_file->name); return LDPS_ERR; } /* Skip the two header lines. */ (void) fgets(buf, sizeof(buf), irfile); (void) fgets(buf, sizeof(buf), irfile); filename = NULL; while (ftell(irfile) < end_offset && fgets(buf, sizeof(buf), irfile) != NULL) { parse_readelf_line(buf, &info); /* Look for file name. */ if (strncmp(info.type, "FILE", 4) == 0) { len = strlen(info.name); p = malloc(len + 1); strncpy(p, info.name, len + 1); filename = p; break; } } if (irfile_was_opened) fclose(irfile); (*release_input_file) (claimed_file->handle); if (filename == NULL) filename = claimed_file->name; if (claimed_file->nsyms == 0) continue; if (strlen(filename) >= sizeof(buf)) { (*message)(LDPL_FATAL, "%s: filename too long", filename); return LDPS_ERR; } strcpy(buf, filename); p = strrchr(buf, '.'); if (p == NULL || (strcmp(p, ".syms") != 0 && strcmp(p, ".c") != 0 && strcmp(p, ".cc") != 0)) { (*message)(LDPL_FATAL, "%s: filename has unknown suffix", filename); return LDPS_ERR; } p[1] = 'o'; p[2] = '\0'; (*message)(LDPL_INFO, "%s: adding new input file", buf); (*add_input_file)(buf); } return LDPS_OK; } enum ld_plugin_status cleanup_hook(void) { (*message)(LDPL_INFO, "cleanup hook called"); return LDPS_OK; } static void parse_readelf_line(char* p, struct sym_info* info) { int len; p += strspn(p, " "); /* Index field. */ p += strcspn(p, " "); p += strspn(p, " "); /* Value field. */ p += strcspn(p, " "); p += strspn(p, " "); /* Size field. */ info->size = atoi(p); p += strcspn(p, " "); p += strspn(p, " "); /* Type field. */ info->type = p; p += strcspn(p, " "); p += strspn(p, " "); /* Binding field. */ info->bind = p; p += strcspn(p, " "); p += strspn(p, " "); /* Visibility field. */ info->vis = p; p += strcspn(p, " "); p += strspn(p, " "); if (*p == '[') { /* Skip st_other. */ p += strcspn(p, "]"); p += strspn(p, "] "); } /* Section field. */ info->sect = p; p += strcspn(p, " "); p += strspn(p, " "); /* Name field. */ len = strcspn(p, "@\n"); if (len > 0 && p[len] == '@') { /* Get the symbol version. */ char* vp = p + len; int vlen; vp += strspn(vp, "@"); vlen = strcspn(vp, "\n"); vp[vlen] = '\0'; if (vlen > 0) info->ver = vp; else info->ver = NULL; } else info->ver = NULL; p[len] = '\0'; if (len > 0) info->name = p; else info->name = NULL; }