diff options
author | Petr Machata <pmachata@redhat.com> | 2013-10-16 17:02:20 +0200 |
---|---|---|
committer | Petr Machata <pmachata@redhat.com> | 2013-10-23 01:00:03 +0200 |
commit | b3d6180e6d1cfc188a44b60dda6a7ac4faf0fdce (patch) | |
tree | b4020ad8dd1ff5e1a0dc33ceabc8f3767f534ff4 /sysdeps | |
parent | b8f0d8b5859a7d69f6aed4904a87ac6a423285f6 (diff) | |
download | ltrace-b3d6180e6d1cfc188a44b60dda6a7ac4faf0fdce.tar.gz |
Linux backend now supports tracing of IFUNC symbols
Diffstat (limited to 'sysdeps')
-rw-r--r-- | sysdeps/linux-gnu/hooks.c | 224 | ||||
-rw-r--r-- | sysdeps/linux-gnu/os.h | 14 |
2 files changed, 236 insertions, 2 deletions
diff --git a/sysdeps/linux-gnu/hooks.c b/sysdeps/linux-gnu/hooks.c index 9a9df76..b4ae2c0 100644 --- a/sysdeps/linux-gnu/hooks.c +++ b/sysdeps/linux-gnu/hooks.c @@ -23,15 +23,23 @@ #include <alloca.h> #include <errno.h> #include <pwd.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -#include "sysdep.h" -#include "vect.h" +#include "backend.h" +#include "breakpoint.h" #include "dict.h" +#include "fetch.h" +#include "library.h" #include "options.h" +#include "prototype.h" +#include "sysdep.h" +#include "type.h" +#include "value.h" +#include "vect.h" static char * append(const char *str1, const char *str2) @@ -209,3 +217,215 @@ os_get_ltrace_conf_filenames(struct vect *retp) return 0; } + +static struct prototype * +void_prototype(void) +{ + static struct prototype ret; + if (ret.return_info == NULL) { + prototype_init(&ret); + ret.return_info = type_get_voidptr(); + ret.own_return_info = 0; + } + return &ret; +} + +int +os_library_symbol_init(struct library_symbol *libsym) +{ + libsym->os = (struct os_library_symbol_data){}; + return 0; +} + +void +os_library_symbol_destroy(struct library_symbol *libsym) +{ +} + +int +os_library_symbol_clone(struct library_symbol *retp, + struct library_symbol *libsym) +{ + retp->os = libsym->os; + return 0; +} + +enum plt_status +os_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret) +{ + if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) + return PLT_DEFAULT; + + bool ifunc = false; +#ifdef STT_GNU_IFUNC + ifunc = GELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC; +#endif + + if (ifunc) { +#define S ".IFUNC" + char *tmp_name = malloc(strlen(name) + sizeof S); + struct library_symbol *tmp = malloc(sizeof *tmp); + if (tmp_name == NULL || tmp == NULL) { + fail: + free(tmp_name); + free(tmp); + return PLT_FAIL; + } + sprintf(tmp_name, "%s%s", name, S); +#undef S + + if (library_symbol_init(tmp, addr, tmp_name, 1, + LS_TOPLT_NONE) < 0) + goto fail; + tmp->proto = void_prototype(); + tmp->os.is_ifunc = 1; + + *ret = tmp; + return PLT_OK; + } + + *ret = NULL; + return PLT_OK; +} + +static enum callback_status +libsym_at_address(struct library_symbol *libsym, void *addrp) +{ + arch_addr_t addr = *(arch_addr_t *)addrp; + return CBS_STOP_IF(addr == libsym->enter_addr); +} + +static void +ifunc_ret_hit(struct breakpoint *bp, struct process *proc) +{ + struct fetch_context *fetch = fetch_arg_init(LT_TOF_FUNCTION, proc, + type_get_voidptr()); + if (fetch == NULL) + return; + + struct breakpoint *nbp = NULL; + int own_libsym = 0; + + struct value value; + value_init(&value, proc, NULL, type_get_voidptr(), 0); + size_t sz = value_size(&value, NULL); + union { + uint64_t u64; + uint32_t u32; + arch_addr_t a; + } u; + + if (fetch_retval(fetch, LT_TOF_FUNCTIONR, proc, + value.type, &value) < 0 + || sz > 8 /* Captures failure as well. */ + || value_extract_buf(&value, (void *) &u, NULL) < 0) { + fail: + fprintf(stderr, + "Couldn't trace the function " + "indicated by IFUNC resolver.\n"); + goto done; + } + + assert(sz == 4 || sz == 8); + /* XXX double casts below: */ + if (sz == 4) + u.a = (arch_addr_t)(uintptr_t)u.u32; + else + u.a = (arch_addr_t)(uintptr_t)u.u64; + + assert(bp->os.ret_libsym != NULL); + + struct library *lib = bp->os.ret_libsym->lib; + assert(lib != NULL); + + /* Look if we already have a symbol with this address. + * Otherwise create a new one. */ + struct library_symbol *libsym + = library_each_symbol(lib, NULL, libsym_at_address, &u.a); + if (libsym == NULL) { + libsym = malloc(sizeof *libsym); + char *name = strdup(bp->os.ret_libsym->name); + + if (libsym == NULL + || name == NULL + || library_symbol_init(libsym, u.a, name, 1, + LS_TOPLT_NONE) < 0) { + free(libsym); + free(name); + goto fail; + } + + /* Snip the .IFUNC token. */ + *strrchr(name, '.') = 0; + + own_libsym = 1; + library_add_symbol(lib, libsym); + } + + nbp = malloc(sizeof *bp); + if (nbp == NULL || breakpoint_init(nbp, proc, u.a, libsym) < 0) + goto fail; + + /* If there already is a breakpoint at that address, that is + * suspicious, but whatever. */ + struct breakpoint *pre_bp = insert_breakpoint(proc, nbp); + if (pre_bp == NULL) + goto fail; + if (pre_bp == nbp) { + /* PROC took our breakpoint, so these resources are + * not ours anymore. */ + nbp = NULL; + own_libsym = 0; + } + +done: + free(nbp); + if (own_libsym) { + library_symbol_destroy(libsym); + free(libsym); + } + fetch_arg_done(fetch); +} + +static int +create_ifunc_ret_bp(struct breakpoint **ret, + struct breakpoint *bp, struct process *proc) +{ + *ret = create_default_return_bp(proc); + if (*ret == NULL) + return -1; + static struct bp_callbacks cbs = { + .on_hit = ifunc_ret_hit, + }; + breakpoint_set_callbacks(*ret, &cbs); + + (*ret)->os.ret_libsym = bp->libsym; + + return 0; +} + +int +os_breakpoint_init(struct process *proc, struct breakpoint *bp) +{ + if (bp->libsym != NULL && bp->libsym->os.is_ifunc) { + static struct bp_callbacks cbs = { + .get_return_bp = create_ifunc_ret_bp, + }; + breakpoint_set_callbacks(bp, &cbs); + } + return 0; +} + +void +os_breakpoint_destroy(struct breakpoint *bp) +{ +} + +int +os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *bp) +{ + return 0; +} diff --git a/sysdeps/linux-gnu/os.h b/sysdeps/linux-gnu/os.h index 62bf38b..60fd604 100644 --- a/sysdeps/linux-gnu/os.h +++ b/sysdeps/linux-gnu/os.h @@ -23,3 +23,17 @@ struct os_process_data { arch_addr_t debug_addr; int debug_state; }; + +#define OS_HAVE_LIBRARY_SYMBOL_DATA +struct os_library_symbol_data { + unsigned is_ifunc : 1; +}; + +#define OS_HAVE_BREAKPOINT_DATA +struct os_breakpoint_data { + /* For breakpoints that track return from IFUNC functions, we + * keep here the IFUNC symbol itself. */ + struct library_symbol *ret_libsym; +}; + +#define OS_HAVE_ADD_FUNC_ENTRY |