找到在 cortex-m4 上发生中断的位置

find where the interrupt happened on cortex-m4

我试图在我的代码中找到特定中断发生的位置。在这种情况下,它位于 stm32f4 微控制器上,中断是 SysTick_Handler。

我想要的基本上是找出系统中断发生的地方。我正在使用 arm-none-eabi-gdb 来尝试查找回溯,但我从那里得到的唯一信息是:

(gdb) bt
#0  SysTick_Handler () at modules/profiling.c:66
#1  <signal handler called>
#2  0x55555554 in ?? () Backtrace stopped: previous frame identical to this frame (corrupt stack?)

如何获取有关程序在中断触发之前所在位置的一些信息?

查看 arm 文档 here,看来我应该能够读取堆栈指针,并从那里获取 PC。但这正是 GDB 中的展开器所做的,不是吗?

这称为调试。最简单的入门方法是在整个代码中到处调用 printf() 。 运行 程序。如果打印出来:

到达A点
到达B点
到达C点

然后死了,那么你知道它在 "C" 和 "D." 之间死了你现在可以通过在 "C" 和 "D"间隔 printf() 调用。

这是初学者入门的最佳方式。许多经验丰富的专家也更喜欢使用 printf() 进行调试。调试器可能会妨碍您。

你在问题的最后是在正确的轨道上。 ARM Cortex-M内核有两个堆栈指针,主堆栈指针(MSP,用于中断)和进程堆栈指针(PSP,用于任务)。

当有优先级中断进来时,当前寄存器值(对于大多数寄存器)被压入当前堆栈(如果中断后台应用程序则为PSP,如果中断较低优先级中断则为MSP),然后堆栈切换到 MSP(如果还没有的话)。

当您第一次进入中断时,link 寄存器(LR,return 地址)的值主要是 F 而不是实际的 return 地址。这个值告诉核心在分支到时如何退出。通常,如果后台任务被中断,您将看到 0xFFFFFFFD 的值,如果较低优先级的中断被中断,您将看到 0xFFFFFFF1 的值。如果您使用浮点单元,这些值将不同。不过,这个值的神奇之处在于第 2 位 (0x4) 告诉您堆栈帧是在 PSP 上还是在 MSP 上。

一旦确定了帧所在的堆栈,就可以通过查看适当的堆栈指针减去 24(6 个 32 位位置)来找到执行的地址。请参见 link 中的图 2.3。这会将您指向中断的 PC。

我们一直以各种形式看到这个问题,人们一直说有两个堆栈。所以我自己用 systick 试了一下。

文档说我们处于线程模式,没有复位,如果你用 openocd 停止,它说

target halted due to debug-request, current mode: Thread 

我有一些代码可以转储寄存器:

20000000 APSR
00000000 IPSR
00000000 EPSR
00000000 CONTROL
00000000 SP_PROCESS
20000D00 SP_PROCESS after I modified it
20000FF0 SP_MAIN
20000FF0 mov r0,sp  
then I dump the stack up to 0x20001000 which is where I know my stack started
20000FF0 00000000 
20000FF4 00000000 
20000FF8 00000000 
20000FFC 0100005F 

我设置并等待 systick 中断,处理程序转储寄存器和 ram,然后进入无限循环。总体上是不好的做法,但这里只有 debugging/learning。在中断之前我准备了一些寄存器:

.thumb_func
.globl iwait
iwait:
    mov r0,#1
    mov r1,#2
    mov r2,#3
    mov r3,#4
    mov r4,#13
    mov r12,r4
    mov r4,#15
    mov r14,r4
    b .

在我看到的处理程序中

20000000 APSR
0000000F IPSR
00000000 EPSR
00000000 CONTROL
20000D00 SP_PROCESS
20000FC0 SP_MAIN
20000FC0 mov r0,sp
20000FC0 0000000F 
20000FC4 20000FFF 
20000FC8 00000000 
20000FCC FFFFFFF9  this is our special lr (not one rjp mentioned)
20000FD0 00000001  this is r0
20000FD4 00000002  this is r1
20000FD8 00000003  this is r2
20000FDC 00000004  this is r3
20000FE0 0000000D  this is r12
20000FE4 0000000F  this is r14/lr
20000FE8 01000074  and this is where we were interrupted from
20000FEC 21000000  this is probably the xpsr mentioned
20000FF0 00000000  stuff that was there before
20000FF4 00000000 
20000FF8 00000000 
20000FFC 0100005F 


01000064 <iwait>:
 1000064:   2001        movs    r0, #1
 1000066:   2102        movs    r1, #2
 1000068:   2203        movs    r2, #3
 100006a:   2304        movs    r3, #4
 100006c:   240d        movs    r4, #13
 100006e:   46a4        mov ip, r4
 1000070:   240f        movs    r4, #15
 1000072:   46a6        mov lr, r4
 1000074:   e7fe        b.n 1000074 <iwait+0x10>
 1000076:   bf00        nop

所以在这种情况下,直接从 ARM 文档中可以看出,它没有使用 sp_process,而是使用 sp_main。它正在推送手册中说它正在推送的项目,包括 interrupted/return 地址,即 0x1000074。

现在,如果我设置 SPSEL 位(注意先设置 PSP),application/thread 模式下的 mov r0,sp 似乎使用 PSP 而不是 MSP。但是随后处理程序将 msp 用于 mov r0,sp 但似乎将

之前 thread/foreground

20000000 APSR
00000000 IPSR
00000000 EPSR
00000000 SP_PROCESS
20000D00 SP_PROCESS modified
00000000 CONTROL
00000002 CONTROL modified
20000FF0 SP_MAIN
20000D00 mov r0,sp

现在在处理程序中

20000000 APSR
0000000F IPSR
00000000 EPSR
00000000 CONTROL (interesting!)
20000CE0 SP_PROCESS
20000FE0 SP_MAIN
20000FE0 mov r0,sp
dump of that stack
20000FE0 0000000F 
20000FE4 20000CFF 
20000FE8 00000000 
20000FEC FFFFFFFD 
20000FF0 00000000 
20000FF4 00000000 
20000FF8 00000000 
20000FFC 0100005F 
dump of sp_process stack
20000CE0 00000001 
20000CE4 00000002 
20000CE8 00000003 
20000CEC 00000004 
20000CF0 0000000D 
20000CF4 0000000F 
20000CF8 01000074 our return value
20000CFC 21000000 

因此,要处于处理人们不断提到的备用堆栈的位置,您必须将自己置于该位置(或您依赖的某些代码)。为什么你会想要为简单的裸机程序这样做,谁知道呢,全零的控制寄存器很好很容易,可以共享一个堆栈就好了。

我不使用 gdb,但你需要让它转储所有寄存器 sp_process 和 sp_main 然后根据你找到的内容,然后在每个和中转储一打左右的单词在那里你应该看到 0xFFFFFFFx 作为标记,然后从它开始倒计时以查看 return 地址。您也可以让您的处理程序读取两个堆栈指针,然后您可以查看 gprs。使用 gnu 汇编程序 mrs rX,psp; rX 夫人,msp;对于进程和主堆栈指针。

正如你们许多人评论的那样,PC 将处于两个不同的堆栈中,我解决它的方法是在汇编中实际找到一个 HardFault_Handling 代码并从那里获取我需要的东西。为了正确获取 PC 值,我使用了以下代码。

register int *r0 __asm("r0");

__asm(  "TST lr, #4\n"
        "ITE EQ\n"
        "MRSEQ r0, MSP\n"
        "MRSNE r0, PSP\n" // stack pointer now in r0
        "ldr r0, [r0, #0x18]\n" // stored pc now in r0
        //"add r0, r0, #6\n" // address to stored pc now in r0
     );

现在可以通过

访问中断发生位置的值
uint32_t PC = *r0;

现在可以用于任何我想要的东西。不幸的是,我没有设法让 GDB 自动为我展开堆栈。但至少我找到了中断触发的位置,这就是目标。