被调用者如何知道,要弹出多少个参数以及 x64 中的顺序?
How callee knows, how many args to pop and in which order in x64?
据此:What are the calling conventions for UNIX & Linux system calls on i386 and x86-64,在 x64-amd System V ABI 中,args 在这些寄存器上连续传递:
%rdi, %rsi, %rdx, %rcx, %r8 and %r9
,依此顺序。第 7 个和更高的 arg 在堆栈上传递。所以问题是,被调用者如何知道,pop
剩余的(第 7 个和更多)args 的数量和顺序?被叫方是否从 argc
知道它?我有一个例子:
#include <stdio.h>
#include <stdlib.h>
int main(){
int i=0;
printf("\n%i;%i;%i;%i;%i;%i;%i\n",i,i+1,i+2,i+3,i+4,i+5,i+6 );
}
未经优化编译:
.text
.section .rodata
.LC0:
.string "\n%i;%i;%i;%i;%i;%i;%i\n"
.text
.globl main
.type main, @function
main:
pushq %rbp #
movq %rsp, %rbp #,
subq , %rsp #,
# a.c:5: int i=0;
movl [=11=], -4(%rbp) #, i
# a.c:6: printf("\n%i;%i;%i;%i;%i;%i;%i\n",i,i+1,i+2,i+3,i+4,i+5,i+6 );
movl -4(%rbp), %eax # i, tmp95
leal 6(%rax), %edi #, _1
movl -4(%rbp), %eax # i, tmp96
leal 5(%rax), %esi #, _2
movl -4(%rbp), %eax # i, tmp97
leal 4(%rax), %r9d #, _3
movl -4(%rbp), %eax # i, tmp98
leal 3(%rax), %r8d #, _4
movl -4(%rbp), %eax # i, tmp99
leal 2(%rax), %ecx #, _5
movl -4(%rbp), %eax # i, tmp100
leal 1(%rax), %edx #, _6
movl -4(%rbp), %eax # i, tmp101
pushq %rdi # _1
pushq %rsi # _2
movl %eax, %esi # tmp101,
leaq .LC0(%rip), %rdi #,
movl [=11=], %eax #,
call printf@PLT #
addq , %rsp #,
movl [=11=], %eax #, _10
# a.c:7: }
leave
ret
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
这些寄存器的顺序有点乱,从最后开始:(我在这里不考虑寄存器的大小,只是说明)
di->si->r9->r8->cx->dx
,然后 si
、di
被压入并重新分配给字符串地址和第一个参数 (i
)。所以现在看起来顺序正确。那么被调用函数如何知道有多少个以及以什么顺序弹出? (si
应该在 di
之前,因为 si
包含 5
和 di
6
)
printf
不知道有多少参数。它必须相信格式字符串与您实际传递的内容相匹配,如果它是错误的,它最终会跳过一些或从堆栈中读取其他随机内容。不采用格式字符串的 Varargs 函数使用不同的方法来表示结束(例如,NULL
哨兵,如 execlp
使用,或程序员手动传递的计数变量)。同样,如果您没有正确标记结束,它会读取错误数量的参数。
据此:What are the calling conventions for UNIX & Linux system calls on i386 and x86-64,在 x64-amd System V ABI 中,args 在这些寄存器上连续传递:
%rdi, %rsi, %rdx, %rcx, %r8 and %r9
,依此顺序。第 7 个和更高的 arg 在堆栈上传递。所以问题是,被调用者如何知道,pop
剩余的(第 7 个和更多)args 的数量和顺序?被叫方是否从 argc
知道它?我有一个例子:
#include <stdio.h>
#include <stdlib.h>
int main(){
int i=0;
printf("\n%i;%i;%i;%i;%i;%i;%i\n",i,i+1,i+2,i+3,i+4,i+5,i+6 );
}
未经优化编译:
.text
.section .rodata
.LC0:
.string "\n%i;%i;%i;%i;%i;%i;%i\n"
.text
.globl main
.type main, @function
main:
pushq %rbp #
movq %rsp, %rbp #,
subq , %rsp #,
# a.c:5: int i=0;
movl [=11=], -4(%rbp) #, i
# a.c:6: printf("\n%i;%i;%i;%i;%i;%i;%i\n",i,i+1,i+2,i+3,i+4,i+5,i+6 );
movl -4(%rbp), %eax # i, tmp95
leal 6(%rax), %edi #, _1
movl -4(%rbp), %eax # i, tmp96
leal 5(%rax), %esi #, _2
movl -4(%rbp), %eax # i, tmp97
leal 4(%rax), %r9d #, _3
movl -4(%rbp), %eax # i, tmp98
leal 3(%rax), %r8d #, _4
movl -4(%rbp), %eax # i, tmp99
leal 2(%rax), %ecx #, _5
movl -4(%rbp), %eax # i, tmp100
leal 1(%rax), %edx #, _6
movl -4(%rbp), %eax # i, tmp101
pushq %rdi # _1
pushq %rsi # _2
movl %eax, %esi # tmp101,
leaq .LC0(%rip), %rdi #,
movl [=11=], %eax #,
call printf@PLT #
addq , %rsp #,
movl [=11=], %eax #, _10
# a.c:7: }
leave
ret
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
这些寄存器的顺序有点乱,从最后开始:(我在这里不考虑寄存器的大小,只是说明)
di->si->r9->r8->cx->dx
,然后 si
、di
被压入并重新分配给字符串地址和第一个参数 (i
)。所以现在看起来顺序正确。那么被调用函数如何知道有多少个以及以什么顺序弹出? (si
应该在 di
之前,因为 si
包含 5
和 di
6
)
printf
不知道有多少参数。它必须相信格式字符串与您实际传递的内容相匹配,如果它是错误的,它最终会跳过一些或从堆栈中读取其他随机内容。不采用格式字符串的 Varargs 函数使用不同的方法来表示结束(例如,NULL
哨兵,如 execlp
使用,或程序员手动传递的计数变量)。同样,如果您没有正确标记结束,它会读取错误数量的参数。