How do the CALL and RET instructions work?
CALL pushes the return address (the address of the next instruction) onto the stack and jumps to the function; RET pops that address back into %rip and resumes there.
* call pushes the return address then jumps; ret pops that saved address into %rip and resumes — overwriting it on the stack hijacks control flow. *
Together they implement the function-call/return mechanism using the stack to remember "where to come back to."
call label does two things:
- Push the address of the following instruction (
push %rip) - Jump to
label
ret undoes it:
- Pop the saved return address into
%rip - Continue executing from there
0x4004e5: callq 0x4004b6 # push 0x4004ea, jump to sumto
0x4004ea: addq $5, %rax # execution resumes here after ret
...
0x4004b6 <sumto>:
...
0x4004d4: retq # pop 0x4004ea into %rip, jump back
The stack is what makes this work for nested and recursive calls: each call pushes its own return address, and the matching ret pops the most recent one — a perfect LIFO chain back through the callers.
Security note: because the return address sits on the stack, overwriting it (a stack buffer overflow) hijacks ret — the basis of classic control-flow exploits.
Go deeper:
Felix Cloutier — CALL — CALL pushing the return address.
Felix Cloutier — RET — RET popping the return address back into the instruction pointer.