summaryrefslogtreecommitdiff
path: root/sysdeps/linux-gnu/mips/trace.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/linux-gnu/mips/trace.c')
-rw-r--r--sysdeps/linux-gnu/mips/trace.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/sysdeps/linux-gnu/mips/trace.c b/sysdeps/linux-gnu/mips/trace.c
new file mode 100644
index 0000000..88e13ac
--- /dev/null
+++ b/sysdeps/linux-gnu/mips/trace.c
@@ -0,0 +1,381 @@
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2013 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2012 Edgar E. Iglesias, Axis Communications
+ * Copyright (C) 2010 Arnaud Patard, Mandriva SA
+ * Copyright (C) 2008,2009 Juan Cespedes
+ * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <sys/ptrace.h>
+#include <asm/ptrace.h>
+#include <assert.h>
+
+#include "backend.h"
+#include "common.h"
+#include "debug.h"
+#include "mips.h"
+#include "proc.h"
+#include "type.h"
+
+#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
+# define PTRACE_PEEKUSER PTRACE_PEEKUSR
+#endif
+
+#if (!defined(PTRACE_POKEUSER) && defined(PTRACE_POKEUSR))
+# define PTRACE_POKEUSER PTRACE_POKEUSR
+#endif
+
+
+/**
+ \addtogroup mips Mips specific functions.
+
+ These are the functions that it looks like I need to implement in
+ order to get ltrace to work on our target.
+
+ @{
+ */
+
+/**
+ \param proc The process that had an event.
+
+ Called by \c next_event() right after the return from wait.
+
+ Most targets just return here. A couple use proc->arch_ptr for a
+ private data area.
+ */
+void
+get_arch_dep(struct process *proc)
+{
+}
+
+/**
+ \param proc Process that had event.
+ \param status From \c wait()
+ \param sysnum 0-based syscall number.
+ \return 1 if syscall, 2 if sysret, 0 otherwise.
+
+ Called by \c next_event() after the call to get_arch_dep()
+
+ It seems that the ptrace call trips twice on a system call, once
+ just before the system call and once when it returns. Both times,
+ the pc points at the instruction just after the mips "syscall"
+ instruction.
+
+ There are several possiblities for system call sets, each is offset
+ by a base from the others. On our system, it looks like the base
+ for the system calls is 4000.
+ */
+int
+syscall_p(struct process *proc, int status, int *sysnum)
+{
+ if (WIFSTOPPED(status)
+ && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
+ /* get the user's pc (plus 8) */
+ long pc = (long)get_instruction_pointer(proc);
+ /* fetch the SWI instruction */
+ int insn = ptrace(PTRACE_PEEKTEXT, proc->pid, pc - 4, 0);
+ int num = ptrace(PTRACE_PEEKTEXT, proc->pid, pc - 8, 0);
+
+ /*
+ On a mips, syscall looks like:
+ 24040fa1 li v0, 0x0fa1 # 4001 --> _exit syscall
+ 0000000c syscall
+ */
+ if(insn!=0x0000000c){
+ return 0;
+ }
+
+ *sysnum = (num & 0xFFFF) - 4000;
+ /* if it is a syscall, return 1 or 2 */
+ if (proc->callstack_depth > 0 &&
+ proc->callstack[proc->callstack_depth - 1].is_syscall &&
+ proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) {
+ return 2;
+ }
+
+ if (*sysnum >= 0) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Based on GDB code. */
+#define mips32_op(x) (x >> 26)
+#define itype_op(x) (x >> 26)
+#define itype_rs(x) ((x >> 21) & 0x1f)
+#define itype_rt(x) ((x >> 16) & 0x1f)
+#define itype_immediate(x) (x & 0xffff)
+
+#define jtype_op(x) (x >> 26)
+#define jtype_target(x) (x & 0x03ffffff)
+
+#define rtype_op(x) (x >> 26)
+#define rtype_rs(x) ((x >> 21) & 0x1f)
+#define rtype_rt(x) ((x >> 16) & 0x1f)
+#define rtype_rd(x) ((x >> 11) & 0x1f)
+#define rtype_shamt(x) ((x >> 6) & 0x1f)
+#define rtype_funct(x) (x & 0x3f)
+
+static int32_t
+mips32_relative_offset (uint32_t inst)
+{
+ return ((itype_immediate(inst) ^ 0x8000) - 0x8000) << 2;
+}
+
+int mips_next_pcs(struct process *proc, uint32_t pc, uint32_t *newpc)
+{
+ uint32_t inst, rx;
+ int op;
+ int rn;
+ int nr = 0;
+
+ inst = ptrace(PTRACE_PEEKTEXT, proc->pid, pc, 0);
+
+ if ((inst & 0xe0000000) != 0) {
+ /* Check for branches. */
+ if (itype_op(inst) >> 2 == 5) {
+ /* BEQL, BNEL, BLEZL, BGTZL: bits 0101xx */
+ op = (itype_op(inst) & 0x03);
+ switch (op)
+ {
+ case 0: /* BEQL */
+ case 1: /* BNEL */
+ case 2: /* BLEZL */
+ case 3: /* BGTZL */
+ newpc[nr++] = pc + 8;
+ newpc[nr++] = pc + 4 +
+ mips32_relative_offset(inst);
+ break;
+ default:
+ newpc[nr++] = pc + 4;
+ break;
+ }
+ } else if (itype_op(inst) == 17 && itype_rs(inst) == 8) {
+ /* Step over the branch. */
+ newpc[nr++] = pc + 8;
+ newpc[nr++] = pc + mips32_relative_offset(inst) + 4;
+ } else {
+ newpc[nr++] = pc + 4;
+ }
+ } else {
+ /* Further subdivide into SPECIAL, REGIMM and other. */
+ switch (op = itype_op(inst) & 0x07)
+ {
+ case 0:
+ op = rtype_funct(inst);
+ switch (op)
+ {
+ case 8: /* JR */
+ case 9: /* JALR */
+ rn = rtype_rs(inst);
+
+ rx = ptrace(PTRACE_PEEKUSER,proc->pid, rn, 0);
+ newpc[nr++] = rx;
+ break;
+ default:
+ case 12: /* SYSCALL */
+ newpc[nr++] = pc + 4;
+ break;
+ }
+ break;
+ case 1:
+ op = itype_rt(inst);
+ switch (op)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ newpc[nr++] = pc + 8;
+ newpc[nr++] = pc + 4 +
+ mips32_relative_offset(inst);
+ break;
+ default:
+ newpc[nr++] = pc + 4;
+ break;
+ }
+ break;
+ case 2: /* J */
+ case 3: /* JAL */
+ rx = jtype_target(inst) << 2;
+ /* Upper four bits get never changed... */
+ newpc[nr++] = rx + ((pc + 4) & ~0x0fffffff);
+ break;
+ case 4: /* BEQ */
+ if (itype_rs(inst) == itype_rt(inst)) {
+ /* Compare the same reg for equality, always
+ * follow the branch. */
+ newpc[nr++] = pc + 4 +
+ mips32_relative_offset(inst);
+ break;
+ }
+ /* Fall through. */
+ default:
+ case 5:
+ case 6:
+ case 7:
+ /* Step over the branch. */
+ newpc[nr++] = pc + 8;
+ newpc[nr++] = pc + mips32_relative_offset(inst) + 4;
+ break;
+ }
+ }
+ if (nr <= 0 || nr > 2)
+ goto fail;
+ if (nr == 2) {
+ if (newpc[1] == 0)
+ goto fail;
+ }
+ if (newpc[0] == 0)
+ goto fail;
+
+ assert(nr == 1 || nr == 2);
+ return nr;
+
+fail:
+ printf("nr=%d pc=%x\n", nr, pc);
+ printf("pc=%x %x\n", newpc[0], newpc[1]);
+ return 0;
+}
+
+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];
+ int nr;
+
+ 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)
+ return SWS_FAIL;
+ }
+
+ ptrace(PTRACE_SYSCALL, proc->pid, 0, 0);
+ return SWS_OK;
+}
+
+/**
+ \param type Function/syscall call or return.
+ \param proc The process that had an event.
+ \param arg_num -1 for return value,
+ \return The argument to fetch.
+
+ A couple of assumptions.
+
+- Type is LT_TOF_FUNCTIONR or LT_TOF_SYSCALLR if arg_num==-1. These
+ types are only used in calls for output_right(), which only uses -1
+ for arg_num.
+- Type is LT_TOF_FUNCTION or LT_TOF_SYSCALL for args 0...4.
+- I'm only displaying the first 4 args (Registers a0..a3). Good
+ enough for now.
+
+ Mips conventions seem to be:
+- syscall parameters: r4...r9
+- syscall return: if(!a3){ return v0;} else{ errno=v0;return -1;}
+- function call: r4..r7. Not sure how to get arg number 5.
+- function return: v0
+
+The argument registers are wiped by a call, so it is a mistake to ask
+for arguments on a return. If ltrace does this, we will need to cache
+arguments somewhere on the call.
+
+I'm not doing any floating point support here.
+
+*/
+long
+gimme_arg(enum tof type, struct process *proc, int arg_num,
+ struct arg_type_info *info)
+{
+ long ret;
+ long addr;
+ debug(2,"type %d arg %d",type,arg_num);
+ if (arg_num == -1) {
+ if(type == LT_TOF_FUNCTIONR) {
+ return ptrace(PTRACE_PEEKUSER,proc->pid,off_v0,0);
+ }
+ if (type == LT_TOF_SYSCALLR) {
+ unsigned a3=ptrace(PTRACE_PEEKUSER, proc->pid,off_a3,0);
+ unsigned v0=ptrace(PTRACE_PEEKUSER, proc->pid,off_v0,0);
+ if(!a3){
+ return v0;
+ }
+ return -1;
+ }
+ }
+ if (type == LT_TOF_FUNCTION || type == LT_TOF_SYSCALL) {
+ /* o32: float args are in f12 and f14 */
+ if ((info->type == ARGTYPE_FLOAT) && (arg_num < 2)) {
+ ret=ptrace(PTRACE_PEEKUSER,proc->pid,off_fpr0+12+arg_num*2,0);
+ debug(2,"ret = %#lx",ret);
+ return ret;
+ }
+ if(arg_num <4){
+ ret=ptrace(PTRACE_PEEKUSER,proc->pid,off_a0+arg_num,0);
+ debug(2,"ret = %#lx",ret);
+ return ret;
+ } else {
+ /* not sure it's going to work for something else than syscall */
+ addr=ptrace(PTRACE_PEEKUSER,proc->pid,off_sp,0);
+ if (addr == -1) {
+ debug(2,"ret = %#lx",addr);
+ return addr;
+ }
+ ret = addr + 4*arg_num;
+ ret=ptrace(PTRACE_PEEKTEXT,proc->pid,addr,0);
+ debug(2,"ret = %#lx",ret);
+ return ret;
+ }
+ }
+ if (type == LT_TOF_FUNCTIONR || type == LT_TOF_SYSCALLR){
+ addr=ptrace(PTRACE_PEEKUSER,proc->pid,off_sp,0);
+ if (addr == -1) {
+ debug(2,"ret = %#lx",addr);
+ return addr;
+ }
+ ret = addr + 4*arg_num;
+ ret=ptrace(PTRACE_PEEKTEXT,proc->pid,addr,0);
+ debug(2,"ret = %#lx",ret);
+ return ret;
+ }
+ fprintf(stderr, "gimme_arg called with wrong arguments\n");
+ return 0;
+}
+
+/**@}*/