【问题标题】:Understanding OSX 16-Byte alignment了解 OSX 16 字节对齐
【发布时间】:2014-01-26 18:30:35
【问题描述】:

所以看起来everyone 知道 OSX 系统调用始终是 16 字节堆栈对齐的。太好了,当你有这样的代码时,这很有意义:

section .data
  message db 'something', 10, 0

section .text
  global start

start:
push    10         ; size of the message (4 bytes)
push    msg        ; the address of the message (4 bytes)
push    1          ; we want to write to STD_OUT (4 bytes)
mov     eax, 4     ; write(...) syscall
sub     esp, 4     ; move stack pointer down to 4 bytes for a total of 16.
int     0x80       ; invoke
add     esp, 16    ; clean

完美,堆栈对齐为 16 个字节,非常有意义。虽然我们调用 syscall(1) (exit) 怎么样。逻辑上看起来像这样:

push    69         ; return value
mov     eax, 1    ; exit(...) syscall
sub     esp, 12   ; push down stack for total of 16 bytes.
int     0x80      ; invoke

虽然这不起作用,但这样做:

push    69         ; return value
mov     eax, 1    ; exit(...) syscall
sub     esp, 4    ; push down stack for total of 8 bytes.
int     0x80      ; invoke

这很好用,但只有 8 个字节???? Osx 很酷,但是这个 ABI 让我发疯了。有人可以阐明我不理解的内容吗?

【问题讨论】:

  • 您确定堆栈指针在您显示的代码之前是 16 字节对齐的吗?此外,正如您所知,Apple 不会在系统调用级别维护二进制兼容性,仅在系统库级别。
  • 是的,事实上我可以只写最后一个代码段,它按预期工作。但它并没有真正与我听到的关于 16 字节对齐的所有内容融为一体。

标签: macos assembly


【解决方案1】:

短版:您可能不需要对齐到 16 个字节,您只需要始终在参数列表之前留出 4 个字节的间隙。

长版:

这就是我认为正在发生的事情:我不确定堆栈是否应该是 16 字节对齐的。但是,逻辑规定,如果需要填充或调整堆栈以实现对齐,则必须在推送系统调用的参数之前发生,而不是之后。在 int 0x80 指令时的堆栈指针与参数实际所在的位置之间不能有任意数量的字节。内核不知道在哪里可以找到实际参数。从堆栈指针中减去 after 推送参数以实现“对齐”不会对齐参数,它通过在堆栈指针和参数之间插入任意数量的字节来对齐堆栈指针。任何其他可能是真的,那不可能是正确的。

那么为什么第一个和第三个 sn-ps 会起作用呢?他们不也在那里插入任意字节吗?他们偶然工作。这是因为它们都恰好插入了 4 个字节。这种调整并不“成功”,因为它实现了堆栈对齐,它是系统调用 ABI 的一部分。显然,系统调用 ABI 期望并要求在参数列表之前有一个 4 字节的插槽。

syscall() 函数的源代码可以在 here 找到。它看起来像这样:

LEAF(___syscall, 0)
    popl    %ecx        // ret addr
    popl    %eax        // syscall number
    pushl   %ecx
    UNIX_SYSCALL_TRAP
    movl    (%esp),%edx // add one element to stack so
    pushl   %ecx        // caller "pop" will work
    jnb 2f
    BRANCH_EXTERN(cerror)
2:
END(___syscall)

要调用这个库函数,调用者将设置堆栈指针指向syscall() 函数的参数,该函数以系统调用号开始,然后具有实际系统调用的实际参数。然而,调用者将使用call 指令调用它,将返回地址压入堆栈。

因此,上面的代码弹出返回地址,将系统调用号弹出到%eax,将返回地址推回堆栈(系统调用号最初所在的位置),然后执行int 0x80。因此,堆栈指针指向返回地址,然后指向参数。还有额外的 4 个字节:返回地址。我怀疑内核忽略了返回地址。我猜它在系统调用 ABI 中的存在可能只是为了使系统调用的 ABI 类似于函数调用。

这对系统调用的对齐要求意味着什么?好吧,这个函数保证改变堆栈的对齐方式,而不是它的调用者设置的方式。调用者大概设置了 16 字节对齐的堆栈,这个函数在中断之前将它移动了 4 个字节。对于系统调用,堆栈需要 16 字节对齐可能只是一个神话。另一方面,16 字节对齐要求对于调用系统库函数来说绝对是真实的。我开发的 Wine 项目被它烧毁了。这对于 128 位 SSE 参数数据类型来说是最必要的,但如果 alignemtn 错误,Apple 会故意让他们的惰性符号解析器崩溃,即使对于不使用此类参数的函数也是如此,以便及早发现问题。系统调用不会受到这种早期故障机制的影响。可能是内核不需要 16 字节对齐。我不确定是否有任何系统调用采用 128 位参数。

【讨论】:

  • You probably don't need to align to 16 bytes, you just need to always leave a 4-byte gap before your argument list. 这几乎就是我的结论。
  • 是的,在这一点上,我非常确信情况确实如此。这在此处备份:michaux.ca/articles/assembly-hello-world-for-os-x.
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 2019-03-07
  • 1970-01-01
  • 1970-01-01
  • 2011-10-14
相关资源
最近更新 更多