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;
+}