Lesson 2 of 8

Calling conventions and the stack frame

Goal

Where arguments go in and the result comes out

01

Explanation

When one function calls another, the two have to agree on where the arguments go and where the result comes back. That agreement is called a calling convention. Programs on x86-64 Linux and macOS follow the System V convention:

  • The first six arguments go in registers, in this order: rdi, then rsi, then rdx, then rcx, then r8, then r9.
  • Any arguments beyond the first six are passed on the stack.
  • The result comes back in rax.

Windows has a different convention that starts with rcx, so first find out which system the program you're reading was built for.

The stack is memory that grows toward lower addresses, and each function reserves a stack frame in it for its local variables. Without optimization, a compiler usually starts a function with two instructions: push rbp saves the frame base of the function that called it, and mov rbp, rsp makes the current top of the stack the base of the new frame. After that, local variables are read at negative offsets from rbp, like [rbp-4], where the square brackets mean the value stored at that address.

     higher addresses
+----------------+
| return address |  [rbp+8]
+----------------+
| saved rbp      |  [rbp]
+----------------+
| a              |  [rbp-4]
| b              |  [rbp-8]
+----------------+
     lower addresses
  • [rbp+8] holds the return address that the call instruction pushed.
  • [rbp] holds the frame base of the calling function.
  • [rbp-4] and [rbp-8] hold the arguments a and b, which the function copied there from edi and esi.

Here's the function behind the frame drawn above, as a compiler emits it without optimization. It copies its two arguments from the registers into its frame and then reads them back from there. The pop rbp at the end restores the previous frame base before ret jumps to the return address:

g:
  push rbp
  mov  rbp, rsp
  mov  [rbp-4], edi   ; a
  mov  [rbp-8], esi   ; b
  mov  eax, [rbp-4]   ; eax = a
  sub  eax, [rbp-8]   ; eax = a - b
  pop  rbp
  ret

Notice that it writes to [rbp-4] and [rbp-8] without first reserving space with sub rsp, N. That's because on Linux a leaf function, one that calls no other function, may use the 128 bytes below the top of the stack without reserving them. That area is called the red zone. A function that calls others reserves its frame with sub rsp, N. Windows has no red zone at all; instead, the caller reserves 32 bytes called the shadow space.

02

Your task

Write the function g(a, b) that matches the assembly function above, the function arg_register(n) that returns the name of the register holding argument number n in the System V convention (1 to 6), and the variable return_register holding the name of the result register. Write the full register names in lowercase, like "rdi".

Checks

5 checks
  1. g subtracts the second argument from the first
  2. g matches the assembly on other values
  3. The first argument is in rdi
  4. All six arguments in order
  5. The result is in rax
03

Hints

0/3
Track lessons0 of 8 done

Read assembly, calling conventions and control flow, then build a disassembler and an emulator for a tiny machine, patch its bytes and check their integrity.

Intermediate level, 8 lessons, 2 hours in total.

main.py
OutputPython (Pyodide)

Loading Python. This only happens once. Your browser keeps it after that.

–/5

Checks start once the environment is ready.

5 checks listed under “Your task”.

Track lessons0 of 8 done

Read assembly, calling conventions and control flow, then build a disassembler and an emulator for a tiny machine, patch its bytes and check their integrity.

Intermediate level, 8 lessons, 2 hours in total.