From 23363ed7503c25ef4024ce0d517f7415c096645d Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Mon, 18 Jun 2012 18:13:49 +0200 Subject: [PATCH] linker: avoid mapping the whole library before load. This patch changes the load_library() function in the dynamic linker to avoid reserving a huge read-only address-space range just to read the ELF header and program header (which are typically very small and easily fit in the first page). Instead, we use the functions in linker_phdr.c to only load the data that we need in a temporary mmap-allocated page of memory, which we release when the function exits. This avoids issues when loading very large libraries, or simply debug versions that only need to load a tiny percentage of their overall file content in RAM. Change-Id: Id3a189fad2119a870a1b3d43dd81380c54ea6044 --- linker/linker.c | 172 ++++++++++++++++++++------------------------------------ 1 file changed, 61 insertions(+), 111 deletions(-) diff --git a/linker/linker.c b/linker/linker.c index 93819e458..1bf017f6b 100644 --- a/linker/linker.c +++ b/linker/linker.c @@ -50,6 +50,7 @@ #include "linker_debug.h" #include "linker_environ.h" #include "linker_format.h" +#include "linker_phdr.h" #define ALLOW_SYMBOLS_FROM_MAIN 1 #define SO_MAX 128 @@ -701,81 +702,6 @@ verify_elf_header(const Elf32_Ehdr* hdr) } -/* get_lib_extents - * Retrieves the base (*base) address where the ELF object should be - * mapped and its overall memory size (*total_sz). - * - * Args: - * fd: Opened file descriptor for the library - * name: The name of the library - * _hdr: Pointer to the header page of the library - * total_sz: Total size of the memory that should be allocated for - * this library - * - * Returns: - * -1 if there was an error while trying to get the lib extents. - * The possible reasons are: - * - Could not determine if the library was prelinked. - * - The library provided is not a valid ELF object - * 0 if the library did not request a specific base offset (normal - * for non-prelinked libs) - * > 0 if the library requests a specific address to be mapped to. - * This indicates a pre-linked library. - */ -static unsigned -get_lib_extents(int fd, const char *name, void *__hdr, unsigned *total_sz) -{ - unsigned req_base; - unsigned min_vaddr = 0xffffffff; - unsigned max_vaddr = 0; - unsigned char *_hdr = (unsigned char *)__hdr; - Elf32_Ehdr *ehdr = (Elf32_Ehdr *)_hdr; - Elf32_Phdr *phdr; - int cnt; - - TRACE("[ %5d Computing extents for '%s'. ]\n", pid, name); - if (verify_elf_header(ehdr) < 0) { - DL_ERR("%5d - %s is not a valid ELF object", pid, name); - return (unsigned)-1; - } - - req_base = (unsigned) is_prelinked(fd, name); - if (req_base == (unsigned)-1) - return -1; - else if (req_base != 0) { - TRACE("[ %5d - Prelinked library '%s' requesting base @ 0x%08x ]\n", - pid, name, req_base); - } else { - TRACE("[ %5d - Non-prelinked library '%s' found. ]\n", pid, name); - } - - phdr = (Elf32_Phdr *)(_hdr + ehdr->e_phoff); - - /* find the min/max p_vaddrs from all the PT_LOAD segments so we can - * get the range. */ - for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) { - if (phdr->p_type == PT_LOAD) { - if ((phdr->p_vaddr + phdr->p_memsz) > max_vaddr) - max_vaddr = phdr->p_vaddr + phdr->p_memsz; - if (phdr->p_vaddr < min_vaddr) - min_vaddr = phdr->p_vaddr; - } - } - - if ((min_vaddr == 0xffffffff) && (max_vaddr == 0)) { - DL_ERR("%5d - No loadable segments found in %s.", pid, name); - return (unsigned)-1; - } - - /* truncate min_vaddr down to page boundary */ - min_vaddr = PAGE_START(min_vaddr); - - /* round max_vaddr up to the next page */ - max_vaddr = PAGE_END(max_vaddr); - - *total_sz = (max_vaddr - min_vaddr); - return (unsigned)req_base; -} /* reserve_mem_region * * This function reserves a chunk of memory to be used for mapping in @@ -826,20 +752,15 @@ static int soinfo_alloc_mem_region(soinfo *si) void *base = mmap(NULL, si->size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { - DL_ERR("%5d mmap of library '%s' failed: %d (%s)\n", - pid, si->name, + DL_ERR("%5d mmap of library '%s' failed (%d bytes): %d (%s)\n", + pid, si->name, si->size, errno, strerror(errno)); - goto err; + return -1; } si->base = (unsigned) base; PRINT("%5d mapped library '%s' to %08x via kernel allocator.\n", pid, si->name, si->base); return 0; - -err: - DL_ERR("OOPS: %5d cannot map library '%s'. no vspace available.", - pid, si->name); - return -1; } #define MAYBE_MAP_FLAG(x,from,to) (((x) & (from)) ? (to) : 0) @@ -861,11 +782,10 @@ err: * 0 on success, -1 on failure. */ static int -soinfo_load_segments(soinfo* si, int fd, void* header) +soinfo_load_segments(soinfo* si, int fd, const Elf32_Phdr* phdr_table, int phdr_count, Elf32_Addr ehdr_phoff) { - Elf32_Ehdr *ehdr = (Elf32_Ehdr *)header; - Elf32_Phdr *phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff); - Elf32_Phdr *phdr0 = 0; /* program header for the first LOAD segment */ + const Elf32_Phdr *phdr = phdr_table; + const Elf32_Phdr *phdr0 = 0; /* program header for the first LOAD segment */ Elf32_Addr base; int cnt; unsigned len; @@ -881,7 +801,7 @@ soinfo_load_segments(soinfo* si, int fd, void* header) TRACE("[ %5d - Begin loading segments for '%s' @ 0x%08x ]\n", pid, si->name, (unsigned)si->base); - for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) { + for (cnt = 0; cnt < phdr_count; ++cnt, ++phdr) { if (phdr->p_type == PT_LOAD) { phdr0 = phdr; @@ -902,8 +822,8 @@ soinfo_load_segments(soinfo* si, int fd, void* header) /* Now go through all the PT_LOAD segments and map them into memory * at the appropriate locations. */ - phdr = (Elf32_Phdr *)((unsigned char *)header + ehdr->e_phoff); - for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) { + phdr = phdr_table; + for (cnt = 0; cnt < phdr_count; ++cnt, ++phdr) { if (phdr->p_type == PT_LOAD) { DEBUG_DUMP_PHDR(phdr, "PT_LOAD", pid); /* we want to map in the segment on a page boundary */ @@ -1054,8 +974,8 @@ soinfo_load_segments(soinfo* si, int fd, void* header) * phdr ehdr->e_phoff */ si->phdr = (Elf32_Phdr *)(base + phdr0->p_vaddr + - ehdr->e_phoff - phdr0->p_offset); - si->phnum = ehdr->e_phnum; + ehdr_phoff - phdr0->p_offset); + si->phnum = phdr_count; TRACE("[ %5d - Finish loading segments for '%s' @ 0x%08x. " "Total memory footprint: 0x%08x bytes ]\n", pid, si->name, @@ -1108,44 +1028,72 @@ get_wr_offset(int fd, const char *name, Elf32_Ehdr *ehdr) } #endif + static soinfo * load_library(const char *name) { int fd = open_library(name); - int cnt; + int ret, cnt; unsigned ext_sz; unsigned req_base; const char *bname; struct stat sb; soinfo *si = NULL; - Elf32_Ehdr *hdr = MAP_FAILED; + Elf32_Ehdr header[1]; + int phdr_count; + void* phdr_mmap = NULL; + Elf32_Addr phdr_size; + const Elf32_Phdr* phdr_table; if (fd == -1) { DL_ERR("Library '%s' not found", name); return NULL; } - /* We have to read the ELF header to figure out what to do with this image. - * Map entire file for this. There won't be much difference in physical - * memory usage or performance. - */ - if (fstat(fd, &sb) < 0) { - DL_ERR("%5d fstat() failed! (%s)", pid, strerror(errno)); + /* Read the ELF header first */ + ret = TEMP_FAILURE_RETRY(read(fd, (void*)header, sizeof(header))); + if (ret < 0) { + DL_ERR("%5d can't read file %s: %s", pid, name, strerror(errno)); + goto fail; + } + if (ret != (int)sizeof(header)) { + DL_ERR("%5d too small to be an ELF executable: %s", pid, name); + goto fail; + } + if (verify_elf_header(header) < 0) { + DL_ERR("%5d not a valid ELF executable: %s", pid, name); goto fail; } - hdr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (hdr == MAP_FAILED) { - DL_ERR("%5d failed to mmap() header of '%s' (%s)", - pid, name, strerror(errno)); + /* Then read the program header table */ + ret = phdr_table_load(fd, header->e_phoff, header->e_phnum, + &phdr_mmap, &phdr_size, &phdr_table); + if (ret < 0) { + DL_ERR("%5d can't load program header table: %s: %s", pid, + name, strerror(errno)); goto fail; } + phdr_count = header->e_phnum; - /* Parse the ELF header and get the size of the memory footprint for - * the library */ - req_base = get_lib_extents(fd, name, hdr, &ext_sz); - if (req_base == (unsigned)-1) + /* Get the load extents and the prelinked load address, if any */ + ext_sz = phdr_table_get_load_size(phdr_table, phdr_count); + if (ext_sz == 0) { + DL_ERR("%5d no loadable segments in file: %s", pid, name); goto fail; + } + + req_base = (unsigned) is_prelinked(fd, name); + if (req_base == (unsigned)-1) { + DL_ERR("%5d can't read end of library: %s: %s", pid, name, + strerror(errno)); + goto fail; + } + if (req_base != 0) { + TRACE("[ %5d - Prelinked library '%s' requesting base @ 0x%08x ]\n", + pid, name, req_base); + } else { + TRACE("[ %5d - Non-prelinked library '%s' found. ]\n", pid, name); + } TRACE("[ %5d - '%s' (%s) wants base=0x%08x sz=0x%08x ]\n", pid, name, (req_base ? "prelinked" : "not pre-linked"), req_base, ext_sz); @@ -1174,17 +1122,19 @@ load_library(const char *name) pid, name, (void *)si->base, (unsigned) ext_sz); /* Now actually load the library's segments into right places in memory */ - if (soinfo_load_segments(si, fd, hdr) < 0) { + if (soinfo_load_segments(si, fd, phdr_table, phdr_count, header->e_phoff) < 0) { goto fail; } - munmap(hdr, sb.st_size); + phdr_table_unload(phdr_mmap, phdr_size); close(fd); return si; fail: if (si) soinfo_free(si); - if (hdr != MAP_FAILED) munmap(hdr, sb.st_size); + if (phdr_mmap != NULL) { + phdr_table_unload(phdr_mmap, phdr_size); + } close(fd); return NULL; } -- 2.11.0