5265ace0e4
Obtained from: elftoolchain.org
855 lines
21 KiB
C
855 lines
21 KiB
C
/*-
|
|
* Copyright (c) 2010-2013 Kai Wang
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "ld.h"
|
|
#include "ld_input.h"
|
|
#include "ld_layout.h"
|
|
#include "ld_output.h"
|
|
#include "ld_script.h"
|
|
#include "ld_symbols.h"
|
|
#include "ld_symver.h"
|
|
#include "ld_strtab.h"
|
|
|
|
ELFTC_VCSID("$Id: ld_symver.c 2917 2013-02-16 07:16:02Z kaiwang27 $");
|
|
|
|
/*
|
|
* Symbol versioning sections are the same for 32bit and 64bit
|
|
* ELF objects.
|
|
*/
|
|
#define Elf_Verdef Elf32_Verdef
|
|
#define Elf_Verdaux Elf32_Verdaux
|
|
#define Elf_Verneed Elf32_Verneed
|
|
#define Elf_Vernaux Elf32_Vernaux
|
|
|
|
static void _add_version_name(struct ld *ld, struct ld_input *li, int ndx,
|
|
const char *name);
|
|
static struct ld_symver_vda *_alloc_vda(struct ld *ld, const char *name,
|
|
struct ld_symver_verdef *svd);
|
|
static struct ld_symver_vna *_alloc_vna(struct ld *ld, const char *name,
|
|
struct ld_symver_verneed *svn);
|
|
static struct ld_symver_verdef *_alloc_verdef(struct ld *ld,
|
|
struct ld_symver_verdef_head *head);
|
|
static struct ld_symver_verneed *_alloc_verneed(struct ld *ld,
|
|
struct ld_input *li, struct ld_symver_verneed_head *head);
|
|
static struct ld_symver_verdef *_load_verdef(struct ld *ld,
|
|
struct ld_input *li, Elf_Verdef *vd);
|
|
static void _load_verdef_section(struct ld *ld, struct ld_input *li, Elf *e,
|
|
Elf_Scn *verdef);
|
|
static void _load_verneed_section(struct ld *ld, struct ld_input *li, Elf *e,
|
|
Elf_Scn *verneed);
|
|
|
|
void
|
|
ld_symver_load_symbol_version_info(struct ld *ld, struct ld_input *li, Elf *e,
|
|
Elf_Scn *versym, Elf_Scn *verneed, Elf_Scn *verdef)
|
|
{
|
|
Elf_Data *d_vs;
|
|
int elferr;
|
|
|
|
if (versym == NULL)
|
|
return;
|
|
|
|
(void) elf_errno();
|
|
if ((d_vs = elf_getdata(versym, NULL)) == NULL) {
|
|
elferr = elf_errno();
|
|
if (elferr != 0)
|
|
ld_fatal(ld, "%s: elf_getdata failed: %s", li->li_name,
|
|
elf_errmsg(elferr));
|
|
return;
|
|
}
|
|
if (d_vs->d_size == 0)
|
|
return;
|
|
|
|
if ((li->li_versym = malloc(d_vs->d_size)) == NULL)
|
|
ld_fatal_std(ld, "malloc");
|
|
memcpy(li->li_versym, d_vs->d_buf, d_vs->d_size);
|
|
li->li_versym_sz = d_vs->d_size / sizeof(uint16_t);
|
|
|
|
_add_version_name(ld, li, 0, "*local*");
|
|
_add_version_name(ld, li, 1, "*global*");
|
|
|
|
if (verneed != NULL)
|
|
_load_verneed_section(ld, li, e, verneed);
|
|
|
|
if (verdef != NULL)
|
|
_load_verdef_section(ld, li, e, verdef);
|
|
}
|
|
|
|
void
|
|
ld_symver_create_verneed_section(struct ld *ld)
|
|
{
|
|
struct ld_input *li;
|
|
struct ld_output *lo;
|
|
struct ld_output_section *os;
|
|
struct ld_output_data_buffer *odb;
|
|
struct ld_symver_verdef *svd;
|
|
struct ld_symver_verneed *svn;
|
|
struct ld_symver_vda *sda;
|
|
struct ld_symver_vna *sna;
|
|
char verneed_name[] = ".gnu.version_r";
|
|
Elf_Verneed *vn;
|
|
Elf_Vernaux *vna;
|
|
uint8_t *buf, *buf2, *end;
|
|
size_t sz;
|
|
|
|
lo = ld->ld_output;
|
|
assert(lo != NULL);
|
|
assert(lo->lo_dynstr != NULL);
|
|
|
|
/*
|
|
* Create .gnu.version_r section.
|
|
*/
|
|
HASH_FIND_STR(lo->lo_ostbl, verneed_name, os);
|
|
if (os == NULL)
|
|
os = ld_layout_insert_output_section(ld, verneed_name,
|
|
SHF_ALLOC);
|
|
os->os_type = SHT_GNU_verneed;
|
|
os->os_flags = SHF_ALLOC;
|
|
os->os_entsize = 0;
|
|
if (lo->lo_ec == ELFCLASS32)
|
|
os->os_align = 4;
|
|
else
|
|
os->os_align = 8;
|
|
|
|
if ((os->os_link = strdup(".dynstr")) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
lo->lo_verneed = os;
|
|
|
|
/*
|
|
* Build Verneed/Vernaux structures.
|
|
*/
|
|
sz = 0;
|
|
STAILQ_FOREACH(li, &ld->ld_lilist, li_next) {
|
|
if (li->li_type != LIT_DSO || li->li_dso_refcnt == 0 ||
|
|
li->li_verdef == NULL)
|
|
continue;
|
|
|
|
svn = NULL;
|
|
STAILQ_FOREACH(svd, li->li_verdef, svd_next) {
|
|
if (svd->svd_flags & VER_FLG_BASE)
|
|
continue;
|
|
|
|
/* Skip version definition that is never ref'ed. */
|
|
if (svd->svd_ref == 0)
|
|
continue;
|
|
|
|
/* Invalid Verdef? */
|
|
if ((sda = STAILQ_FIRST(&svd->svd_aux)) == NULL)
|
|
continue;
|
|
|
|
if (lo->lo_vnlist == NULL) {
|
|
lo->lo_vnlist = calloc(1,
|
|
sizeof(*lo->lo_vnlist));
|
|
if (lo->lo_vnlist == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
STAILQ_INIT(lo->lo_vnlist);
|
|
}
|
|
|
|
/* Allocate Verneed entry. */
|
|
if (svn == NULL) {
|
|
svn = _alloc_verneed(ld, li, lo->lo_vnlist);
|
|
svn->svn_version = VER_NEED_CURRENT;
|
|
svn->svn_cnt = 0;
|
|
svn->svn_fileindex =
|
|
ld_strtab_insert_no_suffix(ld,
|
|
ld->ld_dynstr, svn->svn_file);
|
|
sz += sizeof(Elf_Verneed);
|
|
lo->lo_verneed_num++;
|
|
}
|
|
|
|
/* Allocate Vernaux entry. */
|
|
sna = _alloc_vna(ld, sda->sda_name, svn);
|
|
sna->sna_other = lo->lo_version_index++;
|
|
sna->sna_nameindex = ld_strtab_insert_no_suffix(ld,
|
|
ld->ld_dynstr, sna->sna_name);
|
|
/* TODO: flags? VER_FLG_WEAK */
|
|
svn->svn_cnt++;
|
|
|
|
sz += sizeof(Elf_Vernaux);
|
|
|
|
/*
|
|
* Store the index in Verdef structure, so later we can
|
|
* quickly find the version index for a dynamic symbol,
|
|
* when we build the .gnu.version section.
|
|
*/
|
|
svd->svd_ndx_output = sna->sna_other;
|
|
}
|
|
}
|
|
|
|
if (lo->lo_verneed_num == 0)
|
|
return;
|
|
|
|
/* Store the number of verneed entries in the sh_info field. */
|
|
os->os_info_val = lo->lo_verneed_num;
|
|
|
|
/*
|
|
* Write Verneed/Vernaux structures.
|
|
*/
|
|
if ((buf = malloc(sz)) == NULL)
|
|
ld_fatal_std(ld, "malloc");
|
|
|
|
if ((odb = calloc(1, sizeof(*odb))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
odb->odb_buf = buf;
|
|
odb->odb_size = sz;
|
|
odb->odb_align = os->os_align;
|
|
odb->odb_type = ELF_T_VNEED; /* enable libelf translation */
|
|
|
|
end = buf + sz;
|
|
vn = NULL;
|
|
STAILQ_FOREACH(svn, lo->lo_vnlist, svn_next){
|
|
vn = (Elf_Verneed *) (uintptr_t) buf;
|
|
vn->vn_version = VER_NEED_CURRENT;
|
|
vn->vn_cnt = svn->svn_cnt;
|
|
vn->vn_file = svn->svn_fileindex;
|
|
vn->vn_aux = sizeof(Elf_Verneed);
|
|
vn->vn_next = sizeof(Elf_Verneed) +
|
|
svn->svn_cnt * sizeof(Elf_Vernaux);
|
|
|
|
/*
|
|
* Write Vernaux entries.
|
|
*/
|
|
buf2 = buf + sizeof(Elf_Verneed);
|
|
vna = NULL;
|
|
STAILQ_FOREACH(sna, &svn->svn_aux, sna_next) {
|
|
vna = (Elf_Vernaux *) (uintptr_t) buf2;
|
|
vna->vna_hash = sna->sna_hash;
|
|
vna->vna_flags = 0; /* TODO: VER_FLG_WEAK? */
|
|
vna->vna_other = sna->sna_other;
|
|
vna->vna_name = sna->sna_nameindex;
|
|
vna->vna_next = sizeof(Elf_Vernaux);
|
|
buf2 += sizeof(Elf_Vernaux);
|
|
}
|
|
|
|
/* Set last Vernaux entry's vna_next to 0. */
|
|
if (vna != NULL)
|
|
vna->vna_next = 0;
|
|
|
|
buf += vn->vn_next;
|
|
}
|
|
|
|
/* Set last Verneed entry's vn_next to 0 */
|
|
if (vn != NULL)
|
|
vn->vn_next = 0;
|
|
|
|
assert(buf == end);
|
|
|
|
(void) ld_output_create_section_element(ld, os, OET_DATA_BUFFER,
|
|
odb, NULL);
|
|
}
|
|
|
|
void
|
|
ld_symver_create_verdef_section(struct ld *ld)
|
|
{
|
|
struct ld_script *lds;
|
|
struct ld_output *lo;
|
|
struct ld_output_section *os;
|
|
struct ld_output_data_buffer *odb;
|
|
struct ld_script_version_node *ldvn;
|
|
char verdef_name[] = ".gnu.version_d";
|
|
Elf_Verdef *vd;
|
|
Elf_Verdaux *vda;
|
|
uint8_t *buf, *end;
|
|
char *soname;
|
|
size_t sz;
|
|
|
|
lo = ld->ld_output;
|
|
assert(lo != NULL);
|
|
assert(lo->lo_dynstr != NULL);
|
|
|
|
lds = ld->ld_scp;
|
|
if (STAILQ_EMPTY(&lds->lds_vn))
|
|
return;
|
|
|
|
/*
|
|
* Create .gnu.version_d section.
|
|
*/
|
|
HASH_FIND_STR(lo->lo_ostbl, verdef_name, os);
|
|
if (os == NULL)
|
|
os = ld_layout_insert_output_section(ld, verdef_name,
|
|
SHF_ALLOC);
|
|
os->os_type = SHT_GNU_verdef;
|
|
os->os_flags = SHF_ALLOC;
|
|
os->os_entsize = 0;
|
|
if (lo->lo_ec == ELFCLASS32)
|
|
os->os_align = 4;
|
|
else
|
|
os->os_align = 8;
|
|
|
|
if ((os->os_link = strdup(".dynstr")) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
lo->lo_verdef = os;
|
|
|
|
/*
|
|
* Calculate verdef section size: .gnu.version_d section consists
|
|
* of one file version entry and several symbol version definition
|
|
* entries (with corresponding) auxiliary entries.
|
|
*/
|
|
lo->lo_verdef_num = 1;
|
|
sz = sizeof(Elf_Verdef) + sizeof(Elf_Verdaux);
|
|
STAILQ_FOREACH(ldvn, &lds->lds_vn, ldvn_next) {
|
|
sz += sizeof(Elf_Verdef) + sizeof(Elf_Verdaux);
|
|
if (ldvn->ldvn_dep != NULL)
|
|
sz += sizeof(Elf_Verdaux);
|
|
lo->lo_verdef_num++;
|
|
}
|
|
|
|
/* Store the number of verdef entries in the sh_info field. */
|
|
os->os_info_val = lo->lo_verdef_num;
|
|
|
|
/* Allocate buffer for Verdef/Verdaux entries. */
|
|
if ((buf = malloc(sz)) == NULL)
|
|
ld_fatal_std(ld, "malloc");
|
|
|
|
end = buf + sz;
|
|
|
|
if ((odb = calloc(1, sizeof(*odb))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
odb->odb_buf = buf;
|
|
odb->odb_size = sz;
|
|
odb->odb_align = os->os_align;
|
|
odb->odb_type = ELF_T_VDEF; /* enable libelf translation */
|
|
|
|
/*
|
|
* Set file version name to `soname' if it is provided,
|
|
* otherwise set version name to output file name.
|
|
*/
|
|
if (ld->ld_soname != NULL)
|
|
soname = ld->ld_soname;
|
|
else {
|
|
if ((soname = strrchr(ld->ld_output_file, '/')) == NULL)
|
|
soname = ld->ld_output_file;
|
|
else
|
|
soname++;
|
|
}
|
|
|
|
/* Write file version entry. */
|
|
vd = (Elf_Verdef *) (uintptr_t) buf;
|
|
vd->vd_version = VER_DEF_CURRENT;
|
|
vd->vd_flags |= VER_FLG_BASE;
|
|
vd->vd_ndx = 1;
|
|
vd->vd_cnt = 1;
|
|
vd->vd_hash = elf_hash(soname);
|
|
vd->vd_aux = sizeof(Elf_Verdef);
|
|
vd->vd_next = sizeof(Elf_Verdef) + sizeof(Elf_Verdaux);
|
|
buf += sizeof(Elf_Verdef);
|
|
|
|
/* Write file version auxiliary entry. */
|
|
vda = (Elf_Verdaux *) (uintptr_t) buf;
|
|
vda->vda_name = ld_strtab_insert_no_suffix(ld, ld->ld_dynstr,
|
|
soname);
|
|
vda->vda_next = 0;
|
|
buf += sizeof(Elf_Verdaux);
|
|
|
|
/* Write symbol version definition entries. */
|
|
STAILQ_FOREACH(ldvn, &lds->lds_vn, ldvn_next) {
|
|
vd = (Elf_Verdef *) (uintptr_t) buf;
|
|
vd->vd_version = VER_DEF_CURRENT;
|
|
vd->vd_flags = 0;
|
|
vd->vd_ndx = lo->lo_version_index++;
|
|
vd->vd_cnt = (ldvn->ldvn_dep == NULL) ? 1 : 2;
|
|
vd->vd_hash = elf_hash(ldvn->ldvn_name);
|
|
vd->vd_aux = sizeof(Elf_Verdef);
|
|
if (STAILQ_NEXT(ldvn, ldvn_next) == NULL)
|
|
vd->vd_next = 0;
|
|
else
|
|
vd->vd_next = sizeof(Elf_Verdef) +
|
|
((ldvn->ldvn_dep == NULL) ? 1 : 2) *
|
|
sizeof(Elf_Verdaux);
|
|
buf += sizeof(Elf_Verdef);
|
|
|
|
/* Write version name auxiliary entry. */
|
|
vda = (Elf_Verdaux *) (uintptr_t) buf;
|
|
vda->vda_name = ld_strtab_insert_no_suffix(ld, ld->ld_dynstr,
|
|
ldvn->ldvn_name);
|
|
vda->vda_next = ldvn->ldvn_dep == NULL ? 0 :
|
|
sizeof(Elf_Verdaux);
|
|
buf += sizeof(Elf_Verdaux);
|
|
|
|
if (ldvn->ldvn_dep == NULL)
|
|
continue;
|
|
|
|
/* Write version dependency auxiliary entry. */
|
|
vda = (Elf_Verdaux *) (uintptr_t) buf;
|
|
vda->vda_name = ld_strtab_insert_no_suffix(ld, ld->ld_dynstr,
|
|
ldvn->ldvn_dep);
|
|
vda->vda_next = 0;
|
|
buf += sizeof(Elf_Verdaux);
|
|
}
|
|
|
|
assert(buf == end);
|
|
|
|
(void) ld_output_create_section_element(ld, os, OET_DATA_BUFFER,
|
|
odb, NULL);
|
|
}
|
|
|
|
void
|
|
ld_symver_create_versym_section(struct ld *ld)
|
|
{
|
|
struct ld_output *lo;
|
|
struct ld_output_section *os;
|
|
struct ld_output_data_buffer *odb;
|
|
struct ld_symbol *lsb;
|
|
char versym_name[] = ".gnu.version";
|
|
uint16_t *buf;
|
|
size_t sz;
|
|
int i;
|
|
|
|
lo = ld->ld_output;
|
|
assert(lo != NULL);
|
|
assert(lo->lo_dynsym != NULL);
|
|
assert(ld->ld_dynsym != NULL);
|
|
|
|
/*
|
|
* Create .gnu.version section.
|
|
*/
|
|
HASH_FIND_STR(lo->lo_ostbl, versym_name, os);
|
|
if (os == NULL)
|
|
os = ld_layout_insert_output_section(ld, versym_name,
|
|
SHF_ALLOC);
|
|
os->os_type = SHT_GNU_versym;
|
|
os->os_flags = SHF_ALLOC;
|
|
os->os_entsize = 2;
|
|
os->os_align = 2;
|
|
|
|
if ((os->os_link = strdup(".dynsym")) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
lo->lo_versym = os;
|
|
|
|
/*
|
|
* Write versym table.
|
|
*/
|
|
sz = ld->ld_dynsym->sy_size * sizeof(*buf);
|
|
if ((buf = malloc(sz)) == NULL)
|
|
ld_fatal_std(ld, "malloc");
|
|
|
|
buf[0] = 0; /* special index 0 symbol */
|
|
i = 1;
|
|
STAILQ_FOREACH(lsb, ld->ld_dyn_symbols, lsb_dyn) {
|
|
/*
|
|
* Assign version index according to the following rules:
|
|
*
|
|
* 1. If the symbol is local, the version is *local*.
|
|
*
|
|
* 2. If the symbol is defined in shared libraries and there
|
|
* exists a version definition for this symbol, use the
|
|
* version defined by the shared library.
|
|
*
|
|
* 3. If the symbol is defined in regular objects and the
|
|
* linker creates a shared library, use the version
|
|
* defined in the version script, if provided.
|
|
*
|
|
* 4. Otherwise, the version is *global*.
|
|
*/
|
|
if (lsb->lsb_bind == STB_LOCAL)
|
|
buf[i] = 0; /* Version is *local* */
|
|
else if (lsb->lsb_vd != NULL)
|
|
buf[i] = lsb->lsb_vd->svd_ndx_output;
|
|
else if (ld->ld_dso && ld_symbols_in_regular(lsb))
|
|
buf[i] = ld_symver_search_version_script(ld, lsb);
|
|
else {
|
|
buf[i] = 1; /* Version is *global* */
|
|
}
|
|
i++;
|
|
}
|
|
assert((size_t) i == ld->ld_dynsym->sy_size);
|
|
|
|
if ((odb = calloc(1, sizeof(*odb))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
odb->odb_buf = (void *) buf;
|
|
odb->odb_size = sz;
|
|
odb->odb_align = os->os_align;
|
|
odb->odb_type = ELF_T_HALF; /* enable libelf translation */
|
|
|
|
(void) ld_output_create_section_element(ld, os, OET_DATA_BUFFER,
|
|
odb, NULL);
|
|
}
|
|
|
|
void
|
|
ld_symver_add_verdef_refcnt(struct ld *ld, struct ld_symbol *lsb)
|
|
{
|
|
struct ld_symbol_defver *dv;
|
|
struct ld_symver_verdef *svd;
|
|
struct ld_symver_vda *sda;
|
|
struct ld_input *li;
|
|
const char *ver;
|
|
|
|
li = lsb->lsb_input;
|
|
assert(li != NULL);
|
|
|
|
if (li->li_verdef == NULL)
|
|
return;
|
|
|
|
if (lsb->lsb_ver != NULL)
|
|
ver = lsb->lsb_ver;
|
|
else {
|
|
HASH_FIND_STR(ld->ld_defver, lsb->lsb_name, dv);
|
|
if (dv == NULL || dv->dv_ver == NULL)
|
|
return;
|
|
ver = dv->dv_ver;
|
|
}
|
|
|
|
STAILQ_FOREACH(svd, li->li_verdef, svd_next) {
|
|
if (svd->svd_flags & VER_FLG_BASE)
|
|
continue;
|
|
|
|
/* Invalid Verdef? */
|
|
if ((sda = STAILQ_FIRST(&svd->svd_aux)) == NULL)
|
|
continue;
|
|
|
|
if (!strcmp(ver, sda->sda_name))
|
|
break;
|
|
}
|
|
|
|
if (svd != NULL) {
|
|
svd->svd_ref++;
|
|
lsb->lsb_vd = svd;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_add_version_name(struct ld *ld, struct ld_input *li, int ndx,
|
|
const char *name)
|
|
{
|
|
int i;
|
|
|
|
assert(name != NULL);
|
|
|
|
if (ndx <= 1)
|
|
return;
|
|
|
|
if (li->li_vername == NULL) {
|
|
li->li_vername_sz = 10;
|
|
li->li_vername = calloc(li->li_vername_sz,
|
|
sizeof(*li->li_vername));
|
|
if (li->li_vername == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
}
|
|
|
|
if ((size_t) ndx >= li->li_vername_sz) {
|
|
li->li_vername = realloc(li->li_vername,
|
|
sizeof(*li->li_vername) * li->li_vername_sz * 2);
|
|
if (li->li_vername == NULL)
|
|
ld_fatal_std(ld, "realloc");
|
|
for (i = li->li_vername_sz; (size_t) i < li->li_vername_sz * 2;
|
|
i++)
|
|
li->li_vername[i] = NULL;
|
|
li->li_vername_sz *= 2;
|
|
}
|
|
|
|
if (li->li_vername[ndx] == NULL) {
|
|
li->li_vername[ndx] = strdup(name);
|
|
if (li->li_vername[ndx] == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
}
|
|
}
|
|
|
|
static struct ld_symver_vna *
|
|
_alloc_vna(struct ld *ld, const char *name, struct ld_symver_verneed *svn)
|
|
{
|
|
struct ld_symver_vna *sna;
|
|
|
|
assert(name != NULL);
|
|
|
|
if ((sna = calloc(1, sizeof(*sna))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
if ((sna->sna_name = strdup(name)) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
sna->sna_hash = (uint32_t) elf_hash(sna->sna_name);
|
|
|
|
if (svn != NULL)
|
|
STAILQ_INSERT_TAIL(&svn->svn_aux, sna, sna_next);
|
|
|
|
return (sna);
|
|
}
|
|
|
|
static struct ld_symver_vda *
|
|
_alloc_vda(struct ld *ld, const char *name, struct ld_symver_verdef *svd)
|
|
{
|
|
struct ld_symver_vda *sda;
|
|
|
|
if ((sda = calloc(1, sizeof(*sda))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
if ((sda->sda_name = strdup(name)) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
if (svd != NULL)
|
|
STAILQ_INSERT_TAIL(&svd->svd_aux, sda, sda_next);
|
|
|
|
return (sda);
|
|
}
|
|
|
|
static struct ld_symver_verneed *
|
|
_alloc_verneed(struct ld *ld, struct ld_input *li,
|
|
struct ld_symver_verneed_head *head)
|
|
{
|
|
struct ld_symver_verneed *svn;
|
|
const char *bn;
|
|
|
|
if ((svn = calloc(1, sizeof(*svn))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
if (li->li_soname != NULL)
|
|
bn = li->li_soname;
|
|
else {
|
|
if ((bn = strrchr(li->li_name, '/')) == NULL)
|
|
bn = li->li_name;
|
|
else
|
|
bn++;
|
|
}
|
|
|
|
if ((svn->svn_file = strdup(bn)) == NULL)
|
|
ld_fatal_std(ld, "strdup");
|
|
|
|
STAILQ_INIT(&svn->svn_aux);
|
|
|
|
if (head != NULL)
|
|
STAILQ_INSERT_TAIL(head, svn, svn_next);
|
|
|
|
return (svn);
|
|
}
|
|
|
|
static struct ld_symver_verdef *
|
|
_alloc_verdef(struct ld *ld, struct ld_symver_verdef_head *head)
|
|
{
|
|
struct ld_symver_verdef *svd;
|
|
|
|
if ((svd = calloc(1, sizeof(*svd))) == NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
|
|
STAILQ_INIT(&svd->svd_aux);
|
|
|
|
if (head != NULL)
|
|
STAILQ_INSERT_TAIL(head, svd, svd_next);
|
|
|
|
return (svd);
|
|
}
|
|
|
|
static void
|
|
_load_verneed_section(struct ld *ld, struct ld_input *li, Elf *e,
|
|
Elf_Scn *verneed)
|
|
{
|
|
Elf_Data *d_vn;
|
|
Elf_Verneed *vn;
|
|
Elf_Vernaux *vna;
|
|
GElf_Shdr sh_vn;
|
|
uint8_t *buf, *end, *buf2;
|
|
char *name;
|
|
int elferr, i;
|
|
|
|
if (gelf_getshdr(verneed, &sh_vn) != &sh_vn)
|
|
ld_fatal(ld, "%s: gelf_getshdr failed: %s", li->li_name,
|
|
elf_errmsg(-1));
|
|
|
|
(void) elf_errno();
|
|
if ((d_vn = elf_getdata(verneed, NULL)) == NULL) {
|
|
elferr = elf_errno();
|
|
if (elferr != 0)
|
|
ld_fatal(ld, "%s: elf_getdata failed: %s", li->li_name,
|
|
elf_errmsg(elferr));
|
|
return;
|
|
}
|
|
if (d_vn->d_size == 0)
|
|
return;
|
|
|
|
buf = d_vn->d_buf;
|
|
end = buf + d_vn->d_size;
|
|
while (buf + sizeof(Elf_Verneed) <= end) {
|
|
vn = (Elf_Verneed *) (uintptr_t) buf;
|
|
buf2 = buf + vn->vn_aux;
|
|
i = 0;
|
|
while (buf2 + sizeof(Elf_Vernaux) <= end && i < vn->vn_cnt) {
|
|
vna = (Elf32_Vernaux *) (uintptr_t) buf2;
|
|
name = elf_strptr(e, sh_vn.sh_link,
|
|
vna->vna_name);
|
|
if (name != NULL)
|
|
_add_version_name(ld, li, (int) vna->vna_other,
|
|
name);
|
|
buf2 += vna->vna_next;
|
|
i++;
|
|
}
|
|
if (vn->vn_next == 0)
|
|
break;
|
|
buf += vn->vn_next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_load_verdef_section(struct ld *ld, struct ld_input *li, Elf *e,
|
|
Elf_Scn *verdef)
|
|
{
|
|
struct ld_symver_verdef *svd;
|
|
Elf_Data *d_vd;
|
|
Elf_Verdef *vd;
|
|
Elf_Verdaux *vda;
|
|
GElf_Shdr sh_vd;
|
|
uint8_t *buf, *end, *buf2;
|
|
char *name;
|
|
int elferr, i;
|
|
|
|
if (gelf_getshdr(verdef, &sh_vd) != &sh_vd)
|
|
ld_fatal(ld, "%s: gelf_getshdr failed: %s", li->li_name,
|
|
elf_errmsg(-1));
|
|
|
|
(void) elf_errno();
|
|
if ((d_vd = elf_getdata(verdef, NULL)) == NULL) {
|
|
elferr = elf_errno();
|
|
if (elferr != 0)
|
|
ld_fatal(ld, "%s: elf_getdata failed: %s", li->li_name,
|
|
elf_errmsg(elferr));
|
|
return;
|
|
}
|
|
if (d_vd->d_size == 0)
|
|
return;
|
|
|
|
buf = d_vd->d_buf;
|
|
end = buf + d_vd->d_size;
|
|
while (buf + sizeof(Elf_Verdef) <= end) {
|
|
vd = (Elf_Verdef *) (uintptr_t) buf;
|
|
svd = _load_verdef(ld, li, vd);
|
|
buf2 = buf + vd->vd_aux;
|
|
i = 0;
|
|
while (buf2 + sizeof(Elf_Verdaux) <= end && i < vd->vd_cnt) {
|
|
vda = (Elf_Verdaux *) (uintptr_t) buf2;
|
|
name = elf_strptr(e, sh_vd.sh_link, vda->vda_name);
|
|
if (name != NULL) {
|
|
_add_version_name(ld, li, (int) vd->vd_ndx,
|
|
name);
|
|
(void) _alloc_vda(ld, name, svd);
|
|
}
|
|
if (vda->vda_next == 0)
|
|
break;
|
|
buf2 += vda->vda_next;
|
|
i++;
|
|
}
|
|
if (vd->vd_next == 0)
|
|
break;
|
|
buf += vd->vd_next;
|
|
}
|
|
}
|
|
|
|
static struct ld_symver_verdef *
|
|
_load_verdef(struct ld *ld, struct ld_input *li, Elf_Verdef *vd)
|
|
{
|
|
struct ld_symver_verdef *svd;
|
|
|
|
if (li->li_verdef == NULL) {
|
|
if ((li->li_verdef = calloc(1, sizeof(*li->li_verdef))) ==
|
|
NULL)
|
|
ld_fatal_std(ld, "calloc");
|
|
STAILQ_INIT(li->li_verdef);
|
|
}
|
|
|
|
svd = _alloc_verdef(ld, li->li_verdef);
|
|
svd->svd_version = vd->vd_version;
|
|
svd->svd_flags = vd->vd_flags;
|
|
svd->svd_ndx = vd->vd_ndx;
|
|
svd->svd_cnt = vd->vd_cnt;
|
|
svd->svd_hash = vd->vd_hash;
|
|
|
|
return (svd);
|
|
}
|
|
|
|
uint16_t
|
|
ld_symver_search_version_script(struct ld *ld, struct ld_symbol *lsb)
|
|
{
|
|
struct ld_script *lds;
|
|
struct ld_script_version_node *ldvn;
|
|
struct ld_script_version_entry *ldve, *ldve_g;
|
|
uint16_t ndx, ret_ndx, ret_ndx_g;
|
|
|
|
/* If the symbol version index was known, return it directly. */
|
|
if (lsb->lsb_vndx_known)
|
|
return (lsb->lsb_vndx);
|
|
|
|
/* The symbol version index will be known after searching. */
|
|
lsb->lsb_vndx_known = 1;
|
|
|
|
lds = ld->ld_scp;
|
|
|
|
/* If there isn't a version script, the default version is *global* */
|
|
if (STAILQ_EMPTY(&lds->lds_vn)) {
|
|
lsb->lsb_vndx = 1;
|
|
return (1);
|
|
}
|
|
|
|
/* Search for a match in the version patterns. */
|
|
ndx = 2;
|
|
ldve_g = NULL;
|
|
ret_ndx_g = 0;
|
|
STAILQ_FOREACH(ldvn, &lds->lds_vn, ldvn_next) {
|
|
STAILQ_FOREACH(ldve, ldvn->ldvn_e, ldve_next) {
|
|
assert(ldve->ldve_sym != NULL);
|
|
if (fnmatch(ldve->ldve_sym, lsb->lsb_name, 0) == 0) {
|
|
if (ldve->ldve_local)
|
|
ret_ndx = 0;
|
|
else if (ldvn->ldvn_name != NULL)
|
|
ret_ndx = ndx;
|
|
else
|
|
ret_ndx = 1;
|
|
|
|
/*
|
|
* If the version name is a globbing pattern,
|
|
* we only consider it is a match when there
|
|
* doesn't exist a exact match.
|
|
*/
|
|
if (ldve->ldve_glob) {
|
|
if (ldve_g == NULL) {
|
|
ldve_g = ldve;
|
|
ret_ndx_g = ret_ndx;
|
|
}
|
|
} else {
|
|
lsb->lsb_vndx = ret_ndx;
|
|
return (ret_ndx);
|
|
}
|
|
}
|
|
}
|
|
if (ldvn->ldvn_name != NULL)
|
|
ndx++;
|
|
}
|
|
|
|
/* There is no exact match, check if there is a globbing match. */
|
|
if (ldve_g != NULL) {
|
|
lsb->lsb_vndx = ret_ndx_g;
|
|
return (ret_ndx_g);
|
|
}
|
|
|
|
/*
|
|
* Symbol doesn't match any version definition, set version
|
|
* to *global*.
|
|
*/
|
|
lsb->lsb_vndx = 1;
|
|
return (1);
|
|
}
|