为什么 x86-64 Linux 系统调用会修改 RCX,该值是什么意思?

Why do x86-64 Linux system calls modify RCX, and what does the value mean?

提问人:St.Antario 提问时间:12/27/2017 最后编辑:Peter CordesSt.Antario 更新时间:12/28/2017 访问量:6919

问:

我正在尝试使用 syscall 在 linux 中分配一些内存。这是我尝试过的:sys_brk

BYTES_TO_ALLOCATE equ 0x08

section .text
    global _start

_start:
    mov rax, 12
    mov rdi, BYTES_TO_ALLOCATE
    syscall

    mov rax, 60
    syscall

问题是按照 linux 调用约定,我希望返回值在寄存器中(指向分配的内存的指针)。我在 gdb 中运行了它,在进行系统调用后,我注意到以下寄存器内容raxsys_brk

在系统调用之前

rax            0xc      12
rbx            0x0      0
rcx            0x0      0
rdx            0x0      0
rsi            0x0      0
rdi            0x8      8

系统调用后

rax            0x401000 4198400
rbx            0x0      0
rcx            0x40008c 4194444 ; <---- What does this value mean?
rdx            0x0      0
rsi            0x0      0
rdi            0x8      8

在这种情况下,我不太了解寄存器中的值。使用哪一个作为指向我分配的 8 个字节开头的指针?rcxsys_brk

Linux 程序集 x86-64 系统调用

评论

6赞 Michael Petch 12/27/2017
RCX 和 R11 被 SYSCALL 指令本身破坏。从指令集参考:将 SYSCALL 后面的指令地址保存到 RCX) 中RFLAGS 被存储到 R11
0赞 St.Antario 12/27/2017
@MichaelPetch 很有意思。这意味着为了使用,说事后注册我需要先清除它,对吧?我的意思是例如,然后.clxor cl, clmov cl, 7
0赞 Michael Petch 12/27/2017
在 SYSCALL 之后,您不能依赖 RCXR11 的值。因此,您必须使用其他寄存器之一而不是 RCX 和 R11(和 RAX),或者您必须保存该值(例如堆栈)并在之后恢复它。RCXR11 不是由您设置的,您只是不能使用它们并期望它们在 SYSCALL 之前和之后相同。
0赞 St.Antario 12/27/2017
@MichaelPetch 但是只是清除有什么问题呢?
2赞 Michael Petch 12/27/2017
之前清除它将被 SYSCALL 覆盖。SYSCALL 只会覆盖其中的内容。如果您愿意,可以在 SYSCALL 之后设置它,但如果您执行另一个 SYSCALL,则该值将被破坏。

答:

20赞 Peter Cordes 12/28/2017 #1

系统调用返回值一如既往地位于 中。请参阅 i386 和 x86-64 上 UNIX 和 Linux 系统调用的调用约定是什么rax

请注意,其接口与 / POSIX 函数略有不同;请参见 Linux brk(2) 手册页的 C 库/内核差异部分。具体来说,Linux sys_brk设置程序中断;arg 和返回值都是指针。请参阅程序集 x86 brk() 调用使用。这个答案需要赞成,因为它是这个问题上唯一好的答案。sys_brkbrksbrk


您问题的另一个有趣部分是:

在这种情况下,我不太明白rcx寄存器中的值

你看到的是 syscall/sysret 指令是如何被设计成允许内核恢复用户空间执行但仍然快速的机制。

syscall不执行任何加载或存储,它只修改寄存器。它不是使用特殊的寄存器来保存返回地址,而是简单地使用常规整数寄存器。

内核返回到用户空间代码后的 RCX=RIPR11=RFLAGS 并非巧合避免这种情况的唯一方法是,如果系统调用修改了进程在内核内部的保存或值。(是 GDB 使用的系统调用)。在这种情况下,Linux 将使用 instead 而不是返回用户空间,因为较慢的一般情况可以做到这一点。(请参阅 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?,了解 Linux 系统调用入口点的一些演练。不过,大多数是来自 32 位进程的入口点,而不是来自 64 位进程的入口点。ptracercxr11ptraceiretsysretiretsyscall


syscall 不是将返回地址推送到内核堆栈上(就像那样),而是:int 0x80

  • 设置 RCX=RIP, R11=RFLAGS (因此内核甚至不可能在执行之前看到这些 regs 的原始值)。syscall

  • 使用配置寄存器 (MSR) 中的预配置掩码进行掩码。这允许内核禁用中断 (IF),直到它完成并设置为指向内核堆栈。即使作为入口点的第一条指令,也会有一个漏洞窗口。即使用户空间已经使用,您也可以通过屏蔽 so / 向上获取免费。RFLAGSIA32_FMASKswapgsrspclicldDFrep movsstosstd

    有趣的事实:AMD 的第一个提议/设计并没有掩盖 RFLAGS,但他们在 amd64 邮件列表上的内核开发人员的反馈后对其进行了更改(在 ~2000 年,比第一个芯片早了几年)。syscallswapgs

  • 跳转到配置的入口点(设置 CS:RIP = )。我认为,旧值不会保存在任何地方。syscallIA32_LSTARCS

  • 它不做任何其他事情,内核必须用来访问它保存内核堆栈指针的信息块,因为它仍然具有来自用户空间的值。swapgsrsp

因此,设计需要一个系统调用 ABI 来破坏寄存器,这就是值是什么的原因。syscall

评论

0赞 Sourav Kannantha B 7/9/2021
指令的用例是什么?您提供的链接提到,它是 的配套说明。但我从未见过在指令后被使用!!sysretsyscallsysretsyscall
0赞 Peter Cordes 7/10/2021
@SouravKannanthaB:调用内核,(在内核中)返回到用户空间。所以原因和你为什么不使用 after 是一样的,除非这恰好是你函数的结束。请参阅在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么情况?,了解有关内核 int 0x80 和 syscall 入口点如何工作的一些详细信息。syscallsysretretcall printf
0赞 Fang Zhen 7/30/2021
如果保存 或 ,则 linux 内核使用 而不是 .为什么不直接使用保存和使用来恢复(我认为这会更快?RCX != RIPR11 != RFLAGSiretsysret%rcx/%r11RIP/RFLAGSsysret
0赞 Peter Cordes 7/30/2021
@FangZhen:由于CPU/ISA设计错误。例如,如果 RIP 是非规范的,则 CPU 将 #GP。但是 Intel CPU 将在不更新 RSP 的情况下处理该异常(因此它是用户堆栈),但 CPU 仍处于内核模式。用户空间可以很容易地利用这一点,在使用 ptrace 创建非规范 RIP 之后,让另一个线程修改用作内核堆栈的内存。因此,需要进行一些检查,并且由于很少发生其他任务的寄存器更改信号,因此仅使用简单的检查是最快的。ptrace
0赞 Peter Cordes 7/30/2021
@FangZhen:例如,请参阅 github.com/torvalds/linux/blob/...