#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <assert.h>
#include <errno.h>
#include <libelf.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "libelfP.h"
#ifndef LIBELFBITS
# define LIBELFBITS 32
#endif
static int
compare_sections (const void *a, const void *b)
{
const Elf_Scn **scna = (const Elf_Scn **) a;
const Elf_Scn **scnb = (const Elf_Scn **) b;
if ((*scna)->shdr.ELFW(e,LIBELFBITS)->sh_offset
< (*scnb)->shdr.ELFW(e,LIBELFBITS)->sh_offset)
return -1;
if ((*scna)->shdr.ELFW(e,LIBELFBITS)->sh_offset
> (*scnb)->shdr.ELFW(e,LIBELFBITS)->sh_offset)
return 1;
if ((*scna)->shdr.ELFW(e,LIBELFBITS)->sh_size
< (*scnb)->shdr.ELFW(e,LIBELFBITS)->sh_size)
return -1;
if ((*scna)->shdr.ELFW(e,LIBELFBITS)->sh_size
> (*scnb)->shdr.ELFW(e,LIBELFBITS)->sh_size)
return 1;
if ((*scna)->index < (*scnb)->index)
return -1;
if ((*scna)->index > (*scnb)->index)
return 1;
return 0;
}
static void
sort_sections (Elf_Scn **scns, Elf_ScnList *list)
{
Elf_Scn **scnp = scns;
do
for (size_t cnt = 0; cnt < list->cnt; ++cnt)
*scnp++ = &list->data[cnt];
while ((list = list->next) != NULL);
qsort (scns, scnp - scns, sizeof (*scns), compare_sections);
}
static inline void
fill_mmap (size_t offset, char *last_position, char *scn_start,
char *const shdr_start, char *const shdr_end)
{
size_t written = 0;
if (last_position < shdr_start)
{
written = MIN (scn_start + offset - last_position,
shdr_start - last_position);
memset (last_position, __libelf_fill_byte, written);
}
if (last_position + written != scn_start + offset
&& shdr_end < scn_start + offset)
{
char *fill_start = MAX (shdr_end, scn_start);
memset (fill_start, __libelf_fill_byte,
scn_start + offset - fill_start);
}
}
int
internal_function
__elfw2(LIBELFBITS,updatemmap) (Elf *elf, int change_bo, size_t shnum)
{
bool previous_scn_changed = false;
ElfW2(LIBELFBITS,Ehdr) *ehdr = elf->state.ELFW(elf,LIBELFBITS).ehdr;
if ((elf->state.ELFW(elf,LIBELFBITS).ehdr_flags | elf->flags) & ELF_F_DIRTY)
{
assert (sizeof (ElfW2(LIBELFBITS,Ehdr))
== elf_typesize (LIBELFBITS, ELF_T_EHDR, 1));
if (unlikely (change_bo))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_EHDR]
(*fctp) ((char *) elf->map_address + elf->start_offset, ehdr,
sizeof (ElfW2(LIBELFBITS,Ehdr)), 1);
}
else if (elf->map_address + elf->start_offset != ehdr)
memcpy (elf->map_address + elf->start_offset, ehdr,
sizeof (ElfW2(LIBELFBITS,Ehdr)));
elf->state.ELFW(elf,LIBELFBITS).ehdr_flags &= ~ELF_F_DIRTY;
previous_scn_changed = elf->state.ELFW(elf,LIBELFBITS).phdr == NULL;
}
size_t phnum;
if (unlikely (__elf_getphdrnum_rdlock (elf, &phnum) != 0))
return -1;
if (elf->state.ELFW(elf,LIBELFBITS).phdr != NULL
&& ((elf->state.ELFW(elf,LIBELFBITS).phdr_flags | elf->flags)
& ELF_F_DIRTY))
{
assert (sizeof (ElfW2(LIBELFBITS,Phdr))
== elf_typesize (LIBELFBITS, ELF_T_PHDR, 1));
if (ehdr->e_phoff > ehdr->e_ehsize)
memset (elf->map_address + elf->start_offset + ehdr->e_ehsize,
__libelf_fill_byte, ehdr->e_phoff - ehdr->e_ehsize);
if (unlikely (change_bo))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_PHDR]
(*fctp) (elf->map_address + elf->start_offset + ehdr->e_phoff,
elf->state.ELFW(elf,LIBELFBITS).phdr,
sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum, 1);
}
else
memmove (elf->map_address + elf->start_offset + ehdr->e_phoff,
elf->state.ELFW(elf,LIBELFBITS).phdr,
sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum);
elf->state.ELFW(elf,LIBELFBITS).phdr_flags &= ~ELF_F_DIRTY;
previous_scn_changed = true;
}
char *last_position = ((char *) elf->map_address + elf->start_offset
+ MAX (elf_typesize (LIBELFBITS, ELF_T_EHDR, 1),
ehdr->e_phoff)
+ elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum));
if (shnum > 0)
{
if (unlikely (shnum > SIZE_MAX / sizeof (Elf_Scn *)))
return 1;
Elf_ScnList *list = &elf->state.ELFW(elf,LIBELFBITS).scns;
Elf_Scn **scns = malloc (shnum * sizeof (Elf_Scn *));
if (unlikely (scns == NULL))
{
__libelf_seterrno (ELF_E_NOMEM);
return -1;
}
char *const shdr_start = ((char *) elf->map_address + elf->start_offset
+ ehdr->e_shoff);
char *const shdr_end = shdr_start + shnum * ehdr->e_shentsize;
#undef shdr_fctp
#define shdr_fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_SHDR]
#define shdr_dest ((ElfW2(LIBELFBITS,Shdr) *) shdr_start)
sort_sections (scns, list);
for (size_t cnt = 0; cnt < shnum; ++cnt)
{
Elf_Scn *scn = scns[cnt];
if (!elf->state.ELFW(elf,LIBELFBITS).shdr_malloced
&& (scn->shdr_flags & ELF_F_MALLOCED) == 0
&& scn->shdr.ELFW(e,LIBELFBITS) != &shdr_dest[scn->index])
{
assert ((char *) elf->map_address + elf->start_offset
< (char *) scn->shdr.ELFW(e,LIBELFBITS));
assert ((char *) scn->shdr.ELFW(e,LIBELFBITS)
< ((char *) elf->map_address + elf->start_offset
+ elf->maximum_size));
void *p = malloc (sizeof (ElfW2(LIBELFBITS,Shdr)));
if (unlikely (p == NULL))
{
free (scns);
__libelf_seterrno (ELF_E_NOMEM);
return -1;
}
scn->shdr.ELFW(e,LIBELFBITS)
= memcpy (p, scn->shdr.ELFW(e,LIBELFBITS),
sizeof (ElfW2(LIBELFBITS,Shdr)));
}
if (((char *) elf->map_address + elf->start_offset
<= (char *) scn->data_list.data.d.d_buf)
&& ((char *) scn->data_list.data.d.d_buf
< ((char *) elf->map_address + elf->start_offset
+ elf->maximum_size))
&& (((char *) elf->map_address + elf->start_offset
+ scn->shdr.ELFW(e,LIBELFBITS)->sh_offset)
> (char *) scn->data_list.data.d.d_buf))
{
void *p = malloc (scn->data_list.data.d.d_size);
if (unlikely (p == NULL))
{
free (scns);
__libelf_seterrno (ELF_E_NOMEM);
return -1;
}
scn->data_list.data.d.d_buf = scn->data_base
= memcpy (p, scn->data_list.data.d.d_buf,
scn->data_list.data.d.d_size);
}
}
for (size_t cnt = 0; cnt < shnum; ++cnt)
{
Elf_Scn *scn = scns[cnt];
if (scn->index == 0)
{
assert ((scn->flags & ELF_F_DIRTY) == 0);
continue;
}
ElfW2(LIBELFBITS,Shdr) *shdr = scn->shdr.ELFW(e,LIBELFBITS);
if (shdr->sh_type == SHT_NOBITS)
goto next;
char *scn_start = ((char *) elf->map_address
+ elf->start_offset + shdr->sh_offset);
Elf_Data_List *dl = &scn->data_list;
bool scn_changed = false;
if (scn->data_list_rear != NULL)
do
{
assert (dl->data.d.d_off >= 0);
assert ((GElf_Off) dl->data.d.d_off <= shdr->sh_size);
assert (dl->data.d.d_size <= (shdr->sh_size
- (GElf_Off) dl->data.d.d_off));
if (scn_start + dl->data.d.d_off > last_position
&& (dl->data.d.d_off == 0
|| ((scn->flags | dl->flags | elf->flags)
& ELF_F_DIRTY) != 0))
{
fill_mmap (dl->data.d.d_off, last_position, scn_start,
shdr_start, shdr_end);
}
last_position = scn_start + dl->data.d.d_off;
if ((scn->flags | dl->flags | elf->flags) & ELF_F_DIRTY)
{
if (unlikely (change_bo
&& dl->data.d.d_size != 0
&& dl->data.d.d_type != ELF_T_BYTE))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][dl->data.d.d_type]
size_t align;
align = __libelf_type_align (ELFW(ELFCLASS,LIBELFBITS),
dl->data.d.d_type);
if ((((uintptr_t) last_position)
& (uintptr_t) (align - 1)) == 0)
{
(*fctp) (last_position, dl->data.d.d_buf,
dl->data.d.d_size, 1);
}
else
{
size_t size = dl->data.d.d_size;
void *converted;
if (align < sizeof (void *))
converted = malloc (size);
else
{
int res;
res = posix_memalign (&converted, align, size);
if (res != 0)
converted = NULL;
}
if (converted == NULL)
{
free (scns);
__libelf_seterrno (ELF_E_NOMEM);
return 1;
}
(*fctp) (converted, dl->data.d.d_buf, size, 1);
memcpy (last_position, converted, size);
free (converted);
}
last_position += dl->data.d.d_size;
}
else if (dl->data.d.d_size != 0)
{
memmove (last_position, dl->data.d.d_buf,
dl->data.d.d_size);
last_position += dl->data.d.d_size;
}
scn_changed = true;
}
else
last_position += dl->data.d.d_size;
assert (scn_start + dl->data.d.d_off + dl->data.d.d_size
== last_position);
dl->flags &= ~ELF_F_DIRTY;
dl = dl->next;
}
while (dl != NULL);
else
{
if (scn_start > last_position && previous_scn_changed)
fill_mmap (0, last_position, scn_start,
shdr_start, shdr_end);
last_position = scn_start + shdr->sh_size;
}
previous_scn_changed = scn_changed;
next:
scn->flags &= ~ELF_F_DIRTY;
}
if ((elf->flags & ELF_F_DIRTY)
&& last_position < ((char *) elf->map_address + elf->start_offset
+ ehdr->e_shoff))
memset (last_position, __libelf_fill_byte,
(char *) elf->map_address + elf->start_offset + ehdr->e_shoff
- last_position);
for (size_t cnt = 0; cnt < shnum; ++cnt)
{
Elf_Scn *scn = scns[cnt];
if ((scn->shdr_flags | elf->flags) & ELF_F_DIRTY)
{
if (unlikely (change_bo))
(*shdr_fctp) (&shdr_dest[scn->index],
scn->shdr.ELFW(e,LIBELFBITS),
sizeof (ElfW2(LIBELFBITS,Shdr)), 1);
else
memcpy (&shdr_dest[scn->index],
scn->shdr.ELFW(e,LIBELFBITS),
sizeof (ElfW2(LIBELFBITS,Shdr)));
if (!elf->state.ELFW(elf,LIBELFBITS).shdr_malloced
&& (scn->shdr_flags & ELF_F_MALLOCED) == 0
&& scn->shdr.ELFW(e,LIBELFBITS) != &shdr_dest[scn->index])
{
free (scn->shdr.ELFW(e,LIBELFBITS));
scn->shdr.ELFW(e,LIBELFBITS) = &shdr_dest[scn->index];
}
scn->shdr_flags &= ~ELF_F_DIRTY;
}
}
free (scns);
}
elf->flags &= ~ELF_F_DIRTY;
char *msync_start = ((char *) elf->map_address
+ (elf->start_offset & ~(sysconf (_SC_PAGESIZE) - 1)));
char *msync_end = ((char *) elf->map_address
+ elf->start_offset + ehdr->e_shoff
+ ehdr->e_shentsize * shnum);
(void) msync (msync_start, msync_end - msync_start, MS_SYNC);
return 0;
}
#define FILLBUFSIZE 4096
#define MAX_TMPBUF 32768
static int
fill (int fd, int64_t pos, size_t len, char *fillbuf, size_t *filledp)
{
size_t filled = *filledp;
size_t fill_len = MIN (len, FILLBUFSIZE);
if (unlikely (fill_len > filled) && filled < FILLBUFSIZE)
{
memset (fillbuf + filled, __libelf_fill_byte, fill_len - filled);
*filledp = filled = fill_len;
}
do
{
size_t n = MIN (filled, len);
if (unlikely ((size_t) pwrite_retry (fd, fillbuf, n, pos) != n))
{
__libelf_seterrno (ELF_E_WRITE_ERROR);
return 1;
}
pos += n;
len -= n;
}
while (len > 0);
return 0;
}
int
internal_function
__elfw2(LIBELFBITS,updatefile) (Elf *elf, int change_bo, size_t shnum)
{
char fillbuf[FILLBUFSIZE];
size_t filled = 0;
bool previous_scn_changed = false;
ElfW2(LIBELFBITS,Ehdr) *ehdr = elf->state.ELFW(elf,LIBELFBITS).ehdr;
if ((elf->state.ELFW(elf,LIBELFBITS).ehdr_flags | elf->flags) & ELF_F_DIRTY)
{
ElfW2(LIBELFBITS,Ehdr) tmp_ehdr;
ElfW2(LIBELFBITS,Ehdr) *out_ehdr = ehdr;
assert (sizeof (ElfW2(LIBELFBITS,Ehdr))
== elf_typesize (LIBELFBITS, ELF_T_EHDR, 1));
if (unlikely (change_bo))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_EHDR]
(*fctp) (&tmp_ehdr, ehdr, sizeof (ElfW2(LIBELFBITS,Ehdr)), 1);
out_ehdr = &tmp_ehdr;
}
if (unlikely (pwrite_retry (elf->fildes, out_ehdr,
sizeof (ElfW2(LIBELFBITS,Ehdr)), 0)
!= sizeof (ElfW2(LIBELFBITS,Ehdr))))
{
__libelf_seterrno (ELF_E_WRITE_ERROR);
return 1;
}
elf->state.ELFW(elf,LIBELFBITS).ehdr_flags &= ~ELF_F_DIRTY;
previous_scn_changed = elf->state.ELFW(elf,LIBELFBITS).phdr == NULL;
}
assert (sizeof (ElfW2(LIBELFBITS,Phdr))
== elf_typesize (LIBELFBITS, ELF_T_PHDR, 1));
size_t phnum;
if (unlikely (__elf_getphdrnum_rdlock (elf, &phnum) != 0))
return -1;
if (elf->state.ELFW(elf,LIBELFBITS).phdr != NULL
&& ((elf->state.ELFW(elf,LIBELFBITS).phdr_flags | elf->flags)
& ELF_F_DIRTY))
{
ElfW2(LIBELFBITS,Phdr) *tmp_phdr = NULL;
ElfW2(LIBELFBITS,Phdr) *out_phdr = elf->state.ELFW(elf,LIBELFBITS).phdr;
if (ehdr->e_phoff > ehdr->e_ehsize
&& unlikely (fill (elf->fildes, ehdr->e_ehsize,
ehdr->e_phoff - ehdr->e_ehsize, fillbuf, &filled)
!= 0))
return 1;
if (unlikely (change_bo))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_PHDR]
tmp_phdr = (ElfW2(LIBELFBITS,Phdr) *)
malloc (sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum);
if (unlikely (tmp_phdr == NULL))
{
__libelf_seterrno (ELF_E_NOMEM);
return 1;
}
(*fctp) (tmp_phdr, elf->state.ELFW(elf,LIBELFBITS).phdr,
sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum, 1);
out_phdr = tmp_phdr;
}
size_t phdr_size = sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum;
if (unlikely ((size_t) pwrite_retry (elf->fildes, out_phdr,
phdr_size, ehdr->e_phoff)
!= phdr_size))
{
__libelf_seterrno (ELF_E_WRITE_ERROR);
return 1;
}
free (tmp_phdr);
elf->state.ELFW(elf,LIBELFBITS).phdr_flags &= ~ELF_F_DIRTY;
previous_scn_changed = true;
}
int64_t last_offset;
if (elf->state.ELFW(elf,LIBELFBITS).phdr == NULL)
last_offset = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);
else
last_offset = (ehdr->e_phoff + sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum);
if (shnum > 0)
{
if (unlikely (shnum > SIZE_MAX / (sizeof (Elf_Scn *)
+ sizeof (ElfW2(LIBELFBITS,Shdr)))))
return 1;
int64_t shdr_offset = elf->start_offset + ehdr->e_shoff;
#undef shdr_fctp
#define shdr_fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_SHDR]
ElfW2(LIBELFBITS,Shdr) *shdr_data;
ElfW2(LIBELFBITS,Shdr) *shdr_data_mem = NULL;
if (change_bo || elf->state.ELFW(elf,LIBELFBITS).shdr == NULL
|| (elf->flags & ELF_F_DIRTY))
{
shdr_data_mem = (ElfW2(LIBELFBITS,Shdr) *)
malloc (shnum * sizeof (ElfW2(LIBELFBITS,Shdr)));
if (unlikely (shdr_data_mem == NULL))
{
__libelf_seterrno (ELF_E_NOMEM);
return -1;
}
shdr_data = shdr_data_mem;
}
else
shdr_data = elf->state.ELFW(elf,LIBELFBITS).shdr;
int shdr_flags = elf->flags;
Elf_ScnList *list = &elf->state.ELFW(elf,LIBELFBITS).scns;
Elf_Scn **scns = malloc (shnum * sizeof (Elf_Scn *));
if (unlikely (scns == NULL))
{
free (shdr_data_mem);
__libelf_seterrno (ELF_E_NOMEM);
return -1;
}
sort_sections (scns, list);
for (size_t cnt = 0; cnt < shnum; ++cnt)
{
Elf_Scn *scn = scns[cnt];
if (scn->index == 0)
{
assert ((scn->flags & ELF_F_DIRTY) == 0);
goto next;
}
ElfW2(LIBELFBITS,Shdr) *shdr = scn->shdr.ELFW(e,LIBELFBITS);
if (shdr->sh_type == SHT_NOBITS)
goto next;
int64_t scn_start = elf->start_offset + shdr->sh_offset;
Elf_Data_List *dl = &scn->data_list;
bool scn_changed = false;
if (scn->data_list_rear != NULL)
do
{
if (scn_start + dl->data.d.d_off > last_offset
&& ((previous_scn_changed && dl->data.d.d_off == 0)
|| ((scn->flags | dl->flags | elf->flags)
& ELF_F_DIRTY) != 0))
{
if (unlikely (fill (elf->fildes, last_offset,
(scn_start + dl->data.d.d_off)
- last_offset, fillbuf,
&filled) != 0))
{
fail_free:
free (shdr_data_mem);
free (scns);
return 1;
}
}
last_offset = scn_start + dl->data.d.d_off;
if ((scn->flags | dl->flags | elf->flags) & ELF_F_DIRTY)
{
char tmpbuf[MAX_TMPBUF];
void *buf = dl->data.d.d_buf;
if (unlikely (change_bo))
{
#undef fctp
#define fctp __elf_xfctstom[ELFW(ELFCLASS, LIBELFBITS) - 1][dl->data.d.d_type]
buf = tmpbuf;
if (dl->data.d.d_size > MAX_TMPBUF)
{
buf = malloc (dl->data.d.d_size);
if (unlikely (buf == NULL))
{
__libelf_seterrno (ELF_E_NOMEM);
goto fail_free;
}
}
(*fctp) (buf, dl->data.d.d_buf, dl->data.d.d_size, 1);
}
ssize_t n = pwrite_retry (elf->fildes, buf,
dl->data.d.d_size,
last_offset);
if (unlikely ((size_t) n != dl->data.d.d_size))
{
if (buf != dl->data.d.d_buf && buf != tmpbuf)
free (buf);
__libelf_seterrno (ELF_E_WRITE_ERROR);
goto fail_free;
}
if (buf != dl->data.d.d_buf && buf != tmpbuf)
free (buf);
scn_changed = true;
}
last_offset += dl->data.d.d_size;
dl->flags &= ~ELF_F_DIRTY;
dl = dl->next;
}
while (dl != NULL);
else
{
if (scn_start > last_offset && previous_scn_changed)
{
if (unlikely (fill (elf->fildes, last_offset,
scn_start - last_offset, fillbuf,
&filled) != 0))
goto fail_free;
}
last_offset = scn_start + shdr->sh_size;
}
previous_scn_changed = scn_changed;
next:
if (unlikely (change_bo))
(*shdr_fctp) (&shdr_data[scn->index],
scn->shdr.ELFW(e,LIBELFBITS),
sizeof (ElfW2(LIBELFBITS,Shdr)), 1);
else if (elf->state.ELFW(elf,LIBELFBITS).shdr == NULL
|| (elf->flags & ELF_F_DIRTY))
memcpy (&shdr_data[scn->index], scn->shdr.ELFW(e,LIBELFBITS),
sizeof (ElfW2(LIBELFBITS,Shdr)));
shdr_flags |= scn->shdr_flags;
scn->shdr_flags &= ~ELF_F_DIRTY;
}
if ((elf->flags & ELF_F_DIRTY) && last_offset < shdr_offset
&& unlikely (fill (elf->fildes, last_offset,
shdr_offset - last_offset,
fillbuf, &filled) != 0))
goto fail_free;
if (shdr_flags & ELF_F_DIRTY
&& unlikely ((size_t) pwrite_retry (elf->fildes, shdr_data,
sizeof (ElfW2(LIBELFBITS,Shdr))
* shnum, shdr_offset)
!= sizeof (ElfW2(LIBELFBITS,Shdr)) * shnum))
{
__libelf_seterrno (ELF_E_WRITE_ERROR);
goto fail_free;
}
free (shdr_data_mem);
free (scns);
}
elf->flags &= ~ELF_F_DIRTY;
return 0;
}