最新 x86_64 系统调用入口分析 (基于 5.7.0)

最新 x86_64 系统调用入口分析 (基于5.7.0)

最近的工作涉及到系统调用的录入,但网上的一些分析都比较老,这里记录自己的分析过程,仅供参考。

[En]

The recent work involves the system call entry, but some of the online analysis are relatively old, here to record their own analysis process, for reference only.

x86_64位系统调用使用 SYSCALL 指令进入内核空间,使CPU切换到ring 0。SYSCALL 指令主要工作为从MSR寄存器加载CS/SS,以及系统调用入口(entry_SYSCALL_64),从而进入系统调用处理流程。

IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
    THEN #UD;
FI;
RCX ← RIP; (* Will contain address of next instruction *)
RIP ← IA32_LSTAR;
R11 ← RFLAGS;
RFLAGS ← RFLAGS AND NOT(IA32_FMASK);
CS.Selector ← IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)
(* Set rest of CS to a fixed value *)
CS.Base ← 0;
        (* Flat segment *)
CS.Limit ← FFFFFH;
        (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type ← 11;
        (* Execute/read code, accessed *)
CS.S ← 1;
CS.DPL ← 0;
CS.P ← 1;
CS.L ← 1;
        (* Entry is to 64-bit mode *)
CS.D ← 0;
        (* Required if CS.L = 1 *)
CS.G ← 1;
        (* 4-KByte granularity *)
CPL ← 0;
SS.Selector ← IA32_STAR[47:32] + 8;
        (* SS just above CS *)
(* Set rest of SS to a fixed value *)
SS.Base ← 0;
        (* Flat segment *)
SS.Limit ← FFFFFH;
        (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type ← 3;
        (* Read/write data, accessed *)
SS.S ← 1;
SS.DPL ← 0;
SS.P ← 1;
SS.B ← 1;
        (* 32-bit stack segment *)
SS.G ← 1;
        (* 4-KByte granularity *)
(代码引自 https://www.felixcloutier.com/x86/syscall)

这里主要做了三个工作:

  • 将RIP保存到RCX寄存器,即将SYSCALL指令下一条指令地址保存到RCX,后续用到。
  • 从 IA32_LSTAR MSR 寄存器加载系统调用入口地址。64 位寄存器名为MSR_LSTAR。
  • 从 IA32_STAR MSR 寄存器47-32到加载CS/SS段。64 位寄存器名为 MSR_STAR,其在内核启动过程中初始化。
wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

接下来就是进入 entry_SYSCALL_64处理流程,源码在这
但是这里有一个问题:在较新版内核中,都已支持 PTI 机制,用户态与内核态使用不同页表,而这里 entry_SYSCALL_64 已经属于内核代码,而我们仔细观察entry_SYSCALL_64 实现,在第四行才切换内核页表。想要 entry_SYSCALL_64 能被执行,就需要 cpu_entry_area 的作用了。

SYM_CODE_START(entry_SYSCALL_64)
        UNWIND_HINT_EMPTY
        /*     * Interrupts are off on entry.     * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,     * it is too small to ever cause noticeable irq latency.     */

        swapgs
        /* tss.sp2 is scratch space. */
        movq        %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
        SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp

cpu_entry_area 包括了CPU进入内核需要的所有数据/代码,会被映射到用户态页表。了解点着,但是要注意较新版本cpu_entry_area已经不包含其中的 a set of trampolines;至于为什么看这

/* * Clone the populated PMDs of the entry and irqentry text and force it RO. */
static void pti_clone_entry_text(void){
        pti_clone_pgtable((unsigned long) __entry_text_start,
                          (unsigned long) __irqentry_text_end,
                          PTI_CLONE_PMD);}

其将 __entry_text_start 开头的地址复制,而这又与 entry_SYSCALL_64 有什么关系?我们继续往下找

#define ENTRY_TEXT                          \
        ALIGN_FUNCTION();                   \
        __entry_text_start = .;                 \
        *(.entry.text)                      \
        __entry_text_end = .;

而再看 entry_SYSCALL_64 定义的文件头部

.code64
.section .entry.text, "ax"

所以这里就会把 entry_SYSCALL_64 等一众函数地址拷贝到用户页表,从而实现可访问。具体定义展开这里就不进行了。

call    do_syscall_64

很显然了,接下来就是执行 do_syscall_64 了。后面就是常规操作了。

Original: https://www.cnblogs.com/RayWHL/p/16199553.html
Author: RayWHL
Title: 最新 x86_64 系统调用入口分析 (基于 5.7.0)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/523746/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球