diff options
Diffstat (limited to 'sysdeps/linux-gnu/arm/trace.c')
-rw-r--r-- | sysdeps/linux-gnu/arm/trace.c | 659 |
1 files changed, 612 insertions, 47 deletions
diff --git a/sysdeps/linux-gnu/arm/trace.c b/sysdeps/linux-gnu/arm/trace.c index c528cfb..5e51e91 100644 --- a/sysdeps/linux-gnu/arm/trace.c +++ b/sysdeps/linux-gnu/arm/trace.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -29,10 +29,13 @@ #include <sys/ptrace.h> #include <asm/ptrace.h> -#include "proc.h" +#include "bits.h" #include "common.h" +#include "proc.h" #include "output.h" #include "ptrace.h" +#include "regs.h" +#include "type.h" #if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) # define PTRACE_PEEKUSER PTRACE_PEEKUSR @@ -42,13 +45,9 @@ # define PTRACE_POKEUSER PTRACE_POKEUSR #endif -#define off_r0 ((void *)0) -#define off_r7 ((void *)28) -#define off_ip ((void *)48) -#define off_pc ((void *)60) - void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ proc_archdep *a; if (!proc->arch_ptr) @@ -63,21 +62,28 @@ get_arch_dep(Process *proc) { * -1 on error. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { - /* get the user's pc (plus 8) */ - unsigned pc = ptrace(PTRACE_PEEKUSER, proc->pid, off_pc, 0); + uint32_t pc, ip; + if (arm_get_register(proc, ARM_REG_PC, &pc) < 0 + || arm_get_register(proc, ARM_REG_IP, &ip) < 0) + return -1; + pc = pc - 4; + /* fetch the SWI instruction */ unsigned insn = ptrace(PTRACE_PEEKTEXT, proc->pid, (void *)pc, 0); - int ip = ptrace(PTRACE_PEEKUSER, proc->pid, off_ip, 0); if (insn == 0xef000000 || insn == 0x0f000000 || (insn & 0xffff0000) == 0xdf000000) { /* EABI syscall */ - *sysnum = ptrace(PTRACE_PEEKUSER, proc->pid, off_r7, 0); + uint32_t r7; + if (arm_get_register(proc, ARM_REG_R7, &r7) < 0) + return -1; + *sysnum = r7; } else if ((insn & 0xfff00000) == 0xef900000) { /* old ABI syscall */ *sysnum = insn & 0xfffff; @@ -103,46 +109,605 @@ syscall_p(Process *proc, int status, int *sysnum) { return 0; } -long -gimme_arg(enum tof type, Process *proc, int arg_num, struct arg_type_info *info) +static arch_addr_t +arm_branch_dest(const arch_addr_t pc, const uint32_t insn) { - proc_archdep *a = (proc_archdep *) proc->arch_ptr; + /* Bits 0-23 are signed immediate value. */ + return pc + ((((insn & 0xffffff) ^ 0x800000) - 0x800000) << 2) + 8; +} - if (arg_num == -1) { /* return value */ - return ptrace(PTRACE_PEEKUSER, proc->pid, off_r0, 0); - } +/* Addresses for calling Thumb functions have the bit 0 set. + Here are some macros to test, set, or clear bit 0 of addresses. */ +/* XXX double cast */ +#define IS_THUMB_ADDR(addr) ((uintptr_t)(addr) & 1) +#define MAKE_THUMB_ADDR(addr) ((arch_addr_t)((uintptr_t)(addr) | 1)) +#define UNMAKE_THUMB_ADDR(addr) ((arch_addr_t)((uintptr_t)(addr) & ~1)) - /* deal with the ARM calling conventions */ - if (type == LT_TOF_FUNCTION || type == LT_TOF_FUNCTIONR) { - if (arg_num < 4) { - if (a->valid && type == LT_TOF_FUNCTION) - return a->regs.uregs[arg_num]; - if (a->valid && type == LT_TOF_FUNCTIONR) - return a->func_arg[arg_num]; - return ptrace(PTRACE_PEEKUSER, proc->pid, - (void *)(4 * arg_num), 0); - } else { - return ptrace(PTRACE_PEEKDATA, proc->pid, - proc->stack_pointer + 4 * (arg_num - 4), - 0); +enum { + COND_ALWAYS = 0xe, + COND_NV = 0xf, + FLAG_C = 0x20000000, +}; + +static int +arm_get_next_pcs(struct process *proc, + const arch_addr_t pc, arch_addr_t next_pcs[2]) +{ + uint32_t this_instr; + uint32_t status; + if (proc_read_32(proc, pc, &this_instr) < 0 + || arm_get_register(proc, ARM_REG_CPSR, &status) < 0) + return -1; + + /* In theory, we sometimes don't even need to add any + * breakpoints at all. If the conditional bits of the + * instruction indicate that it should not be taken, then we + * can just skip it altogether without bothering. We could + * also emulate the instruction under the breakpoint. + * + * Here, we make it as simple as possible (though We Accept + * Patches). */ + int nr = 0; + + /* ARM can branch either relatively by using a branch + * instruction, or absolutely, by doing arbitrary arithmetic + * with PC as the destination. */ + const unsigned cond = BITS(this_instr, 28, 31); + const unsigned opcode = BITS(this_instr, 24, 27); + + if (cond == COND_NV) + switch (opcode) { + arch_addr_t addr; + case 0xa: + case 0xb: + /* Branch with Link and change to Thumb. */ + /* XXX double cast. */ + addr = (arch_addr_t) + ((uint32_t)arm_branch_dest(pc, this_instr) + | (((this_instr >> 24) & 0x1) << 1)); + next_pcs[nr++] = MAKE_THUMB_ADDR(addr); + break; } - } else if (type == LT_TOF_SYSCALL || type == LT_TOF_SYSCALLR) { - if (arg_num < 5) { - if (a->valid && type == LT_TOF_SYSCALL) - return a->regs.uregs[arg_num]; - if (a->valid && type == LT_TOF_SYSCALLR) - return a->sysc_arg[arg_num]; - return ptrace(PTRACE_PEEKUSER, proc->pid, - (void *)(4 * arg_num), 0); - } else { - return ptrace(PTRACE_PEEKDATA, proc->pid, - proc->stack_pointer + 4 * (arg_num - 5), - 0); + else + switch (opcode) { + uint32_t operand1, operand2, result = 0; + case 0x0: + case 0x1: /* data processing */ + case 0x2: + case 0x3: + if (BITS(this_instr, 12, 15) != ARM_REG_PC) + break; + + if (BITS(this_instr, 22, 25) == 0 + && BITS(this_instr, 4, 7) == 9) { /* multiply */ + invalid: + fprintf(stderr, + "Invalid update to pc in instruction.\n"); + break; + } + + /* BX <reg>, BLX <reg> */ + if (BITS(this_instr, 4, 27) == 0x12fff1 + || BITS(this_instr, 4, 27) == 0x12fff3) { + enum arm_register reg = BITS(this_instr, 0, 3); + /* XXX double cast: no need to go + * through tmp. */ + uint32_t tmp; + if (arm_get_register_offpc(proc, reg, &tmp) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)tmp; + return 0; + } + + /* Multiply into PC. */ + if (arm_get_register_offpc + (proc, BITS(this_instr, 16, 19), &operand1) < 0) + return -1; + + int c = (status & FLAG_C) ? 1 : 0; + if (BIT(this_instr, 25)) { + uint32_t immval = BITS(this_instr, 0, 7); + uint32_t rotate = 2 * BITS(this_instr, 8, 11); + operand2 = (((immval >> rotate) + | (immval << (32 - rotate))) + & 0xffffffff); + } else { + /* operand 2 is a shifted register. */ + if (arm_get_shifted_register + (proc, this_instr, c, pc, &operand2) < 0) + return -1; + } + + switch (BITS(this_instr, 21, 24)) { + case 0x0: /*and */ + result = operand1 & operand2; + break; + + case 0x1: /*eor */ + result = operand1 ^ operand2; + break; + + case 0x2: /*sub */ + result = operand1 - operand2; + break; + + case 0x3: /*rsb */ + result = operand2 - operand1; + break; + + case 0x4: /*add */ + result = operand1 + operand2; + break; + + case 0x5: /*adc */ + result = operand1 + operand2 + c; + break; + + case 0x6: /*sbc */ + result = operand1 - operand2 + c; + break; + + case 0x7: /*rsc */ + result = operand2 - operand1 + c; + break; + + case 0x8: + case 0x9: + case 0xa: + case 0xb: /* tst, teq, cmp, cmn */ + /* Only take the default branch. */ + result = 0; + break; + + case 0xc: /*orr */ + result = operand1 | operand2; + break; + + case 0xd: /*mov */ + /* Always step into a function. */ + result = operand2; + break; + + case 0xe: /*bic */ + result = operand1 & ~operand2; + break; + + case 0xf: /*mvn */ + result = ~operand2; + break; + } + + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)result; + break; + + case 0x4: + case 0x5: /* data transfer */ + case 0x6: + case 0x7: + /* Ignore if insn isn't load or Rn not PC. */ + if (!BIT(this_instr, 20) + || BITS(this_instr, 12, 15) != ARM_REG_PC) + break; + + if (BIT(this_instr, 22)) + goto invalid; + + /* byte write to PC */ + uint32_t base; + if (arm_get_register_offpc + (proc, BITS(this_instr, 16, 19), &base) < 0) + return -1; + + if (BIT(this_instr, 24)) { + /* pre-indexed */ + int c = (status & FLAG_C) ? 1 : 0; + uint32_t offset; + if (BIT(this_instr, 25)) { + if (arm_get_shifted_register + (proc, this_instr, c, + pc, &offset) < 0) + return -1; + } else { + offset = BITS(this_instr, 0, 11); + } + + if (BIT(this_instr, 23)) + base += offset; + else + base -= offset; + } + + /* XXX two double casts. */ + uint32_t next; + if (proc_read_32(proc, (arch_addr_t)base, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + break; + + case 0x8: + case 0x9: /* block transfer */ + if (!BIT(this_instr, 20)) + break; + /* LDM */ + if (BIT(this_instr, 15)) { + /* Loading pc. */ + int offset = 0; + enum arm_register rn = BITS(this_instr, 16, 19); + uint32_t rn_val; + if (arm_get_register(proc, rn, &rn_val) < 0) + return -1; + + int pre = BIT(this_instr, 24); + if (BIT(this_instr, 23)) { + /* Bit U = up. */ + unsigned reglist + = BITS(this_instr, 0, 14); + offset = bitcount(reglist) * 4; + if (pre) + offset += 4; + } else if (pre) { + offset = -4; + } + + /* XXX double cast. */ + arch_addr_t addr + = (arch_addr_t)(rn_val + offset); + uint32_t next; + if (proc_read_32(proc, addr, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } + break; + + case 0xb: /* branch & link */ + case 0xa: /* branch */ + next_pcs[nr++] = arm_branch_dest(pc, this_instr); + break; + + case 0xc: + case 0xd: + case 0xe: /* coproc ops */ + case 0xf: /* SWI */ + break; + } + + /* Otherwise take the next instruction. */ + if (cond != COND_ALWAYS || nr == 0) + next_pcs[nr++] = pc + 4; + return 0; +} + +/* Return the size in bytes of the complete Thumb instruction whose + * first halfword is INST1. */ + +static int +thumb_insn_size (unsigned short inst1) +{ + if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0) + return 4; + else + return 2; +} + +static int +thumb_get_next_pcs(struct process *proc, + const arch_addr_t pc, arch_addr_t next_pcs[2]) +{ + uint16_t inst1; + uint32_t status; + if (proc_read_16(proc, pc, &inst1) < 0 + || arm_get_register(proc, ARM_REG_CPSR, &status) < 0) + return -1; + + int nr = 0; + + /* We currently ignore Thumb-2 conditional execution support + * (the IT instruction). No branches are allowed in IT block, + * and it's not legal to jump in the middle of it, so unless + * we need to singlestep through large swaths of code, which + * we currently don't, we can ignore them. */ + + if ((inst1 & 0xff00) == 0xbd00) { /* pop {rlist, pc} */ + /* Fetch the saved PC from the stack. It's stored + * above all of the other registers. */ + const unsigned offset = bitcount(BITS(inst1, 0, 7)) * 4; + uint32_t sp; + uint32_t next; + /* XXX two double casts */ + if (arm_get_register(proc, ARM_REG_SP, &sp) < 0 + || proc_read_32(proc, (arch_addr_t)(sp + offset), + &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } else if ((inst1 & 0xf000) == 0xd000) { /* conditional branch */ + const unsigned long cond = BITS(inst1, 8, 11); + if (cond != 0x0f) { /* SWI */ + next_pcs[nr++] = pc + (SBITS(inst1, 0, 7) << 1); + if (cond == COND_ALWAYS) + return 0; + } + } else if ((inst1 & 0xf800) == 0xe000) { /* unconditional branch */ + next_pcs[nr++] = pc + (SBITS(inst1, 0, 10) << 1); + } else if (thumb_insn_size(inst1) == 4) { /* 32-bit instruction */ + unsigned short inst2; + if (proc_read_16(proc, pc + 2, &inst2) < 0) + return -1; + + if ((inst1 & 0xf800) == 0xf000 && (inst2 & 0x8000) == 0x8000) { + /* Branches and miscellaneous control instructions. */ + + if ((inst2 & 0x1000) != 0 + || (inst2 & 0xd001) == 0xc000) { + /* B, BL, BLX. */ + + const int imm1 = SBITS(inst1, 0, 10); + const unsigned imm2 = BITS(inst2, 0, 10); + const unsigned j1 = BIT(inst2, 13); + const unsigned j2 = BIT(inst2, 11); + + int32_t offset + = ((imm1 << 12) + (imm2 << 1)); + offset ^= ((!j2) << 22) | ((!j1) << 23); + + /* XXX double cast */ + uint32_t next = (uint32_t)(pc + offset); + /* For BLX make sure to clear the low bits. */ + if (BIT(inst2, 12) == 0) + next = next & 0xfffffffc; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + return 0; + } else if (inst1 == 0xf3de + && (inst2 & 0xff00) == 0x3f00) { + /* SUBS PC, LR, #imm8. */ + uint32_t next; + if (arm_get_register(proc, ARM_REG_LR, + &next) < 0) + return -1; + next -= inst2 & 0x00ff; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + return 0; + } else if ((inst2 & 0xd000) == 0x8000 + && (inst1 & 0x0380) != 0x0380) { + /* Conditional branch. */ + const int sign = SBITS(inst1, 10, 10); + const unsigned imm1 = BITS(inst1, 0, 5); + const unsigned imm2 = BITS(inst2, 0, 10); + const unsigned j1 = BIT(inst2, 13); + const unsigned j2 = BIT(inst2, 11); + + int32_t offset = (sign << 20) + + (j2 << 19) + (j1 << 18); + offset += (imm1 << 12) + (imm2 << 1); + next_pcs[nr++] = pc + offset; + if (BITS(inst1, 6, 9) == COND_ALWAYS) + return 0; + } + } else if ((inst1 & 0xfe50) == 0xe810) { + int load_pc = 1; + int offset; + const enum arm_register rn = BITS(inst1, 0, 3); + + if (BIT(inst1, 7) && !BIT(inst1, 8)) { + /* LDMIA or POP */ + if (!BIT(inst2, 15)) + load_pc = 0; + offset = bitcount(inst2) * 4 - 4; + } else if (!BIT(inst1, 7) && BIT(inst1, 8)) { + /* LDMDB */ + if (!BIT(inst2, 15)) + load_pc = 0; + offset = -4; + } else if (BIT(inst1, 7) && BIT(inst1, 8)) { + /* RFEIA */ + offset = 0; + } else if (!BIT(inst1, 7) && !BIT(inst1, 8)) { + /* RFEDB */ + offset = -8; + } else { + load_pc = 0; + } + + if (load_pc) { + uint32_t addr; + if (arm_get_register(proc, rn, &addr) < 0) + return -1; + arch_addr_t a = (arch_addr_t)(addr + offset); + uint32_t next; + if (proc_read_32(proc, a, &next) < 0) + return -1; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + } + } else if ((inst1 & 0xffef) == 0xea4f + && (inst2 & 0xfff0) == 0x0f00) { + /* MOV PC or MOVS PC. */ + const enum arm_register rn = BITS(inst2, 0, 3); + uint32_t next; + if (arm_get_register(proc, rn, &next) < 0) + return -1; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + } else if ((inst1 & 0xff70) == 0xf850 + && (inst2 & 0xf000) == 0xf000) { + /* LDR PC. */ + const enum arm_register rn = BITS(inst1, 0, 3); + uint32_t base; + if (arm_get_register(proc, rn, &base) < 0) + return -1; + + int load_pc = 1; + if (rn == ARM_REG_PC) { + base = (base + 4) & ~(uint32_t)0x3; + if (BIT(inst1, 7)) + base += BITS(inst2, 0, 11); + else + base -= BITS(inst2, 0, 11); + } else if (BIT(inst1, 7)) { + base += BITS(inst2, 0, 11); + } else if (BIT(inst2, 11)) { + if (BIT(inst2, 10)) { + if (BIT(inst2, 9)) + base += BITS(inst2, 0, 7); + else + base -= BITS(inst2, 0, 7); + } + } else if ((inst2 & 0x0fc0) == 0x0000) { + const int shift = BITS(inst2, 4, 5); + const enum arm_register rm = BITS(inst2, 0, 3); + uint32_t v; + if (arm_get_register(proc, rm, &v) < 0) + return -1; + base += v << shift; + } else { + /* Reserved. */ + load_pc = 0; + } + + if (load_pc) { + /* xxx double casts */ + uint32_t next; + if (proc_read_32(proc, + (arch_addr_t)base, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } + } else if ((inst1 & 0xfff0) == 0xe8d0 + && (inst2 & 0xfff0) == 0xf000) { + /* TBB. */ + const enum arm_register tbl_reg = BITS(inst1, 0, 3); + const enum arm_register off_reg = BITS(inst2, 0, 3); + + uint32_t table; + if (tbl_reg == ARM_REG_PC) + /* Regcache copy of PC isn't right yet. */ + /* XXX double cast */ + table = (uint32_t)pc + 4; + else if (arm_get_register(proc, tbl_reg, &table) < 0) + return -1; + + uint32_t offset; + if (arm_get_register(proc, off_reg, &offset) < 0) + return -1; + + table += offset; + uint8_t length; + /* XXX double cast */ + if (proc_read_8(proc, (arch_addr_t)table, &length) < 0) + return -1; + + next_pcs[nr++] = pc + 2 * length; + + } else if ((inst1 & 0xfff0) == 0xe8d0 + && (inst2 & 0xfff0) == 0xf010) { + /* TBH. */ + const enum arm_register tbl_reg = BITS(inst1, 0, 3); + const enum arm_register off_reg = BITS(inst2, 0, 3); + + uint32_t table; + if (tbl_reg == ARM_REG_PC) + /* Regcache copy of PC isn't right yet. */ + /* XXX double cast */ + table = (uint32_t)pc + 4; + else if (arm_get_register(proc, tbl_reg, &table) < 0) + return -1; + + uint32_t offset; + if (arm_get_register(proc, off_reg, &offset) < 0) + return -1; + + table += 2 * offset; + uint16_t length; + /* XXX double cast */ + if (proc_read_16(proc, (arch_addr_t)table, &length) < 0) + return -1; + + next_pcs[nr++] = pc + 2 * length; } - } else { - fprintf(stderr, "gimme_arg called with wrong arguments\n"); - exit(1); } + + /* Otherwise take the next instruction. */ + if (nr == 0) + next_pcs[nr++] = pc + thumb_insn_size(inst1); return 0; } + +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *sbp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *add_cb_data) +{ + const arch_addr_t pc = get_instruction_pointer(proc); + + uint32_t cpsr; + if (arm_get_register(proc, ARM_REG_CPSR, &cpsr) < 0) + return SWS_FAIL; + + const unsigned thumb_p = BIT(cpsr, 5); + arch_addr_t next_pcs[2] = {}; + if ((thumb_p ? &thumb_get_next_pcs + : &arm_get_next_pcs)(proc, pc, next_pcs) < 0) + return SWS_FAIL; + + int i; + for (i = 0; i < 2; ++i) { + /* XXX double cast. */ + arch_addr_t target + = (arch_addr_t)(((uintptr_t)next_pcs[i]) | thumb_p); + if (next_pcs[i] != 0 && add_cb(target, add_cb_data) < 0) + return SWS_FAIL; + } + + debug(1, "PTRACE_CONT"); + ptrace(PTRACE_CONT, proc->pid, 0, 0); + return SWS_OK; +} + +size_t +arch_type_sizeof(struct process *proc, struct arg_type_info *info) +{ + if (proc == NULL) + return (size_t)-2; + + switch (info->type) { + case ARGTYPE_VOID: + return 0; + + case ARGTYPE_CHAR: + return 1; + + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + return 2; + + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_POINTER: + return 4; + + case ARGTYPE_FLOAT: + return 4; + case ARGTYPE_DOUBLE: + return 8; + + case ARGTYPE_ARRAY: + case ARGTYPE_STRUCT: + /* Use default value. */ + return (size_t)-2; + + default: + assert(info->type != info->type); + abort(); + } +} + +size_t +arch_type_alignof(struct process *proc, struct arg_type_info *info) +{ + return arch_type_sizeof(proc, info); +} |