Macos 理解OSX 16字节对齐
所以它似乎知道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
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字节的间隔。
这几乎就是我的结论。是的,在这一点上,我非常确信这是事实。此文件已备份到此处:。