Home > OS >  How does transfer of parameters between functions work in Assembler?
How does transfer of parameters between functions work in Assembler?

Time:06-02

Soo I'm trying to understand how Assembler works with stack frames etc. I did some exercises and disassembled some C-Code with GDB using Intel notation. The task now is to find out how the variable 'r' is calculated with transfer of parameters between the 'main' and 'f' functions. I just started learning and kinda got lost on what this example is actually doing. Any ideas or some tips on where to get started?

It's a recursive programm working with faculty:

#include <stdio.h>

unsigned int f(unsigned int i) {
    if (i>1) {
        return i * f(i-1);
    } else {
        return 1;
    }
}

int main() {
    unsigned int i=5, r=0;

    r = f(i);

    printf("i = %d, f(i) = %d\n", i, r);
}

The Assembler Code looks like this:

#include <stdio.h>

unsigned int f(unsigned int i) {
    1149:   f3 0f 1e fa             endbr64 
    114d:   55                      push   rbp
    114e:   48 89 e5                mov    rbp,rsp
    1151:   48 83 ec 10             sub    rsp,0x10
    1155:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  if (i>1) {
    1158:   83 7d fc 01             cmp    DWORD PTR [rbp-0x4],0x1
    115c:   76 13                   jbe    1171 <f 0x28>
    return i * f(i-1);
    115e:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
    1161:   83 e8 01                sub    eax,0x1
    1164:   89 c7                   mov    edi,eax
    1166:   e8 de ff ff ff          call   1149 <f>
    116b:   0f af 45 fc             imul   eax,DWORD PTR [rbp-0x4]
    116f:   eb 05                   jmp    1176 <f 0x2d>
  } else {
    return 1;
    1171:   b8 01 00 00 00          mov    eax,0x1
  }
}
    1176:   c9                      leave
    1177:   c3                      ret


int main() {
    1178:   f3 0f 1e fa             endbr64
    117c:   55                      push   rbp
    117d:   48 89 e5                mov    rbp,rsp
    1180:   48 83 ec 10             sub    rsp,0x10
  unsigned int i=5, r=0;
    1184:   c7 45 f8 05 00 00 00    mov    DWORD PTR [rbp-0x8],0x5
    118b:   c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0

  r = f(i);
    1192:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
    1195:   89 c7                   mov    edi,eax
    1197:   e8 ad ff ff ff          call   1149 <f>
    119c:   89 45 fc                mov    DWORD PTR [rbp-0x4],eax

CodePudding user response:

Study the calling convention for your environment.  A overview of the many calling conventions for a number of architecures: https://www.dyncall.org/docs/manual/manualse11.html

The calling convention specifies:

  1. Where parameters and return values must appear at the one single point of transfer of control of the instruction stream from the caller to the callee.  For parameters being passed, that single point is after the call is made and before the first instruction of the callee (and for return values, at the point where the callee finishes and just before execution resumes in the caller).

    Many conventions combine parameter passing in CPU registers with stack memory for parameters that don't fit in CPU registers.  And even some that don't use CPU registers for parameters still use CPU registers for return values.

  2. What registers a function is allowed to clobber vs. must preserve.  Call-clobbered registers can be assigned new values without concern   Call-preserved registers can be used but must be restored to the value they had upon entry before returning to the caller.  The advantage of call-preserved registers is that since they are preserved by a call, you can use them for variables that need to survive another call.

  3. The meaning & treatment of the stack pointer, regarding memory below and above the current pointer, and alignment requirements for stack allocation.

If the function allocates stack space in some manner, then memory parameters will appear to move farther away from the top of the stack (they don't actually move, of course, but become larger offsets from the current stack or frame pointer).  Compilers know this and adjust their access to stack memory accordingly.

Some compilers set up frame pointers to refer to stack memory.  A frame pointer is a copy of the stack pointer made at some point in the prologue.  Frame pointers are not always necessary but facilitate exception handling and stack unwinding, as well as dynamic stack allocation.

  • Related