[ltrace-commits] 01/03: Support tracing of PLT-less MIPS binaries

Petr Machata pmachata-guest at moszumanska.debian.org
Mon May 4 18:32:28 UTC 2015


This is an automated email from the git hooks/post-receive script.

pmachata-guest pushed a commit to branch master
in repository ltrace.

commit 8350f71012ea6da7213b293d7997bbb6b60cd79a
Author: Petr Machata <pmachata at redhat.com>
Date:   Tue Jan 21 02:37:33 2014 +0100

    Support tracing of PLT-less MIPS binaries
---
 sysdeps/linux-gnu/mips/arch.h |  40 ++-
 sysdeps/linux-gnu/mips/mips.h |   2 +
 sysdeps/linux-gnu/mips/plt.c  | 814 +++++++++++++++++++++++++++++-------------
 3 files changed, 600 insertions(+), 256 deletions(-)

diff --git a/sysdeps/linux-gnu/mips/arch.h b/sysdeps/linux-gnu/mips/arch.h
index 14572cd..16273d2 100644
--- a/sysdeps/linux-gnu/mips/arch.h
+++ b/sysdeps/linux-gnu/mips/arch.h
@@ -1,6 +1,6 @@
 /*
  * This file is part of ltrace.
- * Copyright (C) 2013 Petr Machata
+ * Copyright (C) 2013,2014 Petr Machata, Red Hat Inc.
  * Copyright (C) 2006 Eric Vaitl
  *
  * This program is free software; you can redistribute it and/or
@@ -49,7 +49,6 @@ struct arch_ltelf_data {
 };
 
 #define ARCH_HAVE_FIND_DL_DEBUG
-#define ARCH_HAVE_GET_SYMINFO
 #define ARCH_HAVE_DYNLINK_DONE
 #define ARCH_HAVE_ADD_PLT_ENTRY
 #define ARCH_HAVE_SW_SINGLESTEP
@@ -58,19 +57,44 @@ struct arch_ltelf_data {
 #define ARCH_HAVE_LIBRARY_SYMBOL_DATA
 enum mips_plt_type
 {
+	/* A symbol has associated PLT entry.  */
+	MIPS_PLT_DEFAULT,
+
+	/* The GOT entry contains unresolved value.  RESOLVED_ADDR
+	 * then contains stub address.  */
 	MIPS_PLT_UNRESOLVED,
+
+	/* The GOT entry contained resolved value, and was unresolved.
+	 * The original value was saved to RESOLVED_ADDR.  */
 	MIPS_PLT_RESOLVED,
+
+	/* Symbol needs to be unresolved after it's enabled.
+	 * RESOLVED_ADDR is undefined, instead DATA is carried.  */
+	MIPS_PLT_NEED_UNRESOLVE,
 };
 
+struct mips_unresolve_data;
 struct arch_library_symbol_data {
 	enum mips_plt_type type;
-	GElf_Addr resolved_addr;
-	GElf_Addr stub_addr;
+	union {
+		GElf_Addr resolved_value;
+		struct mips_unresolve_data *data;
+	};
+	GElf_Addr got_entry_addr;
+};
+
+#define ARCH_HAVE_BREAKPOINT_DATA
+struct arch_breakpoint_data {
+};
+
+#define ARCH_HAVE_PROCESS_DATA
+struct arch_process_data {
+	/* Breakpoint that hits when the dynamic linker is about to
+	 * update a GOT entry.  NULL before that address is known.  */
+	struct breakpoint *dl_got_update_bp;
 
-	/* Set for FUNCs that have GOT entries but not PLT entries.  */
-	int gotonly : 1;
-	/* Set for FUNCs that have PLT entries that are always used.  */
-	int pltalways : 1;
+	/* PLT update breakpoint looks here for the handler.  */
+	struct process_stopping_handler *handler;
 };
 
 #endif /* LTRACE_MIPS_ARCH_H */
diff --git a/sysdeps/linux-gnu/mips/mips.h b/sysdeps/linux-gnu/mips/mips.h
index 0cbc715..9d4af33 100644
--- a/sysdeps/linux-gnu/mips/mips.h
+++ b/sysdeps/linux-gnu/mips/mips.h
@@ -1,5 +1,6 @@
 /*
  * This file is part of ltrace.
+ * Copyright (C) 2014 Petr Machata, Red Hat Inc.
  * Copyright (C) 2010 Arnaud Patard, Mandriva SA
  * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc.
  *
@@ -26,6 +27,7 @@
 #define off_pc 64
 #define off_a0 4
 #define off_a3 7
+#define off_t9 25
 #define off_lr 31
 #define off_fpr0 32
 #define off_sp 29
diff --git a/sysdeps/linux-gnu/mips/plt.c b/sysdeps/linux-gnu/mips/plt.c
index 3adf2ec..84e2234 100644
--- a/sysdeps/linux-gnu/mips/plt.c
+++ b/sysdeps/linux-gnu/mips/plt.c
@@ -1,6 +1,6 @@
 /*
  * This file is part of ltrace.
- * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2012,2013,2014 Petr Machata, Red Hat Inc.
  * Copyright (C) 2012 Edgar E. Iglesias, Axis Communications
  * Copyright (C) 2008,2009 Juan Cespedes
  * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc.
@@ -21,24 +21,106 @@
  * 02110-1301 USA
  */
 
-#include <string.h>
-#include <error.h>
+#include <sys/ptrace.h>
 #include <errno.h>
+#include <error.h>
 #include <gelf.h>
-#include <sys/ptrace.h>
+#include <inttypes.h>
+#include <string.h>
 
+#include "sysdeps/linux-gnu/trace.h"
+#include "backend.h"
+#include "breakpoint.h"
 #include "common.h"
 #include "debug.h"
-#include "proc.h"
 #include "library.h"
-#include "breakpoint.h"
-#include "backend.h"
+#include "proc.h"
+#include "vect.h"
+
+#include "mips.h"
 
 /**
    \addtogroup mips
    @{
  */
 
+/*
+ * The following holds for o32 only.  Ltrace currently doesn't support
+ * n32 or n64 ABI's.
+ *
+ * MIPS ABI Supplement has this to say about MIPS-specific SHT_DYNAMIC
+ * entries:
+ *
+ * - DT_PLTGOT This member holds the address of the .got section.
+ *
+ * - DT_MIPS_SYMTABNO This member holds the number of entries in the
+ *   .dynsym section.
+ *
+ * - DT_MIPS_LOCAL_GOTNO This member holds the number of local GOT
+ *   entries.
+ *
+ * - DT_MIPS_GOTSYM This member holds the index of the first dyamic
+ *   symbol table entry that corresponds to an entry in the gobal
+ *   offset table.
+ *
+ * In PLT-enabled binaries, we trace binaries as usual.
+ *
+ * In PLT-disabled binaries, the calls are done through addresses
+ * fetched from GOT.  (For some background, see code in
+ * ports/sysdeps/mips/dl-trampoline.c in glibc.  Also, keep in mind
+ * that MIPS uses branch delay slots.)
+ *
+ * 00400660 <main>:
+ *   [...]
+ *   400670:	3c1c0042 	lui	gp,0x42
+ *   400674:	279c8900 	addiu	gp,gp,-30464 <-- gp = some global pointer?
+ *   [...]
+ *   40068c:	8f828034 	lw	v0,-32716(gp) <-- address from GOT
+ *   400690:	00200825 	move	at,at
+ *   400694:	0040c821 	move	t9,v0
+ *   400698:	0320f809 	jalr	t9    <-- jump at it
+ *   40069c:	00200825 	move	at,at
+ *
+ * GOT initially contains addresses that point to .MIPS.stubs section,
+ * where the jump leads:
+ *
+ *   400820:	8f998010 	lw	t9,-32752(gp) <-- gp = GOT[0]
+ *   400824:	03e07821 	move	t7,ra         <-- t7 = caller's return addr
+ *   400828:	0320f809 	jalr	t9
+ *   40082c:	24180009 	li	t8,9          <-- t8 = .dynsym index
+ *
+ * After the first call, GOT entry is resolved and contains address of
+ * the resolved symbol.  So .MIPS.stubs is avoided except on first
+ * call, and possibly even then, if the binary is prelinked.  So we
+ * have to do the same thing that PPC backend does (see
+ * sysdeps/linux-gnu/ppc/plt.c for more details):
+ *
+ * - We scan .MIPS.stubs and decode the li t8,X instruction to figure
+ *   out which stub corresponds to which symbol.  These stubs will act
+ *   as PLT entries for us--that's where we put breakpoints.
+ *
+ * - After startup, we detect whether GOT entries corresponding to
+ *   MIPS stubs really contain the addresses that we expect.  If they
+ *   don't, presumably prelink has altered them, and they contain
+ *   already resolved addresses.  Where that is the case, we remember
+ *   that address, rewrite the GOT entry to point back to stub, and
+ *   mark the breakpoint as "resolved".  If GOT still contains the
+ *   stub address, we mark the breakpoint as "unresolved".
+ *
+ * - When a "resolved" breakpoint hits, we look up the resolved
+ *   address, move the PC there, and continue the process.
+ *
+ * - When an "unresolved" breakpoint hits, we stop all threads,
+ *   single-step through the whole dynamic linker, waiting for the GOT
+ *   entry to be updated.  When it is, we remember that value, rewrite
+ *   it back, mark the breakpoint as resolved, move the PC, and
+ *   continue the process.
+ *
+ * - As an optimization, we could remember the PC at which the GOT was
+ *   resolved, install there a breakpoint, and next time, instead of
+ *   single-stepping, we could let the process hit that breakpoint.
+ */
+
 /* Are we in pure CPIC mode (the non-PIC ABI extension)?  */
 static inline int
 mips_elf_is_cpic(unsigned int elf_flags)
@@ -47,84 +129,54 @@ mips_elf_is_cpic(unsigned int elf_flags)
 }
 
 /**
-   \param lte Structure containing link table entry information
-   \param ndx Index into .dynsym
-   \param rela Not used.
-   \return Address of GOT table entry
-
-   MIPS ABI Supplement:
-
-   DT_PLTGOT This member holds the address of the .got section.
-
-   DT_MIPS_SYMTABNO This member holds the number of entries in the
-   .dynsym section.
-
-   DT_MIPS_LOCAL_GOTNO This member holds the number of local global
-   offset table entries.
-
-   DT_MIPS_GOTSYM This member holds the index of the first dyamic
-   symbol table entry that corresponds to an entry in the gobal offset
-   table.
-
-   Called by read_elf when building the symbol table.
-
+ * \param lte Structure containing link table entry information
+ * \param ndx Index into .dynsym
+ *
+ * Called by read_elf when building the symbol table.
  */
 GElf_Addr
 arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela)
 {
-    debug(1,"plt_addr %zx ndx %#zx",lte->arch.pltgot_addr, ndx);
-
-    if (mips_elf_is_cpic(lte->ehdr.e_flags)) {
-        /* Return a pointer into the PLT.  */
-        return lte->plt_addr + 16 * 2 + (ndx * 16);
-    }
+	debug(1,"plt_addr %zx ndx %#zx",lte->arch.pltgot_addr, ndx);
 
-    /* Return a pointer to a GOT entry.  */
-    return lte->arch.pltgot_addr +
-	    sizeof(void *) * (lte->arch.mips_local_gotno
-			      + (ndx - lte->arch.mips_gotsym));
+	if (mips_elf_is_cpic(lte->ehdr.e_flags)) {
+		/* Return a pointer into the PLT.  */
+		return lte->plt_addr + 16 * 2 + (ndx * 16);
+	} else {
+		return rela->r_addend;
+	}
 }
-/**
-   \param proc The process to work on.
-   \param sym The library symbol.
-   \return What is at the got table address
 
-   The return value should be the address to put the breakpoint at.
-
-   On the mips the library_symbol.enter_addr is the .got addr for the
-   symbol and the breakpoint.addr is the actual breakpoint address.
-
-   Other processors use a plt, the mips is "special" in that is uses
-   the .got for both function and data relocations. Prior to program
-   startup, return 0.
-
-   \warning MIPS relocations are lazy. This means that the breakpoint
-   may move after the first call. Ltrace dictionary routines don't
-   have a delete and symbol is one to one with breakpoint, so if the
-   breakpoint changes I just add a new breakpoint for the new address.
+/**
+ * \param proc The process to work on.
+ * \param sym The library symbol.
+ * \return What is at the got table address
+ *
+ * The return value should be the address to put the breakpoint at.
+ *
+ * On the mips the library_symbol.enter_addr is the .got addr for the
+ * symbol and the breakpoint.addr is the actual breakpoint address.
+ *
+ * Other processors use a plt, the mips is "special" in that is uses
+ * the .got for both function and data relocations. Prior to program
+ * startup, return 0.
+ *
+ * \warning MIPS relocations are lazy. This means that the breakpoint
+ * may move after the first call. Ltrace dictionary routines don't
+ * have a delete and symbol is one to one with breakpoint, so if the
+ * breakpoint changes I just add a new breakpoint for the new address.
  */
 void *
 sym2addr(struct process *proc, struct library_symbol *sym)
 {
-    long ret;
-
-    if (sym->arch.pltalways
-        || (!sym->arch.gotonly && sym->plt_type == LS_TOPLT_NONE)) {
+	fprintf(stderr, "sym %s addr %p\n", sym->name, sym->enter_addr);
         return sym->enter_addr;
-    }
-
-    if(!proc->pid){
-        return 0;
-    }
-    ret=ptrace(PTRACE_PEEKTEXT, proc->pid, sym->enter_addr, 0);
-    if(ret==-1){
-        ret =0;
-    }
-    return (void *)ret;;
 }
 
 /* Address of run time loader map, used for debugging.  */
-#define DT_MIPS_RLD_MAP         0x70000016
+#ifndef DT_MIPS_RLD_MAP
+# define DT_MIPS_RLD_MAP         0x70000016
+#endif
 int
 arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr,
 		   arch_addr_t *ret)
@@ -145,60 +197,9 @@ arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr,
 	return r;
 }
 
-
-/*
- * MIPS doesn't have traditional got.plt entries with corresponding
- * relocations.
- *
- * sym_index is an offset into the external GOT entries. Filter out
- * stuff that are not functions.
- */
-int
-arch_get_sym_info(struct ltelf *lte, const char *filename,
-		  size_t sym_index, GElf_Rela *rela, GElf_Sym *sym)
-{
-	if (mips_elf_is_cpic(lte->ehdr.e_flags)) {
-		return gelf_getsym(lte->dynsym, ELF64_R_SYM(rela->r_info),
-				   sym) != NULL ? 0 : -1;
-	}
-
-	/* Fixup the offset.  */
-	sym_index += lte->arch.mips_gotsym;
-
-	if (gelf_getsym(lte->dynsym, sym_index, sym) == NULL)
-		return -1;
-
-	if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) {
-		const char *name = lte->dynstr + sym->st_name;
-		debug(2, "sym %s not a function", name);
-		return 1;
-	}
-
-	return 0;
-}
-
-/**
-  MIPS ABI Supplement:
-
-  DT_PLTGOT This member holds the address of the .got section.
-
-  DT_MIPS_SYMTABNO This member holds the number of entries in the
-  .dynsym section.
-
-  DT_MIPS_LOCAL_GOTNO This member holds the number of local global
-  offset table entries.
-
-  DT_MIPS_GOTSYM This member holds the index of the first dyamic
-  symbol table entry that corresponds to an entry in the gobal offset
-  table.
-
- */
 int
 arch_elf_init(struct ltelf *lte, struct library *lib)
 {
-	Elf_Scn *scn;
-	GElf_Shdr shdr;
-
 	/* FIXME: for CPIC we should really scan both GOT tables
 	 * to pick up relocations to external functions.  Right now
 	 * function pointers from the main binary to external functions
@@ -207,38 +208,143 @@ arch_elf_init(struct ltelf *lte, struct library *lib)
 		return 0; /* We are already done.  */
 	}
 
+	Elf_Scn *scn;
+	GElf_Shdr shdr;
 	if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0
 	    || scn == NULL) {
-	fail:
-		error(0, 0, "Couldn't get SHT_DYNAMIC: %s",
-		      elf_errmsg(-1));
+	fail_dyn:
+		fprintf(stderr, "Error reading SHT_DYNAMIC: %s\n",
+			elf_errmsg(-1));
 		return -1;
 	}
 
 	Elf_Data *data = elf_loaddata(scn, &shdr);
 	if (data == NULL)
-		goto fail;
+		goto fail_dyn;
 
 	size_t j;
 	for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {
 		GElf_Dyn dyn;
 		if (gelf_getdyn(data, j, &dyn) == NULL)
-			goto fail;
+			goto fail_dyn;
 
-		if(dyn.d_tag == DT_PLTGOT) {
+		if (dyn.d_tag == DT_PLTGOT)
 			lte->arch.pltgot_addr = dyn.d_un.d_ptr;
-		}
-		if(dyn.d_tag == DT_MIPS_LOCAL_GOTNO){
+		else if (dyn.d_tag == DT_MIPS_LOCAL_GOTNO)
 			lte->arch.mips_local_gotno = dyn.d_un.d_val;
-		}
-		if(dyn.d_tag == DT_MIPS_GOTSYM){
+		else if (dyn.d_tag == DT_MIPS_GOTSYM)
 			lte->arch.mips_gotsym = dyn.d_un.d_val;
+	}
+
+	if (lte->arch.mips_gotsym > lte->dynsym_count) {
+		fprintf(stderr,
+			"%s: DT_MIPS_GOTSYM larger than number of .dynsym's.\n",
+			lib->soname);
+		return -1;
+	}
+
+	/* In non-CPIC code, we need to tell ltrace about the
+	 * .MIPS.stubs entries.
+	 *
+	 * From the subset of .dynsym that is covered by GOT, we take
+	 * those symbols that are FUNC, UND, and have value != 0
+	 * (i.e. either non-WEAK, or defined).  Those ought to have a
+	 * stub associated.
+	 */
+
+	if (elf_get_section_named(lte, ".MIPS.stubs", &scn, &shdr) < 0) {
+	fail_stubs:
+		fprintf(stderr, "Error reading .MIPS.stubs: %s\n",
+			elf_errmsg(-1));
+		return -1;
+	}
+
+	if (scn == NULL) {
+		/* Maybe we don't need any stubs?  */
+		for (j = lte->arch.mips_gotsym; j < lte->dynsym_count; ++j) {
+			GElf_Sym sym_mem, *sym = gelf_getsym(lte->dynsym, j, &sym_mem);
+			if (sym == NULL) {
+				fprintf(stderr,
+					"%s: Error reading .dynsym: %s\n",
+					lte->soname, elf_errmsg(-1));
+				return -1;
+			}
+			if (GELF_ST_TYPE(sym->st_info) == STT_FUNC
+			    && sym->st_shndx == SHN_UNDEF
+			    && sym->st_value != 0) {
+				/* Nope, we do need a stub for this
+				 * symbol.  Warn the user.  */
+				fprintf(stderr,
+					"Can't trace undefined symbols in %s "
+					"without .MIPS.stubs.\n", lte->soname);
+				break;
+			}
 		}
+
+		return 0;
+	}
+
+	data = elf_loaddata(scn, &shdr);
+	if (data == NULL
+	    || data->d_size % 16 != 0 /* Four insns per stub.  */)
+		goto fail_stubs;
+
+	/* Stubs seem to be ordered in reverse order of .dynsym's.
+	 * Quite likely that's deliberate, as then we get to subtract
+	 * some multiple of symbol index from some base, and get to
+	 * the stub.  But to be sure, we'll scan the stubs.  */
+
+	for (j = 0; j < data->d_size / 16; ++j) {
+		uint32_t insn;
+		if (elf_read_u32(data, j * 16 + 12, &insn) < 0)
+			goto fail_stubs;
+
+		if (insn == 0)
+			continue;
+
+		/* 0x2418XXXX encodes lbu 0,t8,XXXX or li t8,XXXX.  */
+		if ((insn & 0xffff0000U) != 0x24180000U)
+			goto fail_stubs;
+
+		unsigned idx = insn & 0xffff;
+		if (idx < lte->arch.mips_gotsym || idx >= lte->dynsym_count)
+			goto fail_stubs;
+
+		GElf_Sym sym_mem, *sym
+			= gelf_getsym(lte->dynsym, idx, &sym_mem);
+		if (sym == NULL)
+			goto fail_stubs;
+
+		/* The GOT is organized as follows: first, there are
+		 * DT_MIPS_LOCAL_GOTNO local entries.  The rest are
+		 * entries for symbols from .dynsym, starting with
+		 * DT_MIPS_GOTSYM-th symbol.  After that there can be
+		 * further entries.  */
+		unsigned got_idx = idx - lte->arch.mips_gotsym
+			+ lte->arch.mips_local_gotno;
+		/* XXX Double cast.  */
+		arch_addr_t got_entry_addr
+			= (arch_addr_t) (uintptr_t) lte->arch.pltgot_addr
+			+ got_idx * 4;
+
+		GElf_Rela rela = {
+			/* XXX double cast.  */
+			.r_offset = (GElf_Addr) (uintptr_t) got_entry_addr,
+			.r_info = GELF_R_INFO(idx, R_MIPS_NONE),
+			.r_addend = j * 16 + shdr.sh_addr,
+		};
+
+		if (VECT_PUSHBACK(&lte->plt_relocs, &rela) < 0)
+			goto fail_stubs;
+
+		fprintf(stderr,
+			"added stub entry for symbol %u at %#lx, GOT @%p\n",
+			idx, (unsigned long) rela.r_addend, got_entry_addr);
 	}
 
 	/* Tell the generic code how many dynamic trace:able symbols
 	 * we've got.  */
-	lte->relplt_count = lte->dynsym_count - lte->arch.mips_gotsym;
+	// lte->relplt_count = lte->dynsym_count - lte->arch.mips_gotsym;
 	return 0;
 }
 
@@ -247,177 +353,389 @@ arch_elf_destroy(struct ltelf *lte)
 {
 }
 
-/* When functions return we check if the symbol needs an updated
-   breakpoint with the resolved address.  */
-void arch_symbol_ret(struct process *proc, struct library_symbol *libsym)
+void
+arch_symbol_ret(struct process *proc, struct library_symbol *libsym)
 {
-	struct breakpoint *bp;
-	arch_addr_t resolved_addr;
-	struct process *leader = proc->leader;
+}
 
-	/* Only deal with unresolved symbols.  */
-	if (libsym->arch.type != MIPS_PLT_UNRESOLVED)
-		return;
+void
+arch_dynlink_done(struct process *proc)
+{
+}
 
-	/* Get out if we are always using the PLT.  */
-	if (libsym->arch.pltalways)
-		return;
+static int
+read_got_entry(struct process *proc, GElf_Addr addr, GElf_Addr *valp)
+{
+	/* XXX double cast.  */
+	arch_addr_t a = (arch_addr_t) (uintptr_t) addr;
+	uint32_t l;
+	if (proc_read_32(proc, a, &l) < 0) {
+		fprintf(stderr, "ptrace read got entry @%#" PRIx64 ": %s\n",
+			addr, strerror(errno));
+		return -1;
+	}
 
-	resolved_addr = sym2addr(proc, libsym);
-	libsym->arch.resolved_addr = (uintptr_t) resolved_addr;
-	libsym->arch.type = MIPS_PLT_RESOLVED;
+	*valp = (GElf_Addr) l;
+	return 0;
+}
 
-	if (libsym->arch.stub_addr == libsym->arch.resolved_addr) {
-		/* Prelinked symbol. No need to add new breakpoint.  */
-		return;
-	}
+struct mips_unresolve_data {
+	struct mips_unresolve_data *self; /* A canary.  */
+	GElf_Addr got_entry_value;
+	GElf_Addr stub_addr;
+};
 
-	bp = malloc(sizeof (*bp));
-	if (bp == NULL) {
-		fprintf(stderr, "Failed to allocate bp for %s\n",
-			libsym->name);
-		return;
+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)
+{
+	if (mips_elf_is_cpic(lte->ehdr.e_flags))
+		return PLT_DEFAULT;
+
+	GElf_Addr got_entry_addr = rela->r_offset;
+	GElf_Addr stub_addr = rela->r_addend;
+
+	fprintf(stderr, "PLT-less arch_elf_add_plt_entry %s = %#llx\n",
+		a_name, stub_addr);
+
+	struct library_symbol *libsym = NULL;
+	if (default_elf_add_plt_entry(proc, lte, a_name, rela, ndx,
+				      &libsym) < 0) {
+		fprintf(stderr, "%s: failed %s(%#llx): %s\n", __func__,
+			a_name, stub_addr, strerror(errno));
+		goto fail;
 	}
+	libsym->arch.got_entry_addr = got_entry_addr;
+
+	GElf_Addr got_entry_value;
+	if (read_got_entry(proc, got_entry_addr, &got_entry_value) < 0)
+		goto fail;
+
+	fprintf(stderr, " + .got contains %#" PRIx64 "\n", got_entry_value);
+
+	if (got_entry_value == stub_addr || got_entry_value == 0) {
+		fprintf(stderr, "   + unresolved\n");
+		libsym->arch.type = MIPS_PLT_UNRESOLVED;
+		libsym->arch.resolved_value = stub_addr;
+
+	} else {
+		fprintf(stderr, "   + resolved\n");
 
-	if (breakpoint_init(bp, leader, resolved_addr, libsym) < 0)
-		goto err;
+		/* Mark the symbol for later unresolving.  We may not
+		 * do this right away, as this is called by ltrace
+		 * core for all symbols, and only later filtered.  We
+		 * only unresolve the symbol before the breakpoint is
+		 * enabled.  */
 
-	if (proc_add_breakpoint(leader, bp) < 0) {
-		breakpoint_destroy(bp);
-		goto err;
+		libsym->arch.type = MIPS_PLT_NEED_UNRESOLVE;
+		libsym->arch.data = malloc(sizeof *libsym->arch.data);
+		if (libsym->arch.data == NULL)
+			goto fail;
+
+		libsym->arch.data->self = libsym->arch.data;
+		libsym->arch.data->got_entry_value = got_entry_value;
+		libsym->arch.data->stub_addr = stub_addr;
 	}
 
-	if (breakpoint_turn_on(bp, leader) < 0) {
-		proc_remove_breakpoint(leader, bp);
-		breakpoint_destroy(bp);
-		goto err;
+	*ret = libsym;
+	return PLT_OK;
+
+fail:
+	if (libsym != NULL) {
+		library_symbol_destroy(libsym);
+		free(libsym);
 	}
-	return;
-err:
-	free(bp);
+	return PLT_FAIL;
 }
 
-static enum callback_status
-cb_enable_breakpoint_sym(struct library_symbol *libsym, void *data)
+int
+arch_library_symbol_init(struct library_symbol *libsym)
 {
-	struct process *proc = data;
-	arch_addr_t bp_addr;
+	/* We set type explicitly in the code above, where we have the
+	 * necessary context.  */
+	libsym->arch.type = MIPS_PLT_DEFAULT;
+	libsym->arch.got_entry_addr = 0;
+	return 0;
+}
 
-	if (!libsym->arch.gotonly)
-		return CBS_CONT;
+void
+arch_library_symbol_destroy(struct library_symbol *libsym)
+{
+	if (libsym->arch.type == MIPS_PLT_NEED_UNRESOLVE) {
+		assert(libsym->arch.data->self == libsym->arch.data);
+		free(libsym->arch.data);
+		libsym->arch.data = NULL;
+	}
+}
 
-	/* Update state.  */
-	bp_addr = sym2addr(proc, libsym);
-	/* XXX The cast to uintptr_t should be removed when
-	 * arch_addr_t becomes integral type.  keywords: double cast.  */
-	libsym->arch.resolved_addr = (uintptr_t) bp_addr;
+int
+arch_library_symbol_clone(struct library_symbol *retp,
+                          struct library_symbol *libsym)
+{
+	retp->arch = libsym->arch;
+	return 0;
+}
 
-	if (libsym->arch.resolved_addr == 0)
-		/* FIXME: What does this mean?  */
-		return CBS_CONT;
+static void
+jump_to_entry_point(struct process *proc, struct breakpoint *bp)
+{
+	/* XXX The double cast should be removed when
+	 * arch_addr_t becomes integral type.  */
+	arch_addr_t rv = (arch_addr_t)
+		(uintptr_t) bp->libsym->arch.resolved_value;
+	set_instruction_pointer(proc, rv);
+	ptrace(PTRACE_POKEUSER, proc->pid, off_t9, rv);
+}
+
+static int
+unresolve_got_entry(struct process *proc, GElf_Addr addr, GElf_Addr value)
+{
+	uint32_t v32 = (uint32_t) value;
+	uint32_t a32 = (uint32_t) addr;
+	if (ptrace(PTRACE_POKETEXT, proc->pid, a32, v32) < 0) {
+		fprintf(stderr, "failed to unresolve GOT entry: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	return 0;
+}
 
+static void
+mark_as_resolved(struct library_symbol *libsym, GElf_Addr value)
+{
 	libsym->arch.type = MIPS_PLT_RESOLVED;
+	libsym->arch.resolved_value = value;
+}
 
-	/* Now, activate the symbol causing a breakpoint to be added.  */
-	if (proc_activate_delayed_symbol(proc, libsym) < 0) {
-		fprintf(stderr, "Failed to activate delayed sym %s\n",
-			libsym->name);
-	}
-	return CBS_CONT;
+static void
+dl_got_update_bp_on_hit(struct breakpoint *bp, struct process *proc)
+{
+	debug(DEBUG_PROCESS, "pid=%d dl_got_update_bp_on_hit %s(%p)",
+	      proc->pid, breakpoint_name(bp), bp->addr);
+	struct process_stopping_handler *self = proc->arch.handler;
+	assert(self != NULL);
+
+	struct library_symbol *libsym = self->breakpoint_being_enabled->libsym;
+	GElf_Addr got_entry_value;
+	if (read_got_entry(proc, libsym->arch.got_entry_addr,
+			   &got_entry_value) < 0)
+		return;
+
+	if (unresolve_got_entry(proc, libsym->arch.got_entry_addr,
+				libsym->arch.resolved_value) < 0)
+		return;
+	mark_as_resolved(libsym, got_entry_value);
+
+	/* cb_on_all_stopped looks if HANDLER is set to NULL as a way
+	 * to check that this was run.  It's an error if it
+	 * wasn't.  */
+	proc->arch.handler = NULL;
+
+	breakpoint_turn_off(bp, proc);
 }
 
 static enum callback_status
-cb_enable_breakpoint_lib(struct process *proc, struct library *lib, void *data)
+cb_keep_stepping_p(struct process_stopping_handler *self)
 {
-	library_each_symbol(lib, NULL, cb_enable_breakpoint_sym, proc);
-	return CBS_CONT;
+	struct process *proc = self->task_enabling_breakpoint;
+	struct library_symbol *libsym = self->breakpoint_being_enabled->libsym;
+
+	GElf_Addr got_entry_value;
+	if (read_got_entry(proc, libsym->arch.got_entry_addr,
+			   &got_entry_value) < 0)
+		return CBS_FAIL;
+
+	/* In UNRESOLVED state, the RESOLVED_VALUE in fact contains
+	 * the PLT entry value.  */
+	if (got_entry_value == libsym->arch.resolved_value)
+		return CBS_CONT;
+	fprintf(stderr, "%#" PRIx64 " resolved to %#" PRIx64 "\n",
+		libsym->arch.got_entry_addr, got_entry_value);
+
+	/* The GOT entry got resolved!  We can migrate the breakpoint
+	 * to RESOLVED and stop single-stepping.  */
+	if (unresolve_got_entry(proc, libsym->arch.got_entry_addr,
+				libsym->arch.resolved_value) < 0)
+		return CBS_FAIL;
+
+	/* Install breakpoint to the address where the change takes
+	 * place.  If we fail, then that just means that we'll have to
+	 * singlestep the next time around as well.  */
+	struct process *leader = proc->leader;
+	if (leader == NULL || leader->arch.dl_got_update_bp != NULL)
+		goto done;
+
+	/* We need to install to the next instruction.  ADDR points to
+	 * a store instruction, so moving the breakpoint one
+	 * instruction forward is safe.  */
+	arch_addr_t addr = get_instruction_pointer(proc) + 4;
+	leader->arch.dl_got_update_bp = insert_breakpoint_at(proc, addr, NULL);
+	if (leader->arch.dl_got_update_bp == NULL)
+		goto done;
+
+	static struct bp_callbacks dl_got_update_cbs = {
+		.on_hit = dl_got_update_bp_on_hit,
+	};
+	leader->arch.dl_got_update_bp->cbs = &dl_got_update_cbs;
+
+	/* Turn it off for now.  We will turn it on again when we hit
+	 * the PLT entry that needs this.  */
+	breakpoint_turn_off(leader->arch.dl_got_update_bp, proc);
+
+done:
+	mark_as_resolved(libsym, got_entry_value);
+	return CBS_STOP;
 }
 
-void arch_dynlink_done(struct process *proc)
+static void
+cb_on_all_stopped(struct process_stopping_handler *self)
 {
-	proc_each_library(proc->leader, NULL, cb_enable_breakpoint_lib, NULL);
+	/* Put that in for dl_got_update_bp_on_hit to see.  */
+	assert(self->task_enabling_breakpoint->arch.handler == NULL);
+	self->task_enabling_breakpoint->arch.handler = self;
+
+	linux_ptrace_disable_and_continue(self);
 }
 
-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)
+static void
+mips_stub_bp_continue(struct breakpoint *bp, struct process *proc)
 {
-	char *name = NULL;
-	int sym_index = ndx + lte->arch.mips_gotsym;
-
-	struct library_symbol *libsym = malloc(sizeof(*libsym));
-	if (libsym == NULL)
-		return PLT_FAIL;
+	switch (bp->libsym->arch.type) {
+		struct process *leader;
+		void (*on_all_stopped)(struct process_stopping_handler *);
+		enum callback_status (*keep_stepping_p)
+			(struct process_stopping_handler *);
+
+	case MIPS_PLT_RESOLVED:
+		fprintf(stderr, "hit resolved BP %s\n", bp->libsym->name);
+		jump_to_entry_point(proc, bp);
+		continue_process(proc->pid);
+		return;
 
-	GElf_Addr addr = arch_plt_sym_val(lte, sym_index, 0);
+	case MIPS_PLT_UNRESOLVED:
+		on_all_stopped = NULL;
+		keep_stepping_p = NULL;
+		leader = proc->leader;
+
+		if (leader != NULL && leader->arch.dl_got_update_bp != NULL
+		    && breakpoint_turn_on(leader->arch.dl_got_update_bp,
+					  proc) >= 0)
+			on_all_stopped = cb_on_all_stopped;
+		else
+			keep_stepping_p = cb_keep_stepping_p;
+
+		if (process_install_stopping_handler
+		    (proc, bp, on_all_stopped, keep_stepping_p, NULL) < 0) {
+			fprintf(stderr,	"mips_stub_bp_continue: "
+				"couldn't install event handler\n");
+			continue_after_breakpoint(proc, bp);
+		}
+		return;
 
-	name = strdup(a_name);
-	if (name == NULL) {
-		fprintf(stderr, "%s: failed %s(%#llx): %s\n", __func__,
-			name, addr, strerror(errno));
-		goto fail;
+	case MIPS_PLT_DEFAULT:
+	case MIPS_PLT_NEED_UNRESOLVE:
+		/* We should never get here.  */
+		break;
 	}
 
-	/* XXX The double cast should be removed when
-	 * arch_addr_t becomes integral type.  */
-	if (library_symbol_init(libsym,
-				(arch_addr_t) (uintptr_t) addr,
-				name, 1, LS_TOPLT_EXEC) < 0) {
-		fprintf(stderr, "%s: failed %s : %llx\n", __func__, name, addr);
-		goto fail;
-	}
+	assert(bp->libsym->arch.type != bp->libsym->arch.type);
+	abort();
+}
 
-	arch_addr_t bp_addr = sym2addr(proc, libsym);
-	/* XXX This cast should be removed when
-	 * arch_addr_t becomes integral type.  keywords: double cast. */
-	libsym->arch.stub_addr = (uintptr_t) bp_addr;
+static void
+mips_stub_bp_retract(struct breakpoint *bp, struct process *proc)
+{
+	/* This should not be an artificial breakpoint.  */
+	struct library_symbol *libsym = bp->libsym;
+	assert(libsym != NULL);
 
-	if (bp_addr == 0) {
-		/* Function pointers without PLT entries.  */
-		libsym->plt_type = LS_TOPLT_NONE;
-		libsym->arch.gotonly = 1;
-		libsym->arch.type = MIPS_PLT_UNRESOLVED;
+	fprintf(stderr, "May need to retract %s.\n", libsym->name);
+}
 
-		/* Delay breakpoint activation until the symbol gets
-		 * resolved.  */
-		libsym->delayed = 1;
-	} else if (mips_elf_is_cpic(lte->ehdr.e_flags)) {
-		libsym->arch.pltalways = 1;
+static void
+mips_stub_bp_install(struct breakpoint *bp, struct process *proc)
+{
+	/* This should not be an artificial breakpoint.  */
+	struct library_symbol *libsym = bp->libsym;
+	assert(libsym != NULL);
+
+	if (libsym->arch.type == MIPS_PLT_NEED_UNRESOLVE) {
+		assert(! "MIPS_PLT_NEED_UNRESOLVE unsupported");
+		abort();
+		/* Here comes unresolve code.  */
 	}
+}
 
-	*ret = libsym;
-	return PLT_OK;
+/* For some symbol types, we need to set up custom callbacks.  */
+int
+arch_breakpoint_init(struct process *proc, struct breakpoint *bp)
+{
+	/* Artificial, entry-point breakpoints are plain.  Breakpoints
+	 * for PLT-ful symbols are also plain.  */
+	if (bp->libsym == NULL || bp->libsym->plt_type != LS_TOPLT_EXEC
+	    || bp->libsym->arch.type == MIPS_PLT_DEFAULT)
+		return 0;
+
+	fprintf(stderr, "new STUB breakpoint at %p\n", bp->addr);
+	static struct bp_callbacks cbs = {
+		.on_continue = mips_stub_bp_continue,
+		.on_retract = mips_stub_bp_retract,
+		.on_install = mips_stub_bp_install,
+	};
+	breakpoint_set_callbacks(bp, &cbs);
 
-fail:
-	free(name);
-	free(libsym);
-	return PLT_FAIL;
+	return 0;
+}
+
+void
+arch_breakpoint_destroy(struct breakpoint *bp)
+{
 }
 
 int
-arch_library_symbol_init(struct library_symbol *libsym)
+arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
 {
-	libsym->arch.pltalways = 0;
-	libsym->arch.gotonly = 0;
-	libsym->arch.type = MIPS_PLT_UNRESOLVED;
-	if (libsym->plt_type == LS_TOPLT_NONE) {
-		libsym->arch.type = MIPS_PLT_RESOLVED;
-	}
+	retp->arch = sbp->arch;
+	return 0;
+}
+
+int
+arch_process_init(struct process *proc)
+{
+	proc->arch.dl_got_update_bp = NULL;
+	proc->arch.handler = NULL;
 	return 0;
 }
 
 void
-arch_library_symbol_destroy(struct library_symbol *libsym)
+arch_process_destroy(struct process *proc)
 {
 }
 
 int
-arch_library_symbol_clone(struct library_symbol *retp,
-                          struct library_symbol *libsym)
+arch_process_clone(struct process *retp, struct process *proc)
 {
-	retp->arch = libsym->arch;
+	retp->arch = proc->arch;
+
+	if (retp->arch.dl_got_update_bp != NULL) {
+		/* Point it to the corresponding breakpoint in RETP.
+		 * It must be there, this part of PROC has already
+		 * been cloned to RETP.  */
+		retp->arch.dl_got_update_bp
+			= address2bpstruct(retp,
+					   retp->arch.dl_got_update_bp->addr);
+
+		assert(retp->arch.dl_got_update_bp != NULL);
+	}
+
 	return 0;
 }
 
+int
+arch_process_exec(struct process *proc)
+{
+	return arch_process_init(proc);
+}
+
 /**@}*/

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/ltrace.git



More information about the ltrace-commits mailing list