blob: 82ee2be1f3850792b2b71ff50b2837b1cdd9df25 [file] [log] [blame]
/* *******************************************************************************
* Copyright (c) 2011-2025 Google, Inc. All rights reserved.
* Copyright (c) 2011 Massachusetts Institute of Technology All rights reserved.
* *******************************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/*
* loader.c: custom private library loader for Linux
*
* original case: i#157
*/
#include "../globals.h"
#include "../module_shared.h"
#include "os_private.h"
#include "../ir/instr.h" /* SEG_GS/SEG_FS */
#include "decode.h"
#include "module.h"
#include "module_private.h"
#include "../heap.h" /* HEAPACCT */
#ifdef LINUX
# include "include/syscall.h"
# include "memquery.h"
# define _GNU_SOURCE 1
# define __USE_GNU 1
# include <link.h> /* struct dl_phdr_info, must be prior to dlfcn.h */
#else
# include <sys/syscall.h>
#endif
#include "tls.h"
#include <dlfcn.h> /* dlsym */
#ifdef LINUX
# include <sys/prctl.h> /* PR_SET_NAME */
#endif
#include <stdlib.h> /* getenv */
#include <dlfcn.h> /* dlopen/dlsym */
#include <unistd.h> /* __environ */
#include <stddef.h> /* offsetof */
extern size_t
wcslen(const wchar_t *str); /* in string.c */
/* Written during initialization only */
/* XXX: i#460, the path lookup itself is a complicated process,
* so we just list possible common but in-complete paths for now.
*/
#define SYSTEM_LIBRARY_PATH_VAR "LD_LIBRARY_PATH"
static char *ld_library_path = NULL;
static const char *const system_lib_paths[] = {
#ifdef X86
"/lib/tls/i686/cmov",
#endif
"/usr/lib",
"/lib",
"/usr/local/lib", /* Ubuntu: /etc/ld.so.conf.d/libc.conf */
#ifdef ANDROID
"/system/lib",
# ifdef ANDROID64
"/system/lib64",
# elif defined(ANDROID32)
"/system/lib32",
# endif
#endif
#ifndef X64
"/usr/lib32",
"/lib32",
# ifdef X86
"/lib32/tls/i686/cmov",
/* 32-bit Ubuntu */
"/lib/i386-linux-gnu",
"/usr/lib/i386-linux-gnu",
# elif defined(ARM)
"/lib/arm-linux-gnueabihf",
"/usr/lib/arm-linux-gnueabihf",
"/lib/arm-linux-gnueabi",
"/usr/lib/arm-linux-gnueabi",
# endif
#else
/* 64-bit Ubuntu */
# ifdef X86
"/lib64/tls/i686/cmov",
# endif
"/usr/lib64",
"/lib64",
# ifdef X86
"/lib/x86_64-linux-gnu", /* /etc/ld.so.conf.d/x86_64-linux-gnu.conf */
"/usr/lib/x86_64-linux-gnu", /* /etc/ld.so.conf.d/x86_64-linux-gnu.conf */
# elif defined(AARCH64)
"/lib/aarch64-linux-gnu",
"/usr/lib/aarch64-linux-gnu",
# elif defined(RISCV64)
"/lib/riscv64-linux-gnu",
"/usr/lib/riscv64-linux-gnu",
# endif
#endif
};
#define NUM_SYSTEM_LIB_PATHS (sizeof(system_lib_paths) / sizeof(system_lib_paths[0]))
#define RPATH_ORIGIN "$ORIGIN"
#define APP_BRK_GAP 64 * 1024 * 1024
static os_privmod_data_t *libdr_opd;
#ifdef LINUX /* XXX i#1285: implement MacOS private loader */
static bool printed_gdb_commands = false;
/* Global so visible in release build gdb */
static char gdb_priv_cmds[4096];
static size_t gdb_priv_cmds_sofar;
#endif
/* pointing to the I/O data structure in privately loaded libc,
* They are used on exit when we need update file_no.
*/
stdfile_t **privmod_stdout;
stdfile_t **privmod_stderr;
stdfile_t **privmod_stdin;
#define LIBC_STDOUT_NAME "stdout"
#define LIBC_STDERR_NAME "stderr"
#define LIBC_STDIN_NAME "stdin"
#define LIBC_EARLY_INIT_NAME "__libc_early_init"
/* We save the original sp from the kernel, for use by TLS setup on Android */
void *kernel_init_sp;
/* forward decls */
static void
privload_init_search_paths(void);
static bool
privload_locate(const char *name, privmod_t *dep, char *filename DR_PARAM_OUT,
bool *client DR_PARAM_OUT);
static privmod_t *
privload_locate_and_load(const char *impname, privmod_t *dependent, bool reachable);
static void
privload_call_lib_func(dcontext_t *dcontext, privmod_t *privmod, fp_t func);
static void
privload_relocate_mod(privmod_t *mod);
static void
privload_create_os_privmod_data(privmod_t *privmod, bool dyn_reloc);
static void
privload_delete_os_privmod_data(privmod_t *privmod);
void
privload_mod_tls_init(privmod_t *mod);
#ifdef LINUX
void
privload_mod_tls_primary_thread_init(privmod_t *mod);
#endif
/***************************************************************************/
/* Register a symbol file with gdb. This symbol needs to be exported so that
* gdb can find it even when full debug information is unavailable. We do
* *not* consider it part of DR's public API.
* i#531: gdb support for private loader
*/
DYNAMORIO_EXPORT void
dr_gdb_add_symbol_file(const char *filename, app_pc textaddr)
{
/* Do nothing. If gdb is attached with libdynamorio.so-gdb.py loaded, it
* will stop here and lift the argument values.
*/
/* XXX: This only passes the text section offset. gdb can accept
* additional "-s<section> <address>" arguments to locate data sections.
* This would be useful for setting watchpoints on client global variables.
*/
}
#ifdef LINUX /* XXX i#1285: implement MacOS private loader */
static void
privload_add_gdb_cmd(elf_loader_t *loader, const char *filename, bool reachable)
{
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
/* Get the text addr to register the ELF with gdb. The section headers
* are not part of the mapped image, so we have to map the whole file.
* XXX: seek to e_shoff and read the section headers to avoid this map.
*/
if (elf_loader_map_file(loader, reachable) != NULL) {
app_pc text_addr =
(app_pc)module_get_text_section(loader->file_map, loader->file_size);
text_addr += loader->load_delta;
print_to_buffer(gdb_priv_cmds, BUFFER_SIZE_ELEMENTS(gdb_priv_cmds),
&gdb_priv_cmds_sofar, "add-symbol-file '%s' %p\n", filename,
text_addr);
/* Add debugging comment about how to get symbol information in gdb. */
if (printed_gdb_commands) {
/* This is a dynamically loaded auxlib, so we print here.
* The client and its direct dependencies are batched up and
* printed in os_loader_init_epilogue.
*/
SYSLOG_INTERNAL_INFO("Paste into GDB to debug DynamoRIO clients:\n"
"add-symbol-file '%s' %p\n",
filename, text_addr);
}
LOG(GLOBAL, LOG_LOADER, 1, "for debugger: add-symbol-file %s %p\n", filename,
text_addr);
if (INTERNAL_OPTION(privload_register_gdb)) {
dr_gdb_add_symbol_file(filename, text_addr);
}
}
}
#endif
/* os specific loader initialization prologue before finalizing the load. */
void
os_loader_init_prologue(void)
{
#ifndef STATIC_LIBRARY
privmod_t *mod;
#endif
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
privload_init_search_paths();
#ifndef STATIC_LIBRARY
/* insert libdynamorio.so */
mod = privload_insert(NULL, get_dynamorio_dll_start(),
get_dynamorio_dll_end() - get_dynamorio_dll_start(),
get_shared_lib_name(get_dynamorio_dll_start()),
get_dynamorio_library_path());
ASSERT(mod != NULL);
/* If DR was loaded by system ld.so, then .dynamic *was* relocated (i#1589) */
privload_create_os_privmod_data(mod, !DYNAMO_OPTION(early_inject));
libdr_opd = (os_privmod_data_t *)mod->os_privmod_data;
DODEBUG({
if (DYNAMO_OPTION(early_inject)) {
/* We've already filled the gap in dynamorio_lib_gap_empty(). We just
* verify here now that we have segment info.
*/
int i;
for (i = 0; i < libdr_opd->os_data.num_segments - 1; i++) {
size_t sz = libdr_opd->os_data.segments[i + 1].start -
libdr_opd->os_data.segments[i].end;
if (sz > 0) {
dr_mem_info_t info;
bool ok = query_memory_ex_from_os(libdr_opd->os_data.segments[i].end,
&info);
ASSERT(ok);
ASSERT(info.base_pc == libdr_opd->os_data.segments[i].end &&
info.size == sz &&
(info.type == DR_MEMTYPE_FREE ||
/* If we reloaded DR, our own loader filled it in. */
info.prot == DR_MEMPROT_NONE));
}
}
}
});
mod->externally_loaded = true;
# ifdef LINUX /*i#1285*/
if (DYNAMO_OPTION(early_inject)) {
/* libdynamorio isn't visible to gdb so add to the cmd list */
byte *dr_base = get_dynamorio_dll_start(), *pref_base;
elf_loader_t dr_ld;
IF_DEBUG(bool success =)
elf_loader_read_headers(&dr_ld, get_dynamorio_library_path());
ASSERT(success);
module_walk_program_headers(dr_base, get_dynamorio_dll_end() - dr_base, false,
false, (byte **)&pref_base, NULL, NULL, NULL, NULL);
dr_ld.load_delta = dr_base - pref_base;
privload_add_gdb_cmd(&dr_ld, get_dynamorio_library_path(), false /*!reach*/);
elf_loader_destroy(&dr_ld);
}
# endif
#endif
}
/* os specific loader initialization epilogue after finalizing the load. */
void
os_loader_init_epilogue(void)
{
#ifdef LINUX /* XXX i#1285: implement MacOS private loader */
/* Print the add-symbol-file commands so they can be copy-pasted into gdb.
* We have to do it in a single syslog so they can be copy pasted.
* For non-internal builds, or for private libs loaded after this point,
* the user must look at the global gdb_priv_cmds buffer in gdb.
* XXX i#531: Support attaching from the gdb script.
*/
ASSERT(!printed_gdb_commands);
printed_gdb_commands = true;
if (gdb_priv_cmds_sofar > 0) {
SYSLOG_INTERNAL_INFO("Paste into GDB to debug DynamoRIO clients:\n"
/* Need to turn off confirm for paste to work. */
"set confirm off\n"
"%s",
gdb_priv_cmds);
}
#endif
}
void
os_loader_exit(void)
{
if (libdr_opd != NULL) {
HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, libdr_opd->os_data.segments, module_segment_t,
libdr_opd->os_data.alloc_segments, ACCT_OTHER, PROTECTED);
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, libdr_opd, os_privmod_data_t, ACCT_OTHER,
PROTECTED);
}
#ifdef LINUX
/* Put printed_gdb_commands into its original state for potential
* re-attaching and os_loader_init_epilogue().
*/
printed_gdb_commands = false;
#endif
}
/* These are called before loader_init for the primary thread for UNIX. */
void
os_loader_thread_init_prologue(dcontext_t *dcontext)
{
/* do nothing */
}
void
os_loader_thread_init_epilogue(dcontext_t *dcontext)
{
/* do nothing */
}
void
os_loader_thread_exit(dcontext_t *dcontext)
{
/* do nothing */
}
void
privload_add_areas(privmod_t *privmod)
{
os_privmod_data_t *opd;
uint i;
/* create and init the os_privmod_data for privmod.
* The os_privmod_data can only be created after heap is ready and
* should be done before adding in vmvector_add,
* so it can be either right before calling to privload_add_areas
* in the privload_load_finalize, or in here.
* We prefer here because it avoids changing the code in
* loader_shared.c, which affects windows too.
*/
privload_create_os_privmod_data(privmod, false /* i#1589: .dynamic not relocated */);
opd = (os_privmod_data_t *)privmod->os_privmod_data;
for (i = 0; i < opd->os_data.num_segments; i++) {
vmvector_add(modlist_areas, opd->os_data.segments[i].start,
opd->os_data.segments[i].end, (void *)privmod);
}
}
void
privload_remove_areas(privmod_t *privmod)
{
uint i;
os_privmod_data_t *opd = (os_privmod_data_t *)privmod->os_privmod_data;
/* walk the program header to remove areas */
for (i = 0; i < opd->os_data.num_segments; i++) {
vmvector_remove(modlist_areas, opd->os_data.segments[i].start,
opd->os_data.segments[i].end);
}
/* NOTE: we create os_privmod_data in privload_add_areas but
* do not delete here, non-symmetry.
* This is because we still need the information in os_privmod_data
* to unmap the segments in privload_unmap_file, which happens after
* privload_remove_areas.
* The create of os_privmod_data should be done when mapping the file
* into memory, but the heap is not ready at that time, so postponed
* until privload_add_areas.
*/
}
void
privload_unmap_file(privmod_t *privmod)
{
/* walk the program header to unmap files, also the tls data */
uint i;
os_privmod_data_t *opd = (os_privmod_data_t *)privmod->os_privmod_data;
/* unmap segments */
IF_DEBUG(size_t size_unmapped = 0);
for (i = 0; i < opd->os_data.num_segments; i++) {
d_r_unmap_file(opd->os_data.segments[i].start,
opd->os_data.segments[i].end - opd->os_data.segments[i].start);
DODEBUG({
size_unmapped +=
opd->os_data.segments[i].end - opd->os_data.segments[i].start;
});
if (i + 1 < opd->os_data.num_segments &&
opd->os_data.segments[i + 1].start > opd->os_data.segments[i].end) {
/* unmap the gap */
d_r_unmap_file(opd->os_data.segments[i].end,
opd->os_data.segments[i + 1].start -
opd->os_data.segments[i].end);
DODEBUG({
size_unmapped +=
opd->os_data.segments[i + 1].start - opd->os_data.segments[i].end;
});
}
}
ASSERT(size_unmapped == privmod->size);
/* XXX i#3570: Better to store the MODLOAD_SEPARATE_BSS flag but there's no
* simple code path to do it so we check the option.
*/
if (INTERNAL_OPTION(separate_private_bss)) {
/* unmap the extra .bss-separating page */
d_r_unmap_file(privmod->base + privmod->size, PAGE_SIZE);
DODEBUG({ size_unmapped += PAGE_SIZE; });
}
/* free segments */
HEAP_ARRAY_FREE(GLOBAL_DCONTEXT, opd->os_data.segments, module_segment_t,
opd->os_data.alloc_segments, ACCT_OTHER, PROTECTED);
/* delete os_privmod_data */
privload_delete_os_privmod_data(privmod);
}
bool
privload_unload_imports(privmod_t *privmod)
{
/* XXX: i#474 unload dependent libraries if necessary */
return true;
}
#ifdef LINUX
/* Core-specific functionality for elf_loader_map_phdrs(). */
static modload_flags_t
privload_map_flags(modload_flags_t init_flags)
{
/* XXX: Keep this condition matching the check in privload_unmap_file()
* (minus MODLOAD_NOT_PRIVLIB since non-privlibs don't reach our unmap).
*/
if (INTERNAL_OPTION(separate_private_bss) && !TEST(MODLOAD_NOT_PRIVLIB, init_flags)) {
/* place an extra no-access page after .bss */
/* XXX: update privload_early_inject call to init_emulated_brk if this changes */
/* XXX: should we avoid this for -early_inject's map of the app and ld.so? */
return init_flags | MODLOAD_SEPARATE_BSS;
}
return init_flags;
}
/* Core-specific functionality for elf_loader_map_phdrs(). */
static void
privload_check_new_map_bounds(elf_loader_t *elf, byte *map_base, byte *map_end)
{
/* This is only called for MAP_FIXED. */
if (get_dynamorio_dll_start() < map_end && get_dynamorio_dll_end() > map_base) {
FATAL_USAGE_ERROR(FIXED_MAP_OVERLAPS_DR, 3, get_application_name(),
get_application_pid(), elf->filename);
ASSERT_NOT_REACHED();
}
}
#endif
#ifdef LINUX
/* XXX i#7192: Consider making this an os.h API, like the related os_map_file and
* os_unmap_file.
*/
static byte *
overlap_map_file_func(file_t f, size_t *size DR_PARAM_INOUT, uint64 offs, app_pc addr,
uint prot, map_flags_t map_flags)
{
/* This works only if the user wants the new mapping only at the given addr,
* and it is acceptable to unmap any mapping already existing there.
*/
ASSERT(TEST(MAP_FILE_FIXED, map_flags));
if (DYNAMO_OPTION(vm_reserve) && is_vmm_reserved_address(addr, *size, NULL, NULL)) {
/* If the initially reserved address was from our vmm range, we need to
* use os_unmap_file to make sure we perform our heap bookkeeping.
* In this case os_unmap_file does not do any munmap syscall, so there
* is not really any unmap-to-map race with other threads.
*/
os_unmap_file(addr, *size);
}
/* MAP_FILE_FIXED (which is MAP_FIXED in the mmap syscall) will cause the
* overlapping region to automatically and atomically get unmapped.
*/
return os_map_file(f, size, offs, addr, prot, map_flags);
}
#endif
/* This only maps, as relocation for ELF requires processing imports first,
* which we have to delay at init time at least.
*/
app_pc
privload_map_and_relocate(const char *filename, size_t *size DR_PARAM_OUT,
modload_flags_t flags)
{
#ifdef LINUX
map_fn_t map_func;
unmap_fn_t unmap_func;
overlap_map_fn_t overlap_map_func;
prot_fn_t prot_func;
app_pc base = NULL;
elf_loader_t loader;
ASSERT_OWN_RECURSIVE_LOCK(!TEST(MODLOAD_NOT_PRIVLIB, flags), &privload_lock);
/* get appropriate function */
/* NOTE: all but the client lib will be added to DR areas list b/c using
* d_r_map_file()
*/
if (dynamo_heap_initialized && !standalone_library) {
map_func = d_r_map_file;
unmap_func = d_r_unmap_file;
/* TODO i#7192: Implement a new d_r_overlap_map_file that performs
* remapping similar to overlap_map_file_func (using just a map call with
* MAP_FIXED, but without any explicit unmap) but also does the required
* bookeeping.
*/
overlap_map_func = NULL;
prot_func = set_protection;
} else {
map_func = os_map_file;
unmap_func = os_unmap_file;
overlap_map_func = overlap_map_file_func;
prot_func = os_set_protection;
}
if (!elf_loader_read_headers(&loader, filename)) {
/* We may want to move the bitwidth check out if is_elf_so_header_common()
* but for now we keep that there and do another check here.
* If loader.buf was not read into it will be all zeroes.
*/
ELF_HEADER_TYPE *elf_header = (ELF_HEADER_TYPE *)loader.buf;
ELF_ALTARCH_HEADER_TYPE *altarch = (ELF_ALTARCH_HEADER_TYPE *)elf_header;
if (!TEST(MODLOAD_NOT_PRIVLIB, flags) && elf_header->e_version == 1 &&
altarch->e_ehsize == sizeof(ELF_ALTARCH_HEADER_TYPE) &&
altarch->e_machine ==
IF_X64_ELSE(IF_AARCHXX_ELSE(EM_ARM, EM_386),
IF_AARCHXX_ELSE(EM_AARCH64, EM_X86_64))) {
/* XXX i#147: Should we try some path substs like s/lib32/lib64/?
* Maybe it's better to error out to avoid loading some unintended lib.
*/
SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_WRONG_BITWIDTH, 3, get_application_name(),
get_application_pid(), filename);
}
return NULL;
}
base = elf_loader_map_phdrs(&loader, false /* fixed */, map_func, unmap_func,
prot_func, privload_check_new_map_bounds, memset,
privload_map_flags(flags), overlap_map_func);
if (base != NULL) {
if (size != NULL)
*size = loader.image_size;
if (!TEST(MODLOAD_NOT_PRIVLIB, flags))
privload_add_gdb_cmd(&loader, filename, TEST(MODLOAD_REACHABLE, flags));
}
elf_loader_destroy(&loader);
return base;
#else
/* XXX i#1285: implement MacOS private loader */
return NULL;
#endif
}
bool
privload_process_imports(privmod_t *mod)
{
#ifdef LINUX
ELF_DYNAMIC_ENTRY_TYPE *dyn;
os_privmod_data_t *opd;
char *strtab, *name;
opd = (os_privmod_data_t *)mod->os_privmod_data;
ASSERT(opd != NULL);
/* 1. get DYNAMIC section pointer */
dyn = (ELF_DYNAMIC_ENTRY_TYPE *)opd->dyn;
/* 2. get dynamic string table */
strtab = (char *)opd->os_data.dynstr;
/* 3. depth-first recursive load, so add into the deps list first */
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag == DT_NEEDED) {
name = strtab + dyn->d_un.d_val;
LOG(GLOBAL, LOG_LOADER, 2, "%s: %s imports from %s\n", __FUNCTION__,
mod->name, name);
if (privload_lookup(name) == NULL) {
privmod_t *impmod =
privload_locate_and_load(name, mod, false /*client dir=>true*/);
if (impmod == NULL)
return false;
if (strstr(name, "libpthread") == name) {
/* i#956: A private libpthread is not fully supported, but many
* libraries import some utilities from it and do not use
* threading. We load it and just do not guarantee things will
* work if thread-related routines are called.
*/
SYSLOG_INTERNAL_WARNING(
"private libpthread.so loaded but not fully supported (i#956)");
}
/* i#852: Identify all libs that import from DR as client libs.
* XXX i#6982: The following condition is never true as
* libdynamorio.so has already been loaded (xref #3850).
*/
if (impmod->base == get_dynamorio_dll_start())
mod->is_client = true;
}
}
++dyn;
}
/* Relocate library's symbols after load dependent libraries (so that we
* can resolve symbols in the global ELF namespace).
*/
if (!mod->externally_loaded) {
privload_relocate_mod(mod);
}
return true;
#else
/* XXX i#1285: implement MacOS private loader */
if (!mod->externally_loaded) {
privload_relocate_mod(mod);
}
return false;
#endif
}
bool
privload_call_entry(dcontext_t *dcontext, privmod_t *privmod, uint reason)
{
os_privmod_data_t *opd = (os_privmod_data_t *)privmod->os_privmod_data;
ASSERT(os_get_priv_tls_base(NULL, TLS_REG_LIB) != NULL);
if (reason == DLL_PROCESS_INIT) {
/* calls init and init array */
LOG(GLOBAL, LOG_LOADER, 3, "%s: calling init routines of %s\n", __FUNCTION__,
privmod->name);
if (opd->init != NULL) {
LOG(GLOBAL, LOG_LOADER, 4, "%s: calling %s init func " PFX "\n", __FUNCTION__,
privmod->name, opd->init);
privload_call_lib_func(dcontext, privmod, opd->init);
}
if (opd->init_array != NULL) {
uint i;
for (i = 0; i < opd->init_arraysz / sizeof(opd->init_array[i]); i++) {
if (opd->init_array[i] != NULL) { /* be paranoid */
LOG(GLOBAL, LOG_LOADER, 4, "%s: calling %s init array func " PFX "\n",
__FUNCTION__, privmod->name, opd->init_array[i]);
privload_call_lib_func(dcontext, privmod, opd->init_array[i]);
}
}
}
return true;
} else if (reason == DLL_PROCESS_EXIT) {
/* calls fini and fini array */
#ifdef ANDROID
/* i#1701: libdl.so fini routines call into libc somehow, which is
* often already unmapped. We just skip them as a workaround.
*/
if (strcmp(privmod->name, "libdl.so") == 0) {
LOG(GLOBAL, LOG_LOADER, 3, "%s: NOT calling fini routines of %s\n",
__FUNCTION__, privmod->name);
return true;
}
#endif
LOG(GLOBAL, LOG_LOADER, 3, "%s: calling fini routines of %s\n", __FUNCTION__,
privmod->name);
if (opd->fini != NULL) {
LOG(GLOBAL, LOG_LOADER, 4, "%s: calling %s fini func " PFX "\n", __FUNCTION__,
privmod->name, opd->fini);
privload_call_lib_func(dcontext, privmod, opd->fini);
}
if (opd->fini_array != NULL) {
uint i;
for (i = 0; i < opd->fini_arraysz / sizeof(opd->fini_array[0]); i++) {
if (opd->fini_array[i] != NULL) { /* be paranoid */
LOG(GLOBAL, LOG_LOADER, 4, "%s: calling %s fini array func " PFX "\n",
__FUNCTION__, privmod->name, opd->fini_array[i]);
privload_call_lib_func(dcontext, privmod, opd->fini_array[i]);
}
}
}
return true;
}
return false;
}
void
privload_redirect_setup(privmod_t *privmod)
{
/* do nothing, the redirection is done when relocating */
}
void
privload_os_finalize(privmod_t *privmod)
{
#ifndef LINUX
return; /* Nothing to do. */
#else
static privmod_t *privmod_ld_linux;
if (strstr(privmod->name, "ld-linux") == privmod->name) {
/* We need to first get the libc version before we clobber ld vars.
* (We could instead look for versioned symbols with "@GLIBC_2.34" in ld
* but we do not have version parsing code in place.)
* We assume ld will not be unloaded.
*/
privmod_ld_linux = privmod;
return;
}
if (strstr(privmod->name, "libc.so") != privmod->name)
return;
os_privmod_data_t *opd = (os_privmod_data_t *)privmod->os_privmod_data;
/* Special handling for standard I/O file descriptors. */
privmod_stdout = (FILE **)get_proc_address_from_os_data(
&opd->os_data, opd->load_delta, LIBC_STDOUT_NAME, NULL);
privmod_stdin = (FILE **)get_proc_address_from_os_data(&opd->os_data, opd->load_delta,
LIBC_STDIN_NAME, NULL);
privmod_stderr = (FILE **)get_proc_address_from_os_data(
&opd->os_data, opd->load_delta, LIBC_STDERR_NAME, NULL);
/* i#5133: glibc 2.32+ has ld.so call a hardcoded initializer before calling the
* regular ELF constructors.
*/
void (*libc_early_init)(bool) = (void (*)(bool))get_proc_address_from_os_data(
&opd->os_data, opd->load_delta, LIBC_EARLY_INIT_NAME, NULL);
if (libc_early_init == NULL) {
return;
}
/* XXX i#5437: Temporary workaround to avoid a SIGFPE in glibc 2.34+
* __libc_early_init(). As we cannot let ld/libc initialize their own TLS with the
* current design, we must explicitly initialize a few variables. Unfortunately
* we have to hardcode their offsets, making this fragile. Long-term we should try
* to find a better solution.
*/
/* Do not try to clobber vars unless we have to: get the libc version. */
# define LIBC_GET_VERSION_NAME "gnu_get_libc_version"
const char *(*libc_ver)(void) = (const char *(*)(void))get_proc_address_from_os_data(
&opd->os_data, opd->load_delta, LIBC_GET_VERSION_NAME, NULL);
if (libc_ver == NULL)
return;
LOG(GLOBAL, LOG_LOADER, 2, "%s: calling %s\n", __FUNCTION__, LIBC_GET_VERSION_NAME);
const char *ver = (*libc_ver)();
LOG(GLOBAL, LOG_LOADER, 2, "%s: libc version is |%s|\n", __FUNCTION__, ver);
if ((ver[0] == '\0' || ver[0] < '2') || ver[1] != '.' || ver[2] < '3' ||
(ver[2] == '3' && ver[3] < '4'))
return;
# ifndef X86
/* XXX i#6611: We have privload_set_pthread_tls_fields() setting the pthread tid
* field on x86, but not other arches. Since we have the glibc version here and
* believe this to be limited to 2.37 we warn about it here.
*/
if (ver[2] == '3' && ver[3] >= '7') {
SYSLOG_INTERNAL_WARNING("glibc 2.37+ i#6611 pthread tid fix NYI for non-x86");
}
# endif
if (privmod_ld_linux == NULL) {
SYSLOG_INTERNAL_WARNING("glibc 2.34+ i#5437 workaround failed: missed ld");
return;
}
os_privmod_data_t *ld_opd = (os_privmod_data_t *)privmod_ld_linux->os_privmod_data;
byte *glro = get_proc_address_from_os_data(&ld_opd->os_data, ld_opd->load_delta,
"_rtld_global_ro", NULL);
if (glro == NULL) {
SYSLOG_INTERNAL_WARNING("glibc 2.34+ i#5437 workaround failed: missed glro");
return;
}
int GLRO_dl_tls_static_size_OFFS = 0;
int GLRO_dl_tls_static_align_OFFS = 0;
# ifdef X86
/* Look for this pattern:
* 0x00007ffff7759d62 <+98>: mov 0x2b0(%rax),%rsi
* 0x00007ffff7759d69 <+105>: mov 0x2a8(%rax),%rbx
* 0x00007ffff7759d70 <+112>: mov 0x18(%rax),%rcx
* 0x00007ffff7759d74 <+116>: add %rsi,%rbx
* 0x00007ffff7759d77 <+119>: mov %rbx,%rax
* 0x00007ffff7759d7a <+122>: mov %rcx,0xa7daf(%rip) # 0x7ffff7801b30
* 0x00007ffff7759d81 <+129>: sub $0x1,%rax
* 0x00007ffff7759d85 <+133>: div %rsi
* We want the 0x2b0 and 0x2a8 offests prior to the OP_div.
* They're always pointer-sized apart; there's never a div before this in the
* function. Naturally this is fragile, but it is better than updating hardcoded
* offsets with each release. See i#5437 for discussion of long-term possible
* solutions.
*/
instr_noalloc_t noalloc;
instr_noalloc_init(GLOBAL_DCONTEXT, &noalloc);
instr_t *instr = instr_from_noalloc(&noalloc);
byte *pc = (byte *)libc_early_init;
int last_large_load_offs = 0;
const int MIN_LOAD_OFFS = 0x100;
const int MAX_INSTRS = 64;
int instr_count = 0;
do {
instr_reset(GLOBAL_DCONTEXT, instr);
pc = decode(GLOBAL_DCONTEXT, pc, instr);
if (instr_get_opcode(instr) == OP_mov_ld &&
opnd_is_base_disp(instr_get_src(instr, 0))) {
int disp = opnd_get_disp(instr_get_src(instr, 0));
if (disp > MIN_LOAD_OFFS)
last_large_load_offs = disp;
}
if (++instr_count > MAX_INSTRS)
break;
} while (instr_get_opcode(instr) != OP_div);
if (instr_get_opcode(instr) == OP_div && last_large_load_offs > 0) {
GLRO_dl_tls_static_size_OFFS = last_large_load_offs;
GLRO_dl_tls_static_align_OFFS = last_large_load_offs + sizeof(void *);
LOG(GLOBAL, LOG_LOADER, 2,
"%s: for glibc 2.34+ workaround found offsets 0x%x 0x%x for glro %p\n",
__FUNCTION__, GLRO_dl_tls_static_size_OFFS, GLRO_dl_tls_static_align_OFFS,
glro);
}
# endif
if (GLRO_dl_tls_static_size_OFFS == 0) {
// We have some versions hardcoded.
# ifdef ARM
// These are the numbers for glibc 2.35.
GLRO_dl_tls_static_size_OFFS = 368;
GLRO_dl_tls_static_align_OFFS = GLRO_dl_tls_static_size_OFFS + 4;
# else
# ifdef X64
// The offsets changed between 2.38 and 2.39.
if (ver[2] == '3' && ver[3] < '9') {
GLRO_dl_tls_static_size_OFFS = 0x2a8;
GLRO_dl_tls_static_align_OFFS = 0x2b0;
} else {
GLRO_dl_tls_static_size_OFFS = 0x2c8;
GLRO_dl_tls_static_align_OFFS = 0x2d0;
}
# else
if (ver[2] == '3' && ver[3] >= '8') {
GLRO_dl_tls_static_size_OFFS = 0x320;
GLRO_dl_tls_static_align_OFFS = 0x324;
} else {
// The offsets changed between 2.35 and 2.36.
GLRO_dl_tls_static_size_OFFS =
(ver[2] == '3' && ver[3] == '5') ? 0x328 : 0x31c;
GLRO_dl_tls_static_align_OFFS =
(ver[2] == '3' && ver[3] == '5') ? 0x32c : 0x320;
}
# endif
# endif
}
size_t val = 4096, written;
if (!safe_write_ex(glro + GLRO_dl_tls_static_size_OFFS, sizeof(val), &val,
&written) ||
written != sizeof(val) ||
!safe_write_ex(glro + GLRO_dl_tls_static_align_OFFS, sizeof(val), &val,
&written) ||
written != sizeof(val)) {
SYSLOG_INTERNAL_WARNING("glibc 2.34+ i#5437 workaround failed: missed write");
} else {
LOG(GLOBAL, LOG_LOADER, 2, "%s: glibc 2.34+ workaround succeeded\n",
__FUNCTION__);
}
LOG(GLOBAL, LOG_LOADER, 2, "%s: calling %s @%p\n", __FUNCTION__, LIBC_EARLY_INIT_NAME,
libc_early_init);
(*libc_early_init)(true);
#endif /* LINUX */
}
static void
privload_init_search_paths(void)
{
privload_add_drext_path();
ld_library_path = getenv(SYSTEM_LIBRARY_PATH_VAR);
}
static privmod_t *
privload_locate_and_load(const char *impname, privmod_t *dependent, bool reachable)
{
char filename[MAXIMUM_PATH];
if (privload_locate(impname, dependent, filename, &reachable))
return privload_load(filename, dependent, reachable);
return NULL;
}
app_pc
privload_load_private_library(const char *name, bool reachable)
{
privmod_t *newmod;
app_pc res = NULL;
acquire_recursive_lock(&privload_lock);
newmod = privload_lookup(name);
if (newmod == NULL)
newmod = privload_locate_and_load(name, NULL, reachable);
else
newmod->ref_count++;
if (newmod != NULL)
res = newmod->base;
release_recursive_lock(&privload_lock);
return res;
}
void
privload_load_finalized(privmod_t *mod)
{
/* nothing further to do */
}
/* If runpath, then DT_RUNPATH is searched; else, DT_RPATH. */
static bool
privload_search_rpath(privmod_t *mod, bool runpath, const char *name,
char *filename DR_PARAM_OUT /* buffer size is MAXIMUM_PATH */)
{
#ifdef LINUX
os_privmod_data_t *opd;
ELF_DYNAMIC_ENTRY_TYPE *dyn;
ASSERT(mod != NULL && "can't look for rpath without a dependent module");
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
/* get the loading module's dir for RPATH_ORIGIN */
opd = (os_privmod_data_t *)mod->os_privmod_data;
/* i#460: if DT_RUNPATH exists we must ignore ignore DT_RPATH and
* search DT_RUNPATH after LD_LIBRARY_PATH.
*/
if (!runpath && opd->os_data.has_runpath)
return false;
const char *moddir_end = strrchr(mod->path, '/');
size_t moddir_len = (moddir_end == NULL ? strlen(mod->path) : moddir_end - mod->path);
const char *strtab;
ASSERT(opd != NULL);
dyn = (ELF_DYNAMIC_ENTRY_TYPE *)opd->dyn;
strtab = (char *)opd->os_data.dynstr;
bool lib_found = false;
/* support $ORIGIN expansion to lib's current directory */
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag == (runpath ? DT_RUNPATH : DT_RPATH)) {
/* DT_RPATH and DT_RUNPATH are each a colon-separated list of paths */
const char *list = strtab + dyn->d_un.d_val;
const char *sep, *origin;
size_t len;
while (*list != '\0') {
/* really we want strchrnul() */
sep = strchr(list, ':');
if (sep == NULL)
len = strlen(list);
else
len = sep - list;
/* support $ORIGIN expansion to lib's current directory */
origin = strstr(list, RPATH_ORIGIN);
char path[MAXIMUM_PATH];
if (origin != NULL && origin < list + len) {
size_t pre_len = origin - list;
snprintf(path, BUFFER_SIZE_ELEMENTS(path), "%.*s%.*s%.*s", pre_len,
list, moddir_len, mod->path,
/* the '/' should already be here */
len - strlen(RPATH_ORIGIN) - pre_len,
origin + strlen(RPATH_ORIGIN));
NULL_TERMINATE_BUFFER(path);
} else {
snprintf(path, BUFFER_SIZE_ELEMENTS(path), "%.*s", len, list);
NULL_TERMINATE_BUFFER(path);
}
if (mod->is_client) {
/* We are adding a client's lib rpath to the general search path. This
* is not bullet proof compliant with what the loader should really
* do. The real problem is that the loader is walking library
* dependencies depth-first, while it should really search
* breadth-first (xref i#3850). This can lead to libraries being
* unlocatable, if the original client library had the proper rpath of
* the library, but a dependency later in the chain did not. In order
* to avoid this, we consider adding the rpath here relatively safe.
* It only affects dependent libraries of the same name in different
* locations. We are only doing this for client libraries, so we are
* not at risk to search for the wrong system libraries.
*/
if (!privload_search_path_exists(path, strlen(path))) {
snprintf(search_paths[search_paths_idx],
BUFFER_SIZE_ELEMENTS(search_paths[search_paths_idx]),
"%.*s", strlen(path), path);
NULL_TERMINATE_BUFFER(search_paths[search_paths_idx]);
LOG(GLOBAL, LOG_LOADER, 1, "%s: added search dir \"%s\"\n",
__FUNCTION__, search_paths[search_paths_idx]);
search_paths_idx++;
}
}
if (!lib_found) {
snprintf(filename, MAXIMUM_PATH, "%s/%s", path, name);
filename[MAXIMUM_PATH - 1] = 0;
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__,
filename);
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename)) {
lib_found = true;
}
}
list += len;
if (sep != NULL)
list += 1;
}
}
++dyn;
}
return lib_found;
#else
/* XXX i#1285: implement MacOS private loader */
#endif
return false;
}
static bool
privload_locate(const char *name, privmod_t *dep,
char *filename DR_PARAM_OUT /* buffer size is MAXIMUM_PATH */,
bool *reachable DR_PARAM_OUT)
{
uint i;
char *lib_paths;
/* We may be passed a full path. */
if (name[0] == '/' && os_file_exists(name, false /*!is_dir*/)) {
snprintf(filename, MAXIMUM_PATH, "%s", name);
filename[MAXIMUM_PATH - 1] = 0;
return true;
}
/* XXX: We have a simple implementation of library search.
* libc implementation can be found at elf/dl-load.c:_dl_map_object.
*/
/* the loader search order: */
/* 0) DT_RPATH */
if (dep != NULL && privload_search_rpath(dep, false /*rpath*/, name, filename))
return true;
/* 1) client lib dir */
for (i = 0; i < search_paths_idx; i++) {
snprintf(filename, MAXIMUM_PATH, "%s/%s", search_paths[i], name);
/* NULL_TERMINATE_BUFFER(filename) */
filename[MAXIMUM_PATH - 1] = 0;
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, filename);
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename)) {
/* If in client or extension dir, always map it reachable */
*reachable = true;
return true;
}
}
/* 2) curpath */
snprintf(filename, MAXIMUM_PATH, "./%s", name);
/* NULL_TERMINATE_BUFFER(filename) */
filename[MAXIMUM_PATH - 1] = 0;
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, filename);
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename))
return true;
/* 3) LD_LIBRARY_PATH */
lib_paths = ld_library_path;
while (lib_paths != NULL) {
char *end = strstr(lib_paths, ":");
if (end != NULL)
*end = '\0';
snprintf(filename, MAXIMUM_PATH, "%s/%s", lib_paths, name);
if (end != NULL) {
*end = ':';
end++;
}
/* NULL_TERMINATE_BUFFER(filename) */
filename[MAXIMUM_PATH - 1] = 0;
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, filename);
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename))
return true;
lib_paths = end;
}
/* 4) DT_RUNPATH */
if (dep != NULL && privload_search_rpath(dep, true /*runpath*/, name, filename))
return true;
/* 5) XXX i#460: We use our system paths instead of /etc/ld.so.cache. */
for (i = 0; i < NUM_SYSTEM_LIB_PATHS; i++) {
/* First try -xarch_root for emulation. */
if (!IS_STRING_OPTION_EMPTY(xarch_root)) {
string_option_read_lock();
snprintf(filename, MAXIMUM_PATH, "%s/%s/%s", DYNAMO_OPTION(xarch_root),
system_lib_paths[i], name);
filename[MAXIMUM_PATH - 1] = '\0';
string_option_read_unlock();
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename))
return true;
}
snprintf(filename, MAXIMUM_PATH, "%s/%s", system_lib_paths[i], name);
/* NULL_TERMINATE_BUFFER(filename) */
filename[MAXIMUM_PATH - 1] = 0;
LOG(GLOBAL, LOG_LOADER, 2, "%s: looking for %s\n", __FUNCTION__, filename);
if (os_file_exists(filename, false /*!is_dir*/) &&
module_file_has_module_header(filename))
return true;
}
/* Cannot find the library */
/* There's a syslog in loader_init() but we want to provide the lib name */
SYSLOG(SYSLOG_ERROR, CLIENT_LIBRARY_UNLOADABLE, 4, get_application_name(),
get_application_pid(), name,
"\n\tUnable to locate library! Try adding path to LD_LIBRARY_PATH");
return false;
}
#pragma weak dlsym
app_pc
get_private_library_address(app_pc modbase, const char *name)
{
#ifdef LINUX
privmod_t *mod;
app_pc res;
acquire_recursive_lock(&privload_lock);
mod = privload_lookup_by_base(modbase);
if (mod == NULL || mod->externally_loaded) {
release_recursive_lock(&privload_lock);
# ifdef STATIC_LIBRARY
/* externally loaded, use dlsym instead */
ASSERT(!DYNAMO_OPTION(early_inject));
return dlsym(modbase, name);
# else
/* Only libdynamorio.so is externally_loaded and we should not be querying
* for it. Unknown libs shouldn't be queried here: get_proc_address should
* be used instead.
*/
ASSERT_NOT_REACHED();
return NULL;
# endif
}
/* Before the heap is initialized, we store the text address in opd, so we
* can't check if opd != NULL to know whether it's valid.
*/
if (dynamo_heap_initialized) {
/* opd is initialized */
os_privmod_data_t *opd = (os_privmod_data_t *)mod->os_privmod_data;
res = get_proc_address_from_os_data(&opd->os_data, opd->load_delta, name, NULL);
release_recursive_lock(&privload_lock);
return res;
} else {
/* opd is not initialized */
/* get_private_library_address is first called on looking up
* USES_DR_VERSION_NAME right after loading client_lib,
* The os_privmod_data is not setup yet then because the heap
* is not initialized, so it is possible opd to be NULL.
* For this case, we have to compute the temporary os_data instead.
*/
ptr_int_t delta;
char *soname;
os_module_data_t os_data;
memset(&os_data, 0, sizeof(os_data));
if (!module_read_os_data(mod->base, false /* .dynamic not relocated (i#1589) */,
&delta, &os_data, &soname)) {
release_recursive_lock(&privload_lock);
return NULL;
}
res = get_proc_address_from_os_data(&os_data, delta, name, NULL);
release_recursive_lock(&privload_lock);
return res;
}
ASSERT_NOT_REACHED();
#else
/* XXX i#1285: implement MacOS private loader */
#endif
return NULL;
}
static void
privload_call_lib_func(dcontext_t *dcontext, privmod_t *privmod, fp_t func)
{
char dummy_str[] = "dummy";
char *dummy_argv[2];
/* XXX: i#475
* The regular loader always passes argc, argv and env to libaries,
* (see libc code elf/dl-init.c), which might be ignored by those
* routines.
* we create dummy argc and argv, and passed with the real __environ.
*/
dummy_argv[0] = dummy_str;
dummy_argv[1] = NULL;
TRY_EXCEPT_ALLOW_NO_DCONTEXT(
dcontext, { func(1, dummy_argv, our_environ); },
{ /* EXCEPT */
SYSLOG_INTERNAL_ERROR("Private library %s init/fini func " PFX " crashed",
privmod->name, func);
});
}
bool
get_private_library_bounds(DR_PARAM_IN app_pc modbase, DR_PARAM_OUT byte **start,
DR_PARAM_OUT byte **end)
{
privmod_t *mod;
bool found = false;
ASSERT(start != NULL && end != NULL);
acquire_recursive_lock(&privload_lock);
mod = privload_lookup_by_base(modbase);
if (mod != NULL) {
*start = mod->base;
*end = mod->base + mod->size;
found = true;
}
release_recursive_lock(&privload_lock);
return found;
}
#ifdef LINUX
# if !defined(STANDALONE_UNIT_TEST) && !defined(STATIC_LIBRARY)
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* If we fail to relocate dynamorio, print the error msg and abort. */
static void
privload_report_relocate_error()
{
/* The problem is that we can't call any normal routines here, or
* even reference global vars like string literals. We thus use
* a char array:
*/
const char aslr_msg[] = { 'E', 'R', 'R', 'O', 'R', ':', ' ', 'f', 'a', 'i', 'l', 'e',
'd', ' ', 't', 'o', ' ', 'r', 'e', 'l', 'o', 'c', 'a', 't',
'e', ' ', 'D', 'y', 'n', 'a', 'm', 'o', 'R', 'I', 'O', '!',
'\n', 'P', 'l', 'e', 'a', 's', 'e', ' ', 'f', 'i', 'l', 'e',
' ', 'a', 'n', ' ', 'i', 's', 's', 'u', 'e', ' ', 'a', 't',
' ', 'h', 't', 't', 'p', ':', '/', '/', 'd', 'y', 'n', 'a',
'm', 'o', 'r', 'i', 'o', '.', 'o', 'r', 'g', '/', 'i', 's',
's', 'u', 'e', 's', '.', '\n' };
# define STDERR_FD 2
os_write(STDERR_FD, aslr_msg, sizeof(aslr_msg));
dynamorio_syscall(SYS_exit_group, 1, -1);
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from module_relocate_symbol and simplified
* for only relocating dynamorio symbols.
*/
static void
privload_relocate_symbol(ELF_REL_TYPE *rel, os_privmod_data_t *opd, bool is_rela)
{
ELF_ADDR *r_addr;
uint r_type;
reg_t addend;
/* ELF_REL_TYPE and ELF_RELA_TYPE differ in where the addend comes from:
* stored in the target location, or in rel->r_addend.
*/
if (is_rela)
addend = ((ELF_RELA_TYPE *)rel)->r_addend;
else
addend = 0;
/* assume everything is read/writable */
r_addr = (ELF_ADDR *)(rel->r_offset + opd->load_delta);
r_type = (uint)ELF_R_TYPE(rel->r_info);
/* handle the most common case, i.e. ELF_R_RELATIVE */
if (r_type == ELF_R_RELATIVE) {
if (is_rela)
*r_addr = addend + opd->load_delta;
else
*r_addr += opd->load_delta;
return;
} else if (r_type == ELF_R_NONE)
return;
/* XXX i#1708: support more relocation types in bootstrap stage */
privload_report_relocate_error();
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from module_relocate_rel for relocating dynamorio. */
static void
privload_relocate_rel(os_privmod_data_t *opd, ELF_REL_TYPE *start, ELF_REL_TYPE *end)
{
ELF_REL_TYPE *rel;
for (rel = start; rel < end; rel++)
privload_relocate_symbol(rel, opd, false);
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from module_relocate_rela for relocating dynamorio. */
static void
privload_relocate_rela(os_privmod_data_t *opd, ELF_RELA_TYPE *start, ELF_RELA_TYPE *end)
{
ELF_RELA_TYPE *rela;
for (rela = start; rela < end; rela++)
privload_relocate_symbol((ELF_REL_TYPE *)rela, opd, true);
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from module_relocate_relr for relocating dynamorio. */
static void
privload_relocate_relr(os_privmod_data_t *opd, const ELF_WORD *relr, size_t size)
{
ELF_ADDR *r_addr = NULL;
for (; size != 0; relr++, size -= sizeof(ELF_WORD)) {
if (!TEST(1, relr[0])) {
r_addr = (ELF_ADDR *)(relr[0] + opd->load_delta);
*r_addr++ += opd->load_delta;
} else {
int i = 0;
for (ELF_WORD bitmap = relr[0]; (bitmap >>= 1) != 0; i++) {
if (TEST(1, bitmap))
r_addr[i] += opd->load_delta;
}
r_addr += CHAR_BIT * sizeof(ELF_WORD) - 1;
}
}
}
/* XXX: This routine may be called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from privload_relocate_os_privmod_data */
static void
privload_early_relocate_os_privmod_data(os_privmod_data_t *opd, byte *mod_base)
{
if (opd->rel != NULL)
privload_relocate_rel(opd, opd->rel, opd->rel + opd->relsz / opd->relent);
if (opd->rela != NULL)
privload_relocate_rela(opd, opd->rela, opd->rela + opd->relasz / opd->relaent);
if (opd->relr != NULL)
privload_relocate_relr(opd, opd->relr, opd->relrsz);
if (opd->jmprel != NULL) {
if (opd->pltrel == DT_REL) {
privload_relocate_rel(opd, (ELF_REL_TYPE *)opd->jmprel,
(ELF_REL_TYPE *)(opd->jmprel + opd->pltrelsz));
} else if (opd->pltrel == DT_RELA) {
privload_relocate_rela(opd, (ELF_RELA_TYPE *)opd->jmprel,
(ELF_RELA_TYPE *)(opd->jmprel + opd->pltrelsz));
} else {
privload_report_relocate_error();
}
}
}
# endif /* !defined(STANDALONE_UNIT_TEST) && !defined(STATIC_LIBRARY) */
/* This routine is duplicated at privload_early_relocate_os_privmod_data. */
static void
privload_relocate_os_privmod_data(os_privmod_data_t *opd, byte *mod_base)
{
if (opd->rel != NULL) {
module_relocate_rel(mod_base, opd, opd->rel, opd->rel + opd->relsz / opd->relent);
}
if (opd->rela != NULL) {
module_relocate_rela(mod_base, opd, opd->rela,
opd->rela + opd->relasz / opd->relaent);
}
if (opd->relr != NULL)
module_relocate_relr(mod_base, opd, opd->relr, opd->relrsz);
if (opd->jmprel != NULL) {
app_pc jmprel_start = opd->jmprel;
app_pc jmprel_end = opd->jmprel + opd->pltrelsz;
/* i#5080: Some libs list JMPREL as overlapping with REL{,A} and it's implied
* that really JMPREL comes after.
*/
if (opd->rel != NULL && jmprel_start >= (app_pc)opd->rel &&
jmprel_start < (app_pc)(opd->rel + opd->relsz / opd->relent))
jmprel_start = (app_pc)(opd->rel + opd->relsz / opd->relent);
if (opd->rela != NULL && jmprel_start >= (app_pc)opd->rela &&
jmprel_start < (app_pc)(opd->rela + opd->relasz / opd->relaent))
jmprel_start = (app_pc)(opd->rela + opd->relasz / opd->relaent);
if (opd->pltrel == DT_REL) {
module_relocate_rel(mod_base, opd, (ELF_REL_TYPE *)jmprel_start,
(ELF_REL_TYPE *)jmprel_end);
} else if (opd->pltrel == DT_RELA) {
module_relocate_rela(mod_base, opd, (ELF_RELA_TYPE *)jmprel_start,
(ELF_RELA_TYPE *)jmprel_end);
} else {
ASSERT(false);
}
}
}
#endif /* LINUX */
static void
privload_relocate_mod(privmod_t *mod)
{
os_privmod_data_t *opd = (os_privmod_data_t *)mod->os_privmod_data;
#ifdef LINUX
ASSERT_OWN_RECURSIVE_LOCK(true, &privload_lock);
LOG(GLOBAL, LOG_LOADER, 3, "relocating %s\n", mod->name);
/* If module has tls block need update its tls offset value.
* This must be done *before* relocating as relocating needs the os_privmod_data_t
* TLS fields set here.
*/
if (opd->tls_block_size != 0)
privload_mod_tls_init(mod);
privload_relocate_os_privmod_data(opd, mod->base);
/* For the primary thread, we now perform TLS block copying, after relocating.
* For subsequent threads this is done in privload_tls_init().
*/
if (opd->tls_block_size != 0)
privload_mod_tls_primary_thread_init(mod);
#else
if (opd->tls_block_size != 0)
privload_mod_tls_init(mod);
#endif
}
static void
privload_create_os_privmod_data(privmod_t *privmod, bool dyn_reloc)
{
os_privmod_data_t *opd;
opd = HEAP_TYPE_ALLOC(GLOBAL_DCONTEXT, os_privmod_data_t, ACCT_OTHER, PROTECTED);
privmod->os_privmod_data = opd;
memset(opd, 0, sizeof(*opd));
/* walk the module's program header to get privmod information */
module_walk_program_headers(privmod->base, privmod->size,
false, /* segments are remapped */
dyn_reloc, &opd->os_data.base_address, NULL,
&opd->max_end, &opd->soname, &opd->os_data);
module_get_os_privmod_data(privmod->base, privmod->size, false /*!relocated*/, opd);
/* We want libunwind to walk app libraries.
* XXX: Is there a cleaner way to do this for some libraries but not others?
*/
if (strstr(privmod->name, "libunwind") == privmod->name) {
LOG(GLOBAL, LOG_LOADER, 2, "Using app imports for %s\n", privmod->name);
opd->use_app_imports = true;
}
}
static void
privload_delete_os_privmod_data(privmod_t *privmod)
{
HEAP_TYPE_FREE(GLOBAL_DCONTEXT, privmod->os_privmod_data, os_privmod_data_t,
ACCT_OTHER, PROTECTED);
privmod->os_privmod_data = NULL;
}
/* i#1589: the client lib is already on the priv lib list, so we share its
* data with loaded_module_areas (which also avoids problems with .dynamic
* not being relocated for priv libs).
*/
bool
privload_fill_os_module_info(app_pc base, DR_PARAM_OUT app_pc *out_base /* relative pc */,
DR_PARAM_OUT app_pc *out_max_end /* relative pc */,
DR_PARAM_OUT char **out_soname,
DR_PARAM_OUT os_module_data_t *out_data)
{
bool res = false;
privmod_t *privmod;
acquire_recursive_lock(&privload_lock);
privmod = privload_lookup_by_base(base);
if (privmod != NULL) {
os_privmod_data_t *opd = (os_privmod_data_t *)privmod->os_privmod_data;
if (out_base != NULL)
*out_base = opd->os_data.base_address;
if (out_max_end != NULL)
*out_max_end = opd->max_end;
if (out_soname != NULL)
*out_soname = opd->soname;
if (out_data != NULL)
module_copy_os_data(out_data, &opd->os_data);
res = true;
}
release_recursive_lock(&privload_lock);
return res;
}
/****************************************************************************
* Function Redirection
*/
static void *
redirect_dlsym(void *handle, const char *symbol);
#if defined(LINUX) && !defined(ANDROID)
/* These are not yet supported by Android's Bionic */
void *
redirect___tls_get_addr();
void *
redirect____tls_get_addr();
#endif
#ifdef LINUX
static int
redirect_dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size,
void *data),
void *data)
{
int res = 0;
struct dl_phdr_info info;
privmod_t *mod;
acquire_recursive_lock(&privload_lock);
for (mod = privload_first_module(); mod != NULL; mod = privload_next_module(mod)) {
ELF_HEADER_TYPE *elf_hdr = (ELF_HEADER_TYPE *)mod->base;
os_privmod_data_t *opd = (os_privmod_data_t *)mod->os_privmod_data;
/* We do want to include externally loaded (if any) and clients as
* clients can contain C++ exception code, which will call here.
*/
if (mod->base == get_dynamorio_dll_start())
continue;
info.dlpi_addr = opd->load_delta;
info.dlpi_name = mod->path;
info.dlpi_phdr = (ELF_PROGRAM_HEADER_TYPE *)(mod->base + elf_hdr->e_phoff);
info.dlpi_phnum = elf_hdr->e_phnum;
res = callback(&info, sizeof(info), data);
if (res != 0)
break;
}
release_recursive_lock(&privload_lock);
return res;
}
/* For some cases we want the client library to walk the app libs: e.g., for
* callstack walking (i#2414).
*/
static int
redirect_dl_iterate_phdr_app(int (*callback)(struct dl_phdr_info *info, size_t size,
void *data),
void *data)
{
int res = 0;
struct dl_phdr_info info;
module_iterator_t *iter = module_iterator_start();
while (module_iterator_hasnext(iter)) {
module_area_t *area = module_iterator_next(iter);
ASSERT(area != NULL);
ELF_HEADER_TYPE *elf_hdr = (ELF_HEADER_TYPE *)area->start;
/* We do want to include externally loaded (if any) and clients as
* clients can contain C++ exception code, which will call here.
*/
if (area->start == get_dynamorio_dll_start())
continue;
app_pc preferred_base =
IF_WINDOWS_ELSE(area->os_data.preferred_base, area->os_data.base_address);
info.dlpi_addr = area->start - preferred_base;
info.dlpi_name = area->full_path;
info.dlpi_phdr = (ELF_PROGRAM_HEADER_TYPE *)(area->start + elf_hdr->e_phoff);
info.dlpi_phnum = elf_hdr->e_phnum;
/* XXX: Fill in new fields dlpi_{adds,subs,tls_modid,tls_data}.
* For now we set the size to exclude them.
*/
size_t size = offsetof(struct dl_phdr_info, dlpi_phnum) + sizeof(info.dlpi_phnum);
res = callback(&info, size, data);
if (res != 0)
break;
}
module_iterator_stop(iter);
// XXX: Pass the private ones too for callstacks or other purposes? Sometimes
// private code is used to replace app code, though we do already have the client
// lib itself on the app list.
return res;
}
# if defined(ARM) && !defined(ANDROID)
typedef struct _unwind_callback_data_t {
void *pc;
void *base;
int size;
} unwind_callback_data_t;
/* Find the exception unwind table (exidx) of the image that contains the
* exception pc.
*/
int
exidx_lookup_callback(struct dl_phdr_info *info, size_t size, void *data)
{
int i;
int res = 0;
unwind_callback_data_t *ucd;
if (data == NULL || size != sizeof(*info))
return res;
ucd = (unwind_callback_data_t *)data;
for (i = 0; i < info->dlpi_phnum; i++) {
/* look for the table */
if (info->dlpi_phdr[i].p_type == PT_ARM_EXIDX) {
/* the location and size of the table for the image */
ucd->base = (void *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
ucd->size = info->dlpi_phdr[i].p_memsz;
}
/* look for the segment */
if (res == 0 && info->dlpi_phdr[i].p_type == PT_LOAD) {
if (ucd->pc >= (void *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr) &&
ucd->pc < (void *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr +
info->dlpi_phdr[i].p_memsz)) {
res = 1;
}
}
}
return res;
}
/* find the exception unwind table that contains the PC during an exception */
void *
redirect___gnu_Unwind_Find_exidx(void *pc, int *count)
{
unwind_callback_data_t ucd;
memset(&ucd, 0, sizeof(ucd));
ucd.pc = pc;
if (redirect_dl_iterate_phdr(exidx_lookup_callback, &ucd) <= 0)
return NULL;
if (count != NULL)
*count = ucd.size / 8 /* exidx table entry size */;
return ucd.base;
}
# endif /* ARM && !ANDROID */
#endif /* LINUX */
typedef struct _redirect_import_t {
const char *name;
app_pc func;
app_pc app_func; /* Used only for dl_iterate_phdr over app libs, so far. */
} redirect_import_t;
static const redirect_import_t redirect_imports[] = {
{ "calloc", (app_pc)redirect_calloc },
{ "malloc", (app_pc)redirect_malloc },
{ "free", (app_pc)redirect_free },
{ "realloc", (app_pc)redirect_realloc },
{ "strdup", (app_pc)redirect_strdup },
/* TODO i#4243: we should also redirect functions including:
* + malloc_usable_size, memalign, valloc, mallinfo, mallopt, etc.
* + tcmalloc: tc_malloc, tc_free, etc.
* + __libc_malloc, __libc_free, etc.
* + OSX: malloc_zone_malloc, etc.? Or just malloc_create_zone?
* + C++ operators in case they don't just call libc malloc?
*/
/* We redirect these for fd isolation. */
{ "open", (app_pc)redirect_open },
{ "close", (app_pc)redirect_close },
/* These libc routines can call pthread functions and cause hangs (i#4928) so
* we use our syscall wrappers instead.
*/
{ "read", (app_pc)os_read },
{ "write", (app_pc)os_write },
#if defined(LINUX) && !defined(ANDROID)
{ "__tls_get_addr", (app_pc)redirect___tls_get_addr },
{ "___tls_get_addr", (app_pc)redirect____tls_get_addr },
#endif
#ifdef LINUX
/* i#1717: C++ exceptions call this */
{ "dl_iterate_phdr", (app_pc)redirect_dl_iterate_phdr,
(app_pc)redirect_dl_iterate_phdr_app },
# if defined(ARM) && !defined(ANDROID)
/* i#1717: C++ exceptions call this on ARM Linux */
{ "__gnu_Unwind_Find_exidx", (app_pc)redirect___gnu_Unwind_Find_exidx },
# endif
#endif
{ "dlsym", (app_pc)redirect_dlsym },
/* We need these for clients that don't use libc (i#1747) */
{ "strlen", (app_pc)strlen },
{ "wcslen", (app_pc)wcslen },
{ "strchr", (app_pc)strchr },
{ "strrchr", (app_pc)strrchr },
{ "strncpy", (app_pc)strncpy },
{ "memcpy", (app_pc)memcpy },
{ "memset", (app_pc)memset },
{ "memmove", (app_pc)memmove },
{ "strncat", (app_pc)strncat },
{ "strcmp", (app_pc)strcmp },
{ "strncmp", (app_pc)strncmp },
{ "memcmp", (app_pc)memcmp },
{ "strstr", (app_pc)strstr },
{ "strcasecmp", (app_pc)strcasecmp },
/* Also redirect the _chk versions (i#1747, i#46) */
{ "memcpy_chk", (app_pc)memcpy },
{ "memset_chk", (app_pc)memset },
{ "memmove_chk", (app_pc)memmove },
{ "strncpy_chk", (app_pc)strncpy },
{ "__memcpy_chk", (app_pc)memcpy },
{ "__memset_chk", (app_pc)memset },
{ "__memmove_chk", (app_pc)memmove },
{ "__strncpy_chk", (app_pc)strncpy },
};
#define REDIRECT_IMPORTS_NUM (sizeof(redirect_imports) / sizeof(redirect_imports[0]))
#ifdef DEBUG
static const redirect_import_t redirect_debug_imports[] = {
{ "calloc", (app_pc)redirect_calloc_initonly },
{ "malloc", (app_pc)redirect_malloc_initonly },
{ "free", (app_pc)redirect_free_initonly },
{ "realloc", (app_pc)redirect_realloc_initonly },
{ "strdup", (app_pc)redirect_strdup_initonly },
};
# define REDIRECT_DEBUG_IMPORTS_NUM \
(sizeof(redirect_debug_imports) / sizeof(redirect_debug_imports[0]))
#endif
bool
privload_redirect_sym(os_privmod_data_t *opd, ptr_uint_t *r_addr, const char *name)
{
int i;
/* iterate over all symbols and redirect syms when necessary, e.g. malloc */
#ifdef DEBUG
if (disallow_unsafe_static_calls) {
for (i = 0; i < REDIRECT_DEBUG_IMPORTS_NUM; i++) {
if (strcmp(redirect_debug_imports[i].name, name) == 0) {
*r_addr = (ptr_uint_t)redirect_debug_imports[i].func;
return true;
}
}
}
#endif
for (i = 0; i < REDIRECT_IMPORTS_NUM; i++) {
if (strcmp(redirect_imports[i].name, name) == 0) {
if (opd->use_app_imports && redirect_imports[i].app_func != NULL)
*r_addr = (ptr_uint_t)redirect_imports[i].app_func;
else
*r_addr = (ptr_uint_t)redirect_imports[i].func;
return true;
}
}
return false;
}
static void *
redirect_dlsym(void *handle, const char *symbol)
{
for (int i = 0; i < REDIRECT_IMPORTS_NUM; i++) {
if (strcmp(redirect_imports[i].name, symbol) == 0)
return redirect_imports[i].func;
}
/* TODO: Look in other libs via module_lookup_symbol() from module_elf.c. */
SYSLOG_INTERNAL_WARNING("dlsym(%s) called by private lib; returning NULL", symbol);
return NULL;
}
/***************************************************************************
* DynamoRIO Early Injection Code
*/
#ifdef LINUX
# if !defined(STANDALONE_UNIT_TEST) && !defined(STATIC_LIBRARY)
/* Find the auxiliary vector and adjust it to look as if the kernel had set up
* the stack for the ELF mapped at map. The auxiliary vector starts after the
* terminating NULL pointer in the envp array.
*/
static void
privload_setup_auxv(char **envp, app_pc map, ptr_int_t delta, app_pc interp_map,
const char *exe_path /*must be persistent*/)
{
ELF_AUXV_TYPE *auxv;
ELF_HEADER_TYPE *elf = (ELF_HEADER_TYPE *)map;
/* The aux vector is after the last environment pointer. */
while (*envp != NULL)
envp++;
auxv = (ELF_AUXV_TYPE *)(envp + 1);
/* fix up the auxv entries that refer to the executable */
for (; auxv->a_type != AT_NULL; auxv++) {
/* the actual addr should be: (base + offs) or (v_addr + delta) */
switch (auxv->a_type) {
case AT_ENTRY:
auxv->a_un.a_val = (ptr_int_t)elf->e_entry + delta;
LOG(GLOBAL, LOG_LOADER, 2, "AT_ENTRY: " PFX "\n", auxv->a_un.a_val);
break;
case AT_PHDR:
auxv->a_un.a_val = (ptr_int_t)map + elf->e_phoff;
LOG(GLOBAL, LOG_LOADER, 2, "AT_PHDR: " PFX "\n", auxv->a_un.a_val);
break;
case AT_PHENT: auxv->a_un.a_val = (ptr_int_t)elf->e_phentsize; break;
case AT_PHNUM: auxv->a_un.a_val = (ptr_int_t)elf->e_phnum; break;
case AT_BASE: /* Android loader reads this */
auxv->a_un.a_val = (ptr_int_t)interp_map;
LOG(GLOBAL, LOG_LOADER, 2, "AT_BASE: " PFX "\n", auxv->a_un.a_val);
break;
case AT_EXECFN: /* Android loader references this, unclear what for */
auxv->a_un.a_val = (ptr_int_t)exe_path;
LOG(GLOBAL, LOG_LOADER, 2, "AT_EXECFN: " PFX " %s\n", auxv->a_un.a_val,
(char *)auxv->a_un.a_val);
break;
/* The rest of these AT_* values don't seem to be important to the
* loader, but we log them.
*/
case AT_EXECFD:
LOG(GLOBAL, LOG_LOADER, 2, "AT_EXECFD: %d\n", auxv->a_un.a_val);
break;
}
}
}
/* Entry point for ptrace injection. */
static void
takeover_ptrace(ptrace_stack_args_t *args)
{
static char home_var[MAXIMUM_PATH + 6 /*HOME=path\0*/];
static char *fake_envp[] = { home_var, NULL };
/* When we come in via ptrace, we have no idea where the environment
* pointer is. We could use /proc/self/environ to read it or go searching
* near the stack base. However, both are fragile and we don't really need
* the environment for anything except for option passing. In the initial
* ptraced process, we can assume our options are in a config file and not
* the environment, so we just set an environment with HOME.
*/
snprintf(home_var, BUFFER_SIZE_ELEMENTS(home_var), "HOME=%s", args->home_dir);
NULL_TERMINATE_BUFFER(home_var);
dynamorio_set_envp(fake_envp);
dynamo_control_via_attach = true;
dynamorio_app_init();
/* We need to wait until dr_inject_process_run() is called to finish
* takeover, and this is an easy way to stop and return control to the
* injector.
*/
dynamorio_syscall(SYS_kill, 2, get_process_id(), SIGTRAP);
dynamo_start(&args->mc);
}
static void
reserve_brk(app_pc post_app)
{
/* We haven't parsed the options yet, so we rely on drinjectlib
* setting this env var if the user passed -no_emulate_brk:
*/
if (getenv(DYNAMORIO_VAR_NO_EMULATE_BRK) == NULL) {
/* i#1004: we're going to emulate the brk via our own mmap.
* Reserve the initial brk now before any of DR's mmaps to avoid overlap.
*/
dynamo_options.emulate_brk = true; /* not parsed yet */
init_emulated_brk(post_app);
} else {
/* i#1004: as a workaround, reserve some space for sbrk() during early injection
* before initializing DR's heap. With early injection, the program break comes
* somewhere after DR's bss section, subject to some ASLR. When we allocate our
* heap, sometimes we mmap right over the break, so any brk() calls will fail.
* When brk() fails, most malloc() implementations fall back to mmap().
* However, sometimes libc startup code needs to allocate memory before libc is
* initialized. In this case it calls brk(), and will crash if it fails.
*
* Ideally we'd just set the break to follow the app's exe, but the kernel
* forbids setting the break to a value less than the current break. I also
* tried to reserve memory by increasing the break by ~20 pages and then
* resetting it, but the kernel unreserves it. The current work around is to
* increase the break by 1. The loader needs to allocate more than a page of
* memory, so this doesn't guarantee that further brk() calls will succeed.
* However, I haven't observed any brk() failures after adding this workaround.
*/
ptr_int_t start_brk;
ASSERT(!dynamo_heap_initialized);
start_brk = dynamorio_syscall(SYS_brk, 1, 0);
dynamorio_syscall(SYS_brk, 1, start_brk + 1);
/* I'd log the results, but logs aren't initialized yet. */
}
}
byte *
map_exe_file_and_brk(file_t f, size_t *size DR_PARAM_INOUT, uint64 offs, app_pc addr,
uint prot, map_flags_t map_flags)
{
/* A little hacky: we assume the MEMPROT_NONE is the overall mmap for the whole
* region, where our goal is to push it back for top-down PIE filling to leave
* room for a reasonable brk.
*/
if (prot == MEMPROT_NONE && offs == 0) {
size_t sz_with_brk = *size + APP_BRK_GAP;
byte *res = os_map_file(f, &sz_with_brk, offs, addr, prot, map_flags);
if (res != NULL)
os_unmap_file(res + sz_with_brk - APP_BRK_GAP, APP_BRK_GAP);
*size = sz_with_brk - APP_BRK_GAP;
return res;
} else
return os_map_file(f, size, offs, addr, prot, map_flags);
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is partially duplicated from module_get_os_privmod_data.
* It paritially fills the os_privmod_data for dynamorio relocation.
* Return true if relocation is required.
*/
static bool
privload_get_os_privmod_data(app_pc base, DR_PARAM_OUT os_privmod_data_t *opd)
{
app_pc mod_base, mod_end;
ELF_HEADER_TYPE *elf_hdr = (ELF_HEADER_TYPE *)base;
ELF_PROGRAM_HEADER_TYPE *prog_hdr;
uint i;
/* walk program headers to get mod_base mod_end and delta */
mod_base = module_vaddr_from_prog_header(base + elf_hdr->e_phoff, elf_hdr->e_phnum,
NULL, &mod_end);
/* delta from preferred address, used for calcuate real address */
opd->load_delta = base - mod_base;
/* At this point one could consider returning false if the load_delta
* is zero. However, this optimisation was found to give only a small
* benefit, and is not safe if RELA relocations are in use. In particular,
* it did not work on AArch64 when libdynamorio.so was built with the BFD
* linker from Debian's binutils 2.26-8.
*/
/* walk program headers to get dynamic section pointer */
prog_hdr = (ELF_PROGRAM_HEADER_TYPE *)(base + elf_hdr->e_phoff);
for (i = 0; i < elf_hdr->e_phnum; i++) {
if (prog_hdr->p_type == PT_DYNAMIC) {
opd->dyn = (ELF_DYNAMIC_ENTRY_TYPE *)(prog_hdr->p_vaddr + opd->load_delta);
opd->dynsz = prog_hdr->p_memsz;
# ifdef DEBUG
} else if (prog_hdr->p_type == PT_TLS && prog_hdr->p_memsz > 0) {
/* XXX: we assume libdynamorio has no tls block b/c we're not calling
* privload_relocate_mod().
*/
privload_report_relocate_error();
# endif /* DEBUG */
}
++prog_hdr;
}
if (opd->dyn == NULL)
return false;
module_init_os_privmod_data_from_dyn(opd, opd->dyn, opd->load_delta);
return true;
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
/* This routine is duplicated from is_elf_so_header_common. */
static bool
privload_mem_is_elf_so_header(byte *mem)
{
/* assume we can directly read from mem */
ELF_HEADER_TYPE *elf_hdr = (ELF_HEADER_TYPE *)mem;
/* ELF magic number */
if (elf_hdr->e_ident[EI_MAG0] != ELFMAG0 || elf_hdr->e_ident[EI_MAG1] != ELFMAG1 ||
elf_hdr->e_ident[EI_MAG2] != ELFMAG2 || elf_hdr->e_ident[EI_MAG3] != ELFMAG3)
return false;
/* libdynamorio should be ET_DYN */
if (elf_hdr->e_type != ET_DYN)
return false;
/* ARM or X86 */
/* i#1684: We do allow mixing arches of the same bitwidth. See the i#1684
* comment in is_elf_so_header_common().
*/
if (
# ifdef X64
elf_hdr->e_machine != EM_X86_64 && elf_hdr->e_machine != EM_AARCH64 &&
elf_hdr->e_machine != EM_RISCV
# else
elf_hdr->e_machine != EM_386 && elf_hdr->e_machine != EM_ARM
# endif
)
return false;
if (elf_hdr->e_ehsize != sizeof(ELF_HEADER_TYPE))
return false;
return true;
}
/* Returns false if the text-data gap is not empty. Else, fills the gap with
* no-access mappings and returns true.
*/
static bool
dynamorio_lib_gap_empty(void)
{
/* XXX: get_dynamorio_dll_start() is already calling
* memquery_library_bounds_by_iterator() which is doing this maps walk: can we
* avoid this extra walk by somehow passing info back to us? Have an
* "interrupted" output param or sthg and is_dynamorio_dll_interrupted()?
*/
memquery_iter_t iter;
bool res = true;
if (memquery_iterator_start(&iter, NULL, false /*no heap*/)) {
byte *dr_start = get_dynamorio_dll_start();
byte *dr_end = get_dynamorio_dll_end();
byte *gap_start = dr_start;
const char *dynamorio_library_path = get_dynamorio_library_path();
while (memquery_iterator_next(&iter) && iter.vm_start < dr_end) {
if (iter.vm_start >= dr_start && iter.vm_end <= dr_end &&
iter.comment[0] != '\0' &&
/* i#3799: ignore the kernel labeling DR's .bss as "[heap]". */
strcmp(iter.comment, "[heap]") != 0 &&
strcmp(iter.comment, dynamorio_library_path) != 0) {
/* There's a non-anon mapping inside: probably vvar and/or vdso. */
res = false;
break;
}
/* i#1659: fill in the text-data segment gap to ensure no mmaps in between.
* The kernel does not do this. Our private loader does, so if we reloaded
* ourselves this is already in place. We do this now rather than in
* os_loader_init_prologue() to prevent our brk mmap from landing here.
*/
if (iter.vm_start > gap_start) {
size_t sz = iter.vm_start - gap_start;
ASSERT(sz > 0);
DEBUG_DECLARE(byte *fill =)
os_map_file(-1, &sz, 0, gap_start, MEMPROT_NONE,
MAP_FILE_COPY_ON_WRITE | MAP_FILE_FIXED);
ASSERT(fill != NULL);
gap_start = iter.vm_end;
} else if (iter.vm_end > gap_start) {
gap_start = iter.vm_end;
}
}
memquery_iterator_stop(&iter);
}
return res;
}
/* XXX: This routine is called before dynamorio relocation when we are in a
* fragile state and thus no globals access or use of ASSERT/LOG/STATS!
*/
void
relocate_dynamorio(byte *dr_map, size_t dr_size, byte *sp)
{
ptr_uint_t argc = *(ptr_uint_t *)sp;
/* Plus 2 to skip argc and null pointer that terminates argv[]. */
const char **env = (const char **)sp + argc + 2;
os_privmod_data_t opd = { { 0 } };
/* We can't use PAGE_SIZE as that may require relocations to access. */
const int min_page_size = 4096;
if (dr_map == NULL) {
/* We can't start with the address of relocate_dynamorio or something as that
* may require relocations to access!
*/
GET_CUR_PC(dr_map);
/* we do not know where dynamorio is, so check backward page by page */
dr_map = (app_pc)ALIGN_BACKWARD(dr_map, min_page_size);
while (dr_map != NULL && !privload_mem_is_elf_so_header(dr_map)) {
dr_map -= min_page_size;
}
}
if (dr_map == NULL)
privload_report_relocate_error();
/* Relocate it */
if (privload_get_os_privmod_data(dr_map, &opd))
privload_early_relocate_os_privmod_data(&opd, dr_map);
os_page_size_init(env, true);
}
/* i#1227: on a conflict with the app we reload ourselves.
* Does not return.
*/
static void
reload_dynamorio(void **init_sp, app_pc conflict_start, app_pc conflict_end)
{
elf_loader_t dr_ld;
os_privmod_data_t opd;
byte *dr_map;
/* We expect at most vvar+vdso+stack+vsyscall => 5 different mappings
* even if they were all in the conflict area.
*/
# define MAX_TEMP_MAPS 16
byte *temp_map[MAX_TEMP_MAPS];
size_t temp_size[MAX_TEMP_MAPS];
uint num_temp_maps = 0, i;
memquery_iter_t iter;
app_pc entry;
byte *cur_dr_map = get_dynamorio_dll_start();
byte *cur_dr_end = get_dynamorio_dll_end();
size_t dr_size = cur_dr_end - cur_dr_map;
IF_DEBUG(bool success =)
elf_loader_read_headers(&dr_ld, get_dynamorio_library_path());
ASSERT(success);
/* XXX: have better strategy for picking base: currently we rely on
* the kernel picking an address, so we have to block out the conflicting
* region first, avoiding any existing mappings (like vvar+vdso: i#2641).
*/
if (memquery_iterator_start(&iter, NULL, false /*no heap*/)) {
/* Strategy: track the leading edge ("tocover_start") of the conflict region.
* Find the next block beyond that edge so we know the safe endpoint for a
* temp mmap.
*/
byte *tocover_start = conflict_start;
while (memquery_iterator_next(&iter)) {
if (iter.vm_start > tocover_start) {
temp_map[num_temp_maps] = tocover_start;
temp_size[num_temp_maps] =
MIN(iter.vm_start, conflict_end) - tocover_start;
tocover_start = iter.vm_end;
if (temp_size[num_temp_maps] > 0) {
temp_map[num_temp_maps] = os_map_file(
-1, &temp_size[num_temp_maps], 0, temp_map[num_temp_maps],
MEMPROT_NONE, MAP_FILE_COPY_ON_WRITE | MAP_FILE_FIXED);
ASSERT(temp_map[num_temp_maps] != NULL);
num_temp_maps++;
}
} else if (iter.vm_end > tocover_start) {
tocover_start = iter.vm_end;
}
if (iter.vm_start >= conflict_end)
break;
}
memquery_iterator_stop(&iter);
if (tocover_start < conflict_end) {
temp_map[num_temp_maps] = tocover_start;
temp_size[num_temp_maps] = conflict_end - tocover_start;
temp_map[num_temp_maps] =
os_map_file(-1, &temp_size[num_temp_maps], 0, temp_map[num_temp_maps],
MEMPROT_NONE, MAP_FILE_COPY_ON_WRITE | MAP_FILE_FIXED);
ASSERT(temp_map[num_temp_maps] != NULL);
num_temp_maps++;
}
}
/* Now load the 2nd libdynamorio.so */
dr_map =
elf_loader_map_phdrs(&dr_ld, false /*!fixed*/, os_map_file, os_unmap_file,
os_set_protection, privload_check_new_map_bounds, memset,
privload_map_flags(0 /*!reachable*/), overlap_map_file_func);
ASSERT(dr_map != NULL);
ASSERT(is_elf_so_header(dr_map, 0));
/* Relocate it */
memset(&opd, 0, sizeof(opd));
module_get_os_privmod_data(dr_map, dr_size, false /*!relocated*/, &opd);
/* XXX: we assume libdynamorio has no tls block b/c we're not calling
* privload_relocate_mod().
*/
ASSERT(opd.tls_block_size == 0);
privload_relocate_os_privmod_data(&opd, dr_map);
for (i = 0; i < num_temp_maps; i++)
os_unmap_file(temp_map[i], temp_size[i]);
entry = (app_pc)dr_ld.ehdr->e_entry + dr_ld.load_delta;
elf_loader_destroy(&dr_ld);
/* Now we transfer control unconditionally to the new DR's _start, after
* first restoring init_sp. We pass along the current (old) DR's bounds
* for removal.
*/
xfer_to_new_libdr(entry, init_sp, cur_dr_map, dr_size);
ASSERT_NOT_REACHED();
}
/* Called from _start in x86.asm. sp is the initial app stack pointer that the
* kernel set up for us, and it points to the usual argc, argv, envp, and auxv
* that the kernel puts on the stack. The 2nd & 3rd args must be 0 in
* the initial call.
*
* We assume that _start has already called relocate_dynamorio() for us and
* that it is now safe to access globals.
*/
void
privload_early_inject(void **sp, byte *old_libdr_base, size_t old_libdr_size)
{
ptr_int_t *argc = (ptr_int_t *)sp; /* Kernel writes an elf_addr_t. */
char **argv = (char **)sp + 1;
char **envp = argv + *argc + 1;
app_pc entry = NULL;
char *exe_path;
char *exe_basename;
app_pc exe_map, exe_end;
elf_loader_t exe_ld;
const char *interp;
priv_mcontext_t mc;
bool success;
memquery_iter_t iter;
app_pc interp_map;
if (*argc == ARGC_PTRACE_SENTINEL) {
/* XXX: Teach the injector to look up takeover_ptrace() and call it
* directly instead of using this sentinel. We come here because we
* can easily find the address of _start in the ELF header.
*/
takeover_ptrace((ptrace_stack_args_t *)sp);
ASSERT_NOT_REACHED();
}
kernel_init_sp = (void *)sp;
/* XXX i#47: for Linux, we can't easily have this option on by default as
* code like get_application_short_name() called from drpreload before
* even _init is run needs to have a non-early default.
*/
dynamo_options.early_inject = true;
/* i#1227: if we reloaded ourselves, unload the old libdynamorio */
if (old_libdr_base != NULL) {
/* i#2641: we can't blindly unload the whole region as vvar+vdso may be
* in the text-data gap.
*/
const char *dynamorio_library_path = get_dynamorio_library_path();
if (memquery_iterator_start(&iter, NULL, false /*no heap*/)) {
while (memquery_iterator_next(&iter)) {
if (iter.vm_start >= old_libdr_base &&
iter.vm_end <= old_libdr_base + old_libdr_size &&
(iter.comment[0] == '\0' /* .bss */ ||
/* The kernel sometimes mis-labels our .bss as "[heap]". */
strcmp(iter.comment, "[heap]") == 0 ||
strcmp(iter.comment, dynamorio_library_path) == 0)) {
os_unmap_file(iter.vm_start, iter.vm_end - iter.vm_start);
}
if (iter.vm_start >= old_libdr_base + old_libdr_size)
break;
}
memquery_iterator_stop(&iter);
}
}
dynamorio_set_envp(envp);
/* argv[0] doesn't actually have to be the path to the exe, so we put the
* real exe path in an environment variable.
*/
exe_path = getenv(DYNAMORIO_VAR_EXE_PATH);
/* i#1677: this happens upon re-launching within gdb, so provide a nice error */
if (exe_path == NULL) {
/* i#1677: avoid assert in get_application_name_helper() */
set_executable_path("UNKNOWN");
apicheck(exe_path != NULL,
DYNAMORIO_VAR_EXE_PATH " env var is not set. "
"Are you re-launching within gdb?");
}
/* i#907: We can't rely on /proc/self/exe for the executable path, so we
* have to tell get_application_name() to use this path.
*/
set_executable_path(exe_path);
/* XXX i#2662: Currently, we only support getting args for early injection.
* Add support for late injection.
*/
set_app_args((int *)argc, argv);
success = elf_loader_read_headers(&exe_ld, exe_path);
apicheck(success,
"Failed to read app ELF headers. Check path and "
"architecture.");
/* Initialize DR's options to avoid syslogs in get_dynamo_library_bounds() and
* for the -xarch_root option below.
*/
dynamorio_app_init_part_one_options();
/* Find range of app */
exe_map = module_vaddr_from_prog_header((app_pc)exe_ld.phdrs, exe_ld.ehdr->e_phnum,
NULL, &exe_end);
/* i#1227: on a conflict with the app (+ room for the brk): reload ourselves */
if (get_dynamorio_dll_start() < exe_end + APP_BRK_GAP &&
get_dynamorio_dll_end() > exe_map) {
elf_loader_destroy(&exe_ld);
reload_dynamorio(sp, exe_map, exe_end + APP_BRK_GAP);
ASSERT_NOT_REACHED();
}
/* i#2641: we can't handle something in the text-data gap.
* Various parts of DR assume there's nothing inside (and we even fill the
* gap with a PROT_NONE mmap later: i#1659), so we reload to avoid it,
* under the assumption that it's rare and we're not paying this cost
* very often.
*/
if (!dynamorio_lib_gap_empty()) {
elf_loader_destroy(&exe_ld);
reload_dynamorio(sp, get_dynamorio_dll_start(), get_dynamorio_dll_end());
ASSERT_NOT_REACHED();
}
exe_map = elf_loader_map_phdrs(&exe_ld,
/* fixed at preferred address,
* will be overridden if preferred base is 0
*/
true,
/* ensure there's space for the brk */
map_exe_file_and_brk, os_unmap_file, os_set_protection,
privload_check_new_map_bounds, memset,
privload_map_flags(MODLOAD_IS_APP /*!reachable*/),
NULL /*overlap_map_func*/);
apicheck(exe_map != NULL,
"Failed to load application. "
"Check path and architecture.");
ASSERT(is_elf_so_header(exe_map, 0));
/* i#1660: the app may have passed a relative path or a symlink to execve,
* yet the kernel will put a resolved path into /proc/self/maps.
* Rather than us here or in pre-execve, plus in drrun or drinjectlib,
* making paths absolute and resolving symlinks to try and match what the
* kernel does, we just read the kernel's resolved path.
* This is prior to memquery_init() but that's fine (it's already being
* called by is_elf_so_header() above).
*/
if (memquery_iterator_start(&iter, exe_map, false /*no heap*/)) {
while (memquery_iterator_next(&iter)) {
if (iter.vm_start == exe_map) {
set_executable_path(iter.comment);
break;
}
}
memquery_iterator_stop(&iter);
}
/* Set the process name with prctl PR_SET_NAME. This makes killall <app>
* work.
*/
exe_basename = strrchr(exe_path, '/');
if (exe_basename == NULL) {
exe_basename = exe_path;
} else {
exe_basename++;
}
dynamorio_syscall(SYS_prctl, 5, PR_SET_NAME, (ptr_uint_t)exe_basename, 0, 0, 0);
reserve_brk(exe_map + exe_ld.image_size +
(INTERNAL_OPTION(separate_private_bss) ? PAGE_SIZE : 0));
interp = elf_loader_find_pt_interp(&exe_ld);
if (interp != NULL) {
char buf[MAXIMUM_PATH];
if (!IS_STRING_OPTION_EMPTY(xarch_root) && !os_file_exists(interp, false)) {
string_option_read_lock();
snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s/%s", DYNAMO_OPTION(xarch_root),
interp);
NULL_TERMINATE_BUFFER(buf);
string_option_read_unlock();
if (os_file_exists(buf, false)) {
LOG(GLOBAL, LOG_SYSCALLS, 2, "replacing interpreter |%s| with |%s|\n",
interp, buf);
interp = buf;
}
}
/* Load the ELF pointed at by PT_INTERP, usually ld.so. */
elf_loader_t interp_ld;
success = elf_loader_read_headers(&interp_ld, interp);
apicheck(success, "Failed to read ELF interpreter headers.");
interp_map = elf_loader_map_phdrs(
&interp_ld, false /* fixed */, os_map_file, os_unmap_file, os_set_protection,
privload_check_new_map_bounds, memset,
privload_map_flags(MODLOAD_IS_APP /*!reachable*/), overlap_map_file_func);
apicheck(interp_map != NULL && is_elf_so_header(interp_map, 0),
"Failed to map ELF interpreter.");
/* On Android, the system loader /system/bin/linker sets itself
* as the interpreter in the ELF header .interp field.
*/
ASSERT_CURIOSITY_ONCE((strcmp(interp, "/system/bin/linker") == 0 ||
elf_loader_find_pt_interp(&interp_ld) == NULL) &&
"The interpreter shouldn't have an interpreter");
entry = (app_pc)interp_ld.ehdr->e_entry + interp_ld.load_delta;
elf_loader_destroy(&interp_ld);
} else {
/* No PT_INTERP, so this is a static exe. */
interp_map = NULL;
entry = (app_pc)exe_ld.ehdr->e_entry + exe_ld.load_delta;
}
privload_setup_auxv(envp, exe_map, exe_ld.load_delta, interp_map, exe_path);
elf_loader_destroy(&exe_ld);
/* Initialize the rest of DR *after* we map the app and interp images. This is
* consistent with our old behavior, and allows the client to do things like call
* dr_get_proc_address() on the app from dr_client_main(). We let
* find_executable_vm_areas re-discover the mappings we made for the app and
* interp images. We do not do the full init before mapping the interp image
* as it complicates recording the mappings for the interp.
*/
if (dynamorio_app_init_part_two_finalize() != SUCCESS)
apicheck(false, "Failed to initialize part two.");
LOG(GLOBAL, LOG_TOP, 1, "early injected into app with this cmdline:\n");
DOLOG(1, LOG_TOP, {
int i;
for (i = 0; i < *argc; i++) {
LOG(GLOBAL, LOG_TOP, 1, "%s ", argv[i]);
}
LOG(GLOBAL, LOG_TOP, 1, "\n");
});
if (RUNNING_WITHOUT_CODE_CACHE()) {
/* Reset the stack pointer back to the beginning and jump to the entry
* point to execute the app natively. This is also useful for testing
* if the app has been mapped correctly without involving DR's code
* cache.
*/
# ifdef X86
asm("mov %0, %%" ASM_XSP "\n\t"
"jmp *%1\n\t"
:
: "r"(sp), "r"(entry));
# elif defined(ARM)
/* TODO i#1551: NYI on ARM */
ASSERT_NOT_REACHED();
# endif
}
memset(&mc, 0, sizeof(mc));
mc.xsp = (reg_t)sp;
mc.pc = entry;
dynamo_start(&mc);
}
# endif /* !defined(STANDALONE_UNIT_TEST) && !defined(STATIC_LIBRARY) */
#else
/* XXX i#1285: implement MacOS private loader */
void
relocate_dynamorio(byte *dr_map, size_t dr_size, byte *sp)
{
ASSERT_NOT_IMPLEMENTED(false);
}
void
privload_early_inject(void **sp, byte *old_libdr_base, size_t old_libdr_size)
{
ASSERT_NOT_IMPLEMENTED(false);
}
#endif