Macos 理解OSX 16字节对齐

Macos 理解OSX 16字节对齐,macos,assembly,Macos,Assembly,所以它似乎知道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

所以它似乎知道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
push    69         ; return value
mov     eax, 1    ; exit(...) syscall
sub     esp, 12   ; push down stack for total of 16 bytes.
int     0x80      ; invoke
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)
完美,堆栈对齐到16个字节,非常有意义。我们调用syscall(1)(
exit
)怎么样。从逻辑上讲,这看起来像这样:

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
push    69         ; return value
mov     eax, 1    ; exit(...) syscall
sub     esp, 12   ; push down stack for total of 16 bytes.
int     0x80      ; invoke
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)
虽然这不起作用,但它确实起作用:

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个字节,只需要在参数列表之前始终留出4个字节的间隙

长版本:

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

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

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

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
push    69         ; return value
mov     eax, 1    ; exit(...) syscall
sub     esp, 12   ; push down stack for total of 16 bytes.
int     0x80      ; invoke
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()
函数的参数,该函数以syscall编号开始,然后具有实际syscall的实际参数。但是,调用方随后将使用
call
指令调用它,这将返回地址推送到堆栈上

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


这对于系统调用的对齐要求意味着什么?这个函数保证改变堆栈的对齐方式,使其与调用方设置堆栈的方式不同。调用者可能设置16字节对齐的堆栈,该函数在中断之前将堆栈移动4字节。对于系统调用,堆栈需要16字节对齐,这可能只是一个神话。另一方面,对于调用系统库函数来说,16字节对齐的要求绝对是真实的。我为之开发的葡萄酒项目被它烧掉了。对于128位SSE参数数据类型来说,这是非常必要的,但是如果alignemtn错误,苹果公司故意让他们的惰性符号解析器爆炸,即使对于不使用此类参数的函数也是如此,这样就可以及早发现问题。系统调用将不受这种早期失败机制的约束。可能内核不需要16字节对齐。我不确定是否有任何系统调用采用128位参数。

是否确定堆栈指针在显示代码之前已对齐16字节?另外,正如你所知道的,苹果在系统调用级别上并没有保持二进制兼容性,只是在系统库级别。是的,事实上,我可以只编写最后一段代码,它可以按预期工作。但这与我所听到的关于16字节对齐的所有内容并不完全一致。
你可能不需要对齐到16字节,你只需要在你的参数列表之前留出4字节的间隔。
这几乎就是我的结论。是的,在这一点上,我非常确信这是事实。此文件已备份到此处:。