linuxlinux-kernelcpu-registers

Maintaining process-independent registers fails in linux kernel


I modified the arm's hardware (on gem5) to add two registers. I want them to be process independent, each process has its own corresponding value stored in these registers, just like ttbr0.

I would like to modify the Linux Kernel (v5.15.36) to maintain these two registers. I have made the following changes, but there is a failure that the value of these two registers becomes 0 which is nosense.

First I added the variables corresponding to these two registers in pt_regs at arch/arm64/include/asm/ptrace.h

struct pt_regs {
    union {
        struct user_pt_regs user_regs;
        struct {
            u64 regs[31];
            u64 sp;
            u64 pc;
            u64 pstate;
        };
    };

    // other original code .......

    u64 isa_domain;     // <================ my register
    u64 inst_priv_base; // <================ my register
};

Also I added the corresponding macro definition at arch/arm/kernel/asm-offsets.h:

  DEFINE(S_ISA_DOMAIN,          offsetof(struct pt_regs, isa_domain));
  DEFINE(S_INST_PRIV_BASE,          offsetof(struct pt_regs, inst_priv_base));

I'll init these two registers when in copying_thread(a child function for fork) at

int copy_thread(unsigned long clone_flags, unsigned long stack_start,
        unsigned long stk_sz, struct task_struct *p, unsigned long tls)
{
    struct pt_regs *childregs = task_pt_regs(p);
    memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));

    fpsimd_flush_task_state(p);

    ptrauth_thread_init_kernel(p);

    if (likely(!(p->flags & (PF_KTHREAD | PF_IO_WORKER)))) {
        *childregs = *current_pt_regs();
        childregs->regs[0] = 0;
        // <=============== init the register
        childregs->inst_priv_base = isa_grid_get_inst_base_pa(user_id);
        regs->isa_domain = 1;
        // other code .....
    }
}

Finally, I save and restore them on the stack in kernel_entry and kernel_exit at /arch/arm64/kernel/entry.S.

.macro  kernel_entry, el, regsize = 64
    // some original code
    stp x24, x25, [sp, #16 * 12]
    stp x26, x27, [sp, #16 * 13]
    stp x28, x29, [sp, #16 * 14]

    // ISA-Grid
    .if \el == 0
    mrs x20, S3_7_C7_C1_0
    str x20, [sp, #S_ISA_DOMAIN]
    mrs x20, S3_7_C7_C1_1
    str x20, [sp, #S_INST_PRIV_BASE]
    .endif
    // some original code
.endm

.macro  kernel_exit, el
    // some original code
    apply_ssbd 0, x0, x1
    .endif

    msr elr_el1, x21            // set up the return data
    msr spsr_el1, x22

    // ISA-Grid
    .if \el == 0
    ldr x20, [sp, #S_ISA_DOMAIN]
    msr S3_7_C7_C1_0, x20
    ldr x20, [sp, #S_INST_PRIV_BASE]
    msr S3_7_C7_C1_1, x20
    .endif

    ldp x0, x1, [sp, #16 * 0]
    ldp x2, x3, [sp, #16 * 1]
    ldp x4, x5, [sp, #16 * 2]
    ldp x6, x7, [sp, #16 * 3]
    ldp x8, x9, [sp, #16 * 4]
    // some original code
.endm

But it seems there are times when the registers will have a value of 0 (I wouldn't initialize these two registers to 0) Is there some maintenance step I'm missing?


Solution

  • Eventually I fixed the bug. The bug wasn't caused by my failure to maintain context restoration and saving, but by the fact that my initialization didn't take into account cases like the init process and the User-mode Helper, where a kernel thread is turned into a user process via kernel_execve.

    Also, my previous approach of maintaining registers in pt_regs was inappropriate (but correct); for per-process-independent variables, they don't need to be maintained every time an exception or interrupt occurs, but only when the process switches. I have now modified the __switch_to() function in the arch/arm64/kernel/process.c file to maintain the variables. It works fine and efficiently now!