原帖 | 海底 | 2026-07-26 20:21 | 👍3 | 阅读约1
记录一个预取指导致死机的坑
死机现象:
Boot33 跳转后无任何日志输出,且Trace32无法正常 Attach;若已在连接状态下运行,则在死机后T32显示Core Power Down,无法进行后续调试。
该问题表现出明显的时序与布局敏感性:在启动早期(如 ns_start())增加一行 early_syslog(),问题可能消失;调整代码布局(如从 QSPI 搬迁至 ITCM)可改变问题出现与否;反之,原本稳定的版本在仅调整编译位置或轻微改动代码后,也可能复现该"零日志、Core Power Down"的现象。
原因分析:
直接原因是在如下配置DDR MPU的地方,意外的访问了DDR地址。C代码如下:
========== 图片 2(C代码) ==========
size = section_get_size(SECTION_DDR1);
if (size) {
up_clean_dcache((uintptr_t)&__ap_ddr1_base, ((uintptr_t)&__ap_ddr1_base + size));
mpu_user_intsram((uintptr_t)&__ap_ddr1_base, size);
}
======================================
C代码看不出问题,对应汇编如下:
========== 图片 3(up_clean_dcache 反汇编) ==========
00006ede
6ede: b4f0 push {r4, r5, r6, r7}
6ee0: f04f 26e0 mov.w r6, #3758153728 @ 0xe000e000
6ee4: 2401 movs r4, #1
6ee6: f8d6 2d80 ldr.w r2, [r6, #3456] @ 0xd80
6eea: 1a0f subs r7, r1, r0
=====================================================
========== 图片 4(veneer 反汇编) ==========
8010ec18 <__up_clean_dcache_veneer>:
8010ec18: f85f f000 ldr.w pc, [pc] @ 8010ec1c <__up_clean_dcache_veneer+0x4>
8010ec1c: 6edf ldr r7, [r3, #108] @ 0x6c
=================================================
T32执行时的Runtime如下:
========== 图片 5(T32 Runtime - 卡死场景,PC=8010EC18) ==========
【寄存器视图】
R0 = 60000000 R8 = 08080808
R1 = 80000000 R9 = 09090909
R2 = 00F00000 R10 = 10101010
R3 = 60000000 R11 = 11111111
R4 = 20000000 R12 = 00000000
R5 = 3009B2BC R13(SP) = 3074D794
R6 = 3074D7AC R14(LR) = 8001A9C7
R7 = 800014DD PC = 8010EC18
XPSR = 91000000 CONTROL = 4
MSP = 3074D794 FAULTMASK = 0
MSPL = 0 BASEPRI = 0
PSP = 30078A70 PRIMASK = 0
PSPL = 0
【反汇编视图】
ZST:8010EC0C CECB ldm r6,{r0,r1,r3,r6,r7}
ZST:8010EC0E 0000 movs r0,r0
ZST:8010EC10 F85FF000 ldr pc,0x8010EC14 ; pc,=0x11D01
ZST:8010EC14 1D01 adds r1,r0,#0x4
ZST:8010EC16 0001 movs r1,r0
ZST:8010EC18 F85FF000 ldr pc,0x8010EC1C ; pc,=0x6EDF <-- 当前PC
ZST:8010EC1C 6EDF ldr r7,[r3,#0x6C]
ZST:8010EC1E 0000 movs r0,r0
================================================================
0x8010EC18指令为跳转到up_clean_dcache()。0x8010EC1C保存的是up_clean_dcache()的地址,不是指令。Ldr从这个地址里取值保存到PC。但是ARM在执行0x8010EC18的时候,将下一个地址的值识别成了一条指令,并对其做了预读取,即读取了r3+0x6C地址里的值。运行时R3寄存器的值为0x60000000,DDR地址,此时DDR还未初始化,导致了访问非法地址,总线卡死。
当我们修改了代码,比如加了打印,函数的地址变了,如下图:
========== 图片 1(T32 Runtime - 修改后场景,PC=8010E770) ==========
【寄存器视图】
R0 = 60000000 R8 = 08080808
R1 = 80000000 R9 = 09090909
R2 = 00F00000 R10 = 10101010
R3 = 60000000 R11 = 11111111
R4 = 20000000 R12 = 00000000
R5 = 3009B2BC R13(SP) = 3074D794
R6 = 3074D7AC R14(LR) = 8001A9A7
R7 = 800014DD PC = 8010E770
XPSR = 91000000 CONTROL = 4
MSP = 3074D794 FAULTMASK = 0
MSPL = 0 BASEPRI = 0
PSP = 30078A70 PRIMASK = 0
PSPL = 0
【反汇编视图】
ZST:8010E760 F85FF000 ldr pc,0x8010E764 ; pc,=0x11F15
ZST:8010E764 1F15 subs r5,r2,#0x4
ZST:8010E766 0001 movs r1,r0
ZST:8010E768 F85FF000 ldr pc,0x8010E76C ; pc,=0xFDA9
ZST:8010E76C FDA90000 stc2 p0,c0,[r9,#0x0]!
ZST:8010E770 F85FF000 ldr pc,0x8010E774 ; pc,=0x6ECF <-- 当前PC
ZST:8010E774 6ECF ldr r7,[r1,#0x6C]
ZST:8010E776 0000 movs r0,r0
================================================================
原本误当做指令预读取的指令(0x8010E770)变成了r1 + 0x6C。运行时R1的值为0x80000000,为有效地址,Core执行后没有卡死。
交叉验证,在原本卡死的场景下,将R3改成有效地址,比如0x20000000,不再复现卡死;而在原本可以执行的场景下,将R1改成无效地址,比如0x60000000,出现卡死的现象。
由此可见,这里死机的原因就是Core做了预读取(Instruction Prefetch和Speculative Data Access)。预取行为与异常触发时序的进一步讨论见 HardFault触发真的是同步的吗?举个例子。
相关笔记