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

编程

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

整体概览

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

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

MSR寄存器相关这里不再介绍,需要相关知识的指路 寄存器总结 以及

Model-specific register。

SYSCALL 指令

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,其在内核启动过程中初始化。

MSR寄存器初始化源码点这

核心为:

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;,至于为什么看这。

那又是怎么实现?

翻来覆去,终于在 pti 初始化处找到了关键点,其实现为

/* * 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 等一众函数地址拷贝到用户页表,从而实现可访问。具体定义展开这里就不进行了。

继续执行

回到 entry_SYSCALL_64,我们跳过一系列处理,可以看到一个关键点:

call    do_syscall_64       

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

以上是 最新x86_64系统调用入口分析(基于5.7.0) 的全部内容, 来源链接: utcz.com/z/520395.html

回到顶部