blob: b334bb66c2fd3e8b4146c99eb25bfa68077883b6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2015 Stanislav Yakovlev and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Stanislav Yakovlev - initial API and implementation
* Emmanuel Touron (Wind River) - initial ARM stepping emulation
* Stanislav Yakovlev - [404627] Add support for ARM VFP registers
*******************************************************************************/
#include <tcf/config.h>
#if ENABLE_DebugContext && !ENABLE_ContextProxy
#ifndef USE_getauxval
# include <features.h>
# define USE_getauxval (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16)))
#endif
#include <stddef.h>
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#if USE_getauxval
# include <sys/auxv.h>
# include <asm/hwcap.h>
#endif
#include <tcf/framework/errors.h>
#include <tcf/framework/cpudefs.h>
#include <tcf/framework/context.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/trace.h>
#include <tcf/services/symbols.h>
#include <tcf/services/runctrl.h>
#include <machine/arm/tcf/disassembler-arm.h>
#include <machine/arm/tcf/stack-crawl-arm.h>
#include <tcf/cpudefs-mdep.h>
#define REG_OFFSET(name) offsetof(REG_SET, name)
RegisterDefinition regs_def[] = {
# define REG_FP user.regs.uregs[11]
# define REG_IP user.regs.uregs[12]
# define REG_SP user.regs.uregs[13]
# define REG_LR user.regs.uregs[14]
# define REG_PC user.regs.uregs[15]
# define REG_CPSR user.regs.uregs[16]
{ "r0", REG_OFFSET(user.regs.uregs[0]), 4, 0, 0},
{ "r1", REG_OFFSET(user.regs.uregs[1]), 4, 1, 1},
{ "r2", REG_OFFSET(user.regs.uregs[2]), 4, 2, 2},
{ "r3", REG_OFFSET(user.regs.uregs[3]), 4, 3, 3},
{ "r4", REG_OFFSET(user.regs.uregs[4]), 4, 4, 4},
{ "r5", REG_OFFSET(user.regs.uregs[5]), 4, 5, 5},
{ "r6", REG_OFFSET(user.regs.uregs[6]), 4, 6, 6},
{ "r7", REG_OFFSET(user.regs.uregs[7]), 4, 7, 7},
{ "r8", REG_OFFSET(user.regs.uregs[8]), 4, 8, 8},
{ "r9", REG_OFFSET(user.regs.uregs[9]), 4, 9, 9},
{ "r10", REG_OFFSET(user.regs.uregs[10]), 4, 10, 10},
{ "fp", REG_OFFSET(user.regs.uregs[11]), 4, 11, 11},
{ "ip", REG_OFFSET(user.regs.uregs[12]), 4, 12, 12},
{ "sp", REG_OFFSET(user.regs.uregs[13]), 4, 13, 13},
{ "lr", REG_OFFSET(user.regs.uregs[14]), 4, 14, 14},
{ "pc", REG_OFFSET(user.regs.uregs[15]), 4, 15, 15},
{ "cpsr", REG_OFFSET(user.regs.uregs[16]), 4, 128, 128},
{ "orig_r0", REG_OFFSET(user.regs.uregs[17]), 4, -1, -1},
{ "vfp", 0, 0, -1, -1, 0, 0, 1, 1 },
{ NULL, 0, 0, 0, 0},
};
RegisterDefinition * regs_index = NULL;
static unsigned regs_cnt = 0;
static unsigned regs_max = 0;
unsigned char BREAK_INST[] = { 0xf0, 0x01, 0xf0, 0xe7 };
static RegisterDefinition * pc_def = NULL;
static RegisterDefinition * lr_def = NULL;
static RegisterDefinition * cpsr_def = NULL;
#include <sys/ptrace.h>
#if !defined(PTRACE_GETHBPREGS)
#define PTRACE_GETHBPREGS 29
#endif
#if !defined(PTRACE_SETHBPREGS)
#define PTRACE_SETHBPREGS 30
#endif
#define ARM_DEBUG_ARCH_V6 1
#define ARM_DEBUG_ARCH_V6_1 2
#define ARM_DEBUG_ARCH_V7_ECP14 3
#define ARM_DEBUG_ARCH_V7_MM 4
#define ARM_DEBUG_ARCH_V7_1 5
#define ARM_DEBUG_ARCH_V8 6
typedef struct ContextExtensionARM {
int sw_stepping;
char opcode[sizeof(BREAK_INST)];
ContextAddress addr;
#if ENABLE_HardwareBreakpoints
#define MAX_HBP 16
#define MAX_HWP 16
#define MAX_HW_BPS (MAX_HBP + MAX_HWP)
uint8_t arch;
uint8_t wp_size;
uint8_t wp_cnt;
uint8_t bp_cnt;
int8_t info_ok;
int hw_stepping;
ContextBreakpoint * triggered_hw_bps[MAX_HW_BPS + 1];
unsigned hw_bps_regs_generation;
ContextBreakpoint * hw_bps[MAX_HW_BPS];
unsigned hw_bps_generation;
unsigned armed;
#endif
} ContextExtensionARM;
static size_t context_extension_offset = 0;
#define EXT(ctx) ((ContextExtensionARM *)((char *)(ctx) + context_extension_offset))
static int arm_get_next_address(Context * ctx, ContextAddress * next_addr);
RegisterDefinition * get_PC_definition(Context * ctx) {
if (!context_has_state(ctx)) return NULL;
return pc_def;
}
int crawl_stack_frame(StackFrame * frame, StackFrame * down) {
return crawl_stack_frame_arm(frame, down);
}
#if defined(ENABLE_add_cpudefs_disassembler) && ENABLE_add_cpudefs_disassembler
void add_cpudefs_disassembler(Context * cpu_ctx) {
add_disassembler(cpu_ctx, "ARM", disassemble_arm);
add_disassembler(cpu_ctx, "Thumb", disassemble_thumb);
}
#endif
static int read_reg(Context *ctx, RegisterDefinition * def, size_t size, ContextAddress * addr) {
size_t i;
uint8_t buf[8];
uint64_t n = 0;
*addr = 0;
assert(!def->big_endian);
assert(size <= def->size);
assert(size <= sizeof(buf));
if (context_read_reg(ctx, def, 0, size, buf) < 0) return -1;
for (i = 0; i < size; i++) n |= (uint64_t)buf[i] << (i * 8);
*addr = (ContextAddress)n;
return 0;
}
#if ENABLE_HardwareBreakpoints
static void clear_bp(ContextBreakpoint * bp) {
unsigned i;
Context * ctx = bp->ctx;
ContextExtensionARM * bps = EXT(ctx);
for (i = 0; i < MAX_HW_BPS; i++) {
if (bps->hw_bps[i] == bp) bps->hw_bps[i] = NULL;
}
}
static int get_bp_info(Context * ctx) {
uint32_t buf = 0;
ContextExtensionARM * bps = EXT(ctx);
if (bps->info_ok) return 0;
if (ptrace(PTRACE_GETHBPREGS, id2pid(ctx->id, NULL), 0, &buf) < 0) {
/* Kernel does not support hardware breakpoints */
bps->arch = 0;
bps->wp_size = 0;
bps->wp_cnt = 0;
bps->bp_cnt = 0;
bps->info_ok = 1;
return 0;
}
bps->arch = (uint8_t)(buf >> 24);
bps->wp_size = (uint8_t)(buf >> 16);
bps->wp_cnt = (uint8_t)(buf >> 8);
bps->bp_cnt = (uint8_t)buf;
if (bps->wp_cnt > MAX_HWP) bps->wp_cnt = MAX_HWP;
if (bps->bp_cnt > MAX_HBP) bps->bp_cnt = MAX_HBP;
bps->info_ok = 1;
return 0;
}
static int is_triggered(Context * ctx, ContextBreakpoint * cb) {
if (ctx->stopped_by_cb != NULL) {
unsigned i = 0;
while (ctx->stopped_by_cb[i] != NULL) {
if (ctx->stopped_by_cb[i] == cb) return 1;
i++;
}
}
return 0;
}
static int set_debug_regs(Context * ctx, int * step_over_hw_bp) {
int i, j;
ContextAddress pc = 0;
Context * grp = context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT);
ContextExtensionARM * ext = EXT(ctx);
ContextExtensionARM * bps = EXT(grp);
pid_t pid = id2pid(ctx->id, NULL);
assert(bps->info_ok);
ext->armed = 0;
*step_over_hw_bp = 0;
if (read_reg(ctx, pc_def, pc_def->size, &pc) < 0) return -1;
for (i = 0; i < bps->bp_cnt + bps->wp_cnt; i++) {
uint32_t cr = 0;
ContextBreakpoint * cb = bps->hw_bps[i];
if (i == 0 && ext->hw_stepping) {
uint32_t vr = 0;
if (ext->hw_stepping == 1) {
vr = (uint32_t)ext->addr;
}
else {
vr = (uint32_t)pc;
cr |= 1u << 22;
}
cr |= 0xfu << 5;
cr |= 0x7u;
if (ptrace(PTRACE_SETHBPREGS, pid, 1, &vr) < 0) return -1;
}
else if (cb != NULL) {
if (i < bps->bp_cnt && cb->address == pc) {
/* Skipping the breakpoint */
*step_over_hw_bp = 1;
}
else if (bps->arch >= ARM_DEBUG_ARCH_V7_ECP14 && i >= bps->bp_cnt && is_triggered(ctx, cb)) {
/* Skipping the watchpoint */
*step_over_hw_bp = 1;
}
else {
uint32_t vr = (uint32_t)(cb->address & ~3);
if (i < bps->bp_cnt) {
cr |= 0xfu << 5;
}
else {
for (j = 0; j < 4; j++) {
if (vr + j < cb->address) continue;
if (vr + j >= cb->address + cb->length) continue;
cr |= 1 << (5 + j);
}
if (cb->access_types & CTX_BP_ACCESS_DATA_READ) cr |= 1 << 3;
if (cb->access_types & CTX_BP_ACCESS_DATA_WRITE) cr |= 1 << 4;
}
cr |= 0x7u;
if (i < bps->bp_cnt) {
if (ptrace(PTRACE_SETHBPREGS, pid, i * 2 + 1, &vr) < 0) return -1;
}
else {
if (ptrace(PTRACE_SETHBPREGS, pid, -(i * 2 + 1), &vr) < 0) return -1;
}
ext->armed |= 1u << i;
}
}
if (cr == 0) {
/* Linux kernel does not allow 0 as Control Register value */
cr |= 0x3u << 1;
cr |= 0xfu << 5;
if (i >= bps->bp_cnt) {
cr |= 1u << 4;
}
}
if (i < bps->bp_cnt) {
if (ptrace(PTRACE_SETHBPREGS, pid, i * 2 + 2, &cr) < 0) return -1;
}
else {
if (ptrace(PTRACE_SETHBPREGS, pid, -(i * 2 + 2), &cr) < 0) return -1;
}
}
ext->hw_bps_regs_generation = bps->hw_bps_generation;
return 0;
}
static int enable_hw_stepping_mode(Context * ctx, int mode) {
int step = 0;
ContextExtensionARM * ext = EXT(ctx);
if (mode == 1 && arm_get_next_address(ctx, &ext->addr) < 0) return -1;
ext->hw_stepping = mode;
return set_debug_regs(ctx, &step);
}
static int disable_hw_stepping_mode(Context * ctx) {
ContextExtensionARM * ext = EXT(ctx);
ext->hw_stepping = 0;
ext->hw_bps_regs_generation--;
return 0;
}
int cpu_bp_get_capabilities(Context * ctx) {
int res = 0;
ContextExtensionARM * bps = EXT(ctx);
if (ctx != context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT)) return 0;
if (get_bp_info(ctx) < 0) return 0;
if (bps->bp_cnt > 0) {
res |= CTX_BP_ACCESS_INSTRUCTION;
}
if (bps->wp_cnt > 0) {
res |= CTX_BP_ACCESS_DATA_READ;
res |= CTX_BP_ACCESS_DATA_WRITE;
}
res |= CTX_BP_ACCESS_VIRTUAL;
return res;
}
int cpu_bp_plant(ContextBreakpoint * bp) {
Context * ctx = bp->ctx;
ContextExtensionARM * bps = EXT(ctx);
assert(bp->access_types);
assert(ctx == context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
if (get_bp_info(ctx) < 0) return -1;
if (bp->access_types & CTX_BP_ACCESS_VIRTUAL) {
if (bp->access_types & CTX_BP_ACCESS_INSTRUCTION) {
unsigned i;
unsigned n = 0;
for (i = 0; i < bps->bp_cnt; i++) {
assert(bps->hw_bps[i] != bp);
if (bps->hw_bps[i] == NULL) {
bps->hw_bps[i] = bp;
bps->hw_bps_generation++;
n++;
break;
}
}
if (n == 0) {
clear_bp(bp);
errno = ERR_UNSUPPORTED;
return -1;
}
}
if (bp->access_types & (CTX_BP_ACCESS_DATA_READ | CTX_BP_ACCESS_DATA_WRITE)) {
unsigned n = 0;
if (bp->length <= bps->wp_size) {
unsigned i;
for (i = bps->bp_cnt; i < bps->bp_cnt + bps->wp_cnt; i++) {
assert(bps->hw_bps[i] != bp);
if (bps->hw_bps[i] == NULL) {
bps->hw_bps[i] = bp;
bps->hw_bps_generation++;
n++;
break;
}
}
}
if (n == 0) {
clear_bp(bp);
errno = ERR_UNSUPPORTED;
return -1;
}
}
return 0;
}
errno = ERR_UNSUPPORTED;
return -1;
}
int cpu_bp_remove(ContextBreakpoint * bp) {
ContextExtensionARM * bps = EXT(bp->ctx);
clear_bp(bp);
bps->hw_bps_generation++;
return 0;
}
int cpu_bp_on_resume(Context * ctx, int * single_step) {
ContextExtensionARM * ext = EXT(ctx);
ContextExtensionARM * bps = EXT(context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
if (ctx->stopped_by_cb != NULL || ext->hw_bps_regs_generation != bps->hw_bps_generation) {
if (set_debug_regs(ctx, single_step) < 0) return -1;
}
return 0;
}
int cpu_bp_on_suspend(Context * ctx, int * triggered) {
unsigned cb_cnt = 0;
ContextExtensionARM * ext = EXT(ctx);
ContextExtensionARM * bps = EXT(context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
int i;
if (ctx->exiting) return 0;
if (bps->bp_cnt > 0) {
ContextAddress pc = 0;
if (read_reg(ctx, pc_def, pc_def->size, &pc) < 0) return -1;
for (i = 0; i < bps->bp_cnt; i++) {
ContextBreakpoint * cb = bps->hw_bps[i];
if (cb != NULL && cb->address == pc && (ext->armed & (1u << i))) {
ext->triggered_hw_bps[cb_cnt++] = cb;
}
}
}
if (bps->wp_cnt > 0) {
siginfo_t siginfo;
pid_t pid = id2pid(ctx->id, NULL);
if (ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo) < 0) return -1;
if (siginfo.si_signo == SIGTRAP && (siginfo.si_code & 0xffff) == 0x0004 && siginfo.si_errno < 0) {
/* Watchpoint */
for (i = bps->bp_cnt; i < bps->bp_cnt + bps->wp_cnt; i++) {
ContextBreakpoint * cb = bps->hw_bps[i];
if (cb != NULL && (ext->armed & (1u << i))) {
if (bps->wp_cnt > 1) {
ContextAddress addr = (ContextAddress)siginfo.si_addr;
if (addr < cb->address || addr >= cb->address + cb->length) continue;
}
ext->triggered_hw_bps[cb_cnt++] = cb;
}
}
}
}
*triggered = cb_cnt > 0;
if (cb_cnt > 0) {
ctx->stopped_by_cb = ext->triggered_hw_bps;
ctx->stopped_by_cb[cb_cnt] = NULL;
}
return 0;
}
#endif /* ENABLE_HardwareBreakpoints */
static Context * arm_ctx;
static uint32_t arm_pc;
static uint32_t arm_instr;
static uint32_t arm_cpsr;
static uint32_t arm_next;
static int arm_evaluate_condition(void) {
int N = ( arm_cpsr >> 31 ) & 1;
int Z = ( arm_cpsr >> 30 ) & 1;
int C = ( arm_cpsr >> 29 ) & 1;
int V = ( arm_cpsr >> 28 ) & 1;
switch (arm_instr >> 28) {
case 0 : return Z;
case 1 : return Z == 0;
case 2 : return C;
case 3 : return C == 0;
case 4 : return N;
case 5 : return N == 0;
case 6 : return V;
case 7 : return V == 0;
case 8 : return C == 0 && Z == 0;
case 9 : return C == 0 || Z == 1;
case 10: return N == V;
case 11: return N != V;
case 12: return Z == 0 && N == V;
case 13: return Z == 1 || N != V;
case 15: return 0;
}
return 1;
}
static uint32_t arm_calc_shift(uint32_t shift_type, uint32_t shift_imm, uint32_t val) {
switch (shift_type) {
case 0: /* logical left */
val = val << shift_imm;
break;
case 1: /* logical right */
if (shift_imm == 0) val = 0;
else val = val >> shift_imm;
break;
case 2: /* arithmetic right */
if (shift_imm == 0) shift_imm = 32;
if (val & 0x80000000) {
if (shift_imm > 32) {
val = 0xffffffff;
}
else {
val = val >> shift_imm;
val |= 0xffffffff << (32 - shift_imm);
}
}
else {
val = val >> shift_imm;
}
break;
case 3: /* rotate right */
if (shift_imm == 0) {
/* Rotate right with extend */
val = val >> 1;
if (arm_cpsr & (1 << 29)) val |= 0x80000000;
}
else {
shift_imm &= 0x1f;
val = (val >> shift_imm) | (val << (32 - shift_imm));
}
break;
}
return val;
}
static int arm_get_next_bx(void) {
uint32_t Rm = arm_instr & 0xf;
return context_read_reg(arm_ctx, regs_index + Rm, 0, 4, &arm_next);
}
static int arm_get_next_data_processing(void) {
int I = (arm_instr & 0x02000000) != 0;
int S = (arm_instr & 0x00100000) != 0;
uint32_t opcode = (arm_instr & 0x01e00000) >> 21;
uint32_t Rn = (arm_instr & 0x000f0000) >> 16;
uint32_t Rd = (arm_instr & 0x0000f000) >> 12;
uint32_t operand2 = (arm_instr & 0x00000fff);
uint32_t op2val = 0;
uint32_t nval = 0;
if (!S && opcode >= 8 && opcode <= 11) return 0;
if (Rd != 15) return 0;
/* Decode operand 2 */
if (I) {
uint8_t shift_dist = (operand2 & 0x0f00) >> 8;
uint8_t shift_const = (operand2 & 0x00ff);
/* rotate const right by 2 * shift_dist */
shift_dist *= 2;
op2val = (shift_const >> shift_dist) | (shift_const << (32 - shift_dist));
}
else {
/* Register and shift */
uint8_t Rm = (operand2 & 0x000f);
uint8_t reg_shift = (operand2 & 0x0010) != 0;
uint8_t shift_type = (operand2 & 0x0060) >> 5;
uint32_t shift_dist = 0;
uint32_t mval = 0;
/* Get the shift distance */
if (reg_shift) {
uint8_t Rs = (operand2 & 0x0f00) >> 8;
if (operand2 & 0x00800) {
return 0;
}
else {
if (context_read_reg(arm_ctx, regs_index + Rs, 0, 4, &shift_dist) < 0) return -1;
}
}
else {
shift_dist = (operand2 & 0x0f80) >> 7;
}
if (context_read_reg(arm_ctx, regs_index + Rm, 0, 4, &mval) < 0) return -1;
if (shift_type == 0 && shift_dist == 0 && opcode == 13) {
/* MOV Rd,Rm */
op2val = mval;
}
else {
/* Apply the shift type to the source register */
switch (shift_type) {
case 0: /* logical left */
op2val = mval << shift_dist;
break;
case 1: /* logical right */
if (!reg_shift && shift_dist == 0) shift_dist = 32;
op2val = mval >> shift_dist;
break;
case 2: /* arithmetic right */
if (!reg_shift && shift_dist == 0) shift_dist = 32;
if (mval & 0x80000000) {
/* Register shifts maybe greater than 32 */
if (shift_dist >= 32) {
op2val = 0xffffffff;
}
else {
op2val = mval >> shift_dist;
op2val |= 0xffffffff << (32 - shift_dist);
}
}
else {
op2val = mval >> shift_dist;
}
break;
case 3: /* rotate right */
if (!reg_shift && shift_dist == 0) {
op2val = mval >> 1;
if (arm_cpsr & (1 << 29)) op2val |= 0x80000000;
}
else {
/* Limit shift distance to 0-31 in case of register shift */
shift_dist &= 0x1f;
op2val = (mval >> shift_dist) | (mval << (32 - shift_dist));
}
break;
}
}
}
if (context_read_reg(arm_ctx, regs_index + Rn, 0, 4, &nval) < 0) return -1;
/* Account for pre-fetch by adjusting PC */
if (Rn == 15) {
/* If the shift amount is specified in the instruction,
* the PC will be 8 bytes ahead. If a register is used
* to specify the shift amount the PC will be 12 bytes
* ahead.
*/
if (!I && (operand2 & 0x0010))
nval += 12;
else
nval += 8;
}
/* Compute values */
switch (opcode) {
case 0: /* AND: Rd:= Op1 AND Op2 */
arm_next = nval & op2val;
break;
case 1: /* EOR: Rd:= Op1 EOR Op2 */
arm_next = nval ^ op2val;
break;
case 2: /* SUB: Rd:= Op1 - Op2 */
arm_next = nval - op2val;
break;
case 3: /* RSB: Rd:= Op2 - Op1 */
arm_next = op2val - nval;
break;
case 4: /* ADD: Rd:= Op1 + Op2 */
arm_next = nval + op2val;
break;
case 5: /* ADC: Rd:= Op1 + Op2 + C */
arm_next = nval + op2val;
if (arm_cpsr & (1 << 29)) arm_next++;
break;
case 6: /* SBC: Rd:= Op1 - Op2 + C */
arm_next = nval - op2val;
if (arm_cpsr & (1 << 29)) arm_next++;
break;
case 7: /* RSC: Rd:= Op2 - Op1 + C */
arm_next = op2val - nval;
if (arm_cpsr & (1 << 29)) arm_next++;
break;
case 8: /* TST: set condition codes on Op1 AND Op2 */
case 9: /* TEQ: set condition codes on Op1 EOR Op2 */
case 10: /* CMP: set condition codes on Op1 - Op2 */
case 11: /* CMN: set condition codes on Op1 + Op2 */
break;
case 12: /* ORR: Rd:= Op1 OR Op2 */
arm_next = nval | op2val;
break;
case 13: /* MOV: Rd:= Op2 */
arm_next = op2val;
break;
case 14: /* BIC: Rd:= Op1 AND NOT Op2 */
arm_next = nval & (~op2val);
break;
case 15: /* MVN: Rd:= NOT Op2 */
arm_next = ~op2val;
break;
}
return 0;
}
static int arm_get_next_ldr(void) {
int I = (arm_instr & (1 << 25)) != 0;
int P = (arm_instr & (1 << 24)) != 0;
int U = (arm_instr & (1 << 23)) != 0;
int B = (arm_instr & (1 << 22)) != 0;
int W = (arm_instr & (1 << 21)) != 0;
int L = (arm_instr & (1 << 20)) != 0;
uint32_t Rn = (arm_instr >> 16) & 0xf;
uint32_t Rd = (arm_instr >> 12) & 0xf;
ContextAddress addr = 0;
unsigned size = B ? 1 : 4;
uint8_t bt = 0;
if (!L || Rd != 15) return 0;
if (read_reg(arm_ctx, regs_index + Rn, 4, &addr) < 0) return -1;
if (Rn == 15) addr += 8;
if (!I && P) {
uint32_t offs = arm_instr & 0xfff;
addr = U ? addr + offs : addr - offs;
}
else if (I && P) {
uint8_t Rm = arm_instr & 0xf;
uint32_t offs = 0;
if (context_read_reg(arm_ctx, regs_index + Rm, 0, 4, &offs) < 0) return -1;
if ((arm_instr & 0x00000ff0) == 0x00000000) {
addr = U ? addr + offs : addr - offs;
}
else {
uint32_t shift_imm = (arm_instr & 0x00000f80) >> 7;
uint32_t shift_type = (arm_instr & 0x00000060) >> 5;
uint32_t val = arm_calc_shift(shift_type, shift_imm, offs);
addr = U ? addr + val : addr - val;
}
}
else if (P || W) {
size = 0;
}
switch (size) {
case 1:
if (context_read_mem(arm_ctx, addr, &bt, 1) < 0) return -1;
arm_next = bt;
break;
case 4:
if (context_read_mem(arm_ctx, addr, &arm_next, 4) < 0) return -1;
break;
}
return 0;
}
static int arm_get_next_ldm(void) {
int P = (arm_instr & (1 << 24)) != 0;
int U = (arm_instr & (1 << 23)) != 0;
int S = (arm_instr & (1 << 22)) != 0;
int L = (arm_instr & (1 << 20)) != 0;
uint32_t Rn = (arm_instr >> 16) & 0xf;
ContextAddress addr = 0;
if (!L || S || Rn == 15) return 0;
if ((arm_instr & (1 << 15)) == 0) return 0;
if (read_reg(arm_ctx, regs_index + Rn, 4, &addr) < 0) return -1;
if (U) {
int i;
for (i = 0; i < 15; i++) {
if (arm_instr & (1 << i)) addr += 4;
}
}
if (P) addr = U ? addr + 4 : addr - 4;
return context_read_mem(arm_ctx, addr, &arm_next, 4);
}
static int arm_get_next_branch(void) {
int32_t imm = arm_instr & 0x00FFFFFF;
if (imm & 0x00800000) imm |= 0xFF000000;
imm = imm << 2;
arm_next = ((int)arm_pc + imm + 8);
return 0;
}
static int arm_get_next_address(Context * ctx, ContextAddress * next_addr) {
ContextAddress addr = 0;
ContextAddress cpsr = 0;
/* read opcode at PC */
if (read_reg(ctx, pc_def, pc_def->size, &addr) < 0) return -1;
if (read_reg(ctx, cpsr_def, cpsr_def->size, &cpsr) < 0) return -1;
if (context_read_mem(ctx, addr, &arm_instr, 4) < 0) return -1;
arm_ctx = ctx;
arm_pc = (uint32_t)addr;
arm_cpsr = (uint32_t)cpsr;
trace(LOG_CONTEXT, "pc 0x%x, opcode 0x%x", arm_pc, arm_instr);
/* decode opcode */
arm_next = arm_pc + 4;
if (arm_evaluate_condition()) {
switch ((arm_instr >> 25) & 7) {
case 0:
case 1:
if ((arm_instr & 0x0ffffff0) == 0x012fff10) {
/* Branch and Exchange */
if (arm_get_next_bx() < 0) return -1;
break;
}
if (arm_get_next_data_processing() < 0) return -1;
break;
case 2:
case 3: /* Load */
if (arm_get_next_ldr() < 0) return -1;
break;
case 4: /* Load/store multiple */
if (arm_get_next_ldm() < 0) return -1;
break;
case 5: /* Branch and branch with link */
if (arm_get_next_branch() < 0) return -1;
break;
}
}
addr = arm_next;
if (addr >= 0xffff0000) {
/* Linux kernel user-mode helpers space - run to return address */
if (read_reg(ctx, lr_def, lr_def->size, &addr) < 0) return -1;
}
*next_addr = addr;
return 0;
}
static int enable_sw_stepping_mode(Context * ctx) {
Context * grp = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
ContextExtensionARM * ext = EXT(grp);
assert(!grp->exited);
assert(!ext->sw_stepping);
if (arm_get_next_address(ctx, &ext->addr) < 0) return -1;
trace(LOG_CONTEXT, "enable_sw_stepping_mode %s 0x%x", ctx->id, (unsigned)ext->addr);
if (context_read_mem(grp, ext->addr, ext->opcode, sizeof(BREAK_INST)) < 0) return -1;
if (context_write_mem(grp, ext->addr, BREAK_INST, sizeof(BREAK_INST)) < 0) return -1;
ext->sw_stepping = 1;
run_ctrl_lock();
return 0;
}
static int disable_sw_stepping_mode(Context * ctx) {
Context * grp = context_get_group(ctx, CONTEXT_GROUP_PROCESS);
ContextExtensionARM * ext = EXT(grp);
if (ext->sw_stepping) {
trace(LOG_CONTEXT, "disable_sw_stepping_mode %s", ctx->id);
run_ctrl_unlock();
ext->sw_stepping = 0;
if (grp->exited) return 0;
return context_write_mem(grp, ext->addr, ext->opcode, sizeof(BREAK_INST));
}
return 0;
}
int cpu_enable_stepping_mode(Context * ctx, uint32_t * is_cont) {
*is_cont = 1;
#if ENABLE_HardwareBreakpoints
{
int mode = 1;
ContextExtensionARM * bps = EXT(context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
if (get_bp_info(ctx) < 0) return -1;
#if defined(ENABLE_MismatchBreakpoints) && ENABLE_MismatchBreakpoints
if (bps->arch >= ARM_DEBUG_ARCH_V7_ECP14) mode = 2;
#endif
if (bps->bp_cnt > 0) return enable_hw_stepping_mode(ctx, mode);
}
#endif /* ENABLE_HardwareBreakpoints */
return enable_sw_stepping_mode(ctx);
}
int cpu_disable_stepping_mode(Context * ctx) {
#if ENABLE_HardwareBreakpoints
{
ContextExtensionARM * bps = EXT(context_get_group(ctx, CONTEXT_GROUP_BREAKPOINT));
if (bps->bp_cnt > 0) return disable_hw_stepping_mode(ctx);
}
#endif /* ENABLE_HardwareBreakpoints */
return disable_sw_stepping_mode(ctx);
}
static RegisterDefinition * alloc_reg(void) {
RegisterDefinition * r = regs_index + regs_cnt++;
assert(regs_cnt <= regs_max);
r->dwarf_id = -1;
r->eh_frame_id = -1;
r->big_endian = big_endian_host();
return r;
}
#if !USE_getauxval
# if defined(ENABLE_arch_armv7l)
# define read_fpsid(X) *(X) = 0
# define read_mvfr0(X) *(X) = 0x222
# else
# define read_fpsid(X) \
__asm __volatile("mrc p10, 7, %0, c0, c0, 0" \
: "=r" (*(X)) : : "memory")
# define read_mvfr0(X) \
__asm __volatile("mrc p10, 7, %0, c7, c0, 0" \
: "=r" (*(X)) : : "memory")
# endif
#endif
static void ini_reg_defs(void) {
int i;
RegisterDefinition * d;
regs_cnt = 0;
regs_max = 800;
regs_index = (RegisterDefinition *)loc_alloc_zero(sizeof(RegisterDefinition) * regs_max);
for (d = regs_def; d->name != NULL; d++) {
RegisterDefinition * r = alloc_reg();
assert(d->parent == NULL);
*r = *d;
if (r->offset == offsetof(REG_SET, REG_FP)) {
r->role = "FP";
}
else if (r->offset == offsetof(REG_SET, REG_SP)) {
r->role = "SP";
}
else if (r->offset == offsetof(REG_SET, REG_PC)) {
r->role = "PC";
pc_def = r;
}
else if (r->offset == offsetof(REG_SET, REG_LR)) {
r->role = "LR";
lr_def = r;
}
else if (r->offset == offsetof(REG_SET, REG_CPSR)) {
cpsr_def = r;
}
if (strcmp(r->name, "vfp") == 0) {
uint32_t fpsid = 0;
#if USE_getauxval
unsigned long hwcap = getauxval(AT_HWCAP);
if ((hwcap & HWCAP_ARM_VFP) == 0) fpsid = 1 << 23;
#else
read_fpsid(&fpsid);
#endif
if ((fpsid & (1 << 23)) == 0) {
int n;
RegisterDefinition * x = NULL;
uint32_t mvfr0 = 0;
#if USE_getauxval
if (hwcap & HWCAP_ARM_VFPv3D16) mvfr0 = 0x221;
else if (hwcap & HWCAP_ARM_VFPv3) mvfr0 = 0x222;
else mvfr0 = 0x110;
#else
read_mvfr0(&mvfr0);
#endif
for (n = 0; n < 3; n++) {
RegisterDefinition * w = NULL;
switch (n) {
case 0:
if (((mvfr0 >> 4) & 0xf) == 0) continue;
break;
case 1:
if (((mvfr0 >> 8) & 0xf) == 0) continue;
break;
case 2:
if ((mvfr0 & 0xf) == 0) continue;
break;
}
w = alloc_reg();
w->no_read = 1;
w->no_write = 1;
w->parent = r;
switch (n) {
case 0:
w->name = "32-bit";
for (i = 0; i < 32; i++) {
char nm[32];
x = alloc_reg();
snprintf(nm, sizeof(nm), "s%d", i);
x->name = loc_strdup(nm);
x->offset = REG_OFFSET(fp.fpregs[i / 2]);
if (r->big_endian) {
if ((i & 1) == 0) x->offset += 4;
}
else {
if ((i & 1) != 0) x->offset += 4;
}
x->size = 4;
x->dwarf_id = 64 + i;
x->eh_frame_id = 64 + i;
x->fp_value = 1;
x->parent = w;
}
break;
case 1:
w->name = "64-bit";
for (i = 0; i < 32; i++) {
char nm[32];
if (i >= 16 && ((mvfr0 >> 8) & 0xf) < 2) continue;
x = alloc_reg();
snprintf(nm, sizeof(nm), "d%d", i);
x->name = loc_strdup(nm);
x->offset = REG_OFFSET(fp.fpregs[i]);
x->size = 8;
x->dwarf_id = 256 + i;
x->eh_frame_id = 256 + i;
x->fp_value = 1;
x->parent = w;
}
break;
case 2:
w->name = "128-bit";
for (i = 0; i < 16; i++) {
char nm[32];
if (i >= 8 && (mvfr0 & 0xf) < 2) continue;
x = alloc_reg();
snprintf(nm, sizeof(nm), "q%d", i);
x->name = loc_strdup(nm);
x->offset = REG_OFFSET(fp.fpregs[i * 2]);
x->size = 16;
x->fp_value = 1;
x->parent = w;
}
break;
}
}
x = alloc_reg();
x->name = "fpscr";
x->offset = REG_OFFSET(fp.fpscr);
x->size = 4;
x->parent = r;
}
}
}
}
void ini_cpudefs_mdep(void) {
ini_reg_defs();
context_extension_offset = context_extension(sizeof(ContextExtensionARM));
}
#endif