Stripping unused library functions / dead code from a static executable - gcc

I'm compiling code for an ARM Cortex-M0 mcu with GCC arm-none-eabi-g++ (4.8.3).
All is fine, but I noticed that when I include and use any function from cstdlib, all functions from that file are included as well. How to get rid of them?
I'm calling malloc() and free() only, but the resulting ELF has system() and isatty() machine code as well.
The mcu has only 32kB flash, so ~0.7kB ballast matters, especially if this keeps happening for other headers.
Right now I use -ffunction-sections -fdata-sections for compiling and -Wl,--gc-sections -Wl,--static while linking, as follows:
arm-none-eabi-g++ -c --std=c++11 -Os -I. -Ilpc1xxx -Idrivers -Wall -mthumb \
-ffunction-sections -fdata-sections -fmessage-length=0 -mcpu=cortex-m0 \
-DTARGET=LPC11xx -fno-builtin -flto -fno-exceptions -o main.o main.cpp
arm-none-eabi-gcc -c --std=c11 -Os -I. -Ilpc1xxx -Idrivers -Wall -mthumb \
-ffunction-sections -fdata-sections -fmessage-length=0 -mcpu=cortex-m0 \
-DTARGET=LPC11xx -fno-builtin -flto -o core_cm0.o lpc1xxx/nxp/core_cm0.c
arm-none-eabi-gcc -nostartfiles -mcpu=cortex-m0 -mthumb -Wl,--gc-sections -flto \
-Os -Wl,--static -T lpc1xxx/memory.ld -o firmware.elf main.o core_cm0.o \
libaeabi-cortexm0/libaeabi-cortexm0.a LPC11xx_handlers.o LPC1xxx_startup.o
Edit: Warning: The -flto flag in my example is wrong – somehow it discards interrupt routines.
The result is that when I do arm-none-eabi-objdump -t firmware.elf, I get among others:
00000fbc g F .text 0000002c _isatty
00001798 g F .text 00000018 fclose
00000e4c g F .text 00000030 _kill
00000e7c g F .text 00000018 _exit
00000fe8 g F .text 00000050 _system
These functions are clearly redundant (and quite useless on mcu at all), yet GCC keeps them in the executable. There are no calls to them, these symbols are not referenced anywhere. It's effectively dead code.
How to get rid of them? Some extra compiler/linker flags?
Edit:
Minimal code to reproduce my problem:
#include <cstdlib>
int main(){
[[gnu::unused]] volatile void * x = malloc(1);
return 0;
}
Command used to compile that:
arm-none-eabi-g++ --std=c++11 -Os -Wall -mthumb -ffunction-sections
-fdata-sections -fmessage-length=0 -mcpu=cortex-m0 -fno-builtin -flto
-fno-exceptions -Wl,--static -Wl,--gc-sections -o main.elf main.cpp
And the main.elf file still has all stdlib bloat.

Using -ffunction-sections is the right thing here, but the issue is that the object file that provides malloc and free is built without it (either LPC11xx_handlers.o, LPC1xxx_startup.o or some of the object files within libaeabi-cortexm0.a). In that case, the linker can only include the whole object file (or with -Wl,--gc-sections, the whole section) that contain functions you need.
The layout of functions in object files and sections is the only thing that actually matters, not which function is defined in the same header as another function.
So to fix your issue, rebuild your standard library files with -ffunction-sections -fdata-sections.

Related

GCC: include math.h function in bare-metal software on ARM (arm-none-eabi-gcc)

I am working on a bare-metal free standing software on a STM32H753. I'm not using neither the libc nor the crt.
Here is the link command line:
arm-none-eabi-gcc -T"xxx.ld" -mfpu=fpv5-d16 -mfloat-abi=hard -mthumb -ffreestanding -nostdlib -nostartfiles --specs=nosys.specs -Wl,--start-group -lc -lm -Wl,--end-group -Wl,-Map=xxx.map -o xxx.elf <list of .o>
Now I need to include math library since I am using sqrt function. i thought the link command line would be sufficient but I get a "sqrt undefined" error.
I tried to add the path to the libm.a: (also tried without -Wl)
arm-none-eabi-gcc -T"xxx.ld" -Wl,-L/opt/gcc-arm-none-eabi-10-2020-q4-major/arm-none-eabi/lib/ -mfpu=fpv5-d16 -mfloat-abi=hard -mthumb -ffreestanding -nostdlib -nostartfiles --specs=nosys.specs -Wl,--start-group -lc -lm -Wl,--end-group -Wl,-Map=build_uP1/base_gen_uP1.map -o build_uP1/base_gen_uP1.elf <list of .o>
But I still get the same error.
I don't understand what options to choose to link with the correct library
Sorry for this self answer but I think I 've found the solution.
my first mistake is that the library must be put at the end of the command line. the order of arguments does matter.
then there are many versions of libm.a in the gcc install so I had to pick the right one
The following line is working:
arm-none-eabi-gcc -T"xxx.ld" -L/opt/gcc-arm-none-eabi-10-2020-q4-major/arm-none-eabi/lib/thumb/v7+fp/hard/ -mfpu=fpv5-d16 -mfloat-abi=hard -mthumb -ffreestanding -nostdlib -nostartfiles --specs=nosys.specs -Wl,-Map=xxx.map -o xxx.elf <list of .o> -lm -lc
I've noticed that one symbol and some data from libc.a are needed: __errno, and impure_data

LTO and overriding stdlib functions in static libraries

I have an embedded platform that brings its own stdlib functions like malloc and printf in a static library. I need to compile this library with LTO. Unfortunately in this combination (-flto + -nostdlib + linking with stdlib replacements from an .a) the linker cannot find the functions.
I have prepared a MWE that should run on most Unix machines but since it contains multiple files I have put it into a repo:
https://github.com/stefanct/lto_static_libs
The included makefile allows to switch on some features on and off for testing:
nostdlib=y: add -nostdlib to the linking stage
nolto=y: disable LTO
libfunc=y: enable a call to a non-standard function within the library (you will see why at the end!)
The gist is to have one module containing a standard function, e.g.:
int puts(const char *s) {
return 2;
}
Compiling that into an object file with -flto, putting it into a static library with gcc-ar and eventually using that when linking with an application.
In my setup (GCC 11 branch built from source and GNU ld 2.31.1 from Debian Buster) I get the following results:
No options set: OK - the printf from the library gets overridden(?) by the standard function:
$ make -B
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type Visibility Size Name
function default 4 lib_func
function default 4 puts
function default 4 printf
gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -o exe main.o -L. -ltest
$ ./exe
hurga
No stdlib but also without LTO: OK - linking works fine(ish - running segfaults but that's to be expected I guess and could be worked around with -nodefaultlibs but I don't care here)
$ make -B nostdlib=y nolto=y
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -c -o libtest.o libtest.c
gcc-nm libtest.o
0000000000000074 T lib_func
0000000000000000 T printf
0000000000000065 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
No stdlib but leaving LTO enabled: suddenly puts is no longer found.
However, as you can see, the object file that gets put into the library contains the function just fine (and evengcc-mn libtest.a shows the same).
This is the case I would like to fix. Why is this breaking?
$ make -B nostdlib=y
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type Visibility Size Name
function default 4 lib_func
function default 4 puts
function default 4 printf
gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
/usr/bin/ld: /tmp/cczvyrrg.ltrans0.ltrans.o: in function `main':
<artificial>:(.text+0xe): undefined reference to `puts'
collect2: error: ld returned 1 exit status
make: *** [makefile:39: exe] Error 1
Interestingly enough, if we call another unrelated (non-standard) function in the same library things start to work again!?
$ make -B nostdlib=y libfunc=y
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -D LIB_FUNC -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type Visibility Size Name
function default 4 lib_func
function default 4 puts
function default 4 printf
gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -D LIB_FUNC -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects -D LIB_FUNC -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
Do I see a bug in binutils/ld? Is this fixed upstream?

CMake w/ARM tools _start symbol undefined

I build an executable file thru CMake. The executable is linked to various static libraries, also built thru CMake in the same project. However, linking is not happening properly.
I run arm-none-eabi objdump -tT executable and see that symbols _start and many of the other functions that I define are undefined.
This is the main CMakeLists.txt file:
set(CMAKE_BUILD_TYPE DEBUG)
# Set C/C++ compile and linking flags
set(GCC_COVERAGE_COMPILE_FLAGS "-march=armv7e-m -mthumb \
-mfloat-abi=softfp -mfpu=fpv4-sp-d16 -Og -fmessage-length=0 \
-fsigned-char -ffunction-sections -fdata-sections -ffreestanding \
-fno-move-loop-invariants -Wall -Wextra -g3")
set(GCC_COVERAGE_LINK_FLAGS
"-T \"${PROJECT_SOURCE_DIR}/ldscripts/mem.ld\" \
-T \"${PROJECT_SOURCE_DIR}/ldscripts/libs.ld\" \
-T \"${PROJECT_SOURCE_DIR}/ldscripts/sections.ld\" \
-nostartfiles -Xlinker --gc-sections -Wl,-Map,\"elka.map\" \
--specs=nano.specs")
#--specs=nano.specs -o \"elka.elf\"")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER__FLAGS} ${GCC_COVERAGE_LINK_FLAGS}")
set(ELKA_LINK_LIBS "")
add_definitions(-DUSE_STDPERIPH_DRIVER)
# Add libraries, subdirectories, etc to include all source and header files
include_directories(${PROJECT_SOURCE_DIR}/inc
${PROJECT_SOURCE_DIR}/inc/CMSIS
${PROJECT_SOURCE_DIR}/inc/Device/STM32F4xx
${PROJECT_SOURCE_DIR}/inc/drivers
${PROJECT_SOURCE_DIR}/inc/elka_hal
${PROJECT_SOURCE_DIR}/inc/FreeRTOS
${PROJECT_SOURCE_DIR}/inc/FreeRTOS/GCC/ARM_CM4F
${PROJECT_SOURCE_DIR}/inc/modules
${PROJECT_SOURCE_DIR}/inc/nvicconf
${PROJECT_SOURCE_DIR}/inc/STM32F4xx_StdPeriph_Driver
${PROJECT_SOURCE_DIR}/inc/utils
)
add_subdirectory(src)
#FIXME make sure that this will create elka executable file
#TODO link against libraries
add_executable(elka
src/main.c
src/_write.c
)
add_dependencies(elka
${ELKA_LINK_LIBS}
)
There are other CMakeLists.txt files in subdirectories. They serve to build the static libraries and add them to the ELKA_LINK_LIBS variable. The main CMakeLists.txt file generates the following command in CMake. to perform linking, followed by the warning:
/usr/bin/arm-none-eabi-gcc -march=armv7e-m -mthumb -mfloat-abi=softfp -mfpu=fpv4-sp-d16 -Og -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections -ffreestanding -fno-move-loop-invariants -Wall -Wextra -g3 -g -T /Programs/elka/elka_firmware/elka_firmware/ldscripts/mem.ld -T /Programs/elka/elka_firmware/elka_firmware/ldscripts/libs.ld -T /Programs/elka/elka_firmware/elka_firmware/ldscripts/sections.ld -nostartfiles -Xlinker --gc-sections -Wl,-Map,elka.map --specs=nano.specs CMakeFiles/elka.dir/src/main.c.obj CMakeFiles/elka.dir/src/_write.c.obj -o elka src/drivers/libelka_drivers.a src/elka_hal/libelka_hal.a src/FreeRTOS/libFreeRTOS.a src/modules/libelka_modules.a src/STM32F4xx_StdPeriph_Driver/libSTM32F4xx_StdPeriph_Driver.a src/utils/libelka_utils.a
/usr/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000008000028
Why is _start undefined, first of all? If that is a trivial answer, then why are the other symbols in this executable undefined? I can post the objdump or other files if pertinent.
In a final note, I have found that the symbols for functions that I have defined in other libraries are undefined in those other static libraries. Below is a sample CMakeLists.txt for one library:
# src/elka_hal
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/inc/elka_hal
)
add_library(elka_hal STATIC
imu.c
)
set(ELKA_LINK_LIBS
${ELKA_LINK_LIBS} elka_hal
PARENT_SCOPE
)
Edit: It seems to link symbols correctly if I take away the -nostartfiles flag from GCC_COVERAGE_LINK_FLAGS and change --specs=nano.specs to --specs=nosys.specs

About -ffunction-sections -fdata-sections and --gc-sections options

In my ARM project, I use following to build os-less application binary:
arm-linux-gcc -Os -ffunction-sections -fdata-sections -o boot.o boot.S
arm-linux-gcc -Os -ffunction-sections -fdata-sections -o main.o main.c
arm-linux-ld -T link.lds --gc-sections -o target.bin boot.o main.o
These works fine. Because If I remove "-ffunction-sections", "-fdata-sections" and "--gc-sections" options, the target.bin file size will increase nearly twice..
But on the x86 platform, same method, I found that:
If I don't use those gcc and ld options, the output is normal, but the output file will be 0 byte if I use those options as arm platform.
-Os -ffunction-sections -fdata-sections and --gc-sections should work on x86 system. Are you sure your program and your linker script are suitable for x86 ? As your program is meant for bare-metal ARM, it probably does not have entry points for your x86 OS, and if there is no entry point, everything is garbaged by --gc-sections option.
BTW, your "question" actually enclose no question.

enforing check about returning a value in gcc

I am compiling some C/C++ files using gcc.
I noticed today a bug that caused my app to crash. It was caused by the fact that my function didn't return any value (see below). Do you know if there is some flag in gcc enforcing these kind of checking or why the compiler is not warning me about this?
I am compiling C files into object files with a basic -g -D_GNU_SOURCE -o outObjectFile -c myFile.c option.
//.c file
int
myFunc(){
...do something
..without return statement
}
//.h file
extern int myFun();
When using GCC, always compile with:
-std=c99 -pedantic -Wall -Wextra -Wwrite-strings for C
-ansi -pedantic -Wall -Wextra -Weffc++ for C++

Resources