Search code examples
cassemblylinux-kernelarm

Kernel breaks on adding new code (that never runs)


I'm trying to add some logic at boundaries between userspace and kernelspace particularly on the ARM architecture.

One such boundary appears to be the vector_swi routine implemented in arch/arm/kernel/entry-common.S. Right now, I have most of my code written in a C function which I would like to call somewhere at the start of vector_swi.

Thus, I did the following:

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

When the contents of my function are as follows everything works fine:

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;
}

However, when I add some additional code, the kernel enters into a crash-reboot loop.

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;
                }
        }
}

Although the first instinct is to blame the logic of the added code, please note that none of it should ever execute since btm_enabled is 0.

I have double-checked and triple-checked to make sure btm_enabled is 0 by adding a sysfs entry to print out the value of the variable (with the added code removed).

Could someone explain what is going on here or what I'm doing wrong?


Solution

  • The first version will probably compile to just a return instruction as it has no side effect. The second needs to load btm_enabled and in the process overwrites one or two system call arguments.

    When calling a C function from assembly language you need to ensure that registers that may be modified do not contain needed information.

    To solve your specific problem, you could update your code to read:

    #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
    

    The new instructions store registers r0-r3, r12 and lr onto the stack and restore them after your function call. These are the only registers a C function is allowed to modify, saving r12 here is unnecessary here is it's value is not used, but doing so keeps the stack 8-byte aligned as required by the ABI.