ff2a54bd37
Update based on cleanup made to the upstream version on OpenUCX. Signed-off-by: Nathan Hjelm <hjelmn@lanl.gov>
398 строки
13 KiB
C
398 строки
13 KiB
C
/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
|
|
/*
|
|
* Copyright (C) Mellanox Technologies Ltd. 2001-2015. ALL RIGHTS RESERVED.
|
|
* Copyright (c) 2016 Los Alamos National Security, LLC. All rights
|
|
* reserved.
|
|
* $COPYRIGHT$
|
|
*
|
|
* Additional copyrights may follow
|
|
*
|
|
* $HEADER$
|
|
*/
|
|
/*
|
|
* Copied from OpenUCX
|
|
*/
|
|
|
|
#include "patcher_linux.h"
|
|
|
|
#include "opal/mca/patcher/base/base.h"
|
|
|
|
#include "opal/constants.h"
|
|
#include "opal/util/sys_limits.h"
|
|
#include "opal/util/output.h"
|
|
#include "opal/prefetch.h"
|
|
|
|
#if defined(HAVE_SYS_AUXV_H)
|
|
#include <sys/auxv.h>
|
|
#endif
|
|
|
|
#include <elf.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <link.h>
|
|
|
|
static void *mca_patcher_linux_dlopen(const char *filename, int flag);
|
|
|
|
typedef struct mca_patcher_linux_dl_iter_context {
|
|
mca_patcher_linux_patch_t *patch;
|
|
bool remove;
|
|
int status;
|
|
} mca_patcher_linux_dl_iter_context_t;
|
|
|
|
OBJ_CLASS_INSTANCE(mca_patcher_linux_patch_got_t, opal_list_item_t, NULL, NULL);
|
|
|
|
static void mca_patcher_linux_patch_construct (mca_patcher_linux_patch_t *patch)
|
|
{
|
|
OBJ_CONSTRUCT(&patch->patch_got_list, opal_list_t);
|
|
}
|
|
|
|
static void mca_patcher_linux_patch_destruct (mca_patcher_linux_patch_t *patch)
|
|
{
|
|
OPAL_LIST_DESTRUCT(&patch->patch_got_list);
|
|
}
|
|
|
|
OBJ_CLASS_INSTANCE(mca_patcher_linux_patch_t, mca_patcher_base_patch_t, mca_patcher_linux_patch_construct,
|
|
mca_patcher_linux_patch_destruct);
|
|
|
|
/* List of patches to be applied to additional libraries */
|
|
static void *(*orig_dlopen) (const char *, int);
|
|
|
|
static const ElfW(Phdr) *
|
|
mca_patcher_linux_get_phdr_dynamic(const ElfW(Phdr) *phdr, uint16_t phnum, int phent)
|
|
{
|
|
for (uint16_t i = 0 ; i < phnum ; ++i, phdr = (ElfW(Phdr)*)((intptr_t) phdr + phent)) {
|
|
if (phdr->p_type == PT_DYNAMIC) {
|
|
return phdr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *mca_patcher_linux_get_dynentry(ElfW(Addr) base, const ElfW(Phdr) *pdyn, ElfW(Sxword) type)
|
|
{
|
|
for (ElfW(Dyn) *dyn = (ElfW(Dyn)*)(base + pdyn->p_vaddr); dyn->d_tag; ++dyn) {
|
|
if (dyn->d_tag == type) {
|
|
return (void *) (uintptr_t) dyn->d_un.d_val;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void * mca_patcher_linux_get_got_entry (ElfW(Addr) base, const ElfW(Phdr) *phdr, int16_t phnum,
|
|
int phent, const char *symbol)
|
|
{
|
|
const ElfW(Phdr) *dphdr;
|
|
void *jmprel, *strtab;
|
|
ElfW(Sym) *symtab;
|
|
size_t pltrelsz;
|
|
|
|
dphdr = mca_patcher_linux_get_phdr_dynamic (phdr, phnum, phent);
|
|
|
|
jmprel = mca_patcher_linux_get_dynentry (base, dphdr, DT_JMPREL);
|
|
symtab = (ElfW(Sym) *) mca_patcher_linux_get_dynentry (base, dphdr, DT_SYMTAB);
|
|
strtab = mca_patcher_linux_get_dynentry (base, dphdr, DT_STRTAB);
|
|
pltrelsz = (size_t) (uintptr_t) mca_patcher_linux_get_dynentry (base, dphdr, DT_PLTRELSZ);
|
|
|
|
for (ElfW(Rela) *reloc = jmprel; (intptr_t) reloc < (intptr_t) jmprel + pltrelsz; ++reloc) {
|
|
#if SIZEOF_VOID_P == 8
|
|
uint32_t relsymidx = ELF64_R_SYM(reloc->r_info);
|
|
#else
|
|
uint32_t relsymidx = ELF32_R_SYM(reloc->r_info);
|
|
#endif
|
|
char *elf_sym = (char *) strtab + symtab[relsymidx].st_name;
|
|
|
|
if (0 == strcmp (symbol, elf_sym)) {
|
|
return (void *)(base + reloc->r_offset);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int mca_patcher_linux_get_aux_phent (void)
|
|
{
|
|
#if !defined(HAVE_SYS_AUXV_H)
|
|
#define MCA_PATCHER_LINUX_AUXV_BUF_LEN 16
|
|
static const char *proc_auxv_filename = "/proc/self/auxv";
|
|
static int phent = 0;
|
|
ElfW(auxv_t) buffer[MCA_PATCHER_LINUX_AUXV_BUF_LEN];
|
|
unsigned count;
|
|
ssize_t nread;
|
|
int fd;
|
|
|
|
/* Can avoid lock here - worst case we'll read the file more than once */
|
|
if (phent == 0) {
|
|
fd = open(proc_auxv_filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"failed to open '%s' for reading: %s", proc_auxv_filename,
|
|
strerror (errno));
|
|
return OPAL_ERROR;
|
|
}
|
|
|
|
/* Use small buffer on the stack, avoid using malloc() */
|
|
do {
|
|
nread = read(fd, buffer, sizeof(buffer));
|
|
if (nread < 0) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"failed to read %" PRIsize_t " bytes from %s (ret=%ld): %s", sizeof (buffer),
|
|
proc_auxv_filename, nread, strerror (errno));
|
|
break;
|
|
}
|
|
|
|
count = nread / sizeof(buffer[0]);
|
|
for (unsigned i = 0 ; i < count && AT_NULL != buffer[i].a_type ; ++i) {
|
|
if (AT_PHENT == buffer[i].a_type) {
|
|
phent = buffer[i].a_un.a_val;
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"read phent from %s: %d", proc_auxv_filename, phent);
|
|
break;
|
|
}
|
|
}
|
|
} while ((count > 0) && (phent == 0));
|
|
|
|
close(fd);
|
|
}
|
|
|
|
return phent;
|
|
#else
|
|
return getauxval (AT_PHENT);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
mca_patcher_linux_modify_got (ElfW(Addr) base, const ElfW(Phdr) *phdr, const char *phname,
|
|
int16_t phnum, int phent, mca_patcher_linux_dl_iter_context_t *ctx)
|
|
{
|
|
long page_size = opal_getpagesize ();
|
|
void **entry, *page;
|
|
int ret;
|
|
|
|
entry = mca_patcher_linux_get_got_entry (base, phdr, phnum, phent, ctx->patch->super.patch_symbol);
|
|
if (entry == NULL) {
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
page = (void *)((intptr_t)entry & ~(page_size - 1));
|
|
ret = mprotect(page, page_size, PROT_READ|PROT_WRITE);
|
|
if (ret < 0) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"failed to modify GOT page %p to rw: %s", page, strerror (errno));
|
|
return OPAL_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (!ctx->remove) {
|
|
if (*entry != (void *) ctx->patch->super.patch_value) {
|
|
mca_patcher_linux_patch_got_t *patch_got = OBJ_NEW(mca_patcher_linux_patch_got_t);
|
|
if (NULL == patch_got) {
|
|
return OPAL_ERR_OUT_OF_RESOURCE;
|
|
}
|
|
|
|
opal_output_verbose (MCA_BASE_VERBOSE_TRACE, opal_patcher_base_framework.framework_output,
|
|
"patch %p (%s): modifying got entry %p. original value %p. new value %p\n", (void *)ctx->patch,
|
|
ctx->patch->super.patch_symbol, (void *) entry, *entry, (void *) ctx->patch->super.patch_value);
|
|
|
|
patch_got->got_entry = entry;
|
|
patch_got->got_orig = *entry;
|
|
|
|
opal_list_append (&ctx->patch->patch_got_list, &patch_got->super);
|
|
|
|
*entry = (void *) ctx->patch->super.patch_value;
|
|
}
|
|
} else {
|
|
/* find the appropriate entry and restore the original value */
|
|
mca_patcher_linux_patch_got_t *patch_got;
|
|
OPAL_LIST_FOREACH_REV(patch_got, &ctx->patch->patch_got_list, mca_patcher_linux_patch_got_t) {
|
|
if (patch_got->got_entry == entry) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_TRACE, opal_patcher_base_framework.framework_output,
|
|
"restoring got entry %p with original value %p\n", (void *) entry, patch_got->got_orig);
|
|
if (*entry == (void *) ctx->patch->super.patch_value) {
|
|
*entry = patch_got->got_orig;
|
|
}
|
|
opal_list_remove_item (&ctx->patch->patch_got_list, &patch_got->super);
|
|
OBJ_RELEASE(patch_got);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int mca_patcher_linux_phdr_iterator(struct dl_phdr_info *info, size_t size, void *data)
|
|
{
|
|
mca_patcher_linux_dl_iter_context_t *ctx = data;
|
|
int phent;
|
|
|
|
phent = mca_patcher_linux_get_aux_phent();
|
|
if (phent <= 0) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"failed to read phent size");
|
|
ctx->status = OPAL_ERR_NOT_SUPPORTED;
|
|
return -1;
|
|
}
|
|
|
|
ctx->status = mca_patcher_linux_modify_got (info->dlpi_addr, info->dlpi_phdr,
|
|
info->dlpi_name, info->dlpi_phnum,
|
|
phent, ctx);
|
|
if (ctx->status == OPAL_SUCCESS) {
|
|
return 0; /* continue iteration and patch all objects */
|
|
} else {
|
|
return -1; /* stop iteration if got a real error */
|
|
}
|
|
}
|
|
|
|
/* called with lock held */
|
|
static int mca_patcher_linux_apply_patch (mca_patcher_linux_patch_t *patch)
|
|
{
|
|
mca_patcher_linux_dl_iter_context_t ctx = {
|
|
.patch = patch,
|
|
.remove = false,
|
|
.status = OPAL_SUCCESS,
|
|
};
|
|
|
|
/* Avoid locks here because we don't modify ELF data structures.
|
|
* Worst case the same symbol will be written more than once.
|
|
*/
|
|
(void) dl_iterate_phdr(mca_patcher_linux_phdr_iterator, &ctx);
|
|
|
|
return ctx.status;
|
|
}
|
|
|
|
static int mca_patcher_linux_remove_patch (mca_patcher_linux_patch_t *patch)
|
|
{
|
|
mca_patcher_linux_dl_iter_context_t ctx = {
|
|
.patch = patch,
|
|
.remove = true,
|
|
.status = OPAL_SUCCESS,
|
|
};
|
|
|
|
/* Avoid locks here because we don't modify ELF data structures.
|
|
* Worst case the same symbol will be written more than once.
|
|
*/
|
|
(void) dl_iterate_phdr(mca_patcher_linux_phdr_iterator, &ctx);
|
|
|
|
return ctx.status;
|
|
}
|
|
|
|
static void *mca_patcher_linux_dlopen(const char *filename, int flag)
|
|
{
|
|
OPAL_PATCHER_BEGIN;
|
|
mca_patcher_linux_patch_t *patch;
|
|
void *handle;
|
|
|
|
handle = orig_dlopen (filename, flag);
|
|
if (handle != NULL) {
|
|
/*
|
|
* Every time a new object is loaded, we must update its relocations
|
|
* with our list of patches (including dlopen itself). This code is less
|
|
* efficient and will modify all existing objects every time, but good
|
|
* enough.
|
|
*/
|
|
opal_mutex_lock (&mca_patcher_linux_module.patch_list_mutex);
|
|
OPAL_LIST_FOREACH(patch, &mca_patcher_linux_module.patch_list, mca_patcher_linux_patch_t) {
|
|
if (!patch->super.patch_data_size) {
|
|
opal_output_verbose (MCA_BASE_VERBOSE_INFO, opal_patcher_base_framework.framework_output,
|
|
"in dlopen(), re-applying '%s' to %p", patch->super.patch_symbol, (void *) patch->super.patch_value);
|
|
/* ignore hook binary patches */
|
|
mca_patcher_linux_apply_patch (patch);
|
|
}
|
|
}
|
|
opal_mutex_unlock (&mca_patcher_linux_module.patch_list_mutex);
|
|
}
|
|
|
|
OPAL_PATCHER_END;
|
|
return handle;
|
|
}
|
|
|
|
static intptr_t mca_patcher_linux_get_orig (const char *symbol, void *replacement)
|
|
{
|
|
const char *error;
|
|
void *func_ptr;
|
|
|
|
func_ptr = dlsym(RTLD_DEFAULT, symbol);
|
|
if (func_ptr == replacement) {
|
|
(void)dlerror();
|
|
func_ptr = dlsym(RTLD_NEXT, symbol);
|
|
if (func_ptr == NULL) {
|
|
error = dlerror();
|
|
opal_output_verbose (MCA_BASE_VERBOSE_ERROR, opal_patcher_base_framework.framework_output,
|
|
"could not find address of original %s(): %s", symbol, error ? error : "Unknown error");
|
|
}
|
|
}
|
|
|
|
opal_output_verbose (MCA_BASE_VERBOSE_INFO, opal_patcher_base_framework.framework_output,
|
|
"original %s() is at %p", symbol, func_ptr);
|
|
|
|
return (intptr_t) func_ptr;
|
|
}
|
|
|
|
static int mca_patcher_linux_patch_symbol (const char *symbol_name, uintptr_t replacement, uintptr_t *orig)
|
|
{
|
|
mca_patcher_linux_patch_t *patch = OBJ_NEW(mca_patcher_linux_patch_t);
|
|
int rc;
|
|
|
|
if (OPAL_UNLIKELY(NULL == patch)) {
|
|
return OPAL_ERR_OUT_OF_RESOURCE;
|
|
}
|
|
|
|
patch->super.patch_symbol = strdup (symbol_name);
|
|
if (NULL == patch->super.patch_symbol) {
|
|
OBJ_RELEASE(patch);
|
|
return OPAL_ERR_OUT_OF_RESOURCE;
|
|
}
|
|
|
|
patch->super.patch_value = mca_patcher_base_addr_text (replacement);
|
|
patch->super.patch_restore = (mca_patcher_base_restore_fn_t) mca_patcher_linux_remove_patch;
|
|
|
|
/* Take lock first to handle a possible race where dlopen() is called
|
|
* from another thread and we may end up not patching it.
|
|
*/
|
|
opal_mutex_lock (&mca_patcher_linux_module.patch_list_mutex);
|
|
do {
|
|
rc = mca_patcher_base_patch_hook (&mca_patcher_linux_module, patch->super.patch_value);
|
|
if (OPAL_SUCCESS != rc) {
|
|
OBJ_RELEASE(patch);
|
|
break;
|
|
}
|
|
|
|
rc = mca_patcher_linux_apply_patch (patch);
|
|
if (OPAL_SUCCESS != rc) {
|
|
OBJ_RELEASE(patch);
|
|
break;
|
|
}
|
|
|
|
*orig = mca_patcher_linux_get_orig (patch->super.patch_symbol, (void *) replacement);
|
|
|
|
opal_list_append (&mca_patcher_linux_module.patch_list, &patch->super.super);
|
|
} while (0);
|
|
opal_mutex_unlock (&mca_patcher_linux_module.patch_list_mutex);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* called with lock held */
|
|
static int mca_patcher_linux_install_dlopen (void)
|
|
{
|
|
return mca_patcher_linux_patch_symbol ("dlopen", (uintptr_t) mca_patcher_linux_dlopen,
|
|
(uintptr_t *) &orig_dlopen);
|
|
}
|
|
|
|
static int mca_patcher_linux_init (void)
|
|
{
|
|
return mca_patcher_linux_install_dlopen ();
|
|
}
|
|
|
|
mca_patcher_base_module_t mca_patcher_linux_module = {
|
|
.patch_init = mca_patcher_linux_init,
|
|
.patch_symbol = mca_patcher_linux_patch_symbol,
|
|
};
|