#include <config.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <system.h>
#include <gelf.h>
#include ELFUTILS_HEADER(dwelf)
#include "elf-knowledge.h"
static int fd = -1;
static Elf *elf = NULL;
static bool replace;
static char *fnew = NULL;
static int fdnew = -1;
static Elf *elfnew = NULL;
static Dwelf_Strtab *strings = NULL;
static Dwelf_Strent **scnstrents = NULL;
static Dwelf_Strent **symstrents = NULL;
static Elf_Data newstrtabdata = { .d_buf = NULL };
static size_t newshnums = 0;
static void **newscnbufs = NULL;
static void
release (void)
{
if (strings != NULL)
dwelf_strtab_free (strings);
free (scnstrents);
free (symstrents);
free (newstrtabdata.d_buf);
for (size_t i = 0; i < newshnums; i++)
free (newscnbufs[i]);
free (newscnbufs);
if (fdnew != -1)
{
unlink (fnew);
elf_end (elfnew);
close (fdnew);
}
elf_end (elf);
close (fd);
}
static void __attribute__ ((noreturn))
fail (const char *msg, const char *fname)
{
release ();
if (fname != NULL)
error (1, 0, "%s: %s", fname, msg);
else
error (1, 0, "%s", msg);
abort();
}
static void __attribute__ ((noreturn))
fail_errno (const char *msg, const char *fname)
{
release ();
if (fname != NULL)
error (1, errno, "%s: %s", fname, msg);
else
error (1, errno, "%s", msg);
abort();
}
static void __attribute__ ((noreturn))
fail_idx (const char *msg, const char *fname, size_t idx)
{
release ();
if (fname != NULL)
error (1, 0, "%s: %s %zd", fname, msg, idx);
else
error (1, 0, "%s %zd", msg, idx);
abort();
}
static void __attribute__ ((noreturn))
fail_elf (const char *msg, const char *fname)
{
release ();
if (fname != NULL)
error (1, 0, "%s: %s: %s", fname, msg, elf_errmsg (-1));
else
error (1, 0, "%s: %s", msg, elf_errmsg (-1));
abort();
}
static void __attribute__ ((noreturn))
fail_elf_idx (const char *msg, const char *fname, size_t idx)
{
release ();
if (fname != NULL)
error (1, 0, "%s: %s %zd: %s", fname, msg, idx, elf_errmsg (-1));
else
error (1, 0, "%s %zd: %s", msg, idx, elf_errmsg (-1));
abort();
}
static size_t
newsecndx (size_t secndx, size_t shdrstrndx, size_t shdrnum,
const char *fname,
const char *what, size_t widx,
const char *member, size_t midx)
{
if (unlikely (secndx == 0 || secndx == shdrstrndx || secndx >= shdrnum))
{
release ();
if (widx == 0)
error (1, 0, "%s: bad section index %zd in %s for %s",
fname, secndx, what, member);
else if (midx == 0)
error (1, 0, "%s: bad section index %zd in %s %zd for %s",
fname, secndx, what, widx, member);
else
error (1, 0, "%s: bad section index %zd in %s %zd for %s %zd",
fname, secndx, what, widx, member, midx);
}
return secndx < shdrstrndx ? secndx : secndx - 1;
}
static void
new_data_buf (Elf_Data *d, const char *fname,
size_t ndx, size_t shdrstrndx, size_t shdrnum)
{
size_t s = d->d_size;
if (s == 0)
fail_idx ("Expected data in section", fname, ndx);
void *b = malloc (d->d_size);
if (b == NULL)
fail_idx ("Couldn't allocated buffer for section", NULL, ndx);
newscnbufs[newsecndx (ndx, shdrstrndx, shdrnum, fname,
"section", ndx, "d_buf", 0)] = d->d_buf = b;
}
int
main (int argc, char **argv)
{
elf_version (EV_CURRENT);
if ((argc != 2 && argc != 4)
|| (argc == 4 && strcmp (argv[1], "-o") != 0))
fail ("Usage argument: [-o <outputfile>] <inputfile>", NULL);
replace = argc == 2;
const char *fname;
if (replace)
fname = argv[1];
else
fname = argv[3];
fd = open (fname, O_RDONLY);
if (fd < 0)
fail_errno ("couldn't open", fname);
elf = elf_begin (fd, ELF_C_READ, NULL);
if (elf == NULL)
fail_elf ("couldn't open ELF file for reading", fname);
GElf_Ehdr ehdr;
if (gelf_getehdr (elf, &ehdr) == NULL)
fail_elf ("Couldn't get ehdr", fname);
size_t shdrstrndx;
if (elf_getshdrstrndx (elf, &shdrstrndx) != 0)
fail_elf ("couldn't get section header string table index", fname);
Elf_Scn *shdrstrscn = elf_getscn (elf, shdrstrndx);
GElf_Shdr shdrstrshdr_mem;
GElf_Shdr *shdrstrshdr = gelf_getshdr (shdrstrscn, &shdrstrshdr_mem);
if (shdrstrshdr == NULL)
fail_elf ("couldn't get section header string table section", fname);
if ((shdrstrshdr->sh_flags & SHF_ALLOC) != 0)
fail ("section header string table is an allocated section", fname);
size_t symtabndx = 0;
Elf_Scn *symtabscn = NULL;
GElf_Shdr symtabshdr_mem;
GElf_Shdr *symtabshdr = NULL;
while ((symtabscn = elf_nextscn (elf, symtabscn)) != NULL)
{
symtabshdr = gelf_getshdr (symtabscn, &symtabshdr_mem);
if (symtabshdr == NULL)
fail_elf ("couldn't get shdr", fname);
if (symtabshdr->sh_type == SHT_SYMTAB)
{
symtabndx = elf_ndxscn (symtabscn);
break;
}
}
if (symtabshdr == NULL)
fail ("No symtab found", fname);
if ((symtabshdr->sh_flags & SHF_ALLOC) != 0)
fail ("symtab is an allocated section", fname);
size_t strtabndx = symtabshdr->sh_link;
Elf_Scn *strtabscn = elf_getscn (elf, strtabndx);
GElf_Shdr strtabshdr_mem;
GElf_Shdr *strtabshdr = gelf_getshdr (strtabscn, &strtabshdr_mem);
if (strtabshdr == NULL)
fail_elf ("Couldn't get strtab section", fname);
if (shdrstrndx == strtabndx)
{
error (0, 0, "%s: Nothing to do, shstrtab == strtab", fname);
release ();
return 0;
}
if ((strtabshdr->sh_flags & SHF_ALLOC) != 0)
fail ("strtab is an allocated section", fname);
size_t phnum;
if (elf_getphdrnum (elf, &phnum) != 0)
fail_elf ("Couldn't get number of phdrs", fname);
bool layout = phnum != 0;
strings = dwelf_strtab_init (true);
if (strings == NULL)
fail ("No memory to create merged string table", NULL);
size_t shdrnum;
if (elf_getshdrnum (elf, &shdrnum) != 0)
fail_elf ("Couldn't get number of sections", fname);
scnstrents = malloc (shdrnum * sizeof (Dwelf_Strent *));
if (scnstrents == NULL)
fail ("couldn't allocate memory for section strings", NULL);
GElf_Off last_offset = 0;
if (layout)
last_offset = (ehdr.e_phoff
+ gelf_fsize (elf, ELF_T_PHDR, phnum, EV_CURRENT));
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn (elf, scn)) != NULL)
{
size_t scnnum = elf_ndxscn (scn);
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
if (shdr == NULL)
fail_elf_idx ("couldn't get shdr", fname, scnnum);
if (shdr->sh_name != 0 && scnnum != shdrstrndx)
{
const char *sname = elf_strptr (elf, shdrstrndx, shdr->sh_name);
if (sname == NULL)
fail_elf_idx ("couldn't get section name", fname, scnnum);
if ((scnstrents[scnnum] = dwelf_strtab_add (strings, sname)) == NULL)
fail ("No memory to add to merged string table", NULL);
}
if (layout)
if ((shdr->sh_flags & SHF_ALLOC) != 0)
{
GElf_Off off = shdr->sh_offset + (shdr->sh_type != SHT_NOBITS
? shdr->sh_size : 0);
if (last_offset < off)
last_offset = off;
}
}
size_t elsize = gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT);
Elf_Data *symd = elf_getdata (symtabscn, NULL);
if (symd == NULL)
fail_elf ("couldn't get symtab data", fname);
size_t symsnum = symd->d_size / elsize;
symstrents = malloc (symsnum * sizeof (Dwelf_Strent *));
if (symstrents == NULL)
fail_errno ("Couldn't allocate memory for symbol strings", NULL);
for (size_t i = 0; i < symsnum; i++)
{
GElf_Sym sym_mem;
GElf_Sym *sym = gelf_getsym (symd, i, &sym_mem);
if (sym == NULL)
fail_elf_idx ("Couldn't get symbol", fname, i);
if (sym->st_name != 0)
{
const char *sname = elf_strptr (elf, strtabndx, sym->st_name);
if (sname == NULL)
fail_elf_idx ("Couldn't get symbol name", fname, i);
if ((symstrents[i] = dwelf_strtab_add (strings, sname)) == NULL)
fail_idx ("No memory to add to merged string table symbol",
fname, i);
}
}
dwelf_strtab_finalize (strings, &newstrtabdata);
if (newstrtabdata.d_size >= shdrstrshdr->sh_size + strtabshdr->sh_size)
fail ("Impossible, merged string table is larger", fname);
struct stat st;
if (fstat (fd, &st) != 0)
fail_errno("Couldn't fstat", fname);
if (replace)
{
size_t fname_len = strlen (fname);
fnew = malloc (fname_len + sizeof (".XXXXXX"));
if (fnew == NULL)
fail_errno ("couldn't allocate memory for new file name", NULL);
strcpy (mempcpy (fnew, fname, fname_len), ".XXXXXX");
fdnew = mkstemp (fnew);
}
else
{
fnew = argv[2];
fdnew = open (fnew, O_WRONLY | O_CREAT, st.st_mode & ALLPERMS);
}
if (fdnew < 0)
fail_errno ("couldn't create output file", fnew);
elfnew = elf_begin (fdnew, ELF_C_WRITE, NULL);
if (elfnew == NULL)
fail_elf ("couldn't open new ELF for writing", fnew);
if (gelf_newehdr (elfnew, gelf_getclass (elf)) == 0)
fail_elf ("Couldn't create new ehdr", fnew);
GElf_Ehdr newehdr;
if (gelf_getehdr (elfnew, &newehdr) == NULL)
fail_elf ("Couldn't get ehdr", fnew);
newehdr.e_ident[EI_DATA] = ehdr.e_ident[EI_DATA];
newehdr.e_type = ehdr.e_type;
newehdr.e_machine = ehdr.e_machine;
newehdr.e_version = ehdr.e_version;
newehdr.e_entry = ehdr.e_entry;
newehdr.e_flags = ehdr.e_flags;
size_t newstrtabndx = newsecndx (strtabndx, shdrstrndx, shdrnum,
fname, "ehdr", 0, "e_shstrndx", 0);
if (newstrtabndx < SHN_LORESERVE)
newehdr.e_shstrndx = newstrtabndx;
else
{
Elf_Scn *zscn = elf_getscn (elfnew, 0);
GElf_Shdr zshdr_mem;
GElf_Shdr *zshdr = gelf_getshdr (zscn, &zshdr_mem);
if (zshdr == NULL)
fail_elf ("Couldn't get section zero", fnew);
zshdr->sh_link = strtabndx;
if (gelf_update_shdr (zscn, zshdr) == 0)
fail_elf ("Couldn't update section zero", fnew);
newehdr.e_shstrndx = SHN_XINDEX;
}
if (gelf_update_ehdr (elfnew, &newehdr) == 0)
fail ("Couldn't update ehdr", fnew);
if (phnum != 0)
{
if (gelf_newphdr (elfnew, phnum) == 0)
fail_elf ("Couldn't create phdrs", fnew);
for (size_t cnt = 0; cnt < phnum; ++cnt)
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = gelf_getphdr (elf, cnt, &phdr_mem);
if (phdr == NULL)
fail_elf_idx ("Couldn't get phdr", fname, cnt);
if (gelf_update_phdr (elfnew, cnt, phdr) == 0)
fail_elf_idx ("Couldn't create phdr", fnew, cnt);
}
}
newshnums = shdrnum - 1;
newscnbufs = calloc (newshnums, sizeof (void *));
if (newscnbufs == NULL)
fail_errno ("Couldn't allocate memory for new section buffers", NULL);
while ((scn = elf_nextscn (elf, scn)) != NULL)
{
size_t ndx = elf_ndxscn (scn);
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
if (shdr == NULL)
fail_elf_idx ("Couldn't get shdr", fname, ndx);
if (ndx == 0 || ndx == shdrstrndx)
continue;
Elf_Scn *newscn = elf_newscn (elfnew);
if (newscn == NULL)
fail_elf_idx ("couldn't create new section", fnew, ndx);
GElf_Shdr newshdr;
newshdr.sh_name = (shdr->sh_name != 0
? dwelf_strent_off (scnstrents[ndx]) : 0);
newshdr.sh_type = shdr->sh_type;
newshdr.sh_flags = shdr->sh_flags;
newshdr.sh_addr = shdr->sh_addr;
newshdr.sh_size = shdr->sh_size;
if (shdr->sh_link != 0)
newshdr.sh_link = newsecndx (shdr->sh_link, shdrstrndx, shdrnum,
fname, "shdr", ndx, "sh_link", 0);
else
newshdr.sh_link = 0;
if (SH_INFO_LINK_P (shdr) && shdr->sh_info != 0)
newshdr.sh_info = newsecndx (shdr->sh_info, shdrstrndx, shdrnum,
fname, "shdr", ndx, "sh_info", 0);
else
newshdr.sh_info = shdr->sh_info;
newshdr.sh_entsize = shdr->sh_entsize;
Elf_Data *newdata = elf_newdata (newscn);
if (newdata == NULL)
fail_elf_idx ("Couldn't create new data for section", fnew, ndx);
if (ndx == strtabndx)
*newdata = newstrtabdata;
else
{
Elf_Data *data = elf_getdata (scn, NULL);
if (data == NULL)
fail_elf_idx ("Couldn't get data from section", fname, ndx);
*newdata = *data;
switch (shdr->sh_type)
{
case SHT_SYMTAB:
case SHT_DYNSYM:
{
const bool update_name = shdr->sh_link == strtabndx;
if (update_name && ndx != symtabndx)
fail ("Only one symbol table using strtab expected", fname);
new_data_buf (newdata, fname, ndx, shdrstrndx, shdrnum);
size_t syms = (data->d_size
/ gelf_fsize (elf, ELF_T_SYM, 1, EV_CURRENT));
for (size_t i = 0; i < syms; i++)
{
GElf_Sym sym;
if (gelf_getsym (data, i, &sym) == NULL)
fail_elf_idx ("Couldn't get symbol", fname, i);
if (GELF_ST_TYPE (sym.st_info) == STT_SECTION
&& sym.st_shndx == shdrstrndx)
fprintf (stderr, "WARNING:"
" symbol table [%zd] contains section symbol %zd"
" for old shdrstrndx %zd\n", ndx, i, shdrstrndx);
else if (sym.st_shndx != SHN_UNDEF
&& sym.st_shndx < SHN_LORESERVE)
sym.st_shndx = newsecndx (sym.st_shndx, shdrstrndx, shdrnum,
fname, "section", ndx, "symbol", i);
if (update_name && sym.st_name != 0)
sym.st_name = dwelf_strent_off (symstrents[i]);
if (gelf_update_sym (newdata, i, &sym) == 0)
fail_elf_idx ("Couldn't update symbol", fnew, i);
}
}
break;
case SHT_GROUP:
{
new_data_buf (newdata, fname, ndx, shdrstrndx, shdrnum);
Elf32_Word *group = (Elf32_Word *) data->d_buf;
Elf32_Word *newgroup = (Elf32_Word *) newdata->d_buf;
size_t words = data->d_size / sizeof (Elf32_Word);
if (words == 0)
fail_idx ("Not enough data in group section", fname, ndx);
newgroup[0] = group[0];
for (size_t i = 1; i < words; i++)
newgroup[i] = newsecndx (group[i], shdrstrndx, shdrnum,
fname, "section", ndx, "group", i);
}
break;
case SHT_SYMTAB_SHNDX:
{
new_data_buf (newdata, fname, ndx, shdrstrndx, shdrnum);
Elf32_Word *shndx = (Elf32_Word *) data->d_buf;
Elf32_Word *newshndx = (Elf32_Word *) newdata->d_buf;
size_t words = data->d_size / sizeof (Elf32_Word);
for (size_t i = 0; i < words; i++)
if (shndx[i] == SHN_UNDEF)
newshndx[i] = SHN_UNDEF;
else
newshndx[i] = newsecndx (shndx[i], shdrstrndx, shdrnum,
fname, "section", ndx, "shndx", i);
}
break;
case SHT_DYNAMIC:
FALLTHROUGH;
default:
break;
}
}
if (layout)
{
newshdr.sh_size = newdata->d_size;
newshdr.sh_addralign = newdata->d_align;
if ((shdr->sh_flags & SHF_ALLOC) != 0)
newshdr.sh_offset = shdr->sh_offset;
else
{
size_t addralign = newshdr.sh_addralign ?: 1;
last_offset = (last_offset + addralign - 1) & ~(addralign - 1);
newshdr.sh_offset = last_offset;
if (newshdr.sh_type != SHT_NOBITS)
last_offset += newshdr.sh_size;
}
}
else
{
newshdr.sh_addralign = 0;
newshdr.sh_size = 0;
newshdr.sh_offset = 0;
}
if (gelf_update_shdr (newscn, &newshdr) == 0)
fail_elf_idx ("Couldn't update section header", fnew, ndx);
}
if (layout)
{
if (gelf_getehdr (elfnew, &newehdr) == NULL)
fail_elf ("Couldn't get ehdr", fnew);
const size_t offsize = gelf_fsize (elf, ELF_T_OFF, 1, EV_CURRENT);
newehdr.e_shoff = ((last_offset + offsize - 1)
& ~((GElf_Off) (offsize - 1)));
newehdr.e_phoff = ehdr.e_phoff;
if (gelf_update_ehdr (elfnew, &newehdr) == 0)
fail_elf ("Couldn't update ehdr", fnew);
elf_flagelf (elfnew, ELF_C_SET, ELF_F_LAYOUT);
}
if (elf_update (elfnew, ELF_C_WRITE) == -1)
fail_elf ("Couldn't write ELF", fnew);
elf_end (elfnew);
elfnew = NULL;
if (fchmod (fdnew, st.st_mode & ALLPERMS) != 0)
error (0, errno, "Couldn't fchmod %s", fnew);
if (fchown (fdnew, st.st_uid, st.st_gid) != 0)
error (0, errno, "Couldn't fchown %s", fnew);
if (replace)
if (rename (fnew, fname) != 0)
fail_errno ("rename", fnew);
close (fdnew);
if (replace)
free (fnew);
fnew = NULL;
fdnew = -1;
release ();
return 0;
}