Procedures and the Call Stack
Topics • Procedures • Call stack • Procedure/stack instructions • Calling conventions • Register-saving conventions Why Procedures?
Why functions? Why methods?
int contains_char(char* haystack, char needle) { while (*haystack != '\0') { if (*haystack == needle) return 1; haystack++; } return 0; }
Procedural Abstraction Implementing Procedures
How does a caller pass arguments to a procedure?
How does a caller get a return value from a procedure?
Where does a procedure store local variables?
How does a procedure know where to return (what code to execute next when done)?
How do procedures share limited registers and memory?
3 Call Chain Example
yoo(…) Call Chain { • yoo • who(…) who(); { who • • • • • ru(); } ru(…) ru ru • • • { ru(); • • • • • } • }
4 First Try (broken)
yoo:
who: jmp who back:
done: jmp back stop:
What if I want to call a function multiple times? First Try (broken)
What if I want to call a function multiple times?
who: 1 2 ru: jmp ru back2: 6 3,7 5 jmp ru 4 back2: done: jmp back2 9 8 stop: Implementing Procedures
How does a caller pass arguments to a procedure?
How does a caller get a return value from a procedure?
Where does a procedure store local variables?
How does a procedure know where to return (what code to execute next when done)?
How do procedures share limited registers and memory? All these need separate storage per call! (not just per procedure) 7 Memory Layout
Addr Perm Contents Managed by Initialized
N 2 -1 Stack RW Procedure context Compiler Run-time
Programmer, Dynamic RW malloc/free, Run-time Heap data structures new/GC Global variables/ Compiler/ RW Startup Statics static data structures Assembler/Linker Compiler/ R String literals Startup Literals Assembler/Linker Compiler/ X Instructions Startup Text Assembler/Linker 0 Call Stack
We see x86 organization. Stack “Bottom” Details differ across architectures, but big ideas are shared. higher Region of memory addresses Managed with stack discipline
%esp holds lowest stack address (address of "top" element) stack grows toward lower addresses Stack Pointer: %esp (not extra-sensory perception)
Stack “Top”
9 IA32 Call Stack: Push Stack “Bottom” pushl Src
higher addresses
stack grows toward lower addresses Stack Pointer: %esp
Stack “Top”
10 IA32 Call Stack: Push Stack “Bottom” pushl Src 1. Fetch value from Src 2. Decrement %esp by 4 (why 4?) higher addresses 3. Store value at new address given by %esp
stack grows toward lower addresses Stack Pointer: %esp -4
Stack “Top”
11 IA32 Call Stack: Pop Stack “Bottom” popl Dest
higher addresses
stack grows toward lower addresses Stack Pointer: %esp
Stack “Top”
12 IA32 Call Stack: Pop Stack “Bottom” popl Dest 1. Load value from address %esp 2. Write value to Dest higher addresses 3. Increment %esp by 4
stack grows toward lower addresses Stack Pointer: %esp
Stack “Top” Those bits are still there; we’re just not using them. 13 Call Chain Example Example
yoo(…) Call Chain { • yoo • who(…) who(); { who • • • • • amI(); } amI(…) amI amI • • • { amI(); • amI • • • • } amI(); • amI • }
Procedure amI is recursive (calls itself)
14 Stack frames support procedure calls. Contents Caller Frame Local variables Function arguments Base/Frame Pointer: Return information %ebp Temporary space Frame for current procedure Management %esp Stack Pointer Space allocated when procedure is entered “Set-up” code Stack “Top” Space deallocated upon return “Finish” code
Why not just give every procedure a permanent chunk of memory to hold its local variables, etc? 15 Example Stack
yoo(…) yoo %ebp { • yoo who • %esp who(); • amI amI • } amI
amI
16 Example Stack
who(…) yoo { • • • yoo who amI(); • • • %ebp amI(); amI amI who • • • %esp } amI
amI
17 Example Stack
amI(…) yoo { • yoo who • amI(); • amI amI who • } amI %ebp amI %esp amI
18 Example Stack
amI(…) yoo { • yoo who • amI(); • amI amI who • } amI amI
amI %ebp amI %esp
19 Example Stack
amI(…) yoo { • yoo who • amI(); • amI amI who • } amI amI
amI amI
%ebp amI %esp
20 Example Stack
amI(…) yoo { • yoo who • amI(); • amI amI who • } amI amI
amI %ebp amI %esp
21 Example Stack
amI(…) yoo { • yoo who • amI(); • amI amI who • } amI %ebp amI %esp amI
22 Example Stack
who(…) yoo { • • • yoo who amI(); • • • %ebp amI(); amI amI who • • • %esp } amI
amI
23 Example Stack
amI(…) yoo { • yoo who • • • amI amI who • } amI %ebp amI %esp amI
24 Example Stack
who(…) yoo { • • • yoo who amI(); • • • %ebp amI(); amI amI who • • • %esp } amI
amI
25 Example Stack
yoo(…) yoo %ebp { • yoo who • %esp who(); • amI amI • } amI
amI
How did we remember where to point %ebp when returning? 26 Procedure Control Flow Instructions
Procedure call: call label 1. Push return address on stack 2. Jump to label
Return address: Address of instruction after call. Example: 804854e: e8 3d 06 00 00 call 8048b90
Procedure return: ret 1. Pop return address from stack 2. Jump to address 27 Procedure Call/Return: 1
804854e: e8 3d 06 00 00 call 8048b90
call 8048b90
0x110 0x10c 0x108 123
%esp 0x108
%eip 0x804854e
%eip = instruction pointer = program counter 28 Procedure Call/Return: 2
804854e: e8 3d 06 00 00 call 8048b90
call 8048b90
0x110 0x110 0x10c 0x10c 0x108 123 0x108 123 0x104 0x8048553
%esp 0x108 %esp 0x1080x104
%eip 0x804854e %eip 0x804854e0x8048553
%eip = instruction pointer = program counter 29 Procedure Call/Return: 3
804854e: e8 3d 06 00 00 call 8048b90
call 8048b90 PC-relative address
0x110 0x110 0x10c 0x10c 0x108 123 0x108 123 0x104 0x8048553
%esp 0x108 %esp 0x1080x104
%eip 0x804854e %eip 0x8048553 + 0x000063d 0x8048b90 %eip: program counter 30 Procedure Call/Return: 4
8048591: c3 ret
ret
0x110 0x110 0x10c 0x10c 0x108 123 0x108 123 0x104 0x8048553 0x8048553
%esp 0x104 %esp 0x1040x108
%eip 0x8048591 %eip 0x80485910x8048553
%eip: program counter 31 IA32/Linux Stack Frame
… Caller Frame Arguments to callee Return Address Caller's base pointer
Saved Registers + Callee Local Variables Frame Stack Registers Base/Frame pointer %ebp Stack pointer %esp Arguments for next call
33 Revisiting swap Calling swap from call_swap int zip1 = 02481; call_swap: int zip2 = 98195; • • • pushl $zip2 # Global Var void call_swap() { pushl $zip1 # Global Var swap(&zip1, &zip2); call swap } • • •
void swap(int *xp, int *yp) { int t0 = *xp; Resulting • int t1 = *yp; Stack • *xp = t1; • *yp = t0; } &zip2 &zip1 Rtn adr %esp
34 Revisiting swap
swap: pushl %ebp movl %esp,%ebp Set pushl %ebx Up void swap(int *xp, int *yp) { movl 12(%ebp),%ecx int t0 = *xp; movl 8(%ebp),%edx int t1 = *yp; movl (%ecx),%eax Body *xp = t1; movl (%edx),%ebx *yp = t0; movl %eax,(%edx) } movl %ebx,(%ecx)
movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret
35 swap Setup #1
Entering Stack Resulting Stack
• • • %ebp • frame • %esp •
&zip2 yp call_swap &zip1 xp %ebp Rtn adr Rtn adr %esp Old %ebp
swap: pushl %ebp movl %esp,%ebp Set pushl %ebx Up
36 swap Setup #2
Entering Stack Resulting Stack
• • • %ebp • frame • %esp •
&zip2 yp call_swap &zip1 xp %ebp Rtn adr Rtn adr %esp Old %ebp
swap: pushl %ebp movl %esp,%ebp Set pushl %ebx Up
37 swap Setup #3
Entering Stack Resulting Stack
• • • %ebp • frame • %esp •
&zip2 yp call_swap &zip1 xp %ebp Rtn adr Rtn adr %esp Old %ebp Old %ebx swap: pushl %ebp movl %esp,%ebp Set pushl %ebx Up
38 swap Body
Entering Stack Resulting Stack
• • • %ebp • frame • %esp Offset relative • to new %ebp &zip2 12 yp call_swap &zip1 8 xp %ebp Rtn adr 4 Rtn adr %esp Old %ebp Old %ebx
movl 12(%ebp),%ecx # get yp movl 8(%ebp),%edx # get xp . . . Body
39 swap Finish #1
Finishing Stack Resulting Stack
• • • • frame • •
yp yp call_swap xp %ebp xp %ebp Rtn adr %esp Rtn adr %esp Old %ebp Old %ebp Old %ebx Old %ebx
movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret 40 swap Finish #2
Finishing Stack Resulting Stack
• • • • frame • •
yp yp call_swap xp %ebp xp %ebp Rtn adr %esp Rtn adr %esp Old %ebp Old %ebp Old %ebx
movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret 41 swap Finish #3
Finishing Stack Resulting Stack
• • • • frame • •
yp yp call_swap xp %ebp xp %ebp Rtn adr %esp Rtn adr %esp Old %ebp Old %ebx
movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret 42 swap Finish #4
Finishing Stack Resulting Stack
• • • • frame • •
yp yp call_swap xp %ebp xp %ebp Rtn adr %esp %esp Old %ebp Old %ebx
movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret 43 Disassembled swap
080483a4
Calling Code 8048409: e8 96 ff ff ff call 80483a4
relative address (little endian) 44 swap Finish #4
Finishing Stack Resulting Stack
• • • • frame • •
yp yp call_swap xp %ebp xp %ebp Rtn adr %esp %esp Old %ebp Old %ebx Observation movl -4(%ebp),%ebx Saved & restored register %ebx movl %ebp,%esp Finish but not %eax, %ecx, or %edx popl %ebp ret 45 Register Saving Conventions When procedure yoo calls who: yoo is the caller who is the callee
Will register contents still be there after a procedure call? yoo: who: • • • • • • movl $12345, %edx movl 8(%ebp), %edx call who ? addl $98195, %edx addl %edx, %eax • • • • • • ret ret
47 Register Saving Conventions When procedure yoo calls who: yoo is the caller who is the callee
Will register contents still be there after a procedure call? Conventions Caller Save Caller saves temporary values in its frame before calling Callee Save Callee saves temporary values in its frame before using
48 IA32/Linux Register Saving Conventions %eax, %edx, %ecx Caller saves prior to call %eax Caller-Save %edx if needs values after call Tempor ar ies %ecx %eax %ebx also used to return Callee-Save %esi integer value Tempor ar ies %edi %ebx, %esi, %edi %esp Callee saves if will use Special %ebp %esp, %ebp special form of callee save restored to original values before returning
49 A Puzzle
C function body: *p = d; Write the C function return x - c; header, types, and order of parameters. assembly: movsbl 12(%ebp),%edx movl 16(%ebp),%eax movl %edx,(%eax) movswl 8(%ebp),%eax movl 20(%ebp),%edx subl %eax,%edx movl %edx,%eax
movsbl = move sign-extending a byte to a long (4-byte) movswl = move sign-extending a word (2-byte) to a long (4-byte) Example: Pointers to Local Variables
Top-Level Call Recursive Procedure int sfact(int x) { void s_helper int val = 1; (int x, int *accum) { s_helper(x, &val); if (x <= 1) { return val; return; } } else { int z = *accum * x; *accum = z; s_helper (x-1,accum); } sfact(3) val = 1 } s_helper(3, &val) val = 3 s_helper(2, &val) val = 6 s_helper(1, &val) val = 6. Pass pointer to update location
51 Creating & Initializing Pointer
Must store val in memory (stack); int sfact(int x) { registers do not have addresses. int val = 1; s_helper(x, &val); return val; } 8 x 4 Rtn adr %esp 0 Old %ebp %ebp Initial part of sfact -4 val = 1 %esp __sfact:sfact: -8 pushl %%ebpebp # Save %%ebpebp Temp. movl %%esp,%ebpesp,%ebp # Set %%ebpebp -12 UnusedSpace subl $16,%esp # Add 16 bytes -16 %esp movl 8(%8(%ebp),%edxebp),%edx # edx = x movl $1,-4(%4(%ebp)ebp) # val = 1
52 Passing Pointer
Must store val in memory (stack); int sfact(int x) { registers do not have addresses. int val = 1; s_helper(x, &val); return val; }
Stack at time of call: 8 x 4 Rtn adr 0 Old %ebp %ebp Calling s_helper from sfact -4 valval =x= !1 leal -4(%ebp),%eax # Compute &val -8 pushl %eax # Push on stack -12 Unused pushl %edx # Push x -16 call s_helper # call %esp movl -4(%ebp),%eax # Return val &val • • • # Finish x
53 IA32/Linux Procedure Summary call, ret, push, pop
Stack discipline fits procedure call / return.* Caller Frame If P calls Q, Q returns before P, including recursion. Arguments
Return Addr Conventions support arbitrary function calls. %ebp Old %ebp Safely store per-call values in local stack frame and callee-saved registers Saved Registers Push function arguments at top of stack before call + Result returned in %eax Local Variables
Argument Build %esp *Take 251 to learn about languages where it doesn't. 54