Move code for tracing IFUNC symbols
- ... from sysdeps/linux-gnu/hooks.c to sysdeps/linux-gnu/trace.c
where it fits better.
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index 6c3258e..220486c 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -46,11 +46,14 @@
#include "breakpoint.h"
#include "debug.h"
#include "events.h"
+#include "fetch.h"
#include "ltrace-elf.h"
#include "options.h"
#include "proc.h"
+#include "prototype.h"
#include "ptrace.h"
#include "type.h"
+#include "value.h"
void
trace_fail_warning(pid_t pid)
@@ -1287,3 +1290,220 @@
free(name);
return i < 0 ? PLT_FAIL : PLT_OK;
}
+
+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;
+ if (arch_translate_address_dyn(proc, u.a, &u.a) < 0) {
+ fprintf(stderr, "Couldn't OPD-translate the address returned"
+ " by the IFUNC resolver.\n");
+ goto done;
+ }
+
+ 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;
+}