/*
* This file is part of ltrace.
* Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc.
* Copyright (C) 1998,2004,2008,2009 Juan Cespedes
* Copyright (C) 2006 Ian Wienand
*
* 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 <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <asm/ptrace.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
#endif
#if (!defined(PTRACE_POKEUSER) && defined(PTRACE_POKEUSR))
# define PTRACE_POKEUSER PTRACE_POKEUSR
#endif
void
get_arch_dep(struct process *proc)
{
proc_archdep *a;
if (!proc->arch_ptr)
proc->arch_ptr = (void *)malloc(sizeof(proc_archdep));
a = (proc_archdep *) (proc->arch_ptr);
a->valid = (ptrace(PTRACE_GETREGS, proc->pid, 0, &a->regs) >= 0);
}
/* Returns 0 if not a syscall,
* 1 if syscall entry, 2 if syscall exit,
* 3 if arch-specific syscall entry, 4 if arch-specific syscall exit,
* -1 on error.
*/
int
syscall_p(struct process *proc, int status, int *sysnum)
{
if (WIFSTOPPED(status)
&& WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
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);
if (insn == 0xef000000 || insn == 0x0f000000
|| (insn & 0xffff0000) == 0xdf000000) {
/* EABI syscall */
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;
} else {
/* TODO: handle swi<cond> variations */
/* one possible reason for getting in here is that we
* are coming from a signal handler, so the current
* PC does not point to the instruction just after the
* "swi" one. */
output_line(proc, "unexpected instruction 0x%x at %p",
insn, pc);
return 0;
}
if ((*sysnum & 0xf0000) == 0xf0000) {
/* arch-specific syscall */
*sysnum &= ~0xf0000;
return ip ? 4 : 3;
}
/* ARM syscall convention: on syscall entry, ip is zero;
* on syscall exit, ip is non-zero */
return ip ? 2 : 1;
}
return 0;
}
static arch_addr_t
arm_branch_dest(const arch_addr_t pc, const uint32_t insn)
{
/* Bits 0-23 are signed immediate value. */
return pc + ((((insn & 0xffffff) ^ 0x800000) - 0x800000) << 2) + 8;
}
/* 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))
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
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;
}
}
/* 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);
}