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 Call Chain yoo(…) { 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 Stack 2 N -1 RW Procedure context Compiler Run-time Programmer, Dynamic Heap RW Run-time malloc/free, data structures new/GC Global variables/ Compiler/ Statics RW Startup static data structures Assembler/Linker Compiler/ Literals R String literals Startup Assembler/Linker Compiler/ Text X Instructions Startup Assembler/Linker 0
Call Stack Stack “Bottom” We see x86 organization. Details differ across architectures, but big ideas are shared. higher addresses Region of memory 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 higher 2. Decrement %esp by 4 (why 4?) 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 higher 2. Write value to Dest 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 Call Chain yoo(…) { 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 Local variables Frame Function arguments Base/Frame Return information Pointer: Temporary space %ebp Frame for current procedure Management %esp Space allocated when procedure is entered Stack Pointer “Set-up” code Space deallocated upon return Stack “Top” “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 • } %ebp amI 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 • } %ebp amI 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 • } %ebp amI 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 <main> 8048553 : 50 pushl %eax 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 <main> 8048553: 50 pushl %eax 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 <main> 8048553: 50 pushl %eax call 8048b90 0x110 0x110 0x10c 0x10c 0x108 123 0x108 123 0x104 0x8048553 %esp 0x108 %esp 0x108 0x104 %eip 0x804854e %eip 0x804854e 0x8048553 %eip = instruction pointer = program counter 29
Procedure Call /Return : 3 804854e: e8 3d 06 00 00 call 8048b90 <main> 8048553: 50 pushl %eax PC-relative address call 8048b90 0x110 0x110 0x10c 0x10c 0x108 123 0x108 123 0x104 0x8048553 %esp 0x108 %esp 0x108 0x104 %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 0x104 0x108 %eip 0x8048591 %eip 0x8048591 0x8048553 %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 Arguments Stack pointer %esp 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) { Resulting int t0 = *xp; • Stack int t1 = *yp; • *xp = t1; • *yp = t0; } &zip2 &zip1 Rtn adr %esp 34
Revisiting swap swap: pushl %ebp Set movl %esp,%ebp Up pushl %ebx movl 12(%ebp),%ecx void swap(int *xp, int *yp) { 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 • • call_swap frame %ebp • • %esp • • &zip2 yp &zip1 xp %ebp %esp Rtn adr Rtn adr Old %ebp swap: pushl %ebp Set movl %esp,%ebp Up pushl %ebx 36
swap Setup #2 Entering Stack Resulting Stack • • call_swap frame %ebp • • %esp • • &zip2 yp &zip1 xp %ebp %esp Rtn adr Rtn adr Old %ebp swap: pushl %ebp Set movl %esp,%ebp Up pushl %ebx 37
swap Setup #3 Entering Stack Resulting Stack • • call_swap frame %ebp • • %esp • • &zip2 yp &zip1 xp %ebp %esp Rtn adr Rtn adr Old %ebp Old %ebx swap: pushl %ebp Set movl %esp,%ebp Up pushl %ebx 38
swap Body Entering Stack Resulting Stack • • call_swap frame %ebp • • %esp • • Offset relative to new %ebp 12 &zip2 yp 8 &zip1 xp %ebp %esp Rtn adr 4 Rtn adr 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 • • call_swap frame • • • • yp yp xp xp %ebp %ebp %esp %esp Rtn adr Rtn adr 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 • • call_swap frame • • • • yp yp xp xp %ebp %ebp %esp %esp Rtn adr Rtn adr Old %ebp Old %ebp Old %ebx movl -4(%ebp),%ebx movl %ebp,%esp Finish popl %ebp ret 41
Recommend
More recommend