SAM9G45死机问题
最近有个项目,用的SAM9G45平台,遇到一个问题,就是运行一段时间‘’死机‘’问题,现象就是下发协议没有反应。这个问题解决耗费了好长时间,现记录如下,希望能够帮助需要的人吧。
首先分析是程序真的死掉了,还是逻辑进入了死循环跳不出来。在心跳PIT中断里面加入灯闪烁,测试发现灯不闪了,说明是程序死掉了。接着分析看看程序死到哪里了?首先看ARM9手册,查看异常,如下:
我猜想,是否进入某个异常了,没有退出,导致PIT中断不能进入。看下启动文件:
Vectors
LDR pc,=resetHandler
undefVector
b undefVector ; Undefined instruction
swiVector
b swiVector ; Software interrupt
prefetchAbortVector
b prefetchAbortVector ; Prefetch abort
dataAbortVector
b dataAbortVector ; Data abort
reservedVector
b reservedVector ; Reserved for future use
irqVector
b irqHandler ; Interrupt
fiqVector
; Fast interrupt
;------------------------------------------------------------------------------
; Handles a fast interrupt request by branching to the address defined in the
; AIC.
;------------------------------------------------------------------------------
fiqHandler
b fiqHandler
看到没有除了IRQ,其它都是死循环,再看下系统外设中断怎么设置的:
/* Initialize AIC
****************/
AT91C_BASE_AIC->AIC_IDCR = 0xFFFFFFFF;
AT91C_BASE_AIC->AIC_SVR[0] = (unsigned int) defaultFiqHandler;
for (i = 1; i < 31; i++) {
AT91C_BASE_AIC->AIC_SVR[i] = (unsigned int) defaultIrqHandler;
}
AT91C_BASE_AIC->AIC_SPU = (unsigned int) defaultSpuriousHandler;
// Unstack nested interrupts
for (i = 0; i < 8 ; i++) {
AT91C_BASE_AIC->AIC_EOICR = 0;
}
再看:
void defaultSpuriousHandler( void )
{
while (1);
}
//------------------------------------------------------------------------------
/// Default handler for fast interrupt requests. Infinite loop.
//------------------------------------------------------------------------------
void defaultFiqHandler( void )
{
while (1);
}
//------------------------------------------------------------------------------
/// Default handler for standard interrupt requests. Infinite loop.
//------------------------------------------------------------------------------
void defaultIrqHandler( void )
{
while (1);
}
看到没有,默认所有的中断和外设都是死循环,修改如下:
Vectors
LDR pc,=resetHandler
undefVector
b undefHandler ; Undefined instruction
swiVector
b swiHandler ; Software interrupt
prefetchAbortVector
b prefetchAbortHandler ; Prefetch abort
dataAbortVector
b dataAbortHandler ; Data abort
reservedVector
b reservedVector ; Reserved for future use
irqVector
b irqHandler ; Interrupt
fiqVector
b fiqHandler ; Fast interrupt
处理函数添加打印标志,例如:
void defaultFiqHandler( void )
{
led_on();
while (1){
printf("defaultFiqHandler\r\n");
delayms(20);
led_on();
delayms(20);
led_off();
}
}
上电测试,发现进入了swiVector,查阅文档发现这个是软中断,通过SWI命令进入。难道代码里面有这部分操作,查看整个C代码,并没有发现有这个操作,然后通过反汇编查看asm文件,全部查找,也没有发现SWI指令,这可奇了怪了,为什么会进入这个中断呢?
另外一个同事通过不停的打LOG,最后定位到某个固定函数不能return,也就是说return没有执行,就触发SWI中断了。至此我们算是找到了死机的地方。可是为什么呢?我怀疑是栈溢出了,于是加大了栈,效果一样。于是我在SWI里面加了一点打印栈的东西,
MOV r0, sp
MRS r1, SPSR
MSR CPSR_c, #ARM_MODE_IRQ :OR: I_BIT :OR: F_BIT
MOV r2, sp
MOV r3, lr
MSR CPSR_c, #ARM_MODE_SVC :OR: I_BIT :OR: F_BIT ;´ò¿ªIRQ,¹Ø±ÕFIQ
BL defaultSwiHandler
汇编里面用R0~R3传递参数;
void defaultSwiHandler(uint32_t *pwPC,uint32_t hwSPSR,uint32_t *pwSPIRQ,uint32_t hwIRQLR)
{
//uint32_t* cur_sp = 0, *cur_lr = 0, *cur_pc = 0;
uint16_t i=0;
uint32_t *p=0;
led_on();
while (1){
printf("START\r\n");
extern unsigned char MainUsbOperFlag;
printf("defaultSwiHandler SP=%X\r\n",(uint32_t)pwPC);
printf("defaultSwiHandler SPSR=%X\r\n",(uint32_t)hwSPSR);
printf("defaultSwiHandler IrqSP=%X\r\n",(uint32_t)pwSPIRQ);
printf("defaultSwiHandler IrqLr=%X\r\n",(uint32_t)hwIRQLR);
printf("defaultSwiHandler MainUsbOperFlag=%u\r\n",MainUsbOperFlag);
p=(uint32_t*)0x00310000;
for(i=0;i<2048;i++){
printf("defaultSwiHandler =%X\r\n",*p);
p--;
}
printf("END\r\n");
//printf("RSTC_RSR =%u\r\n",AT91C_BASE_RSTC->RSTC_RSR);
delayms(60);
led_on();
delayms(60);
led_off();
}
}
打印发现栈里面有一段全是0xFFFFFFFF,难道PC指针因为这个?试了如下语句:
(*((void(*)())(0xFFFFFFFF)))();
果然触发了SWI中断。这样还是不确定是不是这个问题,于是我把正常栈打印出来:
添加如下语句:
{
static uint16_t i=0;
i++;
if(i>100){
__asm {
SWI 0
}
}
}
发现打印出来的栈并没有一段0xFFFFFFFF。
首先还是考虑栈溢出,于是我给栈加了标签:
; 给栈打印标签
LDR R0, = |Image$$ARM_LIB_STACK$$ZI$$Limit| ;ÔÚmapÎļþÀïÃ棬±íʾһ¸öµØÖ·
MOV R1,#2048
STATIC1
SUB R0,#4
MOV R2,#165
STR R2,[R0]
SUB R1,#1
CMP R1,#0
BNE STATIC1
; Enter the C code
IMPORT __main
LDR R0, =__main
BX R0
loop4
B loop4
END
发现栈并没有越界,那只能考虑是某个指针飞了。于是添加下面的宏:
#define my_pi(_Name,__X) do{ \
if((__X)>=0x30E000){ \
while(1){ \
delayms(1000); \
printf("NAME=%d %X\n\r",_Name,(uint32_t)(__X)); \
} \
} \
}while(0)
注:
1、地址需要根据栈里面的0xFFFFFFFF处地址调整。
然后在不能反回的函数里面把用到的指针地址全部打出来,果然发现有问题。函数嵌套调用,上层函数把局部变量的地址传给子函数,子函数获得的地址有时候是错的。在局部变量前加static,在测试,问题解决。至此我想吐血。。。。。
再说下官方给的启动文件一个bug,就是原来的栈设置是这样:
; Setup Stack for each mode
LDR R0, = |Image$$ARM_LIB_STACK$$ZI$$Limit|
; Enter IRQ Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_IRQ:OR:I_BIT:OR:F_BIT
MOV SP, R0
SUB R4, SP, #IRQ_Stack_Size
; Supervisor mode (interrupts enabled)
MSR CPSR_c, #ARM_MODE_SVC :OR: F_BIT
MOV SP, R4
; Enter the C code
IMPORT __main
LDR R0, =__main
BX R0
loop4
B loop4
END
但是,我追了下,发现ARM_MODE_SVC栈在经过__main,进入main时候,被修改了,在SRAM最高处,也就是说ARM_MODE_SVC只能设置在最高地址处,不明白为什么这样。
原因找到,如下:
If you use a scatter file to tailor stack and heap placement, the linker includes a version of the library
heap and stack setup code using the linker defined symbols, ARM_LIB_*, for these region names.
Alternatively you can create your own implementation.
Load_region 0x300000 0x10000 {
Fixed_region 0x300000 {
*.o (VECTOR, +First)
.ANY (+RO)
}
Relocate_region +0 {
*(cstartup +First)
.ANY (+RW +ZI)
}
ScatterAssert((ImageLength(Fixed_region) + ImageLength(Relocate_region)) < 0xE000)
; ARM_LIB_HEAP 0x30F000 EMPTY 0x800 {
; }
; ARM_LIB_STACK 0x310000 EMPTY -0x800 {
; }
ARM_LIB_HEAP 0x30E000 EMPTY 0x800 {
}
ARM_LIB_STACK 0x310000 EMPTY -0x1800 {
}
}
__main
_main_stk
0x003000b4: e59fd00c .... LDR sp,__lit__00000000 ; [0x3000c8] = 0x310000
(这个是汇编文件,__main首先就是修改SP操作,不知道__lit__00000000可不可以修改)
于是我修改了栈设置,把ARM_MODE_SVC设置在最高处:
; Setup Stack for each mode
LDR R0, = |Image$$ARM_LIB_STACK$$ZI$$Limit| ;ÔÚmapÎļþÀïÃ棬±íʾһ¸öµØÖ·
;SUB R0,R0, #SVC_Stack_Size
MSR CPSR_c, #ARM_MODE_SVC:OR:I_BIT:OR:F_BIT ;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
MOV SP, R0
; Enter ABT Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_ABT:OR:I_BIT:OR:F_BIT ;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
SUB R4, R0, #SVC_Stack_Size
MOV SP, R4
; Enter UND Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_UND:OR:I_BIT:OR:F_BIT ;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
SUB R4, R4, #ABT_Stack_Size
MOV SP, R4
; Enter FIQ Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_FIQ:OR:I_BIT:OR:F_BIT ;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
SUB R4, R4, #UND_Stack_Size
MOV SP, R4
; Enter IRQ Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_IRQ:OR:I_BIT:OR:F_BIT ;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
SUB R4, R4, #FIQ_Stack_Size
MOV SP, R4
; Supervisor mode (interrupts enabled)
;MSR CPSR_c, #ARM_MODE_SYS :OR:I_BIT:OR: F_BIT;±£´æµ½×´Ì¬¼Ä´æÆ÷,CPSR_c±íʾCPSRµÄµÍ°Ëλ
;SUB R4, R4, #IRQ_Stack_Size
;MOV SP, R4
MSR CPSR_c, #ARM_MODE_SVC :OR: F_BIT ;´ò¿ªIRQ,¹Ø±ÕFIQ
好了至此完毕。
说句题外话,为什么官网启动代码错误的,也能跑呢,首先看下iqr汇编:
irqHandler
; Save interrupt context on the stack to allow nesting */
SUB lr, lr, #4
STMFD sp!, {lr}
MRS lr, SPSR
STMFD sp!, {r0,r1,lr}
; Write in the IVR to support Protect Mode */
LDR lr, =AT91C_BASE_AIC
LDR r0, [r14, #AIC_IVR]
STR lr, [r14, #AIC_IVR]
; Branch to interrupt handler in Supervisor mode */
MSR CPSR_c, #ARM_MODE_SVC
STMFD sp!, {r1-r4, r12, lr}
MOV lr, pc
BX r0
LDMIA sp!, {r1-r4, r12, lr}
MSR CPSR_c, #ARM_MODE_IRQ :OR: I_BIT
; Acknowledge interrupt */
LDR lr, =AT91C_BASE_AIC
STR lr, [r14, #AIC_EOICR]
; Restore interrupt context and branch back to calling code
LDMIA sp!, {r0,r1,lr}
MSR SPSR_cxsf, lr
LDMIA sp!, {pc}^
在IRQ模式下只用了4个字,我追了下栈,发现main里面,在进如while(1)时候,有入栈操作,但是不会反回,因此占用前四个字,没有影响,我入栈15个寄存器,直接跑飞。
i.main
main
0x00306564: e92d400e .@-. PUSH {r1-r3,lr}
0x00306568: e3a000c0 .... MOV r0,#0xc0
(查看这段汇编,main刚进入,就有刚好4个字节的入栈操作,哎,就是这个恰好隐藏了一个很深的Bug)
再补充一个调试过程当中遇到的奇怪问题,就是刚开始用官方的启动代码调试,中断优先级只能设置为一样,如果不一样,就会死机。原因如下:
首先看栈设置:
; Setup Stack for each mode
LDR R0, = |Image$$ARM_LIB_STACK$$ZI$$Limit|
; Enter IRQ Mode and set its Stack Pointer
MSR CPSR_c, #ARM_MODE_IRQ:OR:I_BIT:OR:F_BIT
MOV SP, R0
SUB R4, SP, #IRQ_Stack_Size
; Supervisor mode (interrupts enabled)
MSR CPSR_c, #ARM_MODE_SVC | F_BIT
MOV SP, R4
; Enter the C code
IMPORT __main
LDR R0, =__main
BX R0
loop4
B loop4
END
首先设置的IRQ中断栈,在
|Image$$ARM_LIB_STACK$$ZI$$Limit|
处,查看MAP文件,在SRAM最高地址处。然后是一个
IRQ_Stack_Size的偏移,设置SVC栈。通过上面的分析可知,这里SVC栈被__main修改为SRAM最高地址处。也就是和
IRQ栈重合。那么再看IRQ中断怎么处理:
MSR CPSR_c, #ARM_MODE_SVC
看到没有在进入具体中断处理函数之前,把模式改为SVC,同时打开了IRQ和FIQ,也就是说在中断处理函数里面是可以进行中断嵌套的。那么这个时候,如果有中断就会有问题了,前面讲的很清楚了。
哎,至此完毕,睡觉,心好累。。。。。。
2019.01.14
看了很多资料,说ARM9不支持非对齐访问,于是我写了如下程序:
while(1){
volatile static uint8_t *pi=(uint8_t *)&spid;
volatile static uint32_t j=0;
printf("pi= %X",(uint32_t)pi);
j=*((uint32_t*)pi);
printf("j= %X",(uint32_t)j);
pi++;
delayms(1000);
}
发现程序能够正常打印,并没有进入异常,因此说明ARM9支持地址非对齐访问。
还没有评论,来说两句吧...