GCC –gc-sections to remove unused code and data

Sometimes it is required to reduce code size. Code size may be higher due to unused functions and data which are declared but not used. Sometimes it is not easier/possible to modify code and remove that.

There are GCC options which can help here:

  1. -ffunction-sections
  2. -fdata-sections
  3. –gc-sections
  4. –print-gc-sections

First two flags are passed at compilation stage. These flags tells compiler to create separate sections for each functions and data. Third flag is passed to linker which checks each section and remove sections which are not used.

-ffunction-sections

By default every functions are combined into single .text function but when we use “-ffunction-sections” flag, compiler creates different text section for each function (e.g. .text.func1, .text.func2, etc)

-fdata-sections

By default variables belongs .data are combined into single .data function but when we use “-fdata-sections” flag, compiler creates different .data section for each data (e.g. .data.var1, .data.var2, etc)

–gc-sections

This tells linker to remove unused sections.

–print-gc-sections

This flag is useful for debugging and identifying removed sections. This prints sections which are removed.

Example:

Consider below C example:

#include

int globalval = 5;

int main()
{
printf (“Hello World!\n”);
return 0;
}

int func(void)
{
printf(“I am function\n”);
return 0;
}

Case 1: Without GCC flags

When we compile program without above flags, compiler includes all functions and data even if those are not used.

~$ gcc -c test.c -o test.o
~$ gcc -o test.bin test.o

Size of program:

~$ size test.bin
   text   data    bss    dec    hex filename
   1253    556      4   1813    715 test.bin

obj-dump snippet

~$ objdump -x test.o
 - - - - - -
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 test.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data	0000000000000004 globalval
0000000000000000 g     F .text	0000000000000015 main
0000000000000000         *UND*	0000000000000000 puts
0000000000000015 g     F .text	0000000000000015 func
 - - - - - -

Case 2: With GCC flags

When we compile program with -ffunction-sections and -fdata-sections flags, compiler creates separate sections for each function and data. When we provide –gc-sections flag while linking, linker removes all unused sections.

~$ gcc -fdata-sections -ffunction-sections -c test.c -o test.o
~$ gcc -Wl,--gc-sections -Wl,--print-gc-sections -o test.bin test.o
/usr/bin/ld: Removing unused section '.rodata.cst4' in file '/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o'
/usr/bin/ld: Removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o'
/usr/bin/ld: Removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o'
/usr/bin/ld: Removing unused section '.data.globalval' in file 'section.o'
/usr/bin/ld: Removing unused section '.text.func' in file 'section.o'

Size of program:

~$ size test.bin
   text   data    bss    dec    hex filename
   1193    536      8   1737    6c9 test.bin

obj-dump snippet

~$ objdump -x test.o
 - - - - - -
SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 test.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .data.globalval	0000000000000000 .data.globalval
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .text.main	0000000000000000 .text.main
0000000000000000 l    d  .text.func	0000000000000000 .text.func
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     O .data.globalval	0000000000000004 globalval
0000000000000000 g     F .text.main	0000000000000015 main
0000000000000000         *UND*	0000000000000000 puts
0000000000000000 g     F .text.func	0000000000000015 func
 - - - - - -

Conclusion

As it can be seen from example, that in second case, different sections for each functions (.text.main and .text.func) and data (.data.globalval) are created. While linking, these are removed ,as they are not used.

The C++ compilation process

Copied from: http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

The C++ compilation process

C++ compilation process

Compiling a source code file in C++ is a four-step process. For example, if you have a C++ source code file named prog1.cpp and you execute the compile command

   g++ -Wall -ansi -o prog1 prog1.cpp

the compilation process looks like this:

  1. The C++ preprocessor copies the contents of the included header files into the source code file, generates macro code, and replaces symbolic constants defined using #define with their values.
  2. The expanded source code file produced by the C++ preprocessor is compiled into the assembly language for the platform.
  3. The assembler code generated by the compiler is assembled into the object code for the platform.
  4. The object code file generated by the assembler is linked together with the object code files for any library functions used to produce an executable file.

By using appropriate compiler options, we can stop this process at any stage.

  1. To stop the process after the preprocessor step, you can use the -E option:
       g++ -E prog1.cpp
    

    The expanded source code file will be printed on standard output (the screen by default); you can redirect the output to a file if you wish. Note that the expanded source code file is often incredibly large – a 20 line source code file can easily produce an expanded file of 20,000 lines or more, depending on which header files were included.

  2. To stop the process after the compile step, you can use the -S option:
       g++ -Wall -ansi -S prog1.cpp
    

    By default, the assembler code for a source file named filename.cpp will be placed in a file namedfilename.s.

  3. To stop the process after the assembly step, you can use the -c option:
       g++ -Wall -ansi -c prog1.cpp
    

    By default, the assembler code for a source file named filename.cpp will be placed in a file namedfilename.o.