Full support of IFUNC tracing on PPC32
diff --git a/sysdeps/linux-gnu/ppc/arch.h b/sysdeps/linux-gnu/ppc/arch.h
index ac9efd3..2add3b8 100644
--- a/sysdeps/linux-gnu/ppc/arch.h
+++ b/sysdeps/linux-gnu/ppc/arch.h
@@ -39,6 +39,7 @@
 
 #define ARCH_HAVE_SW_SINGLESTEP
 #define ARCH_HAVE_ADD_PLT_ENTRY
+#define ARCH_HAVE_ADD_FUNC_ENTRY
 #define ARCH_HAVE_TRANSLATE_ADDRESS
 #define ARCH_HAVE_DYNLINK_DONE
 #define ARCH_HAVE_FETCH_ARG
diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
index c1eb7c7..5e3ffe1 100644
--- a/sysdeps/linux-gnu/ppc/plt.c
+++ b/sysdeps/linux-gnu/ppc/plt.c
@@ -120,9 +120,12 @@
  * catch the point where the slot is resolved, would hit the return
  * breakpoint and that's not currently handled well.
  *
- * On PPC32 with secure PLT, IFUNC symbols in main binary actually
- * don't refer to the resolver itself.  Instead they refer to a PLT
- * slot.
+ * On PPC32 with secure PLT, the address of IFUNC symbols in main
+ * binary actually isn't of the resolver, but of a PLT slot.  We
+ * therefore have to locate the corresponding PLT relocation (which is
+ * of type R_PPC_IRELATIVE) and request that it be traced.  The addend
+ * of that relocation is an address of resolver, and we request
+ * tracing of the xyz.IFUNC symbol there.
  *
  * XXX TODO If we have hardware watch point, we might put a read watch
  * on .plt slot, and discover the offenders this way.  I don't know
@@ -652,6 +655,62 @@
 }
 
 enum plt_status
+arch_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 (lte->ehdr.e_machine != EM_PPC || lte->ehdr.e_type == ET_DYN)
+		return PLT_DEFAULT;
+
+	bool ifunc = false;
+#ifdef STT_GNU_IFUNC
+	ifunc = GELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC;
+#endif
+	if (! ifunc)
+		return PLT_DEFAULT;
+
+	size_t len = vect_size(&lte->plt_relocs);
+	size_t i;
+	for (i = 0; i < len; ++i) {
+		GElf_Rela *rela = VECT_ELEMENT(&lte->plt_relocs, GElf_Rela, i);
+		if (sym->st_value == arch_plt_sym_val(lte, i, rela)) {
+
+			char *tmp_name = linux_append_IFUNC_to_name(name);
+			struct library_symbol *libsym = malloc(sizeof *libsym);
+
+			/* XXX double cast.  */
+			arch_addr_t resolver_addr
+				= (arch_addr_t) (uintptr_t) rela->r_addend;
+
+			if (tmp_name == NULL || libsym == NULL
+			    || 	library_symbol_init(libsym, resolver_addr,
+						    tmp_name, 1,
+						    LS_TOPLT_EXEC) < 0) {
+			fail:
+				free(tmp_name);
+				free(libsym);
+				return PLT_FAIL;
+			}
+
+			if (elf_add_plt_entry(proc, lte, name, rela,
+					      i, ret) < 0) {
+				library_symbol_destroy(libsym);
+				goto fail;
+			}
+
+			libsym->proto = linux_IFUNC_prototype();
+			libsym->next = *ret;
+			*ret = libsym;
+			return PLT_OK;
+		}
+	}
+
+	*ret = NULL;
+	return PLT_OK;
+}
+
+enum plt_status
 arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte,
 		       const char *a_name, GElf_Rela *rela, size_t ndx,
 		       struct library_symbol **ret)
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index 220486c..e648b8f 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -1291,8 +1291,8 @@
 	return i < 0 ? PLT_FAIL : PLT_OK;
 }
 
-static struct prototype *
-void_prototype(void)
+struct prototype *
+linux_IFUNC_prototype(void)
 {
 	static struct prototype ret;
 	if (ret.return_info == NULL) {
@@ -1323,6 +1323,18 @@
 	return 0;
 }
 
+char *
+linux_append_IFUNC_to_name(const char *name)
+{
+#define S ".IFUNC"
+	char *tmp_name = malloc(strlen(name) + sizeof S);
+	if (tmp_name == NULL)
+		return NULL;
+	sprintf(tmp_name, "%s%s", name, S);
+#undef S
+	return tmp_name;
+}
+
 enum plt_status
 os_elf_add_func_entry(struct process *proc, struct ltelf *lte,
 		      const GElf_Sym *sym,
@@ -1338,8 +1350,7 @@
 #endif
 
 	if (ifunc) {
-#define S ".IFUNC"
-		char *tmp_name = malloc(strlen(name) + sizeof S);
+		char *tmp_name = linux_append_IFUNC_to_name(name);
 		struct library_symbol *tmp = malloc(sizeof *tmp);
 		if (tmp_name == NULL || tmp == NULL) {
 		fail:
@@ -1347,13 +1358,11 @@
 			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->proto = linux_IFUNC_prototype();
 		tmp->os.is_ifunc = 1;
 
 		*ret = tmp;
diff --git a/sysdeps/linux-gnu/trace.h b/sysdeps/linux-gnu/trace.h
index a83aea2..94844dd 100644
--- a/sysdeps/linux-gnu/trace.h
+++ b/sysdeps/linux-gnu/trace.h
@@ -142,4 +142,13 @@
  * freeing.  */
 char *linux_elf_find_irelative_name(struct ltelf *lte, GElf_Addr addr);
 
+/* Returns ${NAME}.IFUNC in a newly-malloc'd block, or NULL on
+ * failures.  */
+char *linux_append_IFUNC_to_name(const char *name);
+
+/* Returns a statically allocated prototype that represents the
+ * prototype "void *()".  Never fails.  */
+struct prototype *linux_IFUNC_prototype(void);
+
+
 #endif /* _LTRACE_LINUX_TRACE_H_ */