In my previous blog, I demonstrated how to compile fio
with the -fno-omit-frame-pointer
flag in order to enable stack traces in perf record
. The approach involved modifying the Makefile to add the compiler flag directly. While effective, this method is neither safe nor elegant: users must fully understand the complex Makefile to avoid introducing unintended side effects.
Furthermore, in practice, we often want to automate the build process within a script. Injecting flags programmatically using tools like sed
can be brittle and error-prone—nobody wants to wrestle with obscure sed
syntax or risk breaking a Makefile. So, is there a better way?
Yes, there is. In this post, I’ll walk you through a more robust and script-friendly method to inject the -fno-omit-frame-pointer
flag when building fio
, continuing from the example in my previous blog.
Method 1: Using Make Command-Line Variable Overrides
Our first attempt involves overriding variables via Make’s command-line interface. This method is documented in GNU Make's official manual. For our case, we can try appending the new compiler flag as follows:
make CFLAGS+='-fno-omit-frame-pointer'
To verify that the flag is correctly injected, we can perform a dry run and inspect Make’s variable database:
make CFLAGS+='-fno-omit-frame-pointer' -pn | grep CFLAGS
Relevant output:
-*-command-variables-*- := CFLAGS=-fno-omit-frame-pointer
CFLAGS := -DFIO_VERSION='"fio-3.40-34-g306d8"' -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement -g -ffast-math -D_GNU_SOURCE -include config-host.h -Wimplicit-fallthrough -I. -I. -O3 -march=native -fno-omit-frame-pointer
Although we used +=
, what actually happens here is a :=
override. This means our command-line value replaces the Makefile-defined value unless the latter was marked with override
. As a result, flags defined in the Makefile might be inadvertently ignored, which can cause subtle bugs or missing optimizations.
Let’s compare it with the default behavior by running:
make clean
make distclean
./configure
make -pn | grep CFLAGS
Relevant output:
CFLAGS := -DFIO_VERSION='"fio-3.40-34-g306d8"' -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement -g -ffast-math -D_GNU_SOURCE -include config-host.h -Wimplicit-fallthrough -I. -I. -O3 -march=native -D_GNU_SOURCE -include config-host.h -Wimplicit-fallthrough
We can see that the command-line override caused some flags—such as -D_GNU_SOURCE
, -include config-host.h
, and -Wimplicit-fallthrough
—to be omitted. Clearly, overriding CFLAGS
via the Make command line is not safe in this case.
Method 2: Injecting Flags via configure
Script
Fortunately, fio
uses a configure
script to generate build options. This gives us a safer and more robust way to inject custom flags.
First, let’s check what options the configure
script provides:
./configure --help
Relevant output:
--extra-cflags= Specify extra CFLAGS to pass to compiler
Perfect—we’re given a dedicated option for injecting custom CFLAGS. Let’s use it:
make clean
make distclean
./configure --extra-cflags='-fno-omit-frame-pointer'
make -pn | grep CFLAGS
Relevant output:
CFLAGS := -DFIO_VERSION='"fio-3.40-34-g306d8"' -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement -g -ffast-math -D_GNU_SOURCE -include config-host.h -fno-omit-frame-pointer -Wimplicit-fallthrough -I. -I. -O3 -march=native -D_GNU_SOURCE -include config-host.h -fno-omit-frame-pointer -Wimplicit-fallthrough
This time, the flag is correctly injected without removing or overriding existing ones. Keep in mind the rule: last flag wins. In this case, since -fno-omit-frame-pointer
appears after -O3
, it will successfully override the -fomit-frame-pointer
implied by the optimization level.
How Does It Work?
Let’s take a look under the hood. The Makefile
includes the following logic:
config-host.mak: configure
@if [ ! -e "$@" ]; then \
echo "Running configure ..."; \
./configure; \
else \
echo "$@ is out-of-date, running configure"; \
sed -n "/.*Configured with/s/[^:]*: //p" "$@" | sh; \
fi
ifneq ($(MAKECMDGOALS),clean)
include config-host.mak
endif
As you can see, make
includes config-host.mak
, which is generated by the configure
script. This file defines the build-time variables.
Let’s inspect it:
tail config-host.mak
Sample output:
CONFIG_HAVE_TIMERFD_CREATE=y
CONFIG_HAVE_NO_STRINGOP=y
CONFIG_HAVE_THP=y
LIBS+=-l:libtcmalloc_minimal.so.4
GFIO_LIBS+=
CFLAGS+=-D_GNU_SOURCE -include config-host.h -fno-omit-frame-pointer -Wimplicit-fallthrough
LDFLAGS+=
CC=gcc
BUILD_CFLAGS= -D_GNU_SOURCE -include config-host.h -fno-omit-frame-pointer -Wimplicit-fallthrough
INSTALL_PREFIX=/usr/local
As expected, the -fno-omit-frame-pointer
flag is appended to both CFLAGS
and BUILD_CFLAGS
. This explains how the configure
script safely injects new flags into the build system.
Conclusion
Modifying the Makefile directly to inject compiler flags like -fno-omit-frame-pointer
is risky and not ideal for automation. While using Make’s command-line variable overrides might seem cleaner, it can unintentionally override or exclude essential flags defined in the Makefile.
The safer and more maintainable approach—at least for fio
—is to inject flags via the configure
script using options like --extra-cflags
. This method integrates well with the existing build system and ensures flags are applied without disrupting other settings.
That said, not all configure
scripts are created equal. Although uncommon, a poorly implemented script might ignore or misplace injected flags. To be sure everything worked as expected, always verify the final compiler flags by running:
make -pn | grep CFLAGS
This quick check can save you from subtle bugs or performance issues down the line.
Comments NOTHING