【问题标题】:Kernel breaks on adding new code (that never runs)添加新代码时内核中断(从不运行)
【发布时间】:2018-08-08 15:29:28
【问题描述】:

我正在尝试在用户空间和内核空间之间的边界添加一些逻辑,特别是在 ARM 架构上。

一个这样的边界似乎是在arch/arm/kernel/entry-common.S 中实现的vector_swi 例程。现在,我的大部分代码都是用 C 函数编写的,我想在 vector_swi 开头的某个地方调用它。

因此,我做了以下事情:

ENTRY(vector_swi)
        sub     sp, sp, #S_FRAME_SIZE
        stmia   sp, {r0 - r12}                  @ Calling r0 - r12
 ARM(   add     r8, sp, #S_PC           )
 ARM(   stmdb   r8, {sp, lr}^           )       @ Calling sp, lr
 THUMB( mov     r8, sp                  ) 
 THUMB( store_user_sp_lr r8, r10, S_SP  )       @ calling sp, lr
        mrs     r8, spsr                        @ called from non-FIQ mode, so ok.
        str     lr, [sp, #S_PC]                 @ Save calling PC
        str     r8, [sp, #S_PSR]                @ Save CPSR
        str     r0, [sp, #S_OLD_R0]             @ Save OLD_R0
        zero_fp

#ifdef CONFIG_BTM_BOUNDARIES
        bl btm_entering_kernelspace    @ <--- My function
#endif

当我的函数内容如下时,一切正常:

static int btm_enabled = 0;
asmlinkage inline void btm_entering_kernelspace(void)
{
        int cpu;
        int freq;
        struct acpu_level *level;
        if(!btm_enabled) {
                return;
        }
        cpu = smp_processor_id();
        freq = acpuclk_krait_get_rate(cpu);
        (void) cpu;
        (void) freq;
        (void) level;
}

但是,当我添加一些额外的代码时,内核会进入崩溃重启循环。

static int btm_enabled = 0;
asmlinkage inline void btm_entering_kernelspace(void)
{
        int cpu;
        int freq;
        struct acpu_level *level;
        if(!btm_enabled) {
                return;
        }
        cpu = smp_processor_id();
        freq = acpuclk_krait_get_rate(cpu);
        (void) cpu;
        (void) freq;
        (void) level;
        // --------- Added code ----------
        for (level = drv.acpu_freq_tbl; level->speed.khz != 0; level++) {
                if(level->speed.khz == freq) {
                        break;
                }
        }
}

虽然第一直觉是责怪添加代码的逻辑,但请注意,由于btm_enabled0,因此任何代码都不应该执行。

我通过添加一个sysfs 条目来打印出变量的值(删除了添加的代码),双重检查和三重检查以确保btm_enabled0

有人可以解释这里发生了什么或我做错了什么吗?

【问题讨论】:

  • 新代码可能会更改早期代码的寄存器分配,和/或编译器可能决定在检查/返回之前乐观地对寄存器做一些事情。或者只是将一些寄存器保存/恢复到堆栈,因为该函数现在需要在调用之间保存一些参数。如果 clobbing r0..r3 和堆栈指针下方的内存在 vector_swi 中当时并不安全,那么带有函数调用的第一个版本只是偶然的。我对 Linux 的 ARM 入口点不够熟悉,无法说明 ABI 兼容的函数调用在那里是否安全。

标签: c assembly linux-kernel arm


【解决方案1】:

第一个版本可能会编译成一个返回指令,因为它没有副作用。第二个需要加载btm_enabled 并在此过程中覆盖一个或两个系统调用参数。

从汇编语言调用 C 函数时,您需要确保可以修改的寄存器不包含所需的信息。

要解决您的具体问题,您可以将代码更新为:

#ifdef CONFIG_BTM_BOUNDARIES
        stmdb   sp!, {r0-r3, r12, lr}  @ <--- New instruction
        bl btm_entering_kernelspace    @ <--- My function
        ldmia   sp!, {r0-r3, r12, lr}  @ <--- New instruction
#endif

新指令将寄存器 r0-r3、r12 和 lr 存储到堆栈中,并在函数调用后恢复它们。这些是唯一允许 C 函数修改的寄存器,这里不需要保存 r12,因为它的值没有被使用,但是这样做可以按照 ABI 的要求保持堆栈 8 字节对齐。

【讨论】:

  • 啊,是的,所以我的猜测是正确的,调用破坏的寄存器在那个时候仍然持有一些重要的东西?入口点已经将它们保存到堆栈中,但它无需重新加载即可使用它们,对吧?
  • 是的,大多数系统调用无需重新加载即可使用。
  • C 函数不会自动生成保存和恢复寄存器的程序集吗?我查看了 objdump 的 10-15 个函数,它们都以 mov ip, sp; push {r3, r4, ...} 开头并以 ldm sp, {r3, r4, ....} 结尾
  • 我猜我应该做的是将寄存器保存到堆栈中,调用我的函数,然后将它们加载回来。由于我对程序集没有很好的理解,有什么办法可以让编译器为我生成这个程序集,以便我可以查找它并将其手写到vector_swi
  • 只需在生成的 vmlinux 上使用objdump -S 或任何你的输出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多