61f5c66f62
readelf output for ELFv2 includes st_other bits specifying a function's local entry offset. * testsuite/plugin_test.c (parse_readelf_line): Skip non-visibility st_other output.
608 lines
17 KiB
C
608 lines
17 KiB
C
/* test_plugin.c -- simple linker plugin test
|
|
|
|
Copyright (C) 2008-2014 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;
|
|
};
|
|
|
|
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_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;
|
|
|
|
#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_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;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
(*message)(LDPL_INFO,
|
|
"%s: claim file hook called (offset = %ld, size = %ld)",
|
|
file->name, (long)file->offset, (long)file->filesize);
|
|
|
|
/* Look for the beginning of output from readelf -s. */
|
|
irfile = fdopen(file->fd, "r");
|
|
(void)fseek(irfile, file->offset, SEEK_SET);
|
|
end_offset = file->offset + file->filesize;
|
|
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);
|
|
}
|
|
syms[nsyms].version = NULL;
|
|
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;
|
|
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_v2 == NULL)
|
|
{
|
|
fprintf(stderr, "tv_get_symbols (v2) interface missing\n");
|
|
return LDPS_ERR;
|
|
}
|
|
|
|
for (claimed_file = first_claimed_file;
|
|
claimed_file != NULL;
|
|
claimed_file = claimed_file->next)
|
|
{
|
|
(*get_symbols_v2)(claimed_file->handle, claimed_file->nsyms,
|
|
claimed_file->syms);
|
|
|
|
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)
|
|
{
|
|
(*get_input_file) (claimed_file->handle, &file);
|
|
|
|
/* Look for the beginning of output from readelf -s. */
|
|
irfile = fdopen(file.fd, "r");
|
|
(void)fseek(irfile, file.offset, SEEK_SET);
|
|
end_offset = file.offset + file.filesize;
|
|
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;
|
|
}
|
|
}
|
|
|
|
(*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. */
|
|
/* FIXME: Look for version. */
|
|
len = strlen(p);
|
|
if (len == 0)
|
|
p = NULL;
|
|
else if (p[len-1] == '\n')
|
|
p[--len] = '\0';
|
|
info->name = p;
|
|
}
|