#include <libctf_impl.h>
#include <libctf.h>
#include <gelf.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <libelf.h>
static int
ctf_write_elf(ctf_file_t *fp, Elf *src, Elf *dst, int flags)
{
GElf_Ehdr sehdr, dehdr;
Elf_Scn *sscn, *dscn;
Elf_Data *sdata, *ddata;
GElf_Shdr shdr;
int symtab_idx = -1;
off_t new_offset = 0;
off_t ctfnameoff = 0;
int compress = (flags & CTF_ELFWRITE_F_COMPRESS);
int *secxlate = NULL;
int srcidx, dstidx, pad, i;
int curnmoff = 0;
int changing = 0;
int ret;
size_t nshdr, nphdr, strndx;
void *strdatabuf = NULL, *symdatabuf = NULL;
size_t strdatasz = 0, symdatasz = 0;
void *cdata = NULL;
size_t elfsize, asize;
if ((flags & ~(CTF_ELFWRITE_F_COMPRESS)) != 0) {
ret = ctf_set_errno(fp, EINVAL);
goto out;
}
if (gelf_newehdr(dst, gelf_getclass(src)) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (gelf_getehdr(src, &sehdr) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
(void) memcpy(&dehdr, &sehdr, sizeof (GElf_Ehdr));
if (gelf_update_ehdr(dst, &dehdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (elf_getphdrnum(src, &nphdr) != 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (elf_getshdrnum(src, &nshdr) != 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (elf_getshdrstrndx(src, &strndx) != 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (nphdr != 0) {
(void) elf_flagelf(dst, ELF_C_SET, ELF_F_LAYOUT);
if (gelf_newphdr(dst, nphdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
for (i = 0; i < nphdr; i++) {
GElf_Phdr phdr;
if (gelf_getphdr(src, i, &phdr) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (gelf_update_phdr(dst, i, &phdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
}
}
secxlate = ctf_alloc(sizeof (int) * nshdr);
for (srcidx = dstidx = 0; srcidx < nshdr; srcidx++) {
Elf_Scn *scn = elf_getscn(src, srcidx);
GElf_Shdr shdr;
char *sname;
if (gelf_getshdr(scn, &shdr) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
sname = elf_strptr(src, strndx, shdr.sh_name);
if (sname == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (strcmp(sname, CTF_ELF_SCN_NAME) == 0) {
secxlate[srcidx] = -1;
} else {
secxlate[srcidx] = dstidx++;
curnmoff += strlen(sname) + 1;
}
new_offset = (off_t)dehdr.e_phoff;
}
for (srcidx = 1; srcidx < nshdr; srcidx++) {
char *sname;
sscn = elf_getscn(src, srcidx);
if (gelf_getshdr(sscn, &shdr) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (secxlate[srcidx] == -1) {
changing = 1;
continue;
}
dscn = elf_newscn(dst);
if (dscn == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (changing && nphdr != 0) {
pad = new_offset % shdr.sh_addralign;
if (pad != 0)
new_offset += shdr.sh_addralign - pad;
shdr.sh_offset = new_offset;
}
shdr.sh_link = secxlate[shdr.sh_link];
if (shdr.sh_type == SHT_REL || shdr.sh_type == SHT_RELA)
shdr.sh_info = secxlate[shdr.sh_info];
sname = elf_strptr(src, strndx, shdr.sh_name);
if (sname == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if ((sdata = elf_getdata(sscn, NULL)) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if ((ddata = elf_newdata(dscn)) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
bcopy(sdata, ddata, sizeof (Elf_Data));
if (srcidx == strndx) {
char seclen = strlen(CTF_ELF_SCN_NAME);
strdatasz = ddata->d_size + shdr.sh_size +
seclen + 1;
ddata->d_buf = strdatabuf = ctf_alloc(strdatasz);
if (ddata->d_buf == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size);
(void) strcpy((caddr_t)ddata->d_buf + shdr.sh_size,
CTF_ELF_SCN_NAME);
ctfnameoff = (off_t)shdr.sh_size;
shdr.sh_size += seclen + 1;
ddata->d_size += seclen + 1;
if (nphdr != 0)
changing = 1;
}
if (shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
int nsym = shdr.sh_size / shdr.sh_entsize;
symtab_idx = secxlate[srcidx];
symdatasz = shdr.sh_size;
ddata->d_buf = symdatabuf = ctf_alloc(symdatasz);
if (ddata->d_buf == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
(void) bcopy(sdata->d_buf, ddata->d_buf, shdr.sh_size);
for (i = 0; i < nsym; i++) {
GElf_Sym sym;
short newscn;
(void) gelf_getsym(ddata, i, &sym);
if (sym.st_shndx >= SHN_LORESERVE)
continue;
if ((newscn = secxlate[sym.st_shndx]) !=
sym.st_shndx) {
sym.st_shndx =
(newscn == -1 ? 1 : newscn);
if (gelf_update_sym(ddata, i, &sym) ==
0) {
ret = ctf_set_errno(fp,
ECTF_ELF);
goto out;
}
}
}
}
if (gelf_update_shdr(dscn, &shdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
new_offset = (off_t)shdr.sh_offset;
if (shdr.sh_type != SHT_NOBITS)
new_offset += shdr.sh_size;
}
if (symtab_idx == -1) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if ((dscn = elf_newscn(dst)) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (gelf_getshdr(dscn, &shdr) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
shdr.sh_name = ctfnameoff;
shdr.sh_type = SHT_PROGBITS;
shdr.sh_size = fp->ctf_size;
shdr.sh_link = symtab_idx;
shdr.sh_addralign = 4;
if (changing && nphdr != 0) {
pad = new_offset % shdr.sh_addralign;
if (pad)
new_offset += shdr.sh_addralign - pad;
shdr.sh_offset = new_offset;
new_offset += shdr.sh_size;
}
if ((ddata = elf_newdata(dscn)) == NULL) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (compress != 0) {
int err;
if (ctf_zopen(&err) == NULL) {
ret = ctf_set_errno(fp, err);
goto out;
}
if ((err = ctf_compress(fp, &cdata, &asize, &elfsize)) != 0) {
ret = ctf_set_errno(fp, err);
goto out;
}
ddata->d_buf = cdata;
ddata->d_size = elfsize;
} else {
ddata->d_buf = (void *)fp->ctf_base;
ddata->d_size = fp->ctf_size;
}
ddata->d_align = shdr.sh_addralign;
if (gelf_update_shdr(dscn, &shdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (nphdr != 0) {
size_t align = gelf_fsize(dst, ELF_T_ADDR, 1, EV_CURRENT);
size_t r = new_offset % align;
if (r)
new_offset += align - r;
dehdr.e_shoff = new_offset;
}
if (sehdr.e_shstrndx == SHN_XINDEX)
dehdr.e_shstrndx = SHN_XINDEX;
else
dehdr.e_shstrndx = secxlate[sehdr.e_shstrndx];
if (gelf_update_ehdr(dst, &dehdr) == 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
if (elf_update(dst, ELF_C_WRITE) < 0) {
ret = ctf_set_errno(fp, ECTF_ELF);
goto out;
}
ret = 0;
out:
if (strdatabuf != NULL)
ctf_free(strdatabuf, strdatasz);
if (symdatabuf != NULL)
ctf_free(symdatabuf, symdatasz);
if (cdata != NULL)
ctf_data_free(cdata, fp->ctf_size);
if (secxlate != NULL)
ctf_free(secxlate, sizeof (int) * nshdr);
return (ret);
}
int
ctf_elffdwrite(ctf_file_t *fp, int ifd, int ofd, int flags)
{
int ret;
Elf *ielf, *oelf;
(void) elf_version(EV_CURRENT);
if ((ielf = elf_begin(ifd, ELF_C_READ, NULL)) == NULL)
return (ctf_set_errno(fp, ECTF_ELF));
if ((oelf = elf_begin(ofd, ELF_C_WRITE, NULL)) == NULL)
return (ctf_set_errno(fp, ECTF_ELF));
ret = ctf_write_elf(fp, ielf, oelf, flags);
(void) elf_end(ielf);
(void) elf_end(oelf);
return (ret);
}
int
ctf_elfwrite(ctf_file_t *fp, const char *input, const char *output, int flags)
{
struct stat st;
int ifd, ofd, ret;
if ((ifd = open(input, O_RDONLY)) < 0)
return (ctf_set_errno(fp, errno));
if (fstat(ifd, &st) < 0)
return (ctf_set_errno(fp, errno));
if ((ofd = open(output, O_RDWR | O_CREAT | O_TRUNC, st.st_mode)) < 0)
return (ctf_set_errno(fp, errno));
ret = ctf_elffdwrite(fp, ifd, ofd, flags);
if (close(ifd) != 0 && ret == 0)
ret = ctf_set_errno(fp, errno);
if (close(ofd) != 0 && ret == 0)
ret = ctf_set_errno(fp, errno);
return (ret);
}