Linux 内核 ARM 异常堆栈初始化

Posted

技术标签:

【中文标题】Linux 内核 ARM 异常堆栈初始化【英文标题】:Linux kernel ARM exception stack init 【发布时间】:2014-05-20 16:03:38 【问题描述】:

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

arch/arm/kernel/setup.ccpu_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");

我看到每个堆栈只有三个单词的空间。这就是 arch/arm/kernel/entry-armv.S 中的宏 vector_stub 使用它的方式。它将 R0、LR (parent PC) 和 SPSR (parent 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】:

tl;dr - 我们将模式切换到主管并使用该堆栈。

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

您错过的一点是,在此之后所有异常都切换到SVC_MODE,因此使用current 任务堆栈,它也具有thread_info 结构。 模式切换 在 ARM 系统级汇编器中是一个很难理解的概念。以前设置的寄存器现在完全不同了。注意msrcps 类型说明。事情可以在他们之后完全改变;我已经被这个困惑了几十次了。

spsr 用作vector_stub 表的索引,该表通常会跳转到__irq_svc__irq_usr。只需向下滚动查看您已经找到的 entry-arm.S 的底部。

相关:Physical address of ARM-Linux vector table

【讨论】:

在这个版本中,mov r0, spmovs pc, lr 之前完成。第一个给__irq_svc 一个指向保存的三个单词的指针。第二个改变模式。宏 svc_entry 使用 r0 重新加载值。由于中断被禁用,此静态存储仅使用一次。 啊,以防有人不知道。 ARM 对每种模式都有不同的堆栈。所以sp_irqsp_abtsp_undsp_svc都是指向不同栈的不同寄存器。 Linux 只使用sp_svc,除了这个问题问的很短的时间。

以上是关于Linux 内核 ARM 异常堆栈初始化的主要内容,如果未能解决你的问题,请参考以下文章

Linux 内核 内存管理memblock 分配器 ⑤ ( Linux 内核中定义 memblock 分配器的位置 | ARM64体系架构下 Linux内核初始化 memblock 分配器流程 )

Linux 内核 内存管理Linux 内核内存布局 ④ ( ARM64 架构体系内存分布 | 内核启动源码 start_kernel | 内存初始化 mm_init | mem_init )

ARM 内核SP,LR,PC寄存器

Linux 系统中堆栈的使用方法

ARM64的启动过程之:异常向量表的设定

[架构之路-120]-《软考-系统架构设计师》-计算机体系结构 -2- 一文了解ARM SOC体系结构原理(CPU工作原理指令内存中断堆栈IO初始化)