Date created: Tuesday, June 7, 2016 9:45:06 AM. Last modified: Friday, January 3, 2020 3:34:18 PM
Stack, Heap and Stack Frame
References:
https://en.wikipedia.org/wiki/Stack-based_memory_allocation
http://www.dcs.warwick.ac.uk/oldmodelling/other/eden/advanced/notes/stack.html
http://www.eecg.toronto.edu/~amza/www.mindsec.com/files/x86regs.html
http://ref.x86asm.net/coder32.html
https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/stack.html
https://en.wikipedia.org/wiki/C_dynamic_memory_allocation
http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
https://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Explicit-Reg-Vars.html
https://www.cs.uaf.edu/2010/fall/cs301/lecture/10_04_malloc.html
http://www.csee.umbc.edu/~chang/cs313.s02/stack.shtml
http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64
Contents:
Stack & Heap Overview
Stack
Heap
Stack Frames
Files used: c-files.zip
The stack is the memory allocated per thread (so multi-threaded applications have multiple private stacks allocated, one stack per thread). When a function is called [by the thread] a block is reserved on the top of the stack for local variables and some bookkeeping data. When that function returns the block is unallocated, free for another function call to use that space. The stack is always reserved in a LIFO (last in first out) order, the most recently reserved block is always the next block to be freed.
The heap is memory set aside for dynamic allocation. Unlike the stack there is no enforced pattern to the allocation and deallocation of blocks from the heap; heap memory block can be allocated at any time and freed at any time. This makes it much more complex to keep track of which parts of the heap are allocated or free at any given time. For a single-threaded application there is a single private stack and heap allocated, for multi-threaded applications there is a still a single shared heap space used by all threads of that application (private stacks per thread but shared heap).
Direct from thisWiki page: "The C programming language manages memory statically, automatically, or dynamically. Static-duration variables are allocated in main memory, usually along with the executable code of the program, and persist for the lifetime of the program [in C this could be a global constant for example "const int i = 1; int main() {; ..."]. Automatic-duration variables are allocated on the stack and come and go as functions are called and return [in C this could be variables declared within a function of a fixed size for example "int main() {; int i = 1; ..."]. For static-duration and automatic-duration variables, the size of the allocation must be compile-time constant (except for the case of variable-length automatic arrays). If the required size is not known until run-time (for example, if data of arbitrary size is being read from the user or from a disk file), then using fixed-size data objects is inadequate.
The lifetime of allocated memory can also cause concern. Neither static- nor automatic-duration memory is adequate for all situations. Automatic-allocated data cannot persist across multiple function calls, while static data persists for the life of the program whether it is needed or not. In many situations the programmer requires greater flexibility in managing the lifetime of allocated memory.
These limitations are avoided by using dynamic memory allocation in which memory is more explicitly (but more flexibly) managed, typically, by allocating it from the free store (informally called the "heap"), an area of memory structured for this purpose. In C, the library function malloc() is used to allocate a block of memory on the heap [and calloc(), or new() in C++, for example "int main() {; int *i_ptr = malloc(sizeof(int));..."]. The program accesses this block of memory via a pointer that malloc returns. When the memory is no longer needed, the pointer is passed to free() (delete() in C++) which deallocates the memory so that it can be used for other purposes."
In summary: static-duration variables exist outside the stack in memory that stores program code, automatic-duration memory allocations are made on the stack and automatic-allocated memory allocations are made on the heap.
When a function call is made by a thread the parameters [for that function] are pushed on the top of the stack (at the location pointed to by the stack pointer). If the function requires any local variable storage space for these values is allocated on the stack (the stack pointer is updated to now point after this additionally allocate memory). When the function returns the allocated local variables and the arguments stored on the stack are popped, the return value of the function is returned by pushing it onto the stack, and the stack pointer updated (minus the freed values but still pointing to after the return value).
This first example below shows that program code is loaded into static main memory outside of the heap or stack, the global constant 8 byte integer is not present in the assembly code (highlighting the fact it is not being allocated to the heap or stack). The second example below shows a single 8 byte memory block being created on the stack after main() is called:
$ cat stack1.c #include <stdint.h> const uint8_t i = 8; // Example of static-duration memory not on stack or heap int main () { return 0; } $ gcc -g -c stack1.c $ objdump -d -M intel -S stack1.o stack1.o: file format elf32-i386 Disassembly of section .text: 00000000 : #include <stdint.h> const uint8_t i = 8; int main () { // NB: Intel syntax is destination first... // EBP is the stack Base Pointer register, it holds the base address on the stack. // ESP is the Stack Pointer register, it holds the top address of the stack. 0: 55 push ebp // Push EBP on to the stack (this effectively becomes the return pointer) 1: 89 e5 mov ebp,esp // The Stack Pointer (which points to the next free memory location on the stack), // is being copied into to the stack Base Pointer, the Stack Pointer would normally // be moved down the stack to allocate room between the ESB and ESP, however no // variables exist in this code so the stack is effectively zero size (ESB == ESP). return 0; 3: b8 00 00 00 00 mov eax,0x0 8: 5d pop ebp 9: c3 ret
$ cat stack2.c #include <stdint.h> int main () { uint8_t i = 8; // This will be declared on the stack when this function is called return 0; } $ gcc -g -c stack2.c $ objdump -d -M intel -S stack2.o stack2.o: file format elf32-i386 Disassembly of section .text: 00000000 : // Before main() starts no memory has been allocated for the uint8_t block int main () { 0: 55 push ebp 1: 89 e5 mov ebp,esp // Copy the Stack Pointer (next free address) to the stack Base Pointer marking // the start of the stack space for the program. 3: 83 ec 10 sub esp,0x10 // Move the Stack Pointer down by 0x10 (decimal 16) to allocate memory // for the program between the stack Base Pointer and Stack Pointer // (this is effectively allocating 16 bytes of memory by moving the Stack Pointer // down 16 addresses, each one points to a single byte of addressable memory, // on this test x86_64 Linux-3.13.0-55 machine). 3x uint64_t's instead of a // single uint8_t would case 0x20 to be allocated instead to the stack instead // of 0x10, aligning to the next 16 byte boundary. uint8_t i = 8; 6: c6 45 ff 08 mov BYTE PTR [ebp-0x1],0x8 // Use this new space between the stack Base Pointer and Stack // Pointer to create the uint8_t 8 byte variable and assign the // value of 0x08. The first memory address after the stack // Base Pointer is used as a byte pointer and the value 0x08 // is copied to that memory address. return 0; a: b8 00 00 00 00 mov eax,0x0 f: c9 leave // "leave" is doing the same as "mov esp,ebp; pop ebp;", which is // clearing the stack of this function by copying the stack Base Pointer // back into the Stack Pointer (moving the Stack Pointer back to where // it was at the start of the program). 10: c3 ret
The heap holds the contents of temporary memory allocations (variables) during a function's operations. A pointer keeps track the top of heap similar to the stack pointer of the stack. The heap is not affected by the return of the function call, data that isn't erased (using free() for example) is left on the heap. Generally the heap memory access is slower than stack access.
$ cat heap1.c #include <stdint.h> #include <stdlib.h> int main () { uint8_t *i_ptr = malloc(1); // Dynamic memory allocation on the heap free(i_ptr); return 0; } $ gcc -g -c heap1.c $ objdump -d -M intel -S heap1.o heap1.o: file format elf32-i386 Disassembly of section .text: 00000000 : #include <stdint.h> #include <stdlib.h> int main () { 0: 55 push ebp 1: 89 e5 mov ebp,esp 3: 83 e4 f0 and esp,0xfffffff0 // Align ESP to the nearest nibble (otherwise the allocation below // would not align to an address divisible by 0x10) 6: 83 ec 20 sub esp,0x20 // Subtract 0x20 (decimal 32) from ESP to allocate stack space uint8_t *i_ptr = malloc(1); // Allocate on the heap... 9: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 // A bit more assembly verbosity is required.... 10: e8 fc ff ff ff call 11 <main+0x11> 15: 89 44 24 1c mov DWORD PTR [esp+0x1c],eax free(i_ptr); // Deallocate from the heap... 19: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c] // A bit more assembly verbosity is required.... 1d: 89 04 24 mov DWORD PTR [esp],eax 20: e8 fc ff ff ff call 21 <main+0x21> return 0; 25: b8 00 00 00 00 mov eax,0x0 2a: c9 leave 2b: c3 ret
Same C file as above compiled to assembly (not compiled and linked then debugged as above) to show more detail around the heap allocation:
$ gcc -S -o heap1.s heap1.c $ cat heap1.s .file "heap1.c" .text .globl main .type main, @function main: .LFB2: // NB: Non-Intel syntax is destination last .cfi_startproc push %ebp // Push stack Base Pointer onto stack (for return address) .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp // Copy the Stack Pointer (next free address) to the stack Base Pointer register .cfi_def_cfa_register 5 andl $-16, %esp // Align Stack Pointer to nearest nibble (same as "and esp,0xfffffff0"). subl $32, %esp // Subtract decimal 32 (0x20) from ESP to allocate stack space (same as "sub esp,0x20"). // Now the heap allocation: uint8_t *i_ptr = malloc(1); movl $1, (%esp) // Copy the value "1" on to the top of the stack (1 byte of memory is being allocated by malloc). call malloc // Call the malloc function (it reads from the top of the stack the size value). movl %eax, 28(%esp) // EAX contains the pointer returned by malloc, copy it to the stack (to the address located // at the Stack Pointer + 28). // Now the heap deallocation: free(i_ptr); movl 28(%esp), %eax // Copy the point stored at Stack Pointer + 24 into the EAX register. movl %eax, (%esp) // Copy the value in EAX on to the top of the stack call free // Call the free function (which reads the pointer from the top of the stack) movl $0, %eax // Copy return value to EAX leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4" .section .note.GNU-stack,"",@progbits
In the next heap example below a stack and heap allocation are both made and the addresses for both are displaid:
$ cat heap2.c #include <stdint.h> //uintN_t #include <stdlib.h> //malloc() #include <stdio.h> //printf() int main () { register uint32_t ebp asm ("ebp"); // This is GCC specific. printf("0x%x\n", ebp); //printf("%p\n", __builtin_frame_address(0)); // This is also GCC specific, to print EBP register. register uint32_t esp asm ("esp"); // This is GCC specific. printf("0x%x\n", esp); uint32_t i; // Allocate on stack. printf("%p\n", &i); uint8_t *i_ptr = malloc(1); // Allocate on heap. printf("%p\n", i_ptr); free(i_ptr); // Remove from the heap. return 0; } $ gcc -g -c heap2.c $ objdump -d -M intel -S heap2.o heap2.o: file format elf32-i386 Disassembly of section .text: 00000000 : #include <stdint.h> //uintN_t #include <stdlib.h> //malloc() #include <stdio.h> //printf() int main () { 0: 55 push ebp 1: 89 e5 mov ebp,esp 3: 83 e4 f0 and esp,0xfffffff0 // Align the Stack Pointer to the nearest nibble. 6: 83 ec 20 sub esp,0x20 // Grow the stack by 0x20 bytes. register uint32_t ebp asm ("ebp"); // Prints: 0xbfed48e8 printf("0x%x\n", ebp); 9: 89 e8 mov eax,ebp b: 89 44 24 04 mov DWORD PTR [esp+0x4],eax f: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 16: e8 fc ff ff ff call 17 <main+0x17> register uint32_t esp asm ("esp"); printf("0x%x\n", esp); // Prints: 0xbfed48c0. This is 0x28 less than EBP. 1b: 89 e0 mov eax,esp 1d: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 21: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 28: e8 fc ff ff ff call 29 <main+0x29> uint32_t i; // Make an allocation on the stack. printf("%p\n", &i); // Prints: 0xbfed48d8, the next free stack space is 0xbfed48dc. // This is 16 addresses below the stack Base Pointer but above // the Stack Pointer (so it's an allocation in stack memory). 2d: 8d 44 24 18 lea eax,[esp+0x18] // 31: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 35: c7 04 24 06 00 00 00 mov DWORD PTR [esp],0x6 3c: e8 fc ff ff ff call 3d <main+0x3d> uint8_t *i_ptr = malloc(1); // Allocate 1 byte within heap memory. 41: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 // Use the ESP register address as a pointer and copy the value // 0x1 there (effectivly pushing 0x1 onto the stack). 48: e8 fc ff ff ff call 49 <main+0x49> // Call malloc (reads the size from the top of the stack). 4d: 89 44 24 1c mov DWORD PTR [esp+0x1c],eax // Copy the resulting pointer from EAX to address ESP + 0x1C. printf("%p\n", i_ptr); // Prints: 0x9339008, the next free heap space is 0x9339018, // (this is because malloc is by default allocating 16 bytes // and the last 4 bytes are reserved for bookkeeping so up to // malloc(12) will reserve 16 bytes). 51: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c] 55: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 59: c7 04 24 06 00 00 00 mov DWORD PTR [esp],0x6 60: e8 fc ff ff ff call 61 <main+0x61> free(i_ptr); // Deallocate from the heap. 65: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c] // Copy to EAX the address of i_ptr, which is at ESP + 0x1c. 69: 89 04 24 mov DWORD PTR [esp],eax // Copy the addresss of i_ptr from EAX to the top of the stack. 6c: e8 fc ff ff ff call 6d <main+0x6d> // Call free (reads the address from the top of the stack). return 0; 71: b8 00 00 00 00 mov eax,0x0 76: c9 leave // "leave" is the same as "mov esp,ebp; pop ebp;". 77: c3 ret
Another example with more heap and stack allocations:
$ cat heap3.c #include <stdint.h> //uintN_t #include <stdlib.h> //malloc() #include <stdio.h> //printf() int main () { register uint32_t ebp asm ("ebp"); printf("0x%x\n", ebp); register uint32_t esp asm ("esp"); printf("0x%x\n", esp); uint32_t i; printf("%p\n", &i); uint32_t j; printf("%p\n", &j); uint32_t k; printf("%p\n", &k); void *v_ptr = malloc(20); printf("%p\n", v_ptr); uint32_t *i_ptr = malloc(4); printf("%p\n", i_ptr); uint16_t *j_ptr = malloc(2); printf("%p\n", j_ptr); uint8_t *k_ptr = malloc(1); printf("%p\n", k_ptr); free(v_ptr); free(i_ptr); free(j_ptr); free(k_ptr); return 0; } $ gcc -o heap3 heap3.c $ ./heap3 0xbfa83068 // EBP 0xbfa83030 // ESP 0xbfa83044 // uint32_t i 0xbfa83048 // uint32_t j 0xbfa8304c // uint32_t k 0x97ce008 // void v_ptr 0x97ce020 // uint32_t i_ptr ; Theese three all request from malloc less than 12 bytes so they each fit in a 16 byte page 0x97ce030 // uint36_t j_ptr ; Theese three all request from malloc less than 12 bytes so they each fit in a 16 byte page 0x97ce040 // uint8_t k_ptr ; Theese three all request from malloc less than 12 bytes so they each fit in a 16 byte page
When a function is called the current machine status, stored in a frame, is pushed on the stack. When returning from a function call the machine has to restore to the previous machine status from before the function call. This means that the stack pointer, heap pointer, program counter, the number of local variables and other values need be restored so that execution of the resuming function can continue from where it left off. A stack frame holds this information.
Pushed to the end of the stack of the current calling function are the current register values for registers like EAX/ECX/EDX and others so that the called function can use them, arguments being passed to the called function and then the return address for the calling function so it can resume code execution when the called function returns. At the start of the called function the EBP set to start after the stack of the previous function (a/the red zone can existing between stack frames so they might not sit in memory back to back), the first value on the called function stack points to the EBP of the calling function, local stack variables are allocated and then any other stack allocations required by the function.
Below the example shows the main() function starts and it’s stack frame exists from 0xbfca8748 to 0xbfca8720, foo() is called by main() and it’s stack frame is from 0xbfca8718 to 0xbfca86f0 and so on for bar() which is called by foo(). In each stack frame for foo() and bar() we can see the arguments passed, local variables return address (Extended Instruction Pointer), caller’s EBP etc.
$ cat frame1.c #include <stdint.h> /* uintN_t */ #include <stdio.h> /* printf() */ void foo(uint32_t *parent_esp); void bar(uint32_t *parent_esp); void foo(uint32_t *parent_esp) { register uint32_t *ebp asm ("ebp"); register uint32_t *esp asm ("esp"); uint32_t *i; /* Allocated in the stack frame */ uint32_t *j; /* Allocated in the stack frame */ for (i = parent_esp; i>ebp; i--) { printf("%p: 0x%x\n", (void*)i, *i); } /* printf("foo() ret addr: %p\n", __builtin_return_address(0)); */ printf("%p == EBP in foo()\n", (void*)ebp); for (j = ebp; j>esp; j--) { printf("%p: 0x%x\n", (void*)j, *j); } printf("%p == ESP in foo()\n", (void*)esp); bar(esp); return; } void bar(uint32_t *parent_esp) { register uint32_t *ebp asm ("ebp"); register uint32_t *esp asm ("esp"); uint32_t *i; /* Allocated in the stack frame */ uint32_t *j; /* Allocated in the stack frame */ for (i = parent_esp; i>ebp; i--) { printf("%p: 0x%x\n", (void*)i, *i); } /* printf("bar() ret addr: %p\n", __builtin_return_address(0)); */ printf("%p == EBP in bar()\n", (void*)ebp); for (j = ebp; j>esp; j--) { printf("%p: 0x%x\n", (void*)j, *j); } printf("%p == ESP in bar()\n", (void*)esp); return; } int main() { uint32_t *j; /* Allocated in the stack frame */ register uint32_t *ebp asm ("ebp"); register uint32_t *esp asm ("esp"); /* printf("main() ret addr: %p\n", __builtin_return_address(0)); */ printf("%p == EBP in main()\n", (void*)ebp); for (j = ebp; j>esp; j--) { printf("%p: 0x%x\n", (void*)j, *j); } printf("%p == ESP in main()\n", (void*)esp); foo(esp); return 0; }
$ gcc -pedantic -o frame1 frame1.c $ ./frame1 0xbfca8748 == EBP in main() 0xbfca8748: 0x0 0xbfca8744: 0x0 0xbfca8740: 0x80485e0 0xbfca873c: 0xbfca873c // main() local variable j 0xbfca8738: 0x80485eb 0xbfca8734: 0xb7738000 0xbfca8730: 0xb76fa3c4 0xbfca872c: 0xb758342d 0xbfca8728: 0xb758342d 0xbfca8724: 0xbfca8728 0xbfca8720 == ESP in main() 0xbfca8720: 0xbfca8720 // foo() local variable parent_esp 0xbfca871c: 0x80485d8 // EIP return address for main() 0xbfca8718 == EBP in foo() 0xbfca8718: 0xbfca8748 // EBP of calling function main() 0xbfca8714: 0xb7738938 // foo() local variable ebp 0xbfca8710: 0xb7569a83 // foo() local variable esp 0xbfca870c: 0xbfca870c // foo() local variable j 0xbfca8708: 0xbfca8718 // foo() local variable i 0xbfca8704: 0x804871c 0xbfca8700: 0xb76faac0 0xbfca86fc: 0xb759d2af 0xbfca86f8: 0xb759d2af 0xbfca86f4: 0xbfca86f8 0xbfca86f0 == ESP in foo() 0xbfca86f0: 0xbfca86f0 // bar() local variable parent_esp 0xbfca86ec: 0x80484bf // EIP return address for foo () 0xbfca86e8 == EBP in bar() 0xbfca86e8: 0xbfca8718 // EBP of calling function foo() 0xbfca86e4: 0xb75566c8 // bar() local variable ebp 0xbfca86e0: 0xb7738938 // bar() local variable esp 0xbfca86dc: 0xbfca86dc // bar() local variable j 0xbfca86d8: 0xbfca86e8 // bar() local variable i 0xbfca86d4: 0x80486a2 0xbfca86d0: 0xb76faac0 0xbfca86cc: 0xb759d2af 0xbfca86c8: 0xb759d2af 0xbfca86c4: 0xbfca86c8 0xbfca86c0 == ESP in bar() $ gcc -g -c frame1.c $ objdump -d -M intel -S frame1.o .file "frame1.c" .section .rodata .LC0: .string "%p: 0x%x\n" .LC1: .string "%p == EBP in foo()\n" .LC2: .string "%p == ESP in foo()\n" .text .globl foo .type foo, @function foo: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $40, %esp movl 8(%ebp), %eax movl %eax, -16(%ebp) jmp .L2 .L3: movl -16(%ebp), %eax movl (%eax), %eax movl %eax, 8(%esp) movl -16(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf subl $4, -16(%ebp) .L2: movl %ebp, %eax cmpl %eax, -16(%ebp) ja .L3 movl %ebp, %eax movl %eax, 4(%esp) movl $.LC1, (%esp) call printf movl %ebp, -12(%ebp) jmp .L4 .L5: movl -12(%ebp), %eax movl (%eax), %eax movl %eax, 8(%esp) movl -12(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf subl $4, -12(%ebp) .L4: movl %esp, %eax cmpl %eax, -12(%ebp) ja .L5 movl %esp, %eax movl %eax, 4(%esp) movl $.LC2, (%esp) call printf movl %esp, %eax movl %eax, (%esp) call bar nop leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size foo, .-foo .section .rodata .LC3: .string "%p == EBP in bar()\n" .LC4: .string "%p == ESP in bar()\n" .text .globl bar .type bar, @function bar: .LFB1: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $40, %esp movl 8(%ebp), %eax movl %eax, -16(%ebp) jmp .L8 .L9: movl -16(%ebp), %eax movl (%eax), %eax movl %eax, 8(%esp) movl -16(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf subl $4, -16(%ebp) .L8: movl %ebp, %eax cmpl %eax, -16(%ebp) ja .L9 movl %ebp, %eax movl %eax, 4(%esp) movl $.LC3, (%esp) call printf movl %ebp, -12(%ebp) jmp .L10 .L11: movl -12(%ebp), %eax movl (%eax), %eax movl %eax, 8(%esp) movl -12(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf subl $4, -12(%ebp) .L10: movl %esp, %eax cmpl %eax, -12(%ebp) ja .L11 movl %esp, %eax movl %eax, 4(%esp) movl $.LC4, (%esp) call printf nop leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE1: .size bar, .-bar .section .rodata .LC5: .string "%p == EBP in main()\n" .LC6: .string "%p == ESP in main()\n" .text .globl main .type main, @function main: .LFB2: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp movl %ebp, %eax movl %eax, 4(%esp) movl $.LC5, (%esp) call printf movl %ebp, 28(%esp) jmp .L14 .L15: movl 28(%esp), %eax movl (%eax), %eax movl %eax, 8(%esp) movl 28(%esp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf subl $4, 28(%esp) .L14: movl %esp, %eax cmpl %eax, 28(%esp) ja .L15 movl %esp, %eax movl %eax, 4(%esp) movl $.LC6, (%esp) call printf movl %esp, %eax movl %eax, (%esp) call foo movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE2: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4" .section .note.GNU-stack,"",@progbits
Previous page: Sizeof Reference
Next page: Valgrind Notes