Linux内核ARM异常堆栈初始化

4

我正在使用Freescale i.MX6(ARM Cortex-A9)上的Linux内核3.0.35。在遇到内核OOPS后,我试图理解异常堆栈初始化。到目前为止,这是我所发现的。

arch/arm/kernel/setup.c中的cpu_init()中,我看到异常堆栈正在初始化:

struct stack {
    u32 irq[3];
    u32 abt[3];
    u32 und[3];
} ____cacheline_aligned;

static struct stack stacks[NR_CPUS];

void cpu_init(void)
{
    struct stack *stk = &stacks[cpu];

    ...<snip>

    /*
     * setup stacks for re-entrant exception handlers
     */
    __asm__ (
     "msr   cpsr_c, %1\n\t"
    "add    r14, %0, %2\n\t"
    "mov    sp, r14\n\t"
    "msr    cpsr_c, %3\n\t"
    "add    r14, %0, %4\n\t"
    "mov    sp, r14\n\t"
    "msr    cpsr_c, %5\n\t"
    "add    r14, %0, %6\n\t"
    "mov    sp, r14\n\t"
    "msr    cpsr_c, %7"
        :
        : "r" (stk),
          PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
          "I" (offsetof(struct stack, irq[0])),
          PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
          "I" (offsetof(struct stack, abt[0])),
          PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
          "I" (offsetof(struct stack, und[0])),
          PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
        : "r14");

我看到每个栈只有三个单元的空间。这就是宏 vector_stubarch/arm/kernel/entry-armv.S 中使用它的原因。它将 R0、LR(父级 PC)和 SPSR(父级 CPSR)保存在这三个单元中。然后它跳转到 __irq_svc。这个函数以宏 svc_entry 开始,它创建一个栈帧。

    .macro  svc_entry, stack_hole=0
 UNWIND(.fnstart        )
 UNWIND(.save {r0 - pc}     )
    sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)

那也是我从KGDB看到反汇编代码的方式:
Dump of assembler code for function __irq_svc:
   0xc01402c0 <+0>:  44 d0 4d e2    sub sp, sp, #68 ; 0x44
   0xc01402c4 <+4>:  04 00 1d e3    tst sp, #4
   0xc01402c8 <+8>:  04 d0 4d 02    subeq   sp, sp, #4
   0xc01402cc <+12>:     fe 1f 8d e8    stm sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}
   0xc01402d0 <+16>:     0e 00 90 e8    ldm r0, {r1, r2, r3}
   0xc01402d4 <+20>:     30 50 8d e2    add r5, sp, #48 ; 0x30
   0xc01402d8 <+24>:     00 40 e0 e3    mvn r4, #0
   0xc01402dc <+28>:     44 00 8d e2    add r0, sp, #68 ; 0x44
   0xc01402e0 <+32>:     04 00 80 02    addeq   r0, r0, #4
   0xc01402e4 <+36>:     04 10 2d e5    push    {r1}        ; (str r1, [sp, #-4]!)
   0xc01402e8 <+40>:     0e 10 a0 e1    mov r1, lr
   0xc01402ec <+44>:     1f 00 85 e8    stm r5, {r0, r1, r2, r3, r4}
   0xc01402f0 <+48>:     ad 96 a0 e1    lsr r9, sp, #13
   0xc01402f4 <+52>:     89 96 a0 e1    lsl r9, r9, #13
   0xc01402f8 <+56>:     04 80 99 e5    ldr r8, [r9, #4]
   0xc01402fc <+60>:     01 70 88 e2    add r7, r8, #1
   0xc0140300 <+64>:     04 70 89 e5    str r7, [r9, #4]
   0xc0140304 <+68>:     54 50 9f e5    ldr r5, [pc, #84]   ; 0xc0140360
   0xc0140308 <+72>:     00 50 95 e5    ldr r5, [r5]
   0xc014030c <+76>:     0c 60 95 e5    ldr r6, [r5, #12]
   0xc0140310 <+80>:     4c e0 9f e5    ldr lr, [pc, #76]   ; 0xc0140364
   0xc0140314 <+84>:     07 0b c6 e3    bic r0, r6, #7168   ; 0x1c00
   0xc0140318 <+88>:     1d 00 50 e3    cmp r0, #29
   0xc014031c <+92>:     00 00 50 31    cmpcc   r0, r0
   0xc0140320 <+96>:     0e 00 50 11    cmpne   r0, lr
   0xc0140324 <+100>:    00 00 50 21    cmpcs   r0, r0
   0xc0140328 <+104>:    0d 10 a0 11    movne   r1, sp
   0xc014032c <+108>:    28 e0 4f 12    subne   lr, pc, #40 ; 0x28
   0xc0140330 <+112>:    32 eb ff 1a    bne 0xc013b000 <asm_do_IRQ>
   0xc0140334 <+116>:    04 80 89 e5    str r8, [r9, #4]
   0xc0140338 <+120>:    00 00 99 e5    ldr r0, [r9]
   0xc014033c <+124>:    00 00 38 e3    teq r8, #0
   0xc0140340 <+128>:    00 00 a0 13    movne   r0, #0
   0xc0140344 <+132>:    02 00 10 e3    tst r0, #2
   0xc0140348 <+136>:    06 00 00 1b    blne    0xc0140368 <svc_preempt>
   0xc014034c <+140>:    40 40 9d e5    ldr r4, [sp, #64]   ; 0x40
   0xc0140350 <+144>:    04 f0 6f e1    msr SPSR_fsxc, r4
   0xc0140354 <+148>:    1f f0 7f f5    clrex
   0xc0140358 <+152>:    ff ff dd e8    ldm sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr, pc}^
End of assembler dump.

在异常情况下,SP是保护的R13。如果我理解正确,在该堆栈上没有足够的空间来存储该帧。这意味着我可能遗漏了一些东西。是否有其他地方初始化异常堆栈?

1个回答

5

tl;dr - 我们切换到监督模式并使用该堆栈。

您忽略了通过向量表切换模式将控制权交给CPU的关键点。请参见:entry-armv.S和__vectors_startvector stubs是在主向量表中的branch之后最初发送控制的代码。 vector_stub宏保存三个项目; 一个更正的lrr0和期望模式的spsr(如您所指出的)。

你错过的重点是,所有异常都切换到 SVC_MODE,并使用当前任务的堆栈,该堆栈也具有 thread_info 结构。在 ARM 系统级汇编中,“模式切换”是一个难以理解的概念。之前设置的寄存器现在完全不同。注意 msrcps 类型指令。在它们之后,事情可能会完全改变;我已经因此混淆了数十次。 spsr 用作索引进入 vector_stub 表,通常会跳转到 __irq_svc__irq_usr。只需向下滚动即可查看您已找到的 entry-arm.S 底部。
相关内容:ARM-Linux 向量表的物理地址

在这个版本中,在movs pc, lr之前刚刚执行了mov r0, sp。第一个为__irq_svc提供了指向保存的三个字的指针。第二个更改了模式。宏svc_entry使用r0重新加载值。由于中断被禁用,因此这个静态存储只被使用一次。 - artless noise
啊,如果有人不知道的话。ARM对于每种模式都有不同的堆栈。因此,sp_irqsp_abtsp_undsp_svc都是指向不同堆栈的不同寄存器。Linux只使用sp_svc,除了这个问题所问的短暂时期。 - artless noise

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接