Riscv64-Linux用户态

文摘   2024-12-16 12:07   新加坡  

点击上方蓝字 江湖评谈设为关注/星标




前言

上一篇:Linux内核Arm64用户态,详细叙述了下Arm64的Linux内核调用的用户态。本篇看下Riscv64的内核态到用户态切换。

Linux-Riscv64 To User

依旧通过EA(EntryPoint Address)地址:0x10554来看下这个过程的切换

# file hellohello: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, BuildID[sha1]=549830b5e1cfea28b21ab9cbcd1955f257390d54, for GNU/Linux 4.15.0with debug_info, not stripped# readelf -h helloELF 头:  Magic:  745 446 02 01 01 00 00 00 00 00 00 00 00 00   类别:        ELF64  数据:        2 补码,小端序 (little endian)  Version:     1 (current)  OS/ABI:      UNIX - System V  ABI 版本:    0  类型:        EXEC (可执行文件)  系统架构:     RISC-V  版本:        0x1  入口点地址:  0x10554

断点和Qemu-asm如下:

(lldb) b 0x10554Breakpoint 1: address = 0x0000000000010554(lldb) cProcess 1 resumingProcess 1 stopped* thread #1, stop reason = breakpoint 1.1    frame #0: 0x0000000000010554error: memory read failed for 0x10400
//上条Qemu-asm地址0xffffffff8099f6a2:  10200073          sret 
(lldb) di -s 0xffffffff8099f6a2 -bvmlinux`ret_from_exception:    0xffffffff8099f6a2 <+96>: 73 00 20 10  sret       0xffffffff8099f6a6:       01 00        nop    vmlinux`handle_kernel_stack_overflow:    0xffffffff8099f6a8 <+0>:  f3 9014  csrrw  t6, sscratch, t6    0xffffffff8099f6ac <+4>:  83 302 02  ld     t6, 0x20(tp)    0xffffffff8099f6b0 <+8>:  80f        slli   t6, t6, 0x3    0xffffffff8099f6b2 <+10>: 17 41 bb 00  auipc  sp0xbb4    0xffffffff8099f6b6 <+14>: 13 01 61 e6  addi   spsp-0x19a    0xffffffff8099f6ba <+18>: 791        add    spsp, t6    0xffffffff8099f6bc <+20>: 82 6f        ld     t6, 0x0(sp)    0xffffffff8099f6be <+22>: 17 81 28 00  auipc  sp0x288
Rsicv的几种模式:M-mode,S-mode,U-mode(用户模式)级别一次递减,SRET指令是从高级别模式(M-mode,S-mode切换到低级模式(U-mode),且它本身只能在高级别模式下运行。发生切换的时候,需要的切换的地址保存在了SEPC寄存器里(SSTATUS保存状态,STVEC保存异常或者中断陷入的入口地址)
(lldb) re re SEPC SSTATUS STVEC    sepc = 0x0000000000010554 sstatus = 0x0000000200004020   stvec = 0xffffffff8099f4f0  vmlinux`__kprobes_text_start
Riscv会从ret_from_exceptio内核函数的SRET指令切到用户态的地址0x10554,也即是EA。但此时切换状态位于内核态,所以会发生:error: memory read failed for 0x10400的错误。
陷入点的函数是__kprobes_text_start(handle_exception):
SYM_CODE_START(handle_exception)	csrrw tp, CSR_SCRATCH, tp	bnez tp, .Lsave_context.Lsave_context:        //中级部分省略,便于观看REG_L t1, 0(t0)2:	jalr t1	//会跳到do_page_fault函数j ret_from_exception
do_page_fault如下:
//arch/riscv/kernel/traps.c:359:1#ifdef CONFIG_MMUasmlinkage __visible noinstr void do_page_fault(struct pt_regs *regs){	irqentry_state_t state = irqentry_enter(regs);	handle_page_fault(regs);	local_irq_disable();	irqentry_exit(regs, state);}#endif
实际上这个时候的epc(x64的rip)已经准备好了下次进行入口EA调用:
(lldb) p/x regs->epc(unsigned long0x0000000000010554
irqentry_exit会返回到__kprobes_text_start函数的执行点:j ret_from_exception
//SYM_CODE_START_NOALIGN(ret_from_exception)	REG_L s0, PT_STATUS(sp)        //中省略便于观看	REG_L x1,  PT_RA(sp)	REG_L x3,  PT_GP(sp)	REG_L x4,  PT_TP(sp)	REG_L x5,  PT_T0(sp)	restore_from_x6_to_x31	REG_L x2,  PT_SP(sp)#ifdef CONFIG_RISCV_M_MODE	mret#else	sret   #endif

通过SRET返回陷入到spec:

(lldb) re re stvec sepc sstatus   stvec = 0xffffffff8099f4f0  vmlinux`__kprobes_text_start    sepc = 0x0000000000010554 sstatus = 0x0000000200004020

此时我们真正的来到了用户态入口EA:

(lldb) siProcess 1 stopped* thread #1, stop reason = breakpoint 1.1    frame #0: 0x0000000000010554->  0x10554: jal    0x10576    0x10558: mv     a5, a0    0x1055a: auipc  a0, 0x0    0x1055e: addi   a0, a0, 0xd8

结尾

总结下,EA通过ret_from_exception陷入用户态异常,调用页面处理错误(do_page_fault),处理完成之后通过SSTATUS和STVEC设置用户态状态,然后跳转到用户态执行EA入口。

往期精彩回顾

Riscv级的指令编写

Linux内核Arm64用户态


关注公众号↑↑↑:江湖评谈 


江湖评谈
记录,分享,自由。
 最新文章