【问题标题】:How syscall knows where to jump? [closed]系统调用如何知道跳转到哪里? [关闭]
【发布时间】:2019-11-13 05:02:03
【问题描述】:

Linux 如何通过系统调用确定要执行的另一个进程的地址?像这个例子一样?

mov rax, 59 
mov rdi, progName
syscall

我的问题似乎有点混乱,澄清一下,我要问的是系统调用是如何工作的,与传递的寄存器或参数无关。当调用其他进程时,它如何知道跳转、返回等位置。

【问题讨论】:

  • 目前还不清楚您遇到的问题。 syscall 在由IA32_LSTAR MSR 确定的入口点将控制权转移到内核。然后操作系统加载新程序并调用从文件头读取的入口点(无论如何对于 ELF 文件)。新进程存在于自己的虚拟地址空间中,布局再次由标头和加载器以及 ASLR 确定。物理内存由操作系统动态分配。
  • syscall 将返回地址保存到 RCX。见this
  • 它到达内核中的一个入口点,并且该代码执行某种形式的 if-then else 或查找表以确定哪个系统调用正在发生并导致正确的代码/功能。
  • 所以我不同意你的近距离投票,我也告诉其他未来的读者不要加入这个潮流。不过,它当然可以因为不清楚而被关闭。
  • @jww:我认为您可能想在第二次查看该问题后撤回您的近距离投票。但老实说,我并没有认真考虑你会对我的评论做出怎样的反应。在回复某人的评论时(例如不同意),标记该人是正常的。抱歉,如果我的评论在您对其他事情感到恼火时遇到了您的错误(?)。

标签: linux assembly x86-64 nasm system-calls


【解决方案1】:

系统调用

syscall 指令实际上只是一条 INTEL/AMD CPU 指令。以下是概要:

IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
  THEN #UD;
FI;
RCX ← RIP;
RIP ← IA32_LSTAR;
R11 ← RFLAGS;
RFLAGS ← RFLAGS AND NOT(IA32_FMASK);
CS.Selector ← IA32_STAR[47:32] AND FFFCH
CS.Base ← 0;
CS.Limit ← FFFFFH;
CS.Type ← 11;
CS.S ← 1;
CS.DPL ← 0;
CS.P ← 1;
CS.L ← 1;
CS.D ← 0;
CS.G ← 1;
CPL ← 0;
SS.Selector ← IA32_STAR[47:32] + 8;
SS.Base ← 0;
SS.Limit ← FFFFFH;
SS.Type ← 3;
SS.S ← 1;
SS.DPL ← 0;
SS.P ← 1;
SS.B ← 1;
SS.G ← 1;

最重要的部分是保存和管理RIP寄存器的两条指令:

RCX ← RIP
RIP ← IA32_LSTAR

也就是说,IA32_LSTAR(一个寄存器)中保存的地址一定有代码,RCX是返回地址。

CSSS 段也进行了调整,因此您的内核代码将能够在 CPU 级别 0(特权级别)上进一步运行。

如果您无权执行syscall 或该指令不存在,则可能会出现#UD

RAX 是如何解释的?

这只是内核函数指针表的索引。首先内核进行边界检查(如果RAX > __NR_syscall_max,则返回-ENOSYS),然后分派到(C 语法)sys_call_table[rax](rdi, rsi, rdx, r10, r8, r9);

; Intel-syntax translation of Linux 4.12 syscall entry point
       ...                 ; save user-space registers etc.
    call   [sys_call_table + rax * 8]       ; dispatch to sys_execve() or whatever kernel C function

;;; execve probably won't return via this path, but most other calls will
       ...                 ; restore registers except RAX return value, and return to user-space

现代 Linux 在实践中更加复杂,因为通过更改页表来解决诸如 Meltdown 和 L1TF 之类的 x86 漏洞,因此在用户空间运行时大部分内核内存都不会被映射。上面的代码是call *sys_call_table(, %rax, 8)ENTRY(entry_SYSCALL_64)Linux 4.12 arch/x86/entry/entry_64.S 的字面翻译(来自AT&T 语法)(在添加Spectre/Meltdown 缓解措施之前)。同样相关:What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? 提供了有关系统调用调度的内核方面的更多详细信息。

快吗?

该指令被称为快速。这是因为在过去,人们必须使用诸如INT3 之类的指令。中断利用内核堆栈,它将许多寄存器压入堆栈并使用相当慢的RTE 退出异常状态并返回中断后的地址。这通常要慢得多。

使用syscall,您可以避免大部分开销。但是,按照您的要求,这并没有真正的帮助。

syscall 一起使用的另一条指令是swapgs。这为内核提供了一种访问自己的数据和堆栈的方法。您应该查看有关这些说明的 Intel/AMD 文档以了解更多详细信息。

新流程?

Linux 系统有所谓的任务表。进程中的每个进程和每个线程实际上都称为一个任务。

当您创建一个新进程时,Linux 会创建一个任务。为此,它运行的代码执行以下操作:

  • 确保可执行文件存在
  • 设置新任务(包括解析该可执行文件中的 ELF 程序头,以在新创建的虚拟地址空间中创建内存映射。)
  • 分配堆栈缓冲区
  • 加载可执行文件的前几个块(作为按需分页的优化),为要映射到的虚拟页面分配一些物理页面。
  • 在任务中设置起始地址(来自可执行文件的 ELF 入口点)
  • 将任务标记为就绪(也就是正在运行)

当然,这是超级简化的。

起始地址在您的 ELF 二进制文件中定义。它实际上只需要确定一个地址并将其保存在任务当前RIP 指针中并“返回”到用户空间。正常的请求分页机制会处理剩下的事情:如果代码尚未加载,它将生成#PF page-fault 异常,内核将在此时加载必要的代码。尽管在大多数情况下,加载器已经加载了软件的某些部分作为优化以避免初始页面错误。

(未映射的页面上的#PF 将导致内核向您的进程发送 SIGSEGV 段错误信号,但“有效”页面错误由内核静默处理。)

所有新进程通常都加载到相同的虚拟地址(忽略 PIE + ASLR)。这是可能的,因为我们使用了 MMU(内存管理单元)。该协处理器在虚拟地址空间和物理地址空间之间转换内存地址。

(编者注:MMU 并不是真正的协处理器;在现代 CPU 中,虚拟内存逻辑与 L1 指令/数据缓存一起紧密集成到每个内核中。不过,一些古老的 CPU 确实使用了外部 MMU 芯片。 )

确定地址?

所以,现在我们知道所有进程都具有相同的虚拟地址(Linux 下的 0x400000 是ld 选择的默认地址)。为了确定真实的物理地址,我们使用 MMU。内核如何决定该物理地址?嗯,它有内存分配功能。就这么简单。

它调用“malloc()”类型的函数,该函数搜索当前未使用的内存块并在该位置创建(也称为加载)进程。如果当前没有可用的内存块,内核会检查是否从内存中交换了一些东西。如果失败,则进程创建失败。

在创建进程的情况下,它将分配相当大的内存块开始。分配 1Mb 或 2Mb 缓冲区来启动新进程并不罕见。这使事情进展得更快。

此外,如果进程已经在运行并且您再次启动它,那么已经运行的实例使用的大量内存可以被重用。在这种情况下,内核不会分配/加载这些部分。它将使用 MMU 共享进程的两个实例可以共享的那些页面(即,在大多数情况下,进程的代码部分可以共享,因为它是只读的,部分数据可以在以下情况下共享它也被标记为只读;如果未标记为只读,则如果尚未修改数据,仍然可以共享数据——在这种情况下,它被标记为写时复制。)

【讨论】:

  • 在“新进程”部分,ELF 程序加载器必须解析 ELF 程序头并设置所有映射。不过,它不必将它们放入硬件页表中。但是 SEGFAULT 是一个 POSIX 信号,它只在 invalid 页面错误时传递。您正在谈论响应硬件#PF 异常的请求分页。当页面错误是valid(在应该被映射的页面上)时,内核会静默处理它而不传递SIGSEGV。另外,我不知道为什么关于内核系统调用入口点的答案有一个关于需求分页的部分。
  • 您声称CSSS物理 地址有关。不,segment->linear 发生在 virt->phys 翻译之前,而不是之后。内核地址是虚拟的,包括IA32_LSTAR,并且必须映射到页表中。这就是为什么页表有一个 U/S 位来保护内核页面免受用户空间的影响。 (模熔断)。 syscall本身并没有修改CR3,所以至少需要映射syscall入口点。
  • 我很惊讶你没有提到swapgs这就是内核打算如何找到内核堆栈,并从那里保存更多的内核数据。 (请注意,syscall 不会 修改 RSP)。 swapgs 是 Linux 使用的机制。
  • 哦,我更仔细地阅读了这个问题,它确实询问了一些关于启动新进程的问题,也许还有关于新进程中的虚拟地址的问题。所以这就是为什么有一个关于它的部分。
  • @PeterCordes,是的,问题可能更多是关于如何加载进程而不是syscall 指令本身。我还添加了关于EAX 的一小部分。感谢您的编辑!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-04
  • 2011-01-30
  • 2012-12-07
  • 1970-01-01
  • 2012-12-13
  • 2011-05-17
  • 1970-01-01
相关资源
最近更新 更多