1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-18 17:50:29 +02:00

refactor and simplify ELF loader in objcodes.c

* libguile/objcodes.c (sniff_elf_alignment, alloc_aligned)
  (copy_and_align_elf_data): New helpers for portably re-aligning ELF
  data from read(2) or from a bytevector.
  (load_thunk_from_memory): Simplify!  Now there is only one procedure
  that loads ELF, and it does less: it simply receives the whole image
  in one array, hopefully from mmap.

  (scm_load_thunk_from_file): Use new map_file_contents helper, and go
  through load_thunk_from_memory.
  (scm_load_thunk_from_memory): Pass load_thunk_from_memory a piece of
  memory that it owns, and that is appropriately aligned.
This commit is contained in:
Andy Wingo 2013-04-28 14:23:20 +02:00
parent 0b3b73698c
commit e1aee492d7

View file

@ -156,11 +156,95 @@ check_elf_header (const Elf_Ehdr *header)
return NULL;
}
#define IS_ALIGNED(offset, alignment) \
(!((offset) & ((alignment) - 1)))
#define ALIGN(offset, alignment) \
((offset + (alignment - 1)) & ~(alignment - 1))
/* Return the alignment required by the ELF at DATA, of LEN bytes. */
static size_t
elf_alignment (const char *data, size_t len)
{
Elf_Ehdr *header;
int i;
size_t alignment = 8;
if (len < sizeof(Elf_Ehdr))
return alignment;
header = (Elf_Ehdr *) data;
if (header->e_phoff + header->e_phnum * header->e_phentsize >= len)
return alignment;
for (i = 0; i < header->e_phnum; i++)
{
Elf_Phdr *phdr;
const char *phdr_addr = data + header->e_phoff + i * header->e_phentsize;
if (!IS_ALIGNED ((scm_t_uintptr) phdr_addr, alignof_type (Elf_Phdr)))
return alignment;
phdr = (Elf_Phdr *) phdr_addr;
if (phdr->p_align & (phdr->p_align - 1))
return alignment;
if (phdr->p_align > alignment)
alignment = phdr->p_align;
}
return alignment;
}
/* This function leaks the memory that it allocates. */
static char*
alloc_aligned (size_t len, unsigned alignment)
{
char *ret;
if (alignment == 8)
{
/* FIXME: Assert that we actually have an 8-byte-aligned malloc. */
ret = malloc (len);
}
#if defined(HAVE_SYS_MMAN_H) && defined(MMAP_ANONYMOUS)
else if (alignment == SCM_PAGE_SIZE)
{
ret = mmap (NULL, len, PROT_READ | PROT_WRITE, -1, 0);
if (ret == MAP_FAILED)
SCM_SYSERROR;
}
#endif
else
{
if (len + alignment < len)
abort ();
ret = malloc (len + alignment - 1);
if (!ret)
abort ();
ret = (char *) ALIGN ((scm_t_uintptr) ret, alignment);
}
return ret;
}
static char*
copy_and_align_elf_data (const char *data, size_t len)
{
size_t alignment;
char *copy;
alignment = elf_alignment (data, len);
copy = alloc_aligned (len, alignment);
memcpy(copy, data, len);
return copy;
}
#ifdef HAVE_SYS_MMAN_H
static int
segment_flags_to_prot (Elf_Word flags)
{
int prot = 0;
if (flags & PF_X)
prot |= PROT_EXEC;
if (flags & PF_W)
@ -170,34 +254,7 @@ segment_flags_to_prot (Elf_Word flags)
return prot;
}
static int
map_segments (int fd, char **base,
const Elf_Phdr *from, const Elf_Phdr *to)
{
int prot = segment_flags_to_prot (from->p_flags);
char *ret;
ret = mmap (*base + from->p_vaddr,
to->p_offset + to->p_filesz - from->p_offset,
prot, MAP_PRIVATE, fd, from->p_offset);
if (ret == (char *) -1)
return 1;
if (!*base)
*base = ret;
return 0;
}
static int
mprotect_segments (char *base, const Elf_Phdr *from, const Elf_Phdr *to)
{
return mprotect (base + from->p_vaddr,
to->p_vaddr + to->p_memsz - from->p_vaddr,
segment_flags_to_prot (from->p_flags));
}
#endif
static char*
process_dynamic_segment (char *base, Elf_Phdr *dyn_phdr,
@ -272,152 +329,38 @@ process_dynamic_segment (char *base, Elf_Phdr *dyn_phdr,
#define ABORT(msg) do { err_msg = msg; goto cleanup; } while (0)
#ifdef HAVE_SYS_MMAN_H
static SCM
load_thunk_from_fd_using_mmap (int fd)
#define FUNC_NAME "load-thunk-from-disk"
{
Elf_Ehdr header;
Elf_Phdr *ph;
const char *err_msg = 0;
char *base = 0;
size_t n;
int i;
int start_segment = -1;
int prev_segment = -1;
int dynamic_segment = -1;
SCM init = SCM_BOOL_F, entry = SCM_BOOL_F;
if (full_read (fd, &header, sizeof header) != sizeof header)
ABORT ("object file too small");
if ((err_msg = check_elf_header (&header)))
goto cleanup;
if (lseek (fd, header.e_phoff, SEEK_SET) == (off_t) -1)
goto cleanup;
n = header.e_phnum;
ph = scm_gc_malloc_pointerless (n * sizeof (Elf_Phdr), "segment headers");
if (full_read (fd, ph, n * sizeof (Elf_Phdr)) != n * sizeof (Elf_Phdr))
ABORT ("failed to read program headers");
for (i = 0; i < n; i++)
{
if (!ph[i].p_memsz)
continue;
if (ph[i].p_filesz != ph[i].p_memsz)
ABORT ("expected p_filesz == p_memsz");
if (!ph[i].p_flags)
ABORT ("expected nonzero segment flags");
if (ph[i].p_type == PT_DYNAMIC)
{
if (dynamic_segment >= 0)
ABORT ("expected only one PT_DYNAMIC segment");
dynamic_segment = i;
}
if (start_segment < 0)
{
if (!base && ph[i].p_vaddr)
ABORT ("first loadable vaddr is not 0");
start_segment = prev_segment = i;
continue;
}
if (ph[i].p_flags == ph[start_segment].p_flags)
{
if (ph[i].p_vaddr - ph[prev_segment].p_vaddr
!= ph[i].p_offset - ph[prev_segment].p_offset)
ABORT ("coalesced segments not contiguous");
prev_segment = i;
continue;
}
/* Otherwise we have a new kind of segment. Map previous
segments. */
if (map_segments (fd, &base, &ph[start_segment], &ph[prev_segment]))
goto cleanup;
/* Open a new set of segments. */
start_segment = prev_segment = i;
}
/* Map last segments. */
if (start_segment < 0)
ABORT ("no loadable segments");
if (map_segments (fd, &base, &ph[start_segment], &ph[prev_segment]))
goto cleanup;
if (dynamic_segment < 0)
ABORT ("no PT_DYNAMIC segment");
if ((err_msg = process_dynamic_segment (base, &ph[dynamic_segment],
&init, &entry)))
goto cleanup;
if (scm_is_true (init))
scm_call_0 (init);
/* Finally! Return the thunk. */
return entry;
/* FIXME: munmap on error? */
cleanup:
{
int errno_save = errno;
(void) close (fd);
errno = errno_save;
if (errno)
SCM_SYSERROR;
scm_misc_error (FUNC_NAME, err_msg ? err_msg : "error loading ELF file",
SCM_EOL);
}
}
#undef FUNC_NAME
#endif /* HAVE_SYS_MMAN_H */
static SCM
load_thunk_from_memory (char *data, size_t len)
load_thunk_from_memory (char *data, size_t len, int is_read_only)
#define FUNC_NAME "load-thunk-from-memory"
{
Elf_Ehdr header;
Elf_Ehdr *header;
Elf_Phdr *ph;
const char *err_msg = 0;
char *base = 0;
size_t n, memsz = 0, alignment = 8;
size_t n, alignment = 8;
int i;
int first_loadable = -1;
int start_segment = -1;
int prev_segment = -1;
int dynamic_segment = -1;
SCM init = SCM_BOOL_F, entry = SCM_BOOL_F;
if (len < sizeof header)
if (len < sizeof *header)
ABORT ("object file too small");
memcpy (&header, data, sizeof header);
if ((err_msg = check_elf_header (&header)))
header = (Elf_Ehdr*) data;
if ((err_msg = check_elf_header (header)))
goto cleanup;
n = header.e_phnum;
if (len < header.e_phoff + n * sizeof (Elf_Phdr))
goto cleanup;
ph = (Elf_Phdr*) (data + header.e_phoff);
if (header->e_phnum == 0)
ABORT ("no loadable segments");
n = header->e_phnum;
if (len < header->e_phoff + n * sizeof (Elf_Phdr))
ABORT ("object file too small");
ph = (Elf_Phdr*) (data + header->e_phoff);
/* Check that the segment table is sane. */
for (i = 0; i < n; i++)
{
if (!ph[i].p_memsz)
continue;
if (ph[i].p_filesz != ph[i].p_memsz)
ABORT ("expected p_filesz == p_memsz");
@ -438,84 +381,49 @@ load_thunk_from_memory (char *data, size_t len)
dynamic_segment = i;
}
if (first_loadable < 0)
if (i == 0)
{
if (ph[i].p_vaddr)
if (ph[i].p_vaddr != 0)
ABORT ("first loadable vaddr is not 0");
first_loadable = i;
}
else
{
if (ph[i].p_vaddr < ph[i-1].p_vaddr + ph[i-1].p_memsz)
ABORT ("overlapping segments");
if (ph[i].p_vaddr < memsz)
ABORT ("overlapping segments");
if (ph[i].p_offset + ph[i].p_filesz > len)
ABORT ("segment beyond end of byte array");
memsz = ph[i].p_vaddr + ph[i].p_memsz;
if (ph[i].p_offset + ph[i].p_filesz > len)
ABORT ("segment beyond end of byte array");
}
}
if (first_loadable < 0)
ABORT ("no loadable segments");
if (dynamic_segment < 0)
ABORT ("no PT_DYNAMIC segment");
/* Now copy segments. */
if (!IS_ALIGNED ((scm_t_uintptr) data, alignment))
ABORT ("incorrectly aligned base");
/* We leak this memory, as we leak the memory mappings in
load_thunk_from_fd_using_mmap.
If the file is has an alignment of 8, use the standard malloc.
(FIXME to ensure alignment on non-GNU malloc.) Otherwise use
posix_memalign. We only use mprotect if the aligment is 4096. */
if (alignment == 8)
/* Allow writes to writable pages. */
if (is_read_only)
{
base = malloc (memsz);
if (!base)
goto cleanup;
}
else
if ((errno = posix_memalign ((void **) &base, alignment, memsz)))
goto cleanup;
memset (base, 0, memsz);
for (i = 0; i < n; i++)
{
if (!ph[i].p_memsz)
continue;
memcpy (base + ph[i].p_vaddr,
data + ph[i].p_offset,
ph[i].p_memsz);
if (start_segment < 0)
#ifdef HAVE_SYS_MMAN_H
for (i = 0; i < n; i++)
{
start_segment = prev_segment = i;
continue;
if (ph[i].p_flags == PF_R)
continue;
if (ph[i].p_align != 4096)
continue;
if (mprotect (data + ph[i].p_vaddr,
ph[i].p_memsz,
segment_flags_to_prot (ph[i].p_flags)))
goto cleanup;
}
if (ph[i].p_flags == ph[start_segment].p_flags)
{
prev_segment = i;
continue;
}
if (alignment == 4096)
if (mprotect_segments (base, &ph[start_segment], &ph[prev_segment]))
goto cleanup;
/* Open a new set of segments. */
start_segment = prev_segment = i;
#else
ABORT ("expected writable pages");
#endif
}
/* Mprotect the last segments. */
if (alignment == 4096)
if (mprotect_segments (base, &ph[start_segment], &ph[prev_segment]))
goto cleanup;
if ((err_msg = process_dynamic_segment (base, &ph[dynamic_segment],
if ((err_msg = process_dynamic_segment (data, &ph[dynamic_segment],
&init, &entry)))
goto cleanup;
@ -535,22 +443,40 @@ load_thunk_from_memory (char *data, size_t len)
}
#undef FUNC_NAME
#ifndef HAVE_SYS_MMAN_H
static SCM
load_thunk_from_fd_using_read (int fd)
#define FUNC_NAME "load-thunk-from-disk"
#define SCM_PAGE_SIZE 4096
static char*
map_file_contents (int fd, size_t len, int *is_read_only)
#define FUNC_NAME "load-thunk-from-file"
{
char *data;
size_t len;
struct stat st;
int ret;
ret = fstat (fd, &st);
if (ret < 0)
#ifdef HAVE_SYS_MMAN_H
data = mmap (NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED)
SCM_SYSERROR;
len = st.st_size;
data = scm_gc_malloc_pointerless (len, "objcode");
if (full_read (fd, data, len) != len)
*is_read_only = 1;
#else
if (lseek (fd, 0, SEEK_START) < 0)
{
int errno_save = errno;
(void) close (fd);
errno = errno_save;
SCM_SYSERROR;
}
/* Given that we are using the read fallback, optimistically assume
that the .go files were made with 8-byte alignment.
alignment. */
data = malloc (end);
if (!data)
{
(void) close (fd);
scm_misc_error (FUNC_NAME, "failed to allocate ~A bytes",
scm_list_1 (scm_from_size_t (end)));
}
if (full_read (fd, data, end) != end)
{
int errno_save = errno;
(void) close (fd);
@ -560,11 +486,25 @@ load_thunk_from_fd_using_read (int fd)
scm_misc_error (FUNC_NAME, "short read while loading objcode",
SCM_EOL);
}
(void) close (fd);
return load_thunk_from_memory (data, len);
/* If our optimism failed, fall back. */
{
unsigned alignment = sniff_elf_alignment (data, end);
if (alignment != 8)
{
char *copy = copy_and_align_elf_data (data, end, alignment);
free (data);
data = copy;
}
}
*is_read_only = 0;
#endif
return data;
}
#undef FUNC_NAME
#endif /* ! HAVE_SYS_MMAN_H */
SCM_DEFINE (scm_load_thunk_from_file, "load-thunk-from-file", 1, 0, 0,
(SCM filename),
@ -572,7 +512,9 @@ SCM_DEFINE (scm_load_thunk_from_file, "load-thunk-from-file", 1, 0, 0,
#define FUNC_NAME s_scm_load_thunk_from_file
{
char *c_filename;
int fd;
int fd, is_read_only;
off_t end;
char *data;
SCM_VALIDATE_STRING (1, filename);
@ -581,11 +523,15 @@ SCM_DEFINE (scm_load_thunk_from_file, "load-thunk-from-file", 1, 0, 0,
free (c_filename);
if (fd < 0) SCM_SYSERROR;
#ifdef HAVE_SYS_MMAN_H
return load_thunk_from_fd_using_mmap (fd);
#else
return load_thunk_from_fd_using_read (fd);
#endif
end = lseek (fd, 0, SEEK_END);
if (end < 0)
SCM_SYSERROR;
data = map_file_contents (fd, end, &is_read_only);
(void) close (fd);
return load_thunk_from_memory (data, end, is_read_only);
}
#undef FUNC_NAME
@ -594,10 +540,20 @@ SCM_DEFINE (scm_load_thunk_from_memory, "load-thunk-from-memory", 1, 0, 0,
"")
#define FUNC_NAME s_scm_load_thunk_from_memory
{
char *data;
size_t len;
SCM_VALIDATE_BYTEVECTOR (1, bv);
return load_thunk_from_memory ((char *) SCM_BYTEVECTOR_CONTENTS (bv),
SCM_BYTEVECTOR_LENGTH (bv));
data = (char *) SCM_BYTEVECTOR_CONTENTS (bv);
len = SCM_BYTEVECTOR_LENGTH (bv);
/* Copy data in order to align it, to trace its GC roots and
writable sections, and to keep it in memory. */
data = copy_and_align_elf_data (data, len);
return load_thunk_from_memory (data, len, 0);
}
#undef FUNC_NAME