2.3.2 kernel_entry 0 与kernel_exit 0

Interrupt Pipeline系列文章大纲-CSDN博客

2.3.2.1 概要

        调用kernel_entry 0将进程运行的用户态现场按照struct pt_regs的格式存入进程内核栈。调用kernel_exit 0将已压入进程内核栈的用户态现场恢复到对应的寄存器。struct pt_regs定义在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;

              };

       };

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

};

       在I-pipe中,没有对kernel_entry和kernel_exit做修改。如果已经对二者比较熟悉,可以跳过本节剩余章节。

2.3.2.2 kernel_entry 0

        去掉32位的干扰,只保留入参el为0的情况,核心代码如下:

1

.macro

kernel_entry, el, regsize = 64

2

stp

x0, x1, [sp, #16 * 0]

3

stp

x2, x3, [sp, #16 * 1]

4

stp

x4, x5, [sp, #16 * 2]

5

stp

x6, x7, [sp, #16 * 3]

6

stp

x8, x9, [sp, #16 * 4]

7

stp

x10, x11, [sp, #16 * 5]

8

stp

x12, x13, [sp, #16 * 6]

9

stp

x14, x15, [sp, #16 * 7]

10

stp

x16, x17, [sp, #16 * 8]

11

stp

x18, x19, [sp, #16 * 9]

12

stp

x20, x21, [sp, #16 * 10]

13

stp

x22, x23, [sp, #16 * 11]

14

stp

x24, x25, [sp, #16 * 12]

15

stp

x26, x27, [sp, #16 * 13]

16

stp

x28, x29, [sp, #16 * 14]

17

18

clear_gp_regs

19

mrs

x21, sp_el0

20

ldr_this_cpu

tsk, __entry_task, x20

21

ldr

x19, [tsk, #TSK_TI_FLAGS]

22

disable_step_tsk x19, x20

23

apply_ssbd 1, x22, x23

24

mrs

x22, elr_el1

25

mrs

x23, spsr_el1

26

stp

lr, x21, [sp, #S_LR]

27

28

stp

xzr, xzr, [sp, #S_STACKFRAME]

29

add

x29, sp, #S_STACKFRAME

30

31

stp

x22, x23, [sp, #S_PC]

32

33

mov

w21, #NO_SYSCALL

34

str

w21, [sp, #S_SYSCALLNO]

35

36

msr

sp_el0, tsk

37

38

.endm

第1行,宏kernel_entry有两个参数el和regsize,其中regsize默认为64.

第2~16行,将X0~X29共30个通用寄存器压栈。栈空间是在kernel_ventry中按照struct pt_regs的大小预留好的,所以相当于压入struct pt_regs的reg[0]~reg[29]。

第18行,将X0~X29共30个通用寄存器清零。

第19行,将sp_el0存入X21。

第20行,将当前进程的task_struct结构指针存入tsk。其中tsk就是X28,定义在entry.S:

tsk     .req x28         // current thread_info

第21~22行,检查TSK_TI_FLAGS中是否设置了TIF_SINGLESTEP标记。如果是,则清零MDSCR.SS。el0_irq在调用kernel_entry之后,会立刻打开debug中断,所以必须先清零MDSCR.SS,避免在同时打开debug中断和置位MDSCR.SS的情况下执行调度。TSK_TI_FLAGS定义在arch/arm64/kernel/asm-offsets.c:

DEFINE(TSK_TI_FLAGS,        offsetof(struct task_struct, thread_info.flags));

disable_step_tsk定义在arch/arm64/include/asm/assembler.h:

       .macro    disable_step_tsk, flgs, tmp

       tbz   \flgs, #TIF_SINGLESTEP, 9990f

       mrs \tmp, mdscr_el1

       bic   \tmp, \tmp, #DBG_MDSCR_SS

       msr mdscr_el1, \tmp

       isb   // Synchronise with enable_dbg

9990:

       .endm

第23行,与Speculative Store Bypass漏洞相关代码,可以忽略。

第24行,将elr_el1存入X22。当异常发生时,ARM64自动把返回地址保存在elr_e1寄存器即异常链接寄存器。当调用eret时,自动将elr放到pc寄存器。

第25行,将spsr_el1存入X23。当异常发生时,ARM64自动把PSTATE寄存器的值保存到SPSR_EL1中。

第26行,其中lr就是X30寄存器,X21代表了SP_EL0的值。S_PC定义在arch/arm64/kernel/asm-offsets.c:

DEFINE(S_LR,                 offsetof(struct pt_regs, regs[30]));

将X30压入struct pt_regs的regs[30],将SP_EL0压入struct pt_regs的变量sp。

第28行,将struct pt_regs末尾的变量u64 stackframe[2]清零。S_STACKFRAME,定义在arch/arm64/kernel/asm-offsets.c:

DEFINE(S_STACKFRAME,       offsetof(struct pt_regs, stackframe));

第29行,x29为Frame Pointer寄存器。让X29指向SP+ S_STACKFRAME的位置,即图中的FP’. 栈帧寄存器,应该指向上一个caller的栈帧寄存器在callee中保持的位置。因为从用户栈切到内核栈,因此对于内核栈来说,不存在上一个caller的栈帧,所以pt_regs中定义的u64 stackframe[2]代表的FP/LR的值都设置为0.

第 31行,将X22和X23压栈,就是将elr_el1和spsr_el1的值压入struct pt_regs的pc变量和pstate变量。

第36行,将第20行得到的tsk存入SP_EL0。这样以后,宏定义current就可以通过SP_EL0直接找到当前的进程的task_struct结构,而不需要访存查找。宏定义current定义在arch/arm64/include/asm/current.h:

小Tip:精简代码时去掉了和Speculative Store Bypass漏洞相关代码.

2.3.2.3 kernel_exit 0

       最终返回用户空间时,会调用kernel_exit宏,这里提前分析一下。仍然按照kernel_exit 0来精简代码。

1

.macro

kernel_exit, el

2

3

ldp

x21, x22, [sp, #S_PC]

// load ELR, SPSR

4

5

ldr

x23, [sp, #S_SP]

// load return stack pointer

6

msr

sp_el0, x23

7

8

msr

elr_el1, x21

// set up the return data

9

msr

spsr_el1, x22

10

ldp

x0, x1, [sp, #16 * 0]

11

ldp

x2, x3, [sp, #16 * 1]

12

ldp

x4, x5, [sp, #16 * 2]

13

ldp

x6, x7, [sp, #16 * 3]

14

ldp

x8, x9, [sp, #16 * 4]

15

ldp

x10, x11, [sp, #16 * 5]

16

ldp

x12, x13, [sp, #16 * 6]

17

ldp

x14, x15, [sp, #16 * 7]

18

ldp

x16, x17, [sp, #16 * 8]

19

ldp

x18, x19, [sp, #16 * 9]

20

ldp

x20, x21, [sp, #16 * 10]

21

ldp

x22, x23, [sp, #16 * 11]

22

ldp

x24, x25, [sp, #16 * 12]

23

ldp

x26, x27, [sp, #16 * 13]

24

ldp

x28, x29, [sp, #16 * 14]

25

ldr

lr, [sp, #S_LR]

26

add

sp, sp, #S_FRAME_SIZE

// restore sp

27

28

eret

// tramp_exit->eret

29

.endm

第1行,宏定义kernel_exit,有一个参数el。此处根据el传入0来保留核心代码。

第3行,从内核栈中的pt_regs.pc恢复ELR到X21。从 pt_regs.pstate恢复SPSR到X22寄存器。

第5~6行,从内核栈中的pt_regs.sp恢复sp_el0

第8~9行,结合第3行,恢复了elr_el1和spsr_el1。

第10~24行,从内核栈中的pt_regs.reg[0]~reg[29]恢复到X0~X29通用寄存器。

第25行,从内核栈中的pt_regs.reg[30]恢复到lr即X30通用寄存器。

第26行,内核栈指针sp_el1弹出,向上走#S_FRAME_SIZE,与kernel_entry压栈的大小一致。

第28行,通过eret从EL1异常级别返回到EL0,此时sp会使用sp_el0, ARM64会自动把elr_el1寄存器的值赋值到PC寄存器,并从PC指针继续执行。此处原始代码并非简单的eret,而是通过去掉AARCH64平台修复meltdown漏洞的KPTI补丁简化而来。涉及到的补丁:commit 4bf3286d arm64: entry: Hook up entry trampoline to exception vectors .

点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客

原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值