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

 

Stack & Heap Overview:

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.

 

Stack:

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 

 

Heap:

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

 

Stack Frame:

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