被调用者如何知道,要弹出多少个参数以及 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,然后 sidi 被压入并重新分配给字符串地址和第一个参数 (i)。所以现在看起来顺序正确。那么被调用函数如何知道有多少个以及以什么顺序弹出? (si 应该在 di 之前,因为 si 包含 5di 6

printf不知道有多少参数。它必须相信格式字符串与您实际传递的内容相匹配,如果它是错误的,它最终会跳过一些或从堆栈中读取其他随机内容。不采用格式字符串的 Varargs 函数使用不同的方法来表示结束(例如,NULL 哨兵,如 execlp 使用,或程序员手动传递的计数变量)。同样,如果您没有正确标记结束,它会读取错误数量的参数。