dwarves/elfcreator.c
Domenico Andreoli e714d2eaa1 Adopt SPDX-License-Identifier
Signed-off-by: Domenico Andreoli <domenico.andreoli@linux.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2019-01-18 15:41:48 -03:00

296 lines
6.4 KiB
C

/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright 2009 Red Hat, Inc.
*
* Author: Peter Jones <pjones@redhat.com>
*/
#include <dlfcn.h>
#include <gelf.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include "elfcreator.h"
struct elf_creator {
const char *path;
int fd;
Elf *elf;
GElf_Ehdr *ehdr, ehdr_mem;
Elf *oldelf;
/* just because we have to look this up /so/ often... */
Elf_Scn *dynscn;
GElf_Shdr *dynshdr, dynshdr_mem;
Elf_Data *dyndata;
};
static void clear(ElfCreator *ctor, int do_unlink)
{
if (do_unlink) {
if (ctor->elf)
elf_end(ctor->elf);
if (ctor->fd >= 0)
close(ctor->fd);
if (ctor->path)
unlink(ctor->path);
} else {
if (ctor->elf) {
elf_update(ctor->elf, ELF_C_WRITE_MMAP);
elf_end(ctor->elf);
}
if (ctor->fd >= 0)
close(ctor->fd);
}
memset(ctor, '\0', sizeof(*ctor));
}
ElfCreator *elfcreator_begin(char *path, Elf *elf) {
ElfCreator *ctor = NULL;
GElf_Ehdr ehdr_mem, *ehdr;
GElf_Half machine;
if (!(ctor = calloc(1, sizeof(*ctor))))
return NULL;
clear(ctor, 0);
ctor->path = path;
ctor->oldelf = elf;
ehdr = gelf_getehdr(elf, &ehdr_mem);
machine = ehdr->e_machine;
if ((ctor->fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0755)) < 0) {
err:
clear(ctor, 1);
free(ctor);
return NULL;
}
if (!(ctor->elf = elf_begin(ctor->fd, ELF_C_WRITE_MMAP, elf)))
goto err;
gelf_newehdr(ctor->elf, gelf_getclass(elf));
gelf_update_ehdr(ctor->elf, ehdr);
if (!(ctor->ehdr = gelf_getehdr(ctor->elf, &ctor->ehdr_mem)))
goto err;
return ctor;
}
static Elf_Scn *get_scn_by_type(ElfCreator *ctor, Elf64_Word sh_type)
{
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn(ctor->elf, scn)) != NULL) {
GElf_Shdr *shdr, shdr_mem;
shdr = gelf_getshdr(scn, &shdr_mem);
if (shdr->sh_type == sh_type)
return scn;
}
return NULL;
}
static void update_dyn_cache(ElfCreator *ctor)
{
ctor->dynscn = get_scn_by_type(ctor, SHT_DYNAMIC);
if (ctor->dynscn == NULL)
return;
ctor->dynshdr = gelf_getshdr(ctor->dynscn, &ctor->dynshdr_mem);
ctor->dyndata = elf_getdata(ctor->dynscn, NULL);
}
void elfcreator_copy_scn(ElfCreator *ctor, Elf *src, Elf_Scn *scn)
{
Elf_Scn *newscn;
Elf_Data *indata, *outdata;
GElf_Shdr *oldshdr, oldshdr_mem;
GElf_Shdr *newshdr, newshdr_mem;
newscn = elf_newscn(ctor->elf);
newshdr = gelf_getshdr(newscn, &newshdr_mem);
oldshdr = gelf_getshdr(scn, &oldshdr_mem);
memmove(newshdr, oldshdr, sizeof(*newshdr));
gelf_update_shdr(newscn, newshdr);
indata = NULL;
while ((indata = elf_getdata(scn, indata)) != NULL) {
outdata = elf_newdata(newscn);
*outdata = *indata;
}
if (newshdr->sh_type == SHT_DYNAMIC)
update_dyn_cache(ctor);
}
static GElf_Dyn *get_dyn_by_tag(ElfCreator *ctor, Elf64_Sxword d_tag,
GElf_Dyn *mem, size_t *idx)
{
size_t cnt;
if (!ctor->dyndata)
return NULL;
for (cnt = 1; cnt < ctor->dynshdr->sh_size / ctor->dynshdr->sh_entsize;
cnt++) {
GElf_Dyn *dyn;
if ((dyn = gelf_getdyn(ctor->dyndata, cnt, mem)) == NULL)
break;
if (dyn->d_tag == d_tag) {
*idx = cnt;
return dyn;
}
}
return NULL;
}
static void remove_dyn(ElfCreator *ctor, size_t idx)
{
size_t cnt;
for (cnt = idx; cnt < ctor->dynshdr->sh_size/ctor->dynshdr->sh_entsize;
cnt++) {
GElf_Dyn *dyn, dyn_mem;
if (cnt+1 == ctor->dynshdr->sh_size/ctor->dynshdr->sh_entsize) {
memset(&dyn_mem, '\0', sizeof(dyn_mem));
gelf_update_dyn(ctor->dyndata, cnt, &dyn_mem);
break;
}
dyn = gelf_getdyn(ctor->dyndata, cnt+1, &dyn_mem);
gelf_update_dyn(ctor->dyndata, cnt, dyn);
}
ctor->dynshdr->sh_size--;
gelf_update_shdr(ctor->dynscn, ctor->dynshdr);
update_dyn_cache(ctor);
}
typedef void (*dyn_fixup_fn)(ElfCreator *ctor, Elf64_Sxword d_tag, Elf_Scn *scn);
static void generic_dyn_fixup_fn(ElfCreator *ctor, Elf64_Sxword d_tag, Elf_Scn *scn)
{
GElf_Shdr *shdr, shdr_mem;
GElf_Dyn *dyn, dyn_mem;
size_t idx;
dyn = get_dyn_by_tag(ctor, d_tag, &dyn_mem, &idx);
shdr = gelf_getshdr(scn, &shdr_mem);
if (shdr) {
dyn->d_un.d_ptr = shdr->sh_addr;
gelf_update_dyn(ctor->dyndata, idx, dyn);
} else {
remove_dyn(ctor, idx);
}
}
static void rela_dyn_fixup_fn(ElfCreator *ctor, Elf64_Sxword d_tag, Elf_Scn *scn)
{
GElf_Shdr *shdr, shdr_mem;
GElf_Dyn *dyn, dyn_mem;
size_t idx;
dyn = get_dyn_by_tag(ctor, d_tag, &dyn_mem, &idx);
shdr = gelf_getshdr(scn, &shdr_mem);
if (shdr) {
dyn->d_un.d_ptr = shdr->sh_addr;
gelf_update_dyn(ctor->dyndata, idx, dyn);
} else {
remove_dyn(ctor, idx);
dyn = get_dyn_by_tag(ctor, DT_RELASZ, &dyn_mem, &idx);
if (dyn) {
dyn->d_un.d_val = 0;
gelf_update_dyn(ctor->dyndata, idx, dyn);
}
}
}
static void rel_dyn_fixup_fn(ElfCreator *ctor, Elf64_Sxword d_tag, Elf_Scn *scn)
{
GElf_Shdr *shdr, shdr_mem;
GElf_Dyn *dyn, dyn_mem;
size_t idx;
dyn = get_dyn_by_tag(ctor, d_tag, &dyn_mem, &idx);
shdr = gelf_getshdr(scn, &shdr_mem);
if (shdr) {
dyn->d_un.d_ptr = shdr->sh_addr;
gelf_update_dyn(ctor->dyndata, idx, dyn);
} else {
remove_dyn(ctor, idx);
dyn = get_dyn_by_tag(ctor, DT_RELSZ, &dyn_mem, &idx);
if (dyn) {
dyn->d_un.d_val = 0;
gelf_update_dyn(ctor->dyndata, idx, dyn);
}
}
}
static void fixup_dynamic(ElfCreator *ctor)
{
struct {
Elf64_Sxword d_tag;
Elf64_Word sh_type;
dyn_fixup_fn fn;
} fixups[] = {
{ DT_HASH, SHT_HASH, NULL },
{ DT_STRTAB, SHT_STRTAB, NULL },
{ DT_SYMTAB, SHT_SYMTAB, NULL },
{ DT_RELA, SHT_RELA, rela_dyn_fixup_fn},
{ DT_REL, SHT_REL, rel_dyn_fixup_fn},
{ DT_GNU_HASH, SHT_GNU_HASH, NULL },
{ DT_NULL, SHT_NULL, NULL }
};
int i;
for (i = 0; fixups[i].d_tag != DT_NULL; i++) {
Elf_Scn *scn;
scn = get_scn_by_type(ctor, fixups[i].sh_type);
if (fixups[i].fn)
fixups[i].fn(ctor, fixups[i].d_tag, scn);
else
generic_dyn_fixup_fn(ctor, fixups[i].d_tag, scn);
}
}
void elfcreator_end(ElfCreator *ctor)
{
GElf_Phdr phdr_mem, *phdr;
int m,n;
for (m = 0; (phdr = gelf_getphdr(ctor->oldelf, m, &phdr_mem)) != NULL; m++)
/* XXX this should check if an entry is needed */;
gelf_newphdr(ctor->elf, m);
elf_update(ctor->elf, ELF_C_NULL);
update_dyn_cache(ctor);
for (n = 0; n < m; n++) {
/* XXX this should check if an entry is needed */
phdr = gelf_getphdr(ctor->oldelf, n, &phdr_mem);
if (ctor->dynshdr && phdr->p_type == PT_DYNAMIC)
phdr->p_offset = ctor->dynshdr->sh_offset;
gelf_update_phdr(ctor->elf, n, phdr);
}
fixup_dynamic(ctor);
clear(ctor, 0);
free(ctor);
}