Add elf version support

This commit is contained in:
herman ten brugge 2019-10-22 16:55:20 +02:00
parent 491773ac58
commit 7268fe7239

372
tccelf.c
View File

@ -42,6 +42,34 @@ ST_DATA Section *symtab_section;
/* debug sections */
ST_DATA Section *stab_section, *stabstr_section;
/* elf version information */
ST_DATA int nb_version_sym;
struct sym_info_struct {
char *name;
ElfW(Sym) esym;
};
ST_DATA struct def_need_struct {
char *lib;
char *version;
unsigned int nb_sym_info;
struct sym_info_struct *sym_info;
} *version_sym;
ST_DATA int nb_need;
ST_DATA int dt_verneednum;
ST_DATA struct need_struct {
int offset;
char *lib;
int n_aux;
struct aux_struct {
int offset;
char *version;
int other;
} *aux;
} *need;
ST_DATA ElfW(Half) next_version = 1;
ST_DATA Section *versym_section;
ST_DATA Section *verneed_section;
/* XXX: avoid static variable */
static int new_undef_sym = 0; /* Is there a new undefined sym since last new_undef_sym() */
@ -61,6 +89,10 @@ ST_FUNC void tccelf_new(TCCState *s)
text_section = new_section(s, ".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR);
data_section = new_section(s, ".data", SHT_PROGBITS, SHF_ALLOC | SHF_WRITE);
bss_section = new_section(s, ".bss", SHT_NOBITS, SHF_ALLOC | SHF_WRITE);
versym_section = new_section(s, ".gnu.version", SHT_GNU_versym, SHF_ALLOC);
versym_section->sh_entsize = sizeof(ElfW(Half));
verneed_section = new_section(s, ".gnu.version_r", SHT_GNU_verneed, SHF_ALLOC);
verneed_section->sh_info = 2;
common_section = new_section(s, ".common", SHT_NOBITS, SHF_PRIVATE);
common_section->sh_num = SHN_COMMON;
@ -198,13 +230,18 @@ ST_FUNC Section *new_section(TCCState *s1, const char *name, int sh_type, int sh
sec->sh_type = sh_type;
sec->sh_flags = sh_flags;
switch(sh_type) {
case SHT_GNU_versym:
sec->sh_addralign = 2;
break;
case SHT_HASH:
case SHT_REL:
case SHT_RELA:
case SHT_DYNSYM:
case SHT_SYMTAB:
case SHT_DYNAMIC:
sec->sh_addralign = 4;
case SHT_GNU_verneed:
case SHT_GNU_verdef:
sec->sh_addralign = PTR_SIZE;
break;
case SHT_STRTAB:
sec->sh_addralign = 1;
@ -516,6 +553,220 @@ ST_FUNC void* tcc_get_symbol_err(TCCState *s, const char *name)
}
#endif
ST_FUNC void
save_def_need_index (int *n_def_need, struct def_need_struct **def_need,
unsigned int n, const char *lib, const char *version)
{
/* add index in table */
if (*n_def_need < n + 1) {
*def_need = (struct def_need_struct *)
tcc_realloc (*def_need,
(n + 1) * sizeof (struct def_need_struct));
while (*n_def_need < n + 1) {
(*def_need)[*n_def_need].lib = NULL;
(*def_need)[*n_def_need].version = NULL;
(*def_need)[*n_def_need].nb_sym_info = 0;
(*def_need)[*n_def_need].sym_info = NULL;
(*n_def_need)++;
}
}
if ((*def_need)[n].lib == NULL) {
(*def_need)[n].lib = tcc_strdup (lib);
(*def_need)[n].version = tcc_strdup (version);
}
}
ST_FUNC void
free_def_need (int n_def_need, struct def_need_struct *def_need)
{
int i;
for (i = 0; i < n_def_need; i++) {
tcc_free (def_need[i].lib);
tcc_free (def_need[i].version);
}
tcc_free (def_need);
}
ST_FUNC void
add_version_sym (Section *s, int sym_index, int n_def_need,
struct def_need_struct *def_need, const char *name, unsigned int n)
{
int i, j;
ElfW(Sym) *esym;
/* check if present */
esym = &((ElfW(Sym) *)s->data)[sym_index];
for (i = 0; i < nb_version_sym; i++) {
for (j = 0; j < version_sym[i].nb_sym_info; j++) {
if (strcmp (version_sym[i].sym_info[j].name, name) == 0) {
if (memcmp (&version_sym[i].sym_info[j].esym, esym, sizeof (ElfW(Sym))) == 0) {
return;
}
version_sym[i].nb_sym_info--;
tcc_free (version_sym[i].sym_info[j].name);
for (; j < version_sym[i].nb_sym_info; j++) {
version_sym[i].sym_info[j] = version_sym[i].sym_info[j + 1];
}
}
}
}
/* add new value */
for (i = 0; i < nb_version_sym; i++) {
if (strcmp (def_need[n].lib,
version_sym[i].lib) == 0 &&
strcmp (def_need[n].version,
version_sym[i].version) == 0) {
break;
}
}
if (i == nb_version_sym) {
version_sym = (struct def_need_struct *)
tcc_realloc (version_sym,
(nb_version_sym + 1) * sizeof (struct def_need_struct));
version_sym[nb_version_sym].lib = tcc_strdup (def_need[n].lib);
version_sym[nb_version_sym].version = tcc_strdup (def_need[n].version);
version_sym[nb_version_sym].nb_sym_info = 0;
version_sym[nb_version_sym].sym_info = NULL;
nb_version_sym++;
}
version_sym[i].sym_info = (struct sym_info_struct *)
tcc_realloc (version_sym[i].sym_info,
(version_sym[i].nb_sym_info + 1) * sizeof (struct sym_info_struct));
version_sym[i].sym_info[version_sym[i].nb_sym_info].name = tcc_strdup (name);
version_sym[i].sym_info[version_sym[i].nb_sym_info].esym = *esym;
version_sym[i].nb_sym_info++;
}
static void
add_need_sym (Section *s, const char *soname, const char *version, const char *name, int sym_index)
{
int i;
unsigned long size;
void *ptr;
int ver_index = 0;
struct need_struct *verlib = NULL;
ElfW(Half) vers;
if (soname) {
for (i = 0; i < nb_need; i++) {
if (strcmp (soname, need[i].lib) == 0) {
verlib = &need[i];
break;
}
}
if (verlib == NULL) {
need = (struct need_struct *)
tcc_realloc (need, (nb_need + 1) * sizeof (struct need_struct));
need[nb_need].offset = put_elf_str(s->link, soname);
need[nb_need].lib = tcc_strdup (soname);
need[nb_need].n_aux = 0;
need[nb_need].aux = NULL;
verlib = &need[nb_need];
nb_need++;
}
for (i = 0; i < verlib->n_aux; i++) {
if (strcmp (version, verlib->aux[i].version) == 0) {
ver_index = verlib->aux[i].other;
}
}
if (ver_index == 0) {
verlib->aux = (struct aux_struct *)
tcc_realloc (verlib->aux, (verlib->n_aux + 1) * sizeof (struct aux_struct));
verlib->aux[verlib->n_aux].offset = put_elf_str(s->link, version);
verlib->aux[verlib->n_aux].version = tcc_strdup (version);
ver_index = verlib->aux[verlib->n_aux].other = ++next_version;
verlib->n_aux++;
}
}
/* update versym section */
size = versym_section->data_offset / sizeof (vers);
for (i = size; i < (sym_index + 1); i++) {
ptr = section_ptr_add (versym_section, sizeof (vers));
vers = 0;
memcpy (ptr, &vers, sizeof (vers));
}
ptr = versym_section->data + sym_index * sizeof (vers);
vers = ver_index;
memcpy (ptr, &vers, sizeof (vers));
}
static void
version_add (TCCState *s)
{
int i, j, found;
ElfW(Sym) *sym;
ElfW(Verneed) verneed;
ElfW(Vernaux) aux;
Section *symtab;
int sym_index, end_sym;
const char *name;
void *ptr;
/* add needed symbols */
symtab = s->dynsym;
end_sym = symtab->data_offset / sizeof (ElfSym);
for (sym_index = 0; sym_index < end_sym; ++sym_index) {
sym = &((ElfW(Sym) *)symtab->data)[sym_index];
name = (char *) symtab->link->data + sym->st_name;
found = 0;
for (i = 0; !found && i < nb_version_sym; i++) {
for (j = 0; !found && j < version_sym[i].nb_sym_info; j++) {
if (strcmp (version_sym[i].sym_info[j].name, name) == 0) {
found = 1;
add_need_sym (symtab, version_sym[i].lib, version_sym[i].version, name, sym_index);
}
}
}
if (!found) {
add_need_sym (symtab, NULL, NULL, NULL, sym_index);
}
symtab = s->dynsym; /* symtab can be realocated by add_need_sym */
}
/* generate verneed section */
for (i = 0; i < nb_need; i++) {
verneed.vn_version = 1;
verneed.vn_cnt = need[i].n_aux;
verneed.vn_file = need[i].offset;
verneed.vn_aux = sizeof (verneed);
verneed.vn_next = i == nb_need - 1 ? 0 : sizeof (verneed) + need[i].n_aux * sizeof (aux);
ptr = section_ptr_add (verneed_section, sizeof (verneed));
memcpy (ptr, &verneed, sizeof (verneed));
for (j = 0; j < need[i].n_aux; j++) {
aux.vna_hash = elf_hash (need[i].aux[j].version);
aux.vna_flags = 0;
aux.vna_other = need[i].aux[j].other;
aux.vna_name = need[i].aux[j].offset;
aux.vna_next = j == need[i].n_aux - 1 ? 0 : sizeof (aux);
ptr = section_ptr_add (verneed_section, sizeof (aux));
memcpy (ptr, &aux, sizeof (aux));
}
}
/* free all info */
for (i = 0; i < nb_version_sym; i++) {
for (j = 0; j < version_sym[i].nb_sym_info; j++) {
tcc_free (version_sym[i].sym_info[j].name);
}
tcc_free (version_sym[i].lib);
tcc_free (version_sym[i].version);
tcc_free (version_sym[i].sym_info);
}
tcc_free (version_sym);
nb_version_sym = 0;
version_sym = NULL;
for (i = 0; i < nb_need; i++) {
tcc_free (need[i].lib);
for (j = 0; j < need[i].n_aux; j++) {
tcc_free (need[i].aux[j].version);
}
tcc_free (need[i].aux);
}
tcc_free (need);
dt_verneednum = nb_need;
nb_need = 0;
need = NULL;
}
/* add an elf symbol : check if it is already defined and patch
it. Return symbol index. NOTE that sh_num can be SHN_UNDEF. */
ST_FUNC int set_elf_sym(Section *s, addr_t value, unsigned long size,
@ -1855,6 +2106,9 @@ static void fill_dynamic(TCCState *s1, struct dyn_inf *dyninf)
put_dt(dynamic, DT_RELENT, sizeof(ElfW_Rel));
#endif
#endif
put_dt(dynamic, DT_VERNEED, verneed_section->sh_addr);
put_dt(dynamic, DT_VERNEEDNUM, dt_verneednum);
put_dt(dynamic, DT_VERSYM, versym_section->sh_addr);
if (s1->do_debug)
put_dt(dynamic, DT_DEBUG, 0);
put_dt(dynamic, DT_NULL, 0);
@ -2137,6 +2391,8 @@ static int elf_output_file(TCCState *s1, const char *filename)
".dynstr",
".hash", SHF_ALLOC);
dynstr = s1->dynsym->link;
versym_section->link = s1->dynsym;
verneed_section->link = dynstr;
/* add dynamic section */
dynamic = new_section(s1, ".dynamic", SHT_DYNAMIC,
@ -2157,6 +2413,7 @@ static int elf_output_file(TCCState *s1, const char *filename)
}
}
build_got_entries(s1);
version_add (s1);
}
/* we add a section for symbols */
@ -2752,7 +3009,16 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
int i, j, nb_syms, nb_dts, sym_bind, ret;
ElfW(Sym) *sym, *dynsym;
ElfW(Dyn) *dt, *dynamic;
int nb_versyms;
ElfW(Verdef) *verdef, *vdef;
ElfW(Verneed) *verneed, *vneed;
ElfW(Half) *versym, *vsym;
unsigned char *dynstr;
uint32_t next;
int sym_index;
char *lib, *version;
int n_def_need = 0;
struct def_need_struct *def_need = NULL;
const char *name, *soname;
DLLReference *dllref;
@ -2774,6 +3040,10 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
dynamic = NULL;
dynsym = NULL; /* avoid warning */
dynstr = NULL; /* avoid warning */
nb_versyms = 0;
verdef = NULL;
verneed = NULL;
versym = NULL;
for(i = 0, sh = shdr; i < ehdr.e_shnum; i++, sh++) {
switch(sh->sh_type) {
case SHT_DYNAMIC:
@ -2786,6 +3056,16 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
sh1 = &shdr[sh->sh_link];
dynstr = load_data(fd, sh1->sh_offset, sh1->sh_size);
break;
case SHT_GNU_verdef:
verdef = load_data(fd, sh->sh_offset, sh->sh_size);
break;
case SHT_GNU_verneed:
verneed = load_data(fd, sh->sh_offset, sh->sh_size);
break;
case SHT_GNU_versym:
nb_versyms = sh->sh_size / sizeof(ElfW(Half));
versym = load_data(fd, sh->sh_offset, sh->sh_size);
break;
default:
break;
}
@ -2812,6 +3092,81 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
}
}
if (nb_versyms != nb_syms) {
tcc_free (versym);
versym = NULL;
}
#define DEBUG_VERSION 0
if (versym && verdef) {
lib = NULL;
vdef = verdef;
do {
ElfW(Verdaux) *verdaux =
(ElfW(Verdaux) *) (((char *) vdef) + vdef->vd_aux);
#if DEBUG_VERSION
printf ("verdef: version:%u flags:%u index:%u, hash:%u\n",
vdef->vd_version, vdef->vd_flags, vdef->vd_ndx,
vdef->vd_hash);
#endif
for (i = 0; i < vdef->vd_cnt; i++) {
version = (char *) dynstr + verdaux->vda_name;
if (lib == NULL) {
lib = version;
}
else {
save_def_need_index (&n_def_need, &def_need,
vdef->vd_ndx, lib, version);
}
#if DEBUG_VERSION
printf (" verdaux(%u): %s\n", vdef->vd_ndx, version);
#endif
verdaux = (ElfW(Verdaux) *) (((char *) verdaux) + verdaux->vda_next);
}
next = vdef->vd_next;
vdef = (ElfW(Verdef) *) (((char *) vdef) + next);
} while (next);
}
if (versym && verneed) {
vneed = verneed;
do {
ElfW(Vernaux) *vernaux =
(ElfW(Vernaux) *) (((char *) vneed) + vneed->vn_aux);
lib = (char *) dynstr + vneed->vn_file;
#if DEBUG_VERSION
printf ("verneed: %u %s\n", vneed->vn_version, lib);
#endif
for (i = 0; i < vneed->vn_cnt; i++) {
if ((vernaux->vna_other & 0x8000) == 0) { /* hidden */
version = (char *) dynstr + vernaux->vna_name;
save_def_need_index (&n_def_need, &def_need,
vernaux->vna_other, lib, version);
#if DEBUG_VERSION
printf (" vernaux(%u): %u %u %s\n",
vernaux->vna_other, vernaux->vna_hash,
vernaux->vna_flags, version);
#endif
}
vernaux = (ElfW(Vernaux) *) (((char *) vernaux) + vernaux->vna_next);
}
next = vneed->vn_next;
vneed = (ElfW(Verneed) *) (((char *) vneed) + next);
} while (next);
}
#if DEBUG_VERSION
for (i = 0; i < n_def_need; i++) {
if (def_need[i].lib) {
printf ("%d: lib: %s, version %s\n",
i, def_need[i].lib, def_need[i].version);
}
}
#endif
/* add the dll and its level */
dllref = tcc_mallocz(sizeof(DLLReference) + strlen(soname));
dllref->level = level;
@ -2819,13 +3174,18 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
dynarray_add(&s1->loaded_dlls, &s1->nb_loaded_dlls, dllref);
/* add dynamic symbols in dynsym_section */
for(i = 1, sym = dynsym + 1; i < nb_syms; i++, sym++) {
for(i = 1, sym = dynsym + 1, vsym = versym + 1; i < nb_syms; i++, sym++, vsym++) {
sym_bind = ELFW(ST_BIND)(sym->st_info);
if (sym_bind == STB_LOCAL)
continue;
name = (char *) dynstr + sym->st_name;
set_elf_sym(s1->dynsymtab_section, sym->st_value, sym->st_size,
sym->st_info, sym->st_other, sym->st_shndx, name);
sym_index = set_elf_sym(s1->dynsymtab_section, sym->st_value, sym->st_size,
sym->st_info, sym->st_other, sym->st_shndx, name);
if (versym && n_def_need && (*vsym & 0x8000) == 0 &&
*vsym > 0 && *vsym < n_def_need &&
def_need[*vsym].lib) {
add_version_sym (s1->dynsymtab_section, sym_index, n_def_need, def_need, name, *vsym);
}
}
/* load all referenced DLLs */
@ -2849,9 +3209,13 @@ ST_FUNC int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level)
}
ret = 0;
the_end:
free_def_need (n_def_need, def_need);
tcc_free(dynstr);
tcc_free(dynsym);
tcc_free(dynamic);
tcc_free(verdef);
tcc_free(verneed);
tcc_free(versym);
tcc_free(shdr);
return ret;
}