Linux-arm64中断现场保护详解
零、源码及详细注释
.macro kernel_ventry, el, label, regsize = 64
sub sp, sp, #S_FRAME_SIZE //在堆栈中预留出S_FRAME_SIZE大小的空间
.macro kernel_entry, el, regsize = 64
.if \regsize == 32
mov w0, w0 // zero upper 32 bits of x0
.endif
stp x0, x1, [sp, #16 * 0] //入栈
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
.if \el == 0 // 异常等级0
clear_gp_regs // 清空寄存器
mrs x21, sp_el0 // x21 = sp_el0 如果异常发生在用户态(EL0)则pt_regs将保存用户态堆栈指针sp_el0 */
ldr_this_cpu tsk, __entry_task, x20 // 设置tsk为当前进程的task_struct, x20 = tpidr_el1/tpidr_el2
ldr x19, [tsk, #TSK_TI_FLAGS] // x19 = tsk->thread_info.flags
disable_step_tsk x19, x20 // 关闭mdscr_el1寄存器的SS位
apply_ssbd 1, x22, x23
.else //异常等级不为0
add x21, sp, #S_FRAME_SIZE //X21保存压入pt_regs数据之前的栈地址(栈框的位置) S_FRAME_SIZE:栈帧的大小
get_thread_info tsk //tsk = sp_el0
/* Save the task's original addr_limit and set USER_DS */
ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] //x20 = tsk->thread_info.addr_limit
str x20, [sp, #S_ORIG_ADDR_LIMIT] //pt_regs->orig_addr_limit = x20
mov x20, #USER_DS //X2O = USER_DS = 1<<48
str x20, [tsk, #TSK_TI_ADDR_LIMIT] //tsk->thread_info.addr_limit = 1<<48
/* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */
.endif /* \el == 0 */
mrs x22, elr_el1 //x22 = elr_el1
mrs x23, spsr_el1 //x23 = spsr_el1
stp lr, x21, [sp, #S_LR] //pt_regs.lr = lr,pt_regs.fp = x21 保存lr和fp到栈帧中
/*
* In order to be able to dump the contents of struct pt_regs at the
* time the exception was taken (in case we attempt to walk the call
* stack later), chain it together with the stack frames.
*/
.if \el == 0 //异常等级0
stp xzr, xzr, [sp, #S_STACKFRAME] //清0 pt_regs.stackframe[0]与[1](详见十一)
.else //异常等级不为0
stp x29, x22, [sp, #S_STACKFRAME] //pt_regs.stackframe[0] = x29 , pt_regs.stackframe[1] = x22 = elr_el1
.endif
add x29, sp, #S_STACKFRAME //x29 = pt_regs.stackframe
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
alternative_if ARM64_HAS_PAN
b 1f // skip TTBR0 PAN
alternative_else_nop_endif
.if \el != 0 //异常等级不为0
mrs x21, ttbr0_el1 //x21 = ttbr0_el1
tst x21, #TTBR_ASID_MASK //检测ttbr0_el1的48-63位的ASID
orr x23, x23, #PSR_PAN_BIT //设置spsr_el1的PAN位
b.eq 1f // TTBR0 access already disabled
and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR
.endif
__uaccess_ttbr0_disable x21
1:
#endif
stp x22, x23, [sp, #S_PC] //pt_regs.pc = x22 = elr_el1 pt_regs.pstate = x23 = spsr_el1
/* Not in a syscall by default (el0_svc overwrites for real syscall) */
.if \el == 0 //异常等级为0
mov w21, #NO_SYSCALL //w21 = NO_SYSCALL
str w21, [sp, #S_SYSCALLNO] //pt_regs.syscallno = w21 = NO_SYSCALL
.endif
/*
* Set sp_el0 to current thread_info.
*/
.if \el == 0 //异常等级为0
msr sp_el0, tsk //sp_el0 = tsk
.endif
/*
* Registers that may be useful after this macro is invoked:
*
* x21 - aborted SP
* x22 - aborted PC
* x23 - aborted PSTATE
*/
.endm
注解:
一、ldr_this_cpu
ldr_this_cpu tsk, __entry_task, x20
</arch/arm64/include/asm/assembler.h>
macro ldr_this_cpu dst, sym, tmp
adr_l \dst, \sym //tsk = __entry_task
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
mrs \tmp, tpidr_el1 //x20 = tpidr_el1 读取per_cpu变量时,计算偏移
alternative_else
mrs \tmp, tpidr_el2 //x20 = tpidr_el2 TPIDR_EL1, EL1 Software Thread ID Register
alternative_endif
ldr \dst, [\dst, \tmp] //ldr tsk, [tsk,x20] task += TID 获取__entry_task变量
.endm
二、__entry_task
__entry_task为内核静态定义的percpu变量,在进程切换时,会将next进程的进程描述符保存到该变量中
/*
* We store our current task in sp_el0, which is clobbered by userspace. Keep a
* shadow copy so that we can restore this upon entry from userspace.
*
* This is *only* for exception entry from EL0, and is not valid until we
* __switch_to() a user task.
*/
DEFINE_PER_CPU(struct task_struct *, __entry_task);
static void entry_task_switch(struct task_struct *next)
{
__this_cpu_write(__entry_task, next);
}
三、per_cpu变量获取offset
static inline unsigned long __my_cpu_offset(void)
{
unsigned long off;
/*
* We want to allow caching the value, so avoid using volatile and
* instead use a fake stack read to hazard against barrier().
*/
asm(ALTERNATIVE("mrs %0, tpidr_el1",
"mrs %0, tpidr_el2",
ARM64_HAS_VIRT_HOST_EXTN)
: "=r" (off) :
"Q" (*(const unsigned long *)current_stack_pointer));
return off;
}
四、偏移
</arch/arm64/kernel/asm-offsets.c>
DEFINE(TSK_TI_FLAGS, offsetof(struct task_struct, thread_info.flags));
DEFINE(TSK_TI_ADDR_LIMIT, offsetof(struct task_struct, thread_info.addr_limit));
DEFINE(S_ORIG_ADDR_LIMIT, offsetof(struct pt_regs, orig_addr_limit));
五、disable_step_tsk
disable_step_tsk x19, x20
.macro disable_step_tsk, flgs, tmp
tbz \flgs, #TIF_SINGLESTEP, 9990f //判断x19的#TIF_SINGLESTEP位是否为0,若为0,跳转到9990f
mrs \tmp, mdscr_el1 //x20 = mdscr_el1
bic \tmp, \tmp, #1 //清除x20的第0位,SS:Software step disabled
msr mdscr_el1, \tmp //mdscr_el1 = x20
isb // Synchronise with enable_dbg
9990:
.endm
六、get_thread_info
.macro get_thread_info, rd
mrs \rd, sp_el0 // \rd = sp_el0
.endm
七、S_FRAME_SIZE
DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));
八、struct pt_regs
/*
* This struct defines the way the registers are stored on the stack during an
* exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
* stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
*/
struct pt_regs {
union {
struct user_pt_regs user_regs;
struct {
u64 regs[31];
u64 sp;
u64 pc;
u64 pstate;
};
};
u64 orig_x0;
#ifdef __AARCH64EB__
u32 unused2;
s32 syscallno;
#else
s32 syscallno;
u32 unused2;
#endif
u64 orig_addr_limit;
u64 unused; // maintain 16 byte alignment
u64 stackframe[2];
};
九、thread_info
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
u64 ttbr0; /* saved TTBR0_EL1 */
#endif
union {
u64 preempt_count; /* 0 => preemptible, <0 => bug */
struct {
#ifdef CONFIG_CPU_BIG_ENDIAN
u32 need_resched;
u32 count;
#else
u32 count;
u32 need_resched;
#endif
} preempt;
};
};
十、USER_DS
#define USER_DS TASK_SIZE_64
#define TASK_SIZE_64 (UL(1) << VA_BITS)
十一、S_LR与S_STACKFRAME与S_STACKFRAME
DEFINE(S_STACKFRAME, offsetof(struct pt_regs, stackframe));
DEFINE(S_LR, offsetof(struct pt_regs, regs[30]));
DEFINE(S_STACKFRAME, offsetof(struct pt_regs, stackframe));
#define S_PC 256 /* offsetof(struct pt_regs, pc) */