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(<e->plt_relocs);
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ GElf_Rela *rela = VECT_ELEMENT(<e->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_ */