Assembler implementation of function calls

In this video we’re going to look at actual active records. The examples we’ve given before tend to be functions with no arguments, so in this video we’re going to look at some complex functions with arguments.

First, let’s take a look at the C code and its corresponding assembly instructions

void foo(int bar, int *baz)
{
    char snink[4];
    short *why;
    
    why = (short*)(snink + 2);
    *why = 50;
}

int main(int argc, char **argv)
{
    int i = 4;
    foo(i, &i);
    return 0;
}
Copy the code
foo:
SP = SP - 8 
R1 = SP + 6 
M[SP] = R1 
R1 = M[SP]
M[R1] = .2 50
SP = SP + 8
RET

SP = SP - 4
M[SP] = 4
SP = SP - 8
R1 = M[SP + 8]
R2 = SP + 8
M[SP] = R1
M[SP + 4] = R2
CALL <foo>
SP = SP + 8
RV = 0
Copy the code

At the beginning, it may be blindsided. Now let’s analyze it one by one:

void foo(int bar, int *baz)
{
    char snink[4];
    short*why; . }Copy the code

The activity in memory is recorded as follows:

Note that the course uses a 32-bit machine.

Function argument 0 is always below argument 1, and so on. In the memory area between the parameters and local variables, the function call information is stored to tell us which piece of code called foo, so it depends on the saved PC value for subsequent execution.

By now, you might be wondering about the layout of variables in memory, but let’s start with the main function and see how this layout is created.

int main(int argc, char **argv)
{
    int i = 4;
    foo(i, &i);
    return 0;
}
Copy the code

When called, main already has the following sections, which will be explained when foo is called.

According to the function call principle, we first need to allocate the corresponding memory space for local variables.

int i = 4;
Copy the code

The corresponding assembly instruction is as follows:

SP = SP - 4;
M[SP] = 4;
Copy the code

SP is used here for the stack pointer, minus 4 because stack space is allocated from the highest address to the lowest address.

Next we need to prepare for the call to foo, first we need to reserve space for the arguments, i.e

SP = SP - 8
R1 = M[SP + 8]
R2 = SP + 8
M[SP] = R1
M[SP + 4] = R2;
Copy the code

After that, you give control of your code to the foo function with the following instruction

CALL <foo>
Copy the code

This is a simple jump instruction. Note that we also need to ensure that we can jump back to the current position after the jump to ensure the smooth execution of subsequent instructions.

Let’s take a look at all the assembly instructions so far:

SP = SP - 4
M[SP] = 4
SP = SP - 8
R1 = M[SP + 8]
R2 = SP + 8
M[SP] = R1
M[SP + 4] = R2;
CALL <foo>
SP = SP + 8
Copy the code

The SP = SP + 8 instruction is the instruction that needs to be executed after foo is executed, and its address is saved in foo’s saved PC, which is automatically triggered when the CALL instruction is executed.

Now complete the foo function

void foo(int bar, int *baz)
{
    char snink[4];
    short *why;
    
    why = (short*)(snink + 2);
    *why = 50;
}
Copy the code

First, we need to allocate the corresponding space for local variables (which is determined at compile time), i.e

foo: SP = SP - 8
Copy the code

SP = SP - 8
R1 = SP + 6
M[SP] = R1
R1 = M[SP]
M[R1] = .2 50
Copy the code

After that, we execute the following instruction because we need to reclaim the allocated local space after the function call ends

SP = SP + 8
Copy the code

SP points to the saved PC, so the last instruction is RET. The instruction takes the value from the saved PC and puts it into the PC register, setting SP = SP + 4.

After the above operation, the active record in memory becomes the following.

The PC now points to the instruction after the CALL instruction to reclaim the memory area allocated for the parameter variable, i.e

PC = PC + 8
Copy the code

The last instruction is

RV = 0
Copy the code

RV is a 4-byte register that is used primarily to pass the return value between the caller and the function being called.

Now, to summarize this section, we need to set up the following sections in memory during a function call.

As can be seen from the figure, during the function call, the memory allocation is divided into two parts, the parameters and saved PC are allocated and initialized by the user, and the local variables in the function are allocated and initialized by the function itself.

Next, let’s look at the use of call and RET directives in recursive functions.

int fact(int n)
{
    if(n == 0)
        return 1;
    return n * fact(n - 1);
}
Copy the code

fact: R1 = M[SP + 4] BNE R1, 0, PC + 12 RV = 1 RET R1 = M[SP + 4] R1 = R1 - 1 SP = SP - 4 M[SP] = R1 CALL <fact> SP = SP + 4 R1 = M[SP + 4] RV = RV * R1  RETCopy the code

Note that in a real system the PC points to the next instruction, but in the course, assume the PC points to the current instruction.