【问题标题】:Why do BSD systems need to sub esp,4 when performing a system call?为什么BSD系统在执行系统调用时需要sub esp,4?
【发布时间】:2017-03-14 02:21:28
【问题描述】:

我正在像这样在 OS X(32 位)上执行系统调用:

push 123
mov eax, 1
sub esp, 4
int 0x80

我不太明白sub esp, 4 的差距。

我在某处读到 BSD 及其衍生产品总是存在这种差距,但找不到原因。

我的第一个想法是堆栈对齐,但事实并非如此,因为该行随处可见,而且据我所知 OS X 需要 16 字节堆栈对齐(这里也不是这种情况) .

您知道需要执行 sub esp, 4 的背后隐藏着什么吗?或者可以指出我正确描述它的资源吗?

【问题讨论】:

  • 这是因为你要调用一个函数,那是返回地址的空间。让我找到副本。
  • 首先需要调用,因为该文章中给出的原因是设计决策。他们(内核开发人员)总是打算通过函数调用 int 0x80,因此他们必须考虑出现在参数之前的额外返回地址。当在汇编器中写出 CALL/RET 只是额外的噪音时,就会发生这种情况,因此汇编器开发人员将一个值(与什么值无关)压入堆栈以充当返回的占位符地址然后做int 0x80.
  • 原因是为了隐藏这是一个特殊的系统调用的事实。对于应用程序来说,它应该看起来像一个普通的函数调用。此外,实现可能会酌情使用int 0x80syscall/sysenter。为了避免复制参数,操作系统会考虑返回地址。如果您不使用call,则必须伪造此返回地址。
  • 所以换一种说法:使系统调用的 libc 包装函数更高效,因为它们可以在不复制 args 的情况下执行 int 0x80。在 Unix/Linux 系统中,像 read(2) 这样的系统调用实际上是围绕内核调用的库包装函数,而不是扩展为 inline-asm 的宏。

标签: assembly x86 stack bsd


【解决方案1】:

(社区维基,因为我只是在总结 cmets)

BSD 这样做是为了使系统调用的 libc 包装函数更高效,因为它们可以只执行 int 0x80 而无需复制 args。它为 CALL 推送给包装函数的返回地址留出了空间。

在 Unix/Linux 系统中,像 read(2) 这样的系统调用实际上是围绕内核调用的库包装函数,而不是扩展为 inline-asm 的宏。


Linux 以不同的方式解决了这个问题:通过在寄存器中传递所有系统调用参数。我猜这意味着 32 位包装函数必须从堆栈中加载所有参数,但至少它们不必由内核存储和重新读取。

x86-64 系统调用 ABI 与函数调用约定更兼容:只需要一个 mov r10, rcx,因为 System V 函数调用约定在寄存器中传递参数(并且选择系统调用寄存器以匹配尽可能接近,除了the SYSCALL instruction itself destroys RCX and R11, so the kernel can't see the original values。)

请参阅 标签 wiki,了解有关实际调用约定的更多信息,以及指向 ABI 的链接。

【讨论】:

  • 我知道其他调用约定,我只需要解释 1 个指针间隙空间;) 无论如何感谢您的链接!
  • @mewa:我希望除了你之外的一些未来读者会在某个时候受益。 :)
猜你喜欢
  • 2019-06-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
  • 2016-10-03
  • 2012-02-06
  • 2011-08-01
  • 1970-01-01
相关资源
最近更新 更多