| /* test_plugin.c -- simple linker plugin test |
| |
| Copyright (C) 2008-2021 Free Software Foundation, Inc. |
| Written by Cary Coutant <ccoutant@google.com>. |
| |
| 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #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; |
| } |