【问题标题】:Why do x86-64 Linux system calls modify RCX, and what does the value mean?为什么x86-64 Linux系统调用会修改RCX,值是什么意思?
【发布时间】:2018-06-07 13:53:20
【问题描述】:

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

BYTES_TO_ALLOCATE equ 0x08

section .text
    global _start

_start:
    mov rax, 12
    mov rdi, BYTES_TO_ALLOCATE
    syscall

    mov rax, 60
    syscall

根据 linux 调用约定,我希望返回值在rax 寄存器中(指向分配内存的指针)。我在 gdb 中运行它,在进行 sys_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

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

【问题讨论】:

  • RCX 和 R11 被 SYSCALL 指令本身所破坏。来自指令集参考:将 SYSCALL 之后的指令地址保存到 RCX 之后)RFLAGS 被存储到 R11
  • @MichaelPetch 非常有趣。这意味着为了使用,说cl之后注册我需要先清除它,对吗?我的意思是例如xor cl, cl,然后是mov cl, 7
  • 您不能依赖 SYSCALL 之后的 RCXR11 的值。因此,您必须使用其他寄存器之一而不是 RCXR11 (和 RAX),或者您必须保存值(例如堆栈)和之后恢复它。 RCXR11 不是由你设置的,你只是不能使用它们并期望它们在 SYSCALL 之前和之后是相同的。
  • @MichaelPetch 但是只清除有什么问题?
  • 之前清除它会被 SYSCALL 覆盖。 SYSCALL 只会覆盖其中的内容。如果您愿意,您可以在 SYSCALL 之后设置它,但如果您执行另一个 SYSCALL,则该值将被破坏。

标签: linux assembly x86-64 system-calls


【解决方案1】:

系统调用返回值在rax中,一如既往。见What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

请注意,sys_brkbrk / sbrk POSIX 函数的接口略有不同;见C library/kernel differences section of the Linux brk(2) man page。具体来说,Linux sys_brk设置程序中断; arg 和返回值都是指针。见Assembly x86 brk() call use。该答案需要投票,因为它是该问题上唯一好的答案。


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

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

您将看到 syscall / sysret 指令的设计机制,以允许内核恢复用户空间执行但仍然很快。

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

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


syscall

不是将返回地址推送到内核堆栈(就像int 0x80 那样)
  • 设置 RCX=RIP, R11=RFLAGS(因此在您执行 syscall 之前,内核甚至无法看到这些 reg 的原始值)。

  • 使用来自配置寄存器(IA32_FMASK MSR)的预配置掩码掩码 RFLAGS。这让内核禁用中断 (IF),直到它完成 swapgs 并将 rsp 设置为指向内核堆栈。即使将cli 作为入口点的第一条指令,也会有一个漏洞窗口。您还可以通过屏蔽DF 免费获得cld,即使用户空间使用了stdrep movs / stos 也会向上移动。

    有趣的事实:AMD 提出的第一个 syscall / swapgs 设计没有掩盖 RFLAGS,而是 they changed it after feedback from kernel developers on the amd64 mailing list(大约在 2000 年,比第一颗芯片早几年)。

  • 跳转到配置的 syscall 入口点(设置 CS:RIP = IA32_LSTAR)。我认为旧的CS 值没有保存在任何地方。

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

所以syscall 的设计需要一个系统调用 ABI 来破坏寄存器,这就是为什么值就是它们的样子。

【讨论】:

  • sysret指令的用例是什么?您提供的链接提到,它是syscall 的配套说明。但我从未见过在syscall 指令之后使用sysret!!
  • @SouravKannanthaB:syscall 调用内核,sysret(在内核中)返回用户空间。所以原因与你在call printf 之后不使用ret 的原因相同,除非那恰好是你的函数的结尾。有关内核 int 0x80 和系统调用入口点如何工作的一些详细信息,请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?
  • 如果保存RCX != RIPR11 != RFLAGS,linux内核使用iret而不是sysret。为什么不直接使用保存的RIP/RFLAGS 恢复%rcx/%r11 并使用sysret(我认为这会更快?)
  • @FangZhen:因为 CPU / ISA 设计错误。例如如果 RIP 是非规范的,CPU 将#GP。但是英特尔 CPU 会在不更新 RSP 的情况下处理该异常(因此它是用户堆栈),但 CPU 仍处于内核模式。在使用 ptrace 创建非规范 RIP 之后,用户空间可以通过让另一个线程修改用作内核堆栈的内存来轻松利用这一点。所以需要进行一些检查,由于ptrace或信号改变其他任务的寄存器很少见,所以使用简单的检查是最快的。
  • @FangZhen:例如见github.com/torvalds/linux/blob/…
猜你喜欢
  • 2022-12-12
  • 1970-01-01
  • 2013-10-19
  • 2018-01-21
  • 2018-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多