Looking at the generated assembler

In the previous section, we looked at how the gcc driver program invokes various tools to get its job done. For me, the most interesting part of that process is the conversion from C source code to machine code (expressed in the form of assembler) done by the compiler, cc1.

We can see this part of the process more directly by invoking gcc with the -S option (upper-case “S”):

$ gcc -S hello.c

telling it to merely create an assembler file (with a lower-case .s extension), without doing the later stages we talked about above:

         cc1
hello.c -----> hello.s
gcc -S hello.c

and in addition to the hello executable we built earlier, we now have a hello.s file:

$ ls
hello  hello.c  hello.s

Looking in the hello.s file we see the generated assembler:

      .file   "hello.c"
      .text
      .section        .rodata
.LC0:
      .string "hello world"
      .text
      .globl  main
      .type   main, @function
main:
.LFB0:
      .cfi_startproc
      pushq   %rbp
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp
      .cfi_def_cfa_register 6
      movl    $.LC0, %edi
      call    puts
      movl    $0, %eax
      popq    %rbp
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
.LFE0:
      .size   main, .-main
      .ident  "GCC: (GNU) 10.3.1 20210422 (Red Hat 10.3.1-1)"
      .section        .note.GNU-stack,"",@progbits

It’s not important yet to be able to fully understand the assembler, but for now just know that we have some metadata at the top, followed by the user’s main() function, and then some trailing metadata.

You can even see how the assembler relates to the C code by passing -fverbose-asm to gcc:

$ gcc -S hello.c -fverbose-asm

which leads to this in the output hello.s file:

      .file   "hello.c"
# GNU C17 (GCC) version 10.3.1 20210422 (Red Hat 10.3.1-1) (x86_64-redhat-linux)
#     compiled by GNU C version 10.3.1 20210422 (Red Hat 10.3.1-1), GMP version 6.2.0, MPFR version 4.1.0-p9, MPC version 1.1.0, isl version none
# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# options passed:  hello.c -mtune=generic -march=x86-64 -fverbose-asm
# options enabled:  -faggressive-loop-optimizations -fallocation-dce
# -fasynchronous-unwind-tables -fauto-inc-dec -fdelete-null-pointer-checks
# -fdwarf2-cfi-asm -fearly-inlining -feliminate-unused-debug-symbols
# -feliminate-unused-debug-types -ffp-int-builtin-inexact -ffunction-cse
# -fgcse-lm -fgnu-unique -fident -finline-atomics -fipa-stack-alignment
# -fira-hoist-pressure -fira-share-save-slots -fira-share-spill-slots
# -fivopts -fkeep-static-consts -fleading-underscore -flifetime-dse
# -fmath-errno -fmerge-debug-strings -fpeephole -fplt
# -fprefetch-loop-arrays -freg-struct-return
# -fsched-critical-path-heuristic -fsched-dep-count-heuristic
# -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic
# -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic
# -fsched-stalled-insns-dep -fschedule-fusion -fsemantic-interposition
# -fshow-column -fshrink-wrap-separate -fsigned-zeros
# -fsplit-ivs-in-unroller -fssa-backprop -fstdarg-opt
# -fstrict-volatile-bitfields -fsync-libcalls -ftrapping-math -ftree-cselim
# -ftree-forwprop -ftree-loop-if-convert -ftree-loop-im -ftree-loop-ivcanon
# -ftree-loop-optimize -ftree-parallelize-loops= -ftree-phiprop
# -ftree-reassoc -ftree-scev-cprop -funit-at-a-time -funwind-tables
# -fverbose-asm -fzero-initialized-in-bss -m128bit-long-double -m64 -m80387
# -malign-stringops -mavx256-split-unaligned-load
# -mavx256-split-unaligned-store -mfancy-math-387 -mfp-ret-in-387 -mfxsr
# -mglibc -mieee-fp -mlong-double-80 -mmmx -mno-sse4 -mpush-args -mred-zone
# -msse -msse2 -mstv -mtls-direct-seg-refs -mvzeroupper

      .text
      .section        .rodata
.LC0:
      .string "hello world"
      .text
      .globl  main
      .type   main, @function
main:
.LFB0:
      .cfi_startproc
      pushq   %rbp    #
      .cfi_def_cfa_offset 16
      .cfi_offset 6, -16
      movq    %rsp, %rbp      #,
      .cfi_def_cfa_register 6
# hello.c:4:   printf ("hello world\n");
      movl    $.LC0, %edi     #,
      call    puts    #
# hello.c:5:   return 0;
      movl    $0, %eax        #, _3
# hello.c:6: }
      popq    %rbp    #
      .cfi_def_cfa 7, 8
      ret
      .cfi_endproc
.LFE0:
      .size   main, .-main
      .ident  "GCC: (GNU) 10.3.1 20210422 (Red Hat 10.3.1-1)"
      .section        .note.GNU-stack,"",@progbits

In particular, looking at the heart of the main() function we now have comments showing us how the some of the assembler relates to specific lines in the C source file:

# hello.c:4:   printf ("hello world\n");
      movl    $.LC0, %edi     #,
      call    puts    #
# hello.c:5:   return 0;
      movl    $0, %eax        #, _3
# hello.c:6: }
      popq    %rbp    #
      .cfi_def_cfa 7, 8
      ret

We’ll look at how cc1 actually turns the C into assembler in the next section.