500 lines
16 KiB
C
500 lines
16 KiB
C
/*
|
|
* ARM GDB support
|
|
* arch-specific portion of GDB stub
|
|
*
|
|
* File : arm_stub.c
|
|
* This file is part of RT-Thread RTOS
|
|
* COPYRIGHT (C) 2006, RT-Thread Develop Team
|
|
*
|
|
* The license and distribution terms for this file may be
|
|
* found in the file LICENSE in this distribution or at
|
|
* http://www.rt-thread.org/license/LICENSE
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2014-07-04 Wzyy2 first version
|
|
*/
|
|
#include <rtthread.h>
|
|
#include <rthw.h>
|
|
#include <gdb_stub.h>
|
|
#include <arch_gdb.h>
|
|
|
|
#define PS_N 0x80000000
|
|
#define PS_Z 0x40000000
|
|
#define PS_C 0x20000000
|
|
#define PS_V 0x10000000
|
|
|
|
#define IS_THUMB_ADDR(addr) ((addr) & 1)
|
|
#define MAKE_THUMB_ADDR(addr) ((addr) | 1)
|
|
#define UNMAKE_THUMB_ADDR(addr) ((addr) & ~1)
|
|
|
|
static int compiled_break = 0;
|
|
static unsigned long step_addr = 0;
|
|
static int ins_will_execute(unsigned long ins);
|
|
static unsigned long target_ins(unsigned long *pc, unsigned long ins);
|
|
|
|
|
|
/*struct gdb_arch - Describe architecture specific values.*/
|
|
struct gdb_arch arch_gdb_ops = {
|
|
.gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7} //Little-Endian
|
|
};
|
|
|
|
struct rt_gdb_register
|
|
{
|
|
rt_uint32_t r0;
|
|
rt_uint32_t r1;
|
|
rt_uint32_t r2;
|
|
rt_uint32_t r3;
|
|
rt_uint32_t r4;
|
|
rt_uint32_t r5;
|
|
rt_uint32_t r6;
|
|
rt_uint32_t r7;
|
|
rt_uint32_t r8;
|
|
rt_uint32_t r9;
|
|
rt_uint32_t r10;
|
|
rt_uint32_t fp;
|
|
rt_uint32_t ip;
|
|
rt_uint32_t sp;
|
|
rt_uint32_t lr;
|
|
rt_uint32_t pc;
|
|
rt_uint32_t cpsr;
|
|
rt_uint32_t ORIG_r0;
|
|
}*regs;
|
|
|
|
/**
|
|
* gdb_breakpoint - generate a compiled_breadk
|
|
* It is used to sync up with a debugger and stop progarm
|
|
*/
|
|
void gdb_breakpoint()
|
|
{
|
|
asm(".word 0xe7ffdeff");
|
|
}
|
|
|
|
void gdb_set_register(void *hw_regs)
|
|
{
|
|
regs = (struct rt_gdb_register *)hw_regs;
|
|
}
|
|
void gdb_get_register(unsigned long *gdb_regs)
|
|
{
|
|
int regno;
|
|
/* Initialize all to zero. */
|
|
for (regno = 0; regno < GDB_MAX_REGS; regno++)
|
|
gdb_regs[regno] = 0;
|
|
|
|
gdb_regs[GDB_R0] = regs->r0;
|
|
gdb_regs[GDB_R1] = regs->r1;
|
|
gdb_regs[GDB_R2] = regs->r2;
|
|
gdb_regs[GDB_R3] = regs->r3;
|
|
gdb_regs[GDB_R4] = regs->r4;
|
|
gdb_regs[GDB_R5] = regs->r5;
|
|
gdb_regs[GDB_R6] = regs->r6;
|
|
gdb_regs[GDB_R7] = regs->r7;
|
|
gdb_regs[GDB_R8] = regs->r8;
|
|
gdb_regs[GDB_R9] = regs->r9;
|
|
gdb_regs[GDB_R10] = regs->r10;
|
|
gdb_regs[GDB_FP] = regs->fp;
|
|
gdb_regs[GDB_IP] = regs->ip;
|
|
gdb_regs[GDB_SPT] = regs->sp;
|
|
gdb_regs[GDB_LR] = regs->lr;
|
|
gdb_regs[GDB_PC] = regs->pc;
|
|
gdb_regs[GDB_CPSR] = regs->cpsr;
|
|
|
|
};
|
|
|
|
|
|
void gdb_put_register(unsigned long *gdb_regs)
|
|
{
|
|
regs->r0 = gdb_regs[GDB_R0];
|
|
regs->r1 = gdb_regs[GDB_R1];
|
|
regs->r2 = gdb_regs[GDB_R2];
|
|
regs->r3 = gdb_regs[GDB_R3];
|
|
regs->r4 = gdb_regs[GDB_R4];
|
|
regs->r5 = gdb_regs[GDB_R5];
|
|
regs->r6 = gdb_regs[GDB_R6];
|
|
regs->r7 = gdb_regs[GDB_R7];
|
|
regs->r8 = gdb_regs[GDB_R8];
|
|
regs->r9 = gdb_regs[GDB_R9];
|
|
regs->r10 = gdb_regs[GDB_R10];
|
|
regs->fp = gdb_regs[GDB_FP];
|
|
regs->ip = gdb_regs[GDB_IP];
|
|
regs->sp = gdb_regs[GDB_SPT];
|
|
regs->lr = gdb_regs[GDB_LR];
|
|
regs->pc = gdb_regs[GDB_PC];
|
|
regs->cpsr = gdb_regs[GDB_CPSR];
|
|
}
|
|
|
|
|
|
/* It will be called during process_packet */
|
|
int gdb_arch_handle_exception(char *remcom_in_buffer,
|
|
char *remcom_out_buffer)
|
|
{
|
|
unsigned long addr,curins;
|
|
char *ptr;
|
|
|
|
/*clear single step*/
|
|
if (step_addr) {
|
|
gdb_remove_sw_break(step_addr);
|
|
step_addr = 0;
|
|
}
|
|
|
|
switch (remcom_in_buffer[0]) {
|
|
case 'D':
|
|
case 'k':
|
|
case 'c':
|
|
/*
|
|
* If this was a compiled breakpoint, we need to move
|
|
* to the next instruction or we will breakpoint
|
|
* over and over again
|
|
*/
|
|
ptr = &remcom_in_buffer[1];
|
|
if (gdb_hex2long(&ptr, &addr))
|
|
regs->pc = addr;
|
|
else if (compiled_break == 1)
|
|
regs->pc += 4;
|
|
compiled_break = 0;
|
|
return 0;
|
|
|
|
case 's':
|
|
ptr = &remcom_in_buffer[1];
|
|
if (gdb_hex2long(&ptr, &addr))
|
|
regs->pc = addr;
|
|
|
|
curins = *(unsigned long*)(regs->pc);
|
|
if (ins_will_execute(curins))
|
|
//Decode instruction to decide what the next pc will be
|
|
step_addr = target_ins((unsigned long *)regs->pc, curins);
|
|
else
|
|
step_addr = regs->pc + 4;
|
|
|
|
#ifdef RT_GDB_DEBUG
|
|
rt_kprintf("\n next will be %x \n",step_addr);
|
|
#endif
|
|
gdb_set_sw_break(step_addr);
|
|
|
|
if (compiled_break == 1)
|
|
regs->pc += 4;
|
|
compiled_break = 0;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* flush icache to let the sw breakpoint working */
|
|
void gdb_flush_icache_range(unsigned long start, unsigned long end)
|
|
{
|
|
#ifdef RT_GDB_ICACHE
|
|
extern void mmu_invalidate_icache();
|
|
mmu_invalidate_icache(); //for arm,wo can only invalidate it
|
|
#endif
|
|
}
|
|
|
|
/* register a hook in undef*/
|
|
int gdb_undef_hook(void *regs)
|
|
{
|
|
struct rt_gdb_register *tmp_reg = (struct rt_gdb_register *)regs;
|
|
unsigned long *tmp_pc = (unsigned long *)tmp_reg->pc;
|
|
|
|
/* it is a compiled break */
|
|
if (*tmp_pc == GDB_COMPILED_BREAK) {
|
|
compiled_break = 1;
|
|
gdb_handle_exception(SIGTRAP, regs);
|
|
return 1;
|
|
|
|
}
|
|
/* it is a sw break */
|
|
else if (*tmp_pc == GDB_BREAKINST) {
|
|
gdb_handle_exception(SIGTRAP, regs);
|
|
return 1;
|
|
}
|
|
/*or we just go */
|
|
return 0;
|
|
|
|
}
|
|
|
|
static unsigned long gdb_arch_regs[GDB_MAX_REGS];
|
|
static int ins_will_execute(unsigned long ins)
|
|
{
|
|
unsigned long psr = regs->cpsr; // condition codes
|
|
int res = 0;
|
|
switch ((ins & 0xF0000000) >> 28) {
|
|
case 0x0: // EQ
|
|
res = (psr & PS_Z) != 0;
|
|
break;
|
|
case 0x1: // NE
|
|
res = (psr & PS_Z) == 0;
|
|
break;
|
|
case 0x2: // CS
|
|
res = (psr & PS_C) != 0;
|
|
break;
|
|
case 0x3: // CC
|
|
res = (psr & PS_C) == 0;
|
|
break;
|
|
case 0x4: // MI
|
|
res = (psr & PS_N) != 0;
|
|
break;
|
|
case 0x5: // PL
|
|
res = (psr & PS_N) == 0;
|
|
break;
|
|
case 0x6: // VS
|
|
res = (psr & PS_V) != 0;
|
|
break;
|
|
case 0x7: // VC
|
|
res = (psr & PS_V) == 0;
|
|
break;
|
|
case 0x8: // HI
|
|
res = ((psr & PS_C) != 0) && ((psr & PS_Z) == 0);
|
|
break;
|
|
case 0x9: // LS
|
|
res = ((psr & PS_C) == 0) || ((psr & PS_Z) != 0);
|
|
break;
|
|
case 0xA: // GE
|
|
res = ((psr & (PS_N|PS_V)) == (PS_N|PS_V)) ||
|
|
((psr & (PS_N|PS_V)) == 0);
|
|
break;
|
|
case 0xB: // LT
|
|
res = ((psr & (PS_N|PS_V)) == PS_N) ||
|
|
((psr & (PS_N|PS_V)) == PS_V);
|
|
break;
|
|
case 0xC: // GT
|
|
res = ((psr & (PS_N|PS_V)) == (PS_N|PS_V)) ||
|
|
((psr & (PS_N|PS_V)) == 0);
|
|
res = ((psr & PS_Z) == 0) && res;
|
|
break;
|
|
case 0xD: // LE
|
|
res = ((psr & (PS_N|PS_V)) == PS_N) ||
|
|
((psr & (PS_N|PS_V)) == PS_V);
|
|
res = ((psr & PS_Z) == PS_Z) || res;
|
|
break;
|
|
case 0xE: // AL
|
|
res = 1;
|
|
break;
|
|
case 0xF: // NV
|
|
if (((ins & 0x0E000000) >> 24) == 0xA)
|
|
res = 1;
|
|
else
|
|
res = 0;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static unsigned long RmShifted(int shift)
|
|
{
|
|
unsigned long Rm = gdb_arch_regs[shift & 0x00F];
|
|
int shift_count;
|
|
if ((shift & 0x010) == 0) {
|
|
shift_count = (shift & 0xF80) >> 7;
|
|
} else {
|
|
shift_count = gdb_arch_regs[(shift & 0xF00) >> 8];
|
|
}
|
|
switch ((shift & 0x060) >> 5) {
|
|
case 0x0: // Logical left
|
|
Rm <<= shift_count;
|
|
break;
|
|
case 0x1: // Logical right
|
|
Rm >>= shift_count;
|
|
break;
|
|
case 0x2: // Arithmetic right
|
|
Rm = (unsigned long)((long)Rm >> shift_count);
|
|
break;
|
|
case 0x3: // Rotate right
|
|
if (shift_count == 0) {
|
|
// Special case, RORx
|
|
Rm >>= 1;
|
|
if (gdb_arch_regs[GDB_CPSR] & PS_C) Rm |= 0x80000000;
|
|
} else {
|
|
Rm = (Rm >> shift_count) | (Rm << (32-shift_count));
|
|
}
|
|
break;
|
|
}
|
|
return Rm;
|
|
}
|
|
|
|
// Decide the next instruction to be executed for a given instruction
|
|
static unsigned long target_ins(unsigned long *pc, unsigned long ins)
|
|
{
|
|
unsigned long new_pc, offset, op2;
|
|
unsigned long Rn;
|
|
int i, reg_count, c;
|
|
|
|
gdb_get_register(gdb_arch_regs);
|
|
|
|
switch ((ins & 0x0C000000) >> 26) {
|
|
case 0x0:
|
|
// BX or BLX
|
|
if ((ins & 0x0FFFFFD0) == 0x012FFF10) {
|
|
new_pc = (unsigned long)gdb_arch_regs[ins & 0x0000000F];
|
|
return new_pc;
|
|
}
|
|
// Data processing
|
|
new_pc = (unsigned long)(pc+1);
|
|
if ((ins & 0x0000F000) == 0x0000F000) {
|
|
// Destination register is PC
|
|
if ((ins & 0x0FBF0000) != 0x010F0000) {
|
|
Rn = (unsigned long)gdb_arch_regs[(ins & 0x000F0000) >> 16];
|
|
if ((ins & 0x000F0000) == 0x000F0000) Rn += 8; // PC prefetch!
|
|
if ((ins & 0x02000000) == 0) {
|
|
op2 = RmShifted(ins & 0x00000FFF);
|
|
} else {
|
|
op2 = ins & 0x000000FF;
|
|
i = (ins & 0x00000F00) >> 8; // Rotate count
|
|
op2 = (op2 >> (i*2)) | (op2 << (32-(i*2)));
|
|
}
|
|
switch ((ins & 0x01E00000) >> 21) {
|
|
case 0x0: // AND
|
|
new_pc = Rn & op2;
|
|
break;
|
|
case 0x1: // EOR
|
|
new_pc = Rn ^ op2;
|
|
break;
|
|
case 0x2: // SUB
|
|
new_pc = Rn - op2;
|
|
break;
|
|
case 0x3: // RSB
|
|
new_pc = op2 - Rn;
|
|
break;
|
|
case 0x4: // ADD
|
|
new_pc = Rn + op2;
|
|
break;
|
|
case 0x5: // ADC
|
|
c = (gdb_arch_regs[GDB_CPSR] & PS_C) != 0;
|
|
new_pc = Rn + op2 + c;
|
|
break;
|
|
case 0x6: // SBC
|
|
c = (gdb_arch_regs[GDB_CPSR] & PS_C) != 0;
|
|
new_pc = Rn - op2 + c - 1;
|
|
break;
|
|
case 0x7: // RSC
|
|
c = (gdb_arch_regs[GDB_CPSR] & PS_C) != 0;
|
|
new_pc = op2 - Rn +c - 1;
|
|
break;
|
|
case 0x8: // TST
|
|
case 0x9: // TEQ
|
|
case 0xA: // CMP
|
|
case 0xB: // CMN
|
|
break; // PC doesn't change
|
|
case 0xC: // ORR
|
|
new_pc = Rn | op2;
|
|
break;
|
|
case 0xD: // MOV
|
|
new_pc = op2;
|
|
break;
|
|
case 0xE: // BIC
|
|
new_pc = Rn & ~op2;
|
|
break;
|
|
case 0xF: // MVN
|
|
new_pc = ~op2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return new_pc;
|
|
case 0x1:
|
|
if ((ins & 0x02000010) == 0x02000010) {
|
|
// Undefined!
|
|
return (unsigned long)(pc+1);
|
|
} else {
|
|
if ((ins & 0x00100000) == 0) {
|
|
// STR
|
|
return (unsigned long)(pc+1);
|
|
} else {
|
|
// LDR
|
|
if ((ins & 0x0000F000) != 0x0000F000) {
|
|
// Rd not PC
|
|
return (unsigned long)(pc+1);
|
|
} else {
|
|
Rn = (unsigned long)gdb_arch_regs[(ins & 0x000F0000) >> 16];
|
|
if ((ins & 0x000F0000) == 0x000F0000) Rn += 8; // PC prefetch!
|
|
if (ins & 0x01000000) {
|
|
// Add/subtract offset before
|
|
if ((ins & 0x02000000) == 0) {
|
|
// Immediate offset
|
|
if (ins & 0x00800000) {
|
|
// Add offset
|
|
Rn += (ins & 0x00000FFF);
|
|
} else {
|
|
// Subtract offset
|
|
Rn -= (ins & 0x00000FFF);
|
|
}
|
|
} else {
|
|
// Offset is in a register
|
|
if (ins & 0x00800000) {
|
|
// Add offset
|
|
Rn += RmShifted(ins & 0x00000FFF);
|
|
} else {
|
|
// Subtract offset
|
|
Rn -= RmShifted(ins & 0x00000FFF);
|
|
}
|
|
}
|
|
}
|
|
return *(unsigned long *)Rn;
|
|
}
|
|
}
|
|
}
|
|
return (unsigned long)(pc+1);
|
|
case 0x2: // Branch, LDM/STM
|
|
if ((ins & 0x02000000) == 0) {
|
|
// LDM/STM
|
|
if ((ins & 0x00100000) == 0) {
|
|
// STM
|
|
return (unsigned long)(pc+1);
|
|
} else {
|
|
// LDM
|
|
if ((ins & 0x00008000) == 0) {
|
|
// PC not in list
|
|
return (unsigned long)(pc+1);
|
|
} else {
|
|
Rn = (unsigned long)gdb_arch_regs[(ins & 0x000F0000) >> 16];
|
|
if ((ins & 0x000F0000) == 0x000F0000) Rn += 8; // PC prefetch!
|
|
offset = ins & 0x0000FFFF;
|
|
reg_count = 0;
|
|
for (i = 0; i < 15; i++) {
|
|
if (offset & (1<<i)) reg_count++;
|
|
}
|
|
if (ins & 0x00800000) {
|
|
// Add offset
|
|
Rn += reg_count*4;
|
|
} else {
|
|
// Subtract offset
|
|
Rn -= 4;
|
|
}
|
|
return *(unsigned long *)Rn;
|
|
}
|
|
}
|
|
} else {
|
|
// Branch
|
|
if (ins_will_execute(ins)) {
|
|
offset = (ins & 0x00FFFFFF) << 2;
|
|
if (ins & 0x00800000)
|
|
offset |= 0xFC000000; // sign extend
|
|
new_pc = (unsigned long)(pc+2) + offset;
|
|
// If its BLX, make new_pc a thumb address.
|
|
if ((ins & 0xFE000000) == 0xFA000000) {
|
|
if ((ins & 0x01000000) == 0x01000000)
|
|
new_pc |= 2;
|
|
new_pc = MAKE_THUMB_ADDR(new_pc);
|
|
}
|
|
|
|
return new_pc;
|
|
} else {
|
|
// Falls through
|
|
return (unsigned long)(pc+1);
|
|
}
|
|
}
|
|
case 0x3: // Coprocessor & SWI
|
|
if (((ins & 0x03000000) == 0x03000000) && ins_will_execute(ins)) {
|
|
// SWI
|
|
// TODO(wzyy2) some problems.
|
|
extern unsigned long vector_swi;
|
|
return vector_swi;
|
|
} else {
|
|
return (unsigned long)(pc+1);
|
|
}
|
|
default:
|
|
// Never reached - but fixes compiler warning.
|
|
return 0;
|
|
}
|
|
}
|