Search code examples
cassemblyx86-64calling-convention

How are C structs returned


I'm wondering how a struct is returned in something like:

typedef struct number {
   uint64_t a, b, c, d;
}number;


number get_number(){
   number res = {0,0,0,0};
   return res;
}

which disassembles to

0000000000001149 <get_number>:
    1149:   55                      push   rbp
    114a:   48 89 e5                mov    rbp,rsp
    114d:   48 89 7d d8             mov    QWORD PTR [rbp-0x28],rdi
    1151:   48 c7 45 e0 00 00 00    mov    QWORD PTR [rbp-0x20],0x0
    1158:   00
    1159:   48 c7 45 e8 00 00 00    mov    QWORD PTR [rbp-0x18],0x0
    1160:   00
    1161:   48 c7 45 f0 00 00 00    mov    QWORD PTR [rbp-0x10],0x0
    1168:   00
    1169:   48 c7 45 f8 00 00 00    mov    QWORD PTR [rbp-0x8],0x0
    1170:   00
    1171:   48 8b 4d d8             mov    rcx,QWORD PTR [rbp-0x28]
    1175:   48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
    1179:   48 8b 55 e8             mov    rdx,QWORD PTR [rbp-0x18]
    117d:   48 89 01                mov    QWORD PTR [rcx],rax
    1180:   48 89 51 08             mov    QWORD PTR [rcx+0x8],rdx
    1184:   48 8b 45 f0             mov    rax,QWORD PTR [rbp-0x10]
    1188:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
    118c:   48 89 41 10             mov    QWORD PTR [rcx+0x10],rax
    1190:   48 89 51 18             mov    QWORD PTR [rcx+0x18],rdx
    1194:   48 8b 45 d8             mov    rax,QWORD PTR [rbp-0x28]
    1198:   5d                      pop    rbp
    1199:   c3                      ret

From the disassembly it looks like before calling the function the required space is allocated on the stack and the function fills in those values.

But in the second part it looks like rdi is treated as pointer to a number struct where the values are also saved. What is that about?

And when using a C function in assembler how do I know where the result is?


Solution

  • A calling convention typically does not specifically dictate any code or code sequences, it dictates only state — such as registers and memory, which goes to parameter passing and the stack: where parameters and return values go, what state must be preserved by the call (i.e. some registers and allocated stack memory), and what is scratch (i.e. some registers, and memory below the current stack pointer).  It may also dictate things like stack alignment requirements.

    The calling convention speaks to state as per above: but only at very specific points in time, namely at the exact boundary when control is transferred from caller to callee, and again when control is transferred back from callee to caller.  Thus, the callee has an expectation that the caller has setup all the parameters as expected before its first instruction runs.  The caller has the expectation that the callee has setup all the return values (and preserved what ever it must preserve) before the first instruction of its resumption from the call.

    For these purposes, the calling convention does not dictate machine code instructions or even sequences of instructions; it only establishes expectation of values and locations at the points of transfer.