64-bit Assembly Execution Deep Dive: Function Call Mechanics and Stack Frame Management

Function Call Mechanism

A fundamental question that often arises: When function A calls function B, how does B know where to return after completion?

Consider the following code snippet where main invokes compute. Once compute finishes execution, control must flow back to main's return statement. But these two functions are separate entities—how does compute know exactly where to resume execution in main?

void compute() {
    int value = 25;
    value = value * 2;
}

int main() {
    compute();
    return 0;
}

Let's analyze this through disassembly examination.

In the main function, only a call instruction appears at the surface level. The key lies in understanding what happens beneath this instrcution.

At address +8, the next instruction hasn't executed yet—the instruction at +4 just completed. The critical address to note is 0x0000555555555364, wich corresponds to position +18. Theoretically, after compute finishes, the CPU should execute the code at this address. Debugging reveals the specific mechanism.

When execution reaches position +13, both rsp and rbp registers hold the value 0x7fffffffdf10.

Entering compute reveals that the call instruction decrements rsp by 8 bytes, indicating an 8-byte value pushed onto the stack.

Examining this pushed value shows 0x0000555555555364. Dissecting the code at this address confirms that call pushes the return address—the location of the next instruction to execute in the calling function—onto the stack. Upon completion, compute pops this address from the stack and transfers control there, thus bridging the gap between compute and main.

Stack Frame Balance

Stack balance refers to maintaining the stack pointer at the same position before and after a function call. Several operations involve stack manipulation during function invocation:

  • The call instruction pushes the return address onto the stack
  • If a called function acccepts more than 6 parameters, starting from the 7th parameter, arguments are passed through the stack

Consider this example:

void processData(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10) {
    p1 = p1 + 10;
    p2 = p2 + 10;
    p3 = p3 + 10;
    p4 = p4 + 10;
    p5 = p5 + 10;
    p6 = p6 + 10;
    p7 = p7 + 10;
    p8 = p8 + 10;
    p9 = p9 + 10;
    p10 = p10 + 10;
    int result = p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + p10;
    result = result * 2;
}

int main() {
    int x1 = 1;
    int x2 = 2;
    int x3 = 3;
    int x4 = 4;
    int x5 = 5;
    int x6 = 6;
    int x7 = 7;
    int x8 = 8;
    int x9 = 9;
    int x10 = 10;
    processData(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10);
    return 0;
}

The disassembly of main reveals additional instructions beyond the basic call.

Dynamic debugging demonstrates how stack balance is achieved.

When execution reaches position +105, the rsp register and stack contents can be observed. Then, as extra parameters are pushed onto the stack, the stack grows by 0x20 bytes—from 0x7fffffffdef0 to 0x7fffffffded0.

Upon entering processData, the return address pushed by call gets popped during the ret instruction. After processData completes and control returns to main, rsp has not yet returned to its balanced position.

However, immediately following the processData call, main executes the instruction to restore stack balance.

In summary, stack imbalance occurs within the calling function during the call sequence, while stack rebalancing happens immediately when control returns from the called function to the caller.

Tags: assembly x86-64 stack-frame function-call low-level-programming

Posted on Thu, 14 May 2026 04:05:49 +0000 by zild1221