x86_64系统调用过程

news/2025/1/22 14:50:38/文章来源:https://www.cnblogs.com/Natsunobourei/p/18237929

x86_64系统调用过程

本文所述Linux内核版本为v6.4.0

一、概述

在x86_64架构下,系统调用会经历以下过程:

  1. 将系统调用号存入rax寄存器,参数依次存入rdirsirdxr10r8r9寄存器,第7个及之后的参数会通过栈传递。
  2. 执行syscall指令,该指令会保存syscall指令下一条指令的地址,然后将权限从用户态转换到内核态,并将rip设置为entry_SYSCALL_64程序的入口地址。
  3. 执行entry_SYSCALL_64程序,内核会保存用户态的上下文,包括寄存器和堆栈指针,然后调用do_syscall_64函数来完成系统调用功能。
  4. 系统调用处理函数执行完毕后,内核将返回值放入rax寄存器,然后内核恢复之前保存的用户态上下文,包括寄存器和堆栈指针。
  5. 内核执行sysret指令,将控制权返回给用户态程序。

二、MSR寄存器

从80486之后的x86架构CPU,内部增加了一组新的寄存器,统称为MSR寄存器(Model Specific Registers),这些寄存器不像上面列出的寄存器是固定的,这些寄存器可能随着不同的版本有所变化,主要用来支持一些新的功能。

随着x86CPU不断更新换代,MSR寄存器变的越来越多,但与此同时,有一部分MSR寄存器随着版本迭代,慢慢固化下来,成为了变化中那部分不变的。

在早期的x86架构CPU上,系统调用依赖于软中断实现,如Linux中的int 80。软中断是一个比较慢的操作,因为执行软中断就需要内存查表,通过IDTR定位到IDT,再取出函数地址进行执行。

而系统调用是一个频繁触发的动作,如此这般势必对性能有所影响。在进入奔腾时代后,就使用几个特定的MSR寄存器,分别存储了执行系统调用时内核系统调用入口函数所需要的参数,不再需要内存查表。快速系统调用还提供了专门的CPU指令sysenter/sysexit用来发起系统调用和退出系统调用(在64位上,这一对指令升级为syscall/sysret)。

三、段选择符

段选择符结构如下:

image-20240607154222921
  • Index:所对应的段描述符处于GDTLDT中的索引。

  • TI:表示对应段描述符保存在GDT中还是LDT中,0表示全局描述符表GDT,1表示局部描述符表LDT

  • RPL:当该段选择符装入cs寄存器时,设置CPU当前的特权级CPL的值为RPL,也就是cs寄存器中的RPL就是CPL

CPL值为0,表示CPU当前特权级别为Ring0(内核态),值为3,表示表示CPU当前特权级别为Ring3(用户态)。

四、段描述符

GDT全局段描述符表中的每个条目都有一个这样的复杂的结构:

image-20240607003309591

  • BASE :段首地址的线性地址。

  • LIMIT :该段最后一个地址的偏移量。

  • MORE:包括段的各种标志(如类型、特权级别等),结构如下:

image-20240607003958353

  • DPL:表示访问这个段CPU要求的最小优先级(保存在cs寄存器的CPL特权级)。当DPL为0时,只有CPL为0才能访问,DPL为3时,CPL为0为3都可以访问这个段。

五、SYSCALL指令

syscall指令主要做了三个工作:

  • rip寄存器内容保存到rcx寄存器。
  • MSR_LSTAR寄存器中的系统调用处理程序入口地址存入rip寄存器。
  • MSR_STAR 寄存器的 [47:32] 存入 csss段选择寄存器。

MSR寄存器初始化核心代码为:

// MSR_STAR的[63:48]存入用户代码段选择符,[47:32]存入内核代码段选择符
// wrmsr函数第一个参数表示要写入的MSR编号,第二个参数表示要写入低32位的值,第三个参数表示要写入高32位的值
wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
// 使用系统调用处理程序entry_SYSCALL_64地址填充MSR_LSTAR寄存器
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

cs代码段寄存器指向包含程序指令的段,在cs寄存器中RPL用于表示当前CPU的特权级CPL

CPL为0是最高权限(内核态使用),CPL为3是用户态使用。

  • __USER32_CS 是用户代码段选择符的值,低两位为 0b11

  • __KERNEL_CS 是内核代码段选择符的值,低两位为 0b00

由于syscall指令将内核代码段选择符的值存入了 csss段选择寄存器,当前CPU特权级别从Ring3变为Ring0,即由用户态转变为了内核态。

接下来就是进入entry_SYSCALL_64处理流程。

六、entry_SYSCALL_64

arch/x86/entry/entry_64.S中的entry_SYSCALL_64程序源码如下:

SYM_CODE_START(entry_SYSCALL_64)UNWIND_HINT_ENTRYENDBR/* 交换gs寄存器的值 */swapgs/* tss.sp2 is scratch space. *//* 将当前的栈指针保存到tss中的sp2字段 */movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)/* 使用%rsp作为临时寄存器来切换到内核态页表(KPTI内核页表隔离) */SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp/* 从用户栈切换到内核栈 */movq	PER_CPU_VAR(pcpu_hot + X86_top_of_stack), %rspSYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)ANNOTATE_NOENDBR/* 构建用户态寄存器上下文(struct pt_regs) *//* Construct struct pt_regs on stack */pushq	$__USER_DS				/* pt_regs->ss */pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */pushq	%r11					/* pt_regs->flags */pushq	$__USER_CS				/* pt_regs->cs */pushq	%rcx					/* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)pushq	%rax					/* pt_regs->orig_ax *//* 保存剩余寄存器 */PUSH_AND_CLEAR_REGS rax=$-ENOSYS/* IRQs are off. *//* 将当前内核栈指针作为参数,相当于传递了一个用户态的pt_regs */movq	%rsp, %rdi/* Sign extend the lower 32bit as syscall numbers are treated as int *//* 将系统调用号也作为参数传递 */movslq	%eax, %rsi/* clobbers %rax, make sure it is after saving the syscall nr *//* 关闭分支预测 */IBRS_ENTERUNTRAIN_RET/* 函数执行系统调用功能,并将返回值存入rax寄存器 */call	do_syscall_64		/* returns with IRQs disabled *//** Try to use SYSRET instead of IRET if we're returning to* a completely clean 64-bit userspace context.  If we're not,* go to the slow exit path.* In the Xen PV case we must use iret anyway.*//* do_syscall_64执行过程中产生异常或其他特殊情况,会跳转到慢退出路径 */ALTERNATIVE "", "jmp	swapgs_restore_regs_and_return_to_usermode", \X86_FEATURE_XENPVmovq	RCX(%rsp), %rcxmovq	RIP(%rsp), %r11cmpq	%rcx, %r11	/* SYSRET requires RCX == RIP */jne	swapgs_restore_regs_and_return_to_usermode/** On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP* in kernel space.  This essentially lets the user take over* the kernel, since userspace controls RSP.** If width of "canonical tail" ever becomes variable, this will need* to be updated to remain correct on both old and new CPUs.** Change top bits to match most significant bit (47th or 56th bit* depending on paging mode) in the address.*/
#ifdef CONFIG_X86_5LEVELALTERNATIVE "shl $(64 - 48), %rcx; sar $(64 - 48), %rcx", \"shl $(64 - 57), %rcx; sar $(64 - 57), %rcx", X86_FEATURE_LA57
#elseshl	$(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcxsar	$(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx
#endif/* If this changed %rcx, it was not canonical */cmpq	%rcx, %r11jne	swapgs_restore_regs_and_return_to_usermodecmpq	$__USER_CS, CS(%rsp)		/* CS must match SYSRET */jne	swapgs_restore_regs_and_return_to_usermodemovq	R11(%rsp), %r11cmpq	%r11, EFLAGS(%rsp)		/* R11 == RFLAGS */jne	swapgs_restore_regs_and_return_to_usermode/** SYSCALL clears RF when it saves RFLAGS in R11 and SYSRET cannot* restore RF properly. If the slowpath sets it for whatever reason, we* need to restore it correctly.** SYSRET can restore TF, but unlike IRET, restoring TF results in a* trap from userspace immediately after SYSRET.  This would cause an* infinite loop whenever #DB happens with register state that satisfies* the opportunistic SYSRET conditions.  For example, single-stepping* this user code:**           movq	$stuck_here, %rcx*           pushfq*           popq %r11*   stuck_here:** would never get past 'stuck_here'.*/testq	$(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11jnz	swapgs_restore_regs_and_return_to_usermode/* nothing to check for RSP */cmpq	$__USER_DS, SS(%rsp)		/* SS must match SYSRET */jne	swapgs_restore_regs_and_return_to_usermode/** We win! This label is here just for ease of understanding* perf profiles. Nothing jumps here.*//* 若通过所有检查,使用sysret来返回用户态 */
syscall_return_via_sysret:/* 恢复分支预测 */IBRS_EXIT/* 从栈中恢复寄存器的值 */POP_REGS pop_rdi=0/** Now all regs are restored except RSP and RDI.* Save old stack pointer and switch to trampoline stack.*/movq	%rsp, %rdi/* 切换回用户栈 */movq	PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rspUNWIND_HINT_END_OF_STACKpushq	RSP-RDI(%rdi)	/* RSP */pushq	(%rdi)		/* RDI *//** We are on the trampoline stack.  All regs except RDI are live.* We can do future final exit work right here.*//* 清除内核栈内容 */STACKLEAK_ERASE_NOCLOBBER/* 切换回用户态页表 */SWITCH_TO_USER_CR3_STACK scratch_reg=%rdipopq	%rdipopq	%rsp
SYM_INNER_LABEL(entry_SYSRETQ_unsafe_stack, SYM_L_GLOBAL)ANNOTATE_NOENDBRswapgs/* 切换回用户态,Ring0 -> Ring3 */sysretq
SYM_INNER_LABEL(entry_SYSRETQ_end, SYM_L_GLOBAL)ANNOTATE_NOENDBR/* 正常返回情况不会被执行 */int3
SYM_CODE_END(entry_SYSCALL_64)

七、内核页表隔离KPTI

内核页表隔离(Kernel page-table isolation,缩写KPTI,也简称PTI,旧称KAISER)是Linux内核中的一种强化技术,旨在更好地隔离用户空间与内核空间的内存来提高安全性,缓解现代x86CPU中的“熔断(Meltdown)”硬件安全缺陷。

在 KPTI机制中,内核态空间的内存和用户态空间的内存的隔离进一步得到了增强。

image-20240606003335605
  • 内核态中的页表包括用户空间内存的页表和内核空间内存的页表。
  • 用户态的页表只包括用户空间内存的页表以及必要的内核空间内存的页表,如用于处理系统调用、中断等信息的内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/721618.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第二章 InnoDB存储引擎

2.1 InnoDB存储引擎概述 InnoDB从MySQL5.5版本开始是默认的表存储引擎,是第一个完整支持ACID事务的MySQL存储引擎 特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效利用以及使用内存的CPU2.2 InnoDB存储引擎的版本 InnoDB存储引擎包含于所有MyS…

DVWA靶场学习(一)—— Brute Force

Brute Force 暴力破解其实就是利用不同的账户和密码进行多次尝试。 因为用户在设置密码时可能会选用比较容易记忆的口令,因此,可以使用一些保存常用密码的字典或者结合用户的个人信息进行爆破。 DVWA安全等级有Low,Medium,High和Impossible四种,随着安全等级的提高,网站的…

m基于PSO粒子群优化的LDPC码NMS译码算法最优归一化参数计算和误码率matlab仿真

1.算法仿真效果 matlab2022a仿真结果如下:2.算法涉及理论知识概要低密度奇偶校验码(Low-Density Parity-Check Code, LDPC码)因其优越的纠错性能和近似香农极限的潜力,在现代通信系统中扮演着重要角色。归一化最小和(Normalized Min-Sum, NMS)译码算法作为LDPC码的一种高效软…

RAVEN2

RAVEN2主机发现和nmap扫描 nmap -sT --min-rate 10000 -p- 192.168.56.108PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 111/tcp open rpcbind 54037/tcp open unknown MAC Address: 00:0C:29:60:6F:30 (VMware)nmap -sT -sV -sC -O -p22,80,111,540…

mos管为什么会有寄生二极管 寄生二极管的示意图/作用参数/方向判定

mos管为什么会有寄生二极管 mos管会有寄生二极管是因为mos管的源极和漏极之间的电阻会发生变化,这种变化会导致mos管内部的电压发生变化,从而产生一个寄生二极管。寄生二极管可以抑制mos管的漏电,从而提高mos管的效率。 寄生二极管 漏极和源极之间有一个寄生二极管,即“体二…

scrapy-分布式爬虫

一 介绍 原来scrapy的Scheduler维护的是本机的任务队列(存放Request对象及其回调函数等信息)+本机的去重队列(存放访问过的url地址)所以实现分布式爬取的关键就是,找一台专门的主机上运行一个共享的队列比如Redis,然后重写Scrapy的Scheduler,让新的Scheduler到共享队列存…

使用itextPDF实现PDF电子公章工具类

使用itextPDF实现PDF电子公章工具类 一、制作公章 在线网站:印章生成器 - Kalvin在线工具 (kalvinbg.cn) 然后对公章进行下载保存盖章图片:二、生成数字签名 2.1: java工具keytool生成p12数字证书文件 Keytool是用于管理和证书的工具,位于%JAVA_HOME%/bin目录。 使用JDK的…

前后端分离的四种开发模式

前后端分离已经成为了开发的主流模式,很多老铁认为前后端分离就是各干各的,其实不然。 前后端分离有多种模式,我们一一详解。1. 前后端完全分离 在这种模式下,前端和后端是完全独立的两个系统。前端使用一种框架(如React、Angular、Vue.js等)来实现用户界面,通过API调用…

2024/6/7

今天进行了数据库相关实验。 (1)查询所有供应商情况,先按城市升序排列,城市相同按供应商名称降序排列。(2)查询所有零件情况,先按零件名称升序排列,零件名称相同按重量降序排列。(3)查询项目名中含有“厂”的项目情况。(4)查询供应商名称中第二个字为“方”的供应商…

美团面试:百亿级分片,如何设计基因算法?

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…

Bug记录:Content-Type application/json;charset=UTF-8 is not supported异常解决

Content-Type application/json;charset=UTF-8 is not supported异常解决 前提:确定不是因为Content-Type导致的异常,controller层有注解@RequestBody。 报错详情:确定不是因为缺少Jackson依赖或者版本过低:注意到报错信息上边有一条警告日志: .c.j.MappingJackson2HttpMe…

6.7哈希表

哈希表 哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。哈希表是根据关键码的值而直接进行访问的数据结构。数组就是一张哈希表。哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。…