0

I am trying to understand the assembly output of a simple c++ program. This is my C++ program.

void func()
{}

int main()
{
    func();
}

when I use g++ with --save-temps option to get the assembly code for the above program I get the following assembly code.

    .file   "main.cpp"
    .text
    .globl  _Z4funcv
    .type   _Z4funcv, @function
_Z4funcv:
.LFB0:
    .cfi_startproc
     pushq  %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   _Z4funcv, .-_Z4funcv
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    call    _Z4funcv
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

According to my knowledge on assembly there should be 3 sections of any assembly program which are data, text and bss. Also text section should start with 'global _start'. I can't see any of them in this assembly code. Can someone please help me to understand the above assembly code. If you can relate to C++ code as well, It would be great.

Any kind of help is greatly appreciated.

3
  • 1
    I think that you should typically find those sections within the map file which corresponds with the object (binary) code, not in the assembly source code. Commented Dec 29, 2014 at 12:05
  • _start would most likely be in the CRT (C runtime) that you link against. It will in turn call your main function. Also, there's no need to define sections that you're not using.
    – Michael
    Commented Dec 29, 2014 at 12:10
  • 2
    In the second line you have the assembler directive .text. This is the text section. Because you have no global variables, you don't need the other sections.
    – rkhb
    Commented Dec 29, 2014 at 12:25

1 Answer 1

5

Well, here it is line by line...

 .file   "main.cpp"                # Debugging info (not essential)
    .text                          # Start of text section (i.e. your code)
    .globl  _Z4funcv               # Let the function _Z4funcv be callable
                                   # from outside (e.g. from your main routine)
    .type   _Z4funcv, @function    # Debugging info (possibly not essential)
_Z4funcv:                          # _Z4funcv is effectively the "name" of your 
                                   # function (C++ "mangles" the name; exactly 
                                   # how depends on your compiler -- Google "C++
                                   # name mangling" for more).
.LFB0:                             # Debugging info (possibly not essential)
    .cfi_startproc                 # Provides additional debug info (ditto)
     pushq  %rbp                   # Store base pointer of caller function
                                   # (standard function prologue -- Google 
                                   # "calling convention" or "cdecl")
    .cfi_def_cfa_offset 16         # Provides additional debug info (ditto)
    .cfi_offset 6, -16             # Provides additional debug info (ditto)
    movq    %rsp, %rbp             # Reset base pointer to a sensible place
                                   # for this function to put its local 
                                   # variables (if any).  Standard function
                                   # prologue.
    .cfi_def_cfa_register 6        # Debug ...
    popq    %rbp                   # Restore the caller's base pointer
                                   # Standard function epilogue
    .cfi_def_cfa 7, 8              # Debug...
    ret                            # Return from function
    .cfi_endproc                   # Debug...
.LFE0:                             # Debug...
    .size   _Z4funcv, .-_Z4funcv   # Debug...
    .globl  main                   # Declares that the main function
                                   # is callable from outside
    .type   main, @function        # Debug...
main:                              # Your main routine (name not mangled)
.LFB1:                             # Debug...
    .cfi_startproc                 # Debug...
    pushq   %rbp                   # Store caller's base pointer
                                   # (standard prologue)
    .cfi_def_cfa_offset 16         # Debug...
    .cfi_offset 6, -16             # Debug...
    movq    %rsp, %rbp             # Reset base pointer 
                                   # (standard prologue)
    .cfi_def_cfa_register 6        # Debug...
    call    _Z4funcv               # Call `func` (note name mangled)
    movl    $0, %eax               # Put `0` in eax (eax is return value)
    popq    %rbp                   # Restore caller's base pointer
                                   # (standard epilogue)
    .cfi_def_cfa 7, 8              # Debug...
    ret                            # Return from main function
    .cfi_endproc                   # Debug...
.LFE1:
    .size   main, .-main           # Debug...
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"  # fluff 
    .section    .note.GNU-stack,"",@progbits       # fluff

The linker knows to look for main (and not start) if it is using the standard C or C++ library (which it usually is, unless you tell it otherwise). It links some stub code (which contains start) into the final executable.

So, really, the only important bits are...

    .text                          
    .globl  _Z4funcv               
_Z4funcv:
    pushq  %rbp
    movq    %rsp, %rbp
    popq    %rbp
    ret
    .globl  main
main:
    pushq   %rbp
    movq    %rsp, %rbp
    call    _Z4funcv
    movl    $0, %eax
    popq    %rbp
    ret

If you want to start from scratch, and not have all the complicated standard library stuff getting in the way of your discovery, you can do something like this and achieve the same result as your C++ code:

   .text
    .globl  _func
_func:                   # Just as above, really
    push  %ebp
    mov    %esp, %ebp
    pop    %ebp
    ret
    .globl  _start
_start:                  # A few changes here
    push   %ebp
    mov    %esp, %ebp
    call    _func
    movl    $1, %eax     # Invoke the Linux 'exit' syscall
    movl    $0, %ebx     # With a return value of 0 (pick any char!)
    int $0x80            # Actual invocation

The exit syscall is a bit painful, but necessary. If you don't have it, it tries to keep going and run the code that is "past" your code. As that could be important code or data, the machine should stop you with a Segmentation Fault error. Having the exit call avoids all this. If you are using the standard library (as will happen automatically in your C++ example) the exit stuff is taken care of by the linker.

Compile with gcc -nostdlib -o test test.s (noting that gcc is specifically told not to use the standard library). I should say that this is for a 32-bit system, and quite likely will not work on 64-bit. I don't have a 64-bit system to test on, but perhaps some helpful StackOverflower will chip in with a 64-bit translation.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.