gccx86-64reverse-engineeringcalling-conventiondebug-mode

Why did additional pointer arguments disappear in assembly?


C Code:

void PtrArg1(int* a,int* b,int* c, int* d, int* e, int* f)
{
    return;
}

void PtrArg2(int* a,int* b,int* c, int* d, int* e, int* f, int* g, int* h)
{
    return;
}

Compiling with

gcc -c -m64 -o basics basics.c -O0

Running

objdump -d basics -M intel -r

then results in the following disassembly (Intel syntax):

000000000000000b <PtrArg1>:
   b:   f3 0f 1e fa             endbr64 
   f:   55                      push   rbp
  10:   48 89 e5                mov    rbp,rsp
  13:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  17:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  1b:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  1f:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  23:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  27:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  2b:   90                      nop
  2c:   5d                      pop    rbp
  2d:   c3                      ret    

000000000000002e <PtrArg2>:
  2e:   f3 0f 1e fa             endbr64 
  32:   55                      push   rbp
  33:   48 89 e5                mov    rbp,rsp
  36:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  3a:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  3e:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  42:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  46:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  4a:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  4e:   90                      nop
  4f:   5d                      pop    rbp
  50:   c3                      ret 

The number of arguments differs for PtrArg1 and PtrArg2, but the assembly instructions are the same for both. Why?


Solution

  • This is due to the calling convention (System V AMD64 ABI, current version 1.0). The first six parameters are passed in integer registers, all others are pushed onto the stack.

    After executing till location PtrArg2+0x4e, you get the following stack layout:

    +----------+-----------------+
    |  offset  |     content     |
    +----------+-----------------+
    | rbp-0x30 | f               |
    | rbp-0x28 | e               |
    | rbp-0x20 | d               |
    | rbp-0x18 | c               |
    | rbp-0x10 | b               |
    | rbp-0x8  | a               |
    | rbp+0x0  | saved rbp value |
    | rbp+0x8  | return address  |
    | rbp+0x10 | g               |
    | rbp+0x18 | h               |
    +----------+-----------------+
    

    Since g and h are pushed by the caller, you get the same disassembly for both functions. For the caller

    void Caller()
    {
        PtrArg2(1, 2, 3, 4, 5, 6, 7, 8);
    }
    

    (I ommitted the necessary casts for clarity) we would get the following disassembly:

    Caller():
        push    rbp
        mov     rbp, rsp
        push    8
        push    7
        mov     r9d, 6
        mov     r8d, 5
        mov     ecx, 4
        mov     edx, 3
        mov     esi, 2
        mov     edi, 1
        call    PtrArg2
        add     rsp, 16
        nop
        leave
        ret
    

    (see compiler explorer)

    The parameters h = 8 and g = 7 are pushed onto the stack, before calling PtrArg2.