[Ltrace-devel] [PATCH v2] Tracing PLT-less MIPS binaries
Faraz Shahbazker
faraz.shahbazker at imgtec.com
Mon Jan 26 19:09:29 UTC 2015
Patch enclosed (on top of pmachata/mips)
Thanks,
Faraz Shahbazker
-------------- next part --------------
diff --git a/sysdeps/linux-gnu/mips/arch.h b/sysdeps/linux-gnu/mips/arch.h
index 16273d2..ffaf1b3 100644
--- a/sysdeps/linux-gnu/mips/arch.h
+++ b/sysdeps/linux-gnu/mips/arch.h
@@ -46,6 +46,8 @@ struct arch_ltelf_data {
size_t pltgot_addr;
size_t mips_local_gotno;
size_t mips_gotsym;
+ int mips_pie_main;
+ int mips_prelinked;
};
#define ARCH_HAVE_FIND_DL_DEBUG
diff --git a/sysdeps/linux-gnu/mips/plt.c b/sysdeps/linux-gnu/mips/plt.c
index 84e2234..ee5c811 100644
--- a/sysdeps/linux-gnu/mips/plt.c
+++ b/sysdeps/linux-gnu/mips/plt.c
@@ -139,7 +139,7 @@ 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)) {
+ if (mips_elf_is_cpic(lte->ehdr.e_flags) && lte->plt_addr != 0) {
/* Return a pointer into the PLT. */
return lte->plt_addr + 16 * 2 + (ndx * 16);
} else {
@@ -204,7 +204,7 @@ arch_elf_init(struct ltelf *lte, struct library *lib)
* to pick up relocations to external functions. Right now
* function pointers from the main binary to external functions
* can't be traced in CPIC mode. */
- if (mips_elf_is_cpic(lte->ehdr.e_flags)) {
+ if (mips_elf_is_cpic(lte->ehdr.e_flags) && lte->plt_addr != 0) {
return 0; /* We are already done. */
}
@@ -234,6 +234,8 @@ arch_elf_init(struct ltelf *lte, struct library *lib)
lte->arch.mips_local_gotno = dyn.d_un.d_val;
else if (dyn.d_tag == DT_MIPS_GOTSYM)
lte->arch.mips_gotsym = dyn.d_un.d_val;
+ else if (dyn.d_tag == DT_GNU_PRELINKED)
+ lte->arch.mips_prelinked = 1;
}
if (lte->arch.mips_gotsym > lte->dynsym_count) {
@@ -342,6 +344,11 @@ arch_elf_init(struct ltelf *lte, struct library *lib)
idx, (unsigned long) rela.r_addend, got_entry_addr);
}
+ /* For PIE main binaries, GOT entries are not biased until
+ * we hit the entry-point. Mark such ltelfs for correction */
+ if (lte->ehdr.e_type == ET_DYN && lib->type == LT_LIBTYPE_MAIN)
+ lte->arch.mips_pie_main = 1;
+
/* Tell the generic code how many dynamic trace:able symbols
* we've got. */
// lte->relplt_count = lte->dynsym_count - lte->arch.mips_gotsym;
@@ -390,11 +397,11 @@ 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))
+ if (mips_elf_is_cpic(lte->ehdr.e_flags) && (lte->plt_addr != 0))
return PLT_DEFAULT;
- GElf_Addr got_entry_addr = rela->r_offset;
- GElf_Addr stub_addr = rela->r_addend;
+ GElf_Addr got_entry_addr = rela->r_offset + lte->bias;
+ GElf_Addr stub_addr = rela->r_addend + lte->bias;
fprintf(stderr, "PLT-less arch_elf_add_plt_entry %s = %#llx\n",
a_name, stub_addr);
@@ -414,6 +421,10 @@ arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte,
fprintf(stderr, " + .got contains %#" PRIx64 "\n", got_entry_value);
+ /* For PIE main binaries, GOT entries are not yet biased */
+ if (lte->arch.mips_pie_main && !lte->arch.mips_prelinked)
+ got_entry_value += lte->bias;
+
if (got_entry_value == stub_addr || got_entry_value == 0) {
fprintf(stderr, " + unresolved\n");
libsym->arch.type = MIPS_PLT_UNRESOLVED;
@@ -650,7 +661,9 @@ mips_stub_bp_retract(struct breakpoint *bp, struct process *proc)
struct library_symbol *libsym = bp->libsym;
assert(libsym != NULL);
- fprintf(stderr, "May need to retract %s.\n", libsym->name);
+ /* Restore resolved .got entry before detaching */
+ unresolve_got_entry(proc, libsym->arch.got_entry_addr,
+ libsym->arch.resolved_value);
}
static void
@@ -661,9 +674,14 @@ mips_stub_bp_install(struct breakpoint *bp, struct process *proc)
assert(libsym != NULL);
if (libsym->arch.type == MIPS_PLT_NEED_UNRESOLVE) {
- assert(! "MIPS_PLT_NEED_UNRESOLVE unsupported");
- abort();
- /* Here comes unresolve code. */
+ /* Re-route the got-entry to the stub and save resolved address
+ for the break-point handler */
+ GElf_Addr got_entry_value = libsym->arch.data->got_entry_value;
+
+ if (unresolve_got_entry(proc, libsym->arch.got_entry_addr,
+ libsym->arch.data->stub_addr) < 0)
+ return;
+ mark_as_resolved(libsym, got_entry_value);
}
}
diff --git a/sysdeps/linux-gnu/mips/trace.c b/sysdeps/linux-gnu/mips/trace.c
index 88e13ac..6e673ce 100644
--- a/sysdeps/linux-gnu/mips/trace.c
+++ b/sysdeps/linux-gnu/mips/trace.c
@@ -264,30 +264,110 @@ fail:
return 0;
}
+/**
+ * \param proc The process to work on.
+ * \param pc The current program counter
+ * \return newpc Array of next possible PC values
+ * \return nr Count of next possible PC values
+ *
+ * An atomic Read-Modify-Write, starting with LL and ending with SC needs to
+ * be treated as a single instruction and stepped over, otherwise ERET issued
+ * within the SYSCALL will cause the write to fail, even for a single thread
+ * of execution. LL and SC must exist within a 2048-byte contiguous region.
+ *
+ * We record all outgoing branches from the LL-SC sequence and try to set
+ * breakpoints at their destinations. Currently, ltrace only allows 2
+ * breakpoints per single step, so if there are more than one outgoing
+ * branches within the LL-SC sequence, single-stepping will eventually fail.
+ *
+ * The first possible PC value is always the instruction following the
+ * store-conditional, or if no SC is found within 2048 bytes, the next
+ * instruction following LL. Rest are destination addresses of outgoing
+ * branches within the LL-SC sequence.
+ */
+#define inrange(x,lo,hi) ((x)<=(hi) && (x)>=(lo))
+static int
+mips_atomic_next_pcs(struct process *proc, uint32_t pc, uint32_t *newpcs)
+{
+ uint32_t spc, lladdr = pc, scaddr = pc + 4;
+ int nr = 0;
+
+ for (lladdr = pc; scaddr - lladdr <= 2048; scaddr += 4) {
+ /* Found SC, now stepover trailing branch */
+ uint32_t inst;
+ if (proc_read_32(proc, (arch_addr_t)scaddr, &inst) >= 0 &&
+ itype_op(inst) == 0x38) {
+ newpcs[nr++] = scaddr + 4;
+ break;
+ }
+ }
+
+ /* No SC within 2048 bytes, assume LL is standalone */
+ if (nr == 0) {
+ scaddr = 0;
+ newpcs[nr++] = lladdr + 4;
+ }
+
+ /* Scan LL<->SC range for branches going outside that range */
+ for (spc = lladdr + 4; spc < scaddr; spc += 4) {
+ uint32_t scanpcs[2];
+ int snr = mips_next_pcs(proc, spc, scanpcs);
+
+ if (!inrange(scanpcs[0], lladdr, scaddr)) {
+ if ((newpcs = realloc(newpcs,
+ (nr + 1) * sizeof(pc))) != NULL)
+ newpcs[nr++] = scanpcs[0];
+ }
+
+ if ((snr == 2) && !inrange(scanpcs[1], lladdr, scaddr)) {
+ if ((newpcs = realloc(newpcs,
+ (nr + 1) * sizeof(pc))) != NULL)
+ newpcs[nr++] = scanpcs[1];
+ }
+ }
+
+ assert(nr > 0);
+ return nr;
+}
+
enum sw_singlestep_status
arch_sw_singlestep(struct process *proc, struct breakpoint *bp,
int (*add_cb)(arch_addr_t, struct sw_singlestep_data *),
struct sw_singlestep_data *add_cb_data)
{
uint32_t pc = (uint32_t) get_instruction_pointer(proc);
- uint32_t newpcs[2];
+ uint32_t *newpcs;
int nr;
+ uint32_t inst;
+
+ if (proc_read_32(proc, (arch_addr_t)pc, &inst) < 0)
+ return SWS_FAIL;
- nr = mips_next_pcs(proc, pc, newpcs);
+ newpcs = malloc(2 * sizeof(uint32_t));
+
+ /* Starting an atomic read-modify-write sequence */
+ if (itype_op(inst) == 0x30)
+ nr = mips_atomic_next_pcs(proc, pc, newpcs);
+ else
+ nr = mips_next_pcs(proc, pc, newpcs);
while (nr-- > 0) {
arch_addr_t baddr = (arch_addr_t) newpcs[nr];
+
/* Not sure what to do here. We've already got a bp? */
if (DICT_HAS_KEY(proc->leader->breakpoints, &baddr)) {
fprintf(stderr, "skip %p %p\n", baddr, add_cb_data);
continue;
}
- if (add_cb(baddr, add_cb_data) < 0)
+ if (add_cb(baddr, add_cb_data) < 0) {
+ free(newpcs);
return SWS_FAIL;
+ }
}
ptrace(PTRACE_SYSCALL, proc->pid, 0, 0);
+ free(newpcs);
return SWS_OK;
}
More information about the Ltrace-devel
mailing list