0

According to this: What are the calling conventions for UNIX & Linux system calls on i386 and x86-64, in x64-amd System V ABI, the args are passed successively on these registers: %rdi, %rsi, %rdx, %rcx, %r8 and %r9, in that order. The 7th and higher arg is passed on the stack. So the question is, how does the callee know, how many and in which order to pop the remained (7th and more) args? Does the callee know it from argc? I have and example:

#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 );
}

Compiled without optimization:

.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    $16, %rsp   #,
# a.c:5:    int i=0;    
    movl    $0, -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    $0, %eax    #,
    call    printf@PLT  #
    addq    $16, %rsp   #,
    movl    $0, %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

Here is somehow misorder those register, it goes (from last):(i am not regarding the size of register here, just ilustration) di->si->r9->r8->cx->dx, then si,di are pushed and reasign to string address and first argument (i). So now it seems in correct order. So how does the callee function knows, how many, and in what order to pop ? (si should be before di, since si contains 5 and di 6)

autistic456
  • 183
  • 1
  • 10
  • 1
    The callee doesn't pop them at all, it leaves them on the stack. Are you asking how it knows how many to *use* from the stack? It has to deduce it somehow from the fixed arguments; in this case, from how many format specifiers appear in the format string. – Nate Eldredge Jun 14 '20 at 15:02
  • This is always the case in C. A variadic function is never told exactly how many arguments it was actually called with. The caller has to somehow tell it (via the fixed arguments or some other way) how many arguments it should look for and of what types, and it is up to the caller to ensure that it actually passes the correct number and types of arguments. The callee can only hope it was done correctly. – Nate Eldredge Jun 14 '20 at 15:04
  • @NateEldredge how is deduction done (from the function source?). If callee does not `pop` the args, (which is in conflict with the answer from the link, which says callee `pop`), then who clears the stack after the call? or those args (pushed on the stack before call) remain there as garbare after callee return? – autistic456 Jun 14 '20 at 15:06
  • The linked Q&A says that x86-64 System V's calling convention is calle*R* pops, same as i386 System V. Nowhere in that Q&A is callee-pop mentioned, and no existing x86 calling conventions use callee-pops for variadic functions. Also, the `addq $16, %rsp` in your compiler output is there to pop the args. It's a coincidence that it matches the `subq $16, %rsp` in the prologue; passing more args would reveal that. The prologue is matched by `leave` in the epilogue. (This is un-optimized code so it's normal that GCC wastes instructions popping the args right before `leave`.) – Peter Cordes Jun 15 '20 at 16:41

1 Answers1

1

printf doesn't know how many arguments there are. It has to trust that the format string matches what you actually passed, and if it's wrong, it'll end up skipping some or reading other random stuff off the stack. Varargs functions that don't take a format string use a different approach to signal the end (e.g., a NULL sentinel like execlp uses, or a count variable that the programmer passes manually). Again, if you don't mark the end correctly, it'll read the wrong number of arguments.

  • Ok, but I would like to know, how is that implemented in asm. Is there a loop, which loads from the top of the stack (in callee) as long as its value is not NULL? (in case of execlp). Or the callee relates on the `rsi` which could contain the number of args from for example `main`? – autistic456 Jun 14 '20 at 15:16
  • @autistic456 Basically yes, but before it starts looping through the stack, remember that it has to check the registers. – Joseph Sible-Reinstate Monica Jun 14 '20 at 15:18
  • @autistic456: typically variadic functions dump their register args to an array so they can index them. (That's how GCC compiles them.) The indexing has a cutoff between that dump space vs. stack args, because it's not contiguous. Note that printf potentially even needs random access to its args for stuff like `printf("%2$*1$d", width, num)`. Related: [Technically, how do variadic functions work? How does printf work?](https://stackoverflow.com/q/23104628), but its asm example is for pure stack args, no handling register args. – Peter Cordes Jun 14 '20 at 15:24
  • Also related: [How do vararg functions find out the number of arguments in machine code?](https://stackoverflow.com/a/31520099) mentions a bit about how FP vs. integer args are handled. – Peter Cordes Jun 14 '20 at 15:27