Trace Debian Build Flags with dpkg-buildflags

A package that builds fine on your machine but loses its hardening flags on the buildd is usually a dpkg-buildflags problem in disguise. This gets you inspecting the flags Debian's packaging tools hand you, passing them into a build correctly, and tracing exactly where a surprising value came from. The examples use dpkg-buildflags 1.22.6, installed with dpkg-dev on this machine.

Allow about fifteen minutes. You need a shell and dpkg-dev. Inspecting and building are ordinary user commands; editing the system configuration under /etc/dpkg/ needs elevated privileges but is not part of the main workflow.

1. Confirm the installed tool and its flag set

Check the executable rather than trusting that a guide written for another dpkg release still describes your machine:

$ dpkg-buildflags --version
Debian dpkg-buildflags version 1.22.6.

$ dpkg-buildflags --list
ASFLAGS
ASFLAGS_FOR_BUILD
CFLAGS
CFLAGS_FOR_BUILD
CPPFLAGS
CPPFLAGS_FOR_BUILD
CXXFLAGS
CXXFLAGS_FOR_BUILD
...
RUSTFLAGS
RUSTFLAGS_FOR_BUILD

The list is vendor-specific and can grow between releases, so do not hard-code a complete copy of it into a build script when the tool can just give it to you. The _FOR_BUILD variables are for tools that run on the build machine during a cross-build; the unqualified variables are for the host being built.

Checkpoint: if dpkg-buildflags is missing, install the distribution package that provides it, normally dpkg-dev. Do not substitute hand-written flags and assume they behave the same way.

2. Inspect the defaults before passing them on

The default action is --dump. It prints one flag per line, name and value separated by an equals sign:

$ dpkg-buildflags --dump
ASFLAGS=
CFLAGS=-g -O2 ...
CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=3
LDFLAGS=-Wl,-Bsymbolic-functions ...

The exact values depend on the vendor, architecture, compiler and build directory. On this installation the vendor defaults bring debugging information, optimisation, stack protection, format checks, position-independent-executable support, link-time optimisation and reproducible-build path mappings. Treat that as observed output, not a universal constant.

For a compact explanation of every feature and where each result came from, use:

$ dpkg-buildflags --status
dpkg-buildflags: status: vendor is Ubuntu
dpkg-buildflags: status: hardening features: ...
dpkg-buildflags: status: CFLAGS [vendor]: -g -O2 ...
dpkg-buildflags: status: LDFLAGS [vendor]: ...

--query gives similar diagnostics in structured form. Reach for --query-features hardening, or another known area such as qa, when you need the enabled state of individual features. None of these commands change a build or the system; they only read configuration.

3. Pass flags to a configure or make command

For a build system that accepts command-line variables, ask dpkg-buildflags to quote the complete argument list for you:

$ ./configure $(dpkg-buildflags --export=cmdline)
$ make $(dpkg-buildflags --export=cmdline)

--export=cmdline is the right choice for a configure script or a make invocation: it includes the supported compilation flags whose names start with an upper-case character and quotes values in shell syntax. Do not bolt an unquoted $(dpkg-buildflags --dump) onto a command; that output contains assignments, not command-line arguments.

For a shell build that wants environment variables instead, evaluate the shell export form:

$ eval "$(dpkg-buildflags --export=sh)" && make

Warning: that is safe only when the output comes straight from the installed dpkg-buildflags command. Never eval output that has been edited or combined with untrusted text. If you only need one variable, do not export everything:

$ cflags=$(dpkg-buildflags --get CFLAGS)
$ cc $cflags -c example.c -o example.o

Keep the value in a shell variable when its words need to become separate compiler arguments; a quoted "$cflags" would pass the entire value as one argument. Use the build system's documented flag-variable interface where you can.

4. Integrate flags in debian/rules

A Debian package should collect its own flags while it builds, rather than relying on a caller to have exported them. The supplied Makefile fragment loads the supported values into make variables:

include /usr/share/dpkg/buildflags.mk

build-arch:
	$(CC) -o example example.c $(CPPFLAGS) $(CFLAGS) $(LDFLAGS)

That include exports nothing on its own. If the package's build system reads environment variables instead, use this form:

DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/buildflags.mk

build-arch:
	$(MAKE) -C src

For tighter control, include the fragment and export only the variables the child build actually needs:

include /usr/share/dpkg/buildflags.mk
export CPPFLAGS CFLAGS LDFLAGS

Checkpoint: run the package's build target and read its log. If a build system ignores the variables, pass the relevant make or configure arguments explicitly, or fall back to dpkg-buildflags --get for fine-grained placement.

5. Apply a temporary override while testing

Environment variables are the least persistent override. This asks for an unoptimised C flag set for one command, and confirms its origin is env:

$ DEB_CFLAGS_SET='-O0 -Wall' dpkg-buildflags --get CFLAGS
-O0 -Wall
$ DEB_CFLAGS_SET='-O0 -Wall' dpkg-buildflags --origin CFLAGS
env

Do not put user override variables in debian/rules. Maintainers use the corresponding DEB_flag_MAINT_... variables there instead, so a package's declared build policy wins over a caller's temporary rebuild preference.

6. Change feature areas deliberately

Feature areas are controlled through DEB_BUILD_OPTIONS for a user and DEB_BUILD_MAINT_OPTIONS for a maintainer, using an area, an equals sign, and comma-separated additions or removals:

$ DEB_BUILD_MAINT_OPTIONS='hardening=-all,+format' \
    dpkg-buildflags --query-features hardening
Feature: format
Enabled: yes
...
Feature: fortify
Enabled: no
...

Feature settings are processed left to right, and the maintainer variable overrides the user variable. The all feature is handy for a deliberate baseline, but disabling hardening can reduce protection and can invalidate package expectations, so keep such changes local to a diagnostic build and record why you made them.

Tip: sanitiser options are for testing, not production packages, and link-time optimisation can affect reproducibility too. Capture the original state with dpkg-buildflags --status before changing a feature; removing the temporary environment assignment or closing the shell undoes an environment-only test.

7. Trace an unexpected value

When a flag is not what you expected, ask for its value and origin separately:

$ dpkg-buildflags --get CFLAGS
-g -O2 ...
$ dpkg-buildflags --origin CFLAGS
vendor

The reported origin is one of vendor, system, user or env. Dpkg-buildflags checks the environment first, then the user file at $XDG_CONFIG_HOME/dpkg/buildflags.conf, falling back to $HOME/.config/dpkg/buildflags.conf, and finally the system file /etc/dpkg/buildflags.conf. A configuration file can contain SET, STRIP, APPEND and PREPEND directives; blank lines and lines starting with # are ignored.

Warning: read those files before editing them. If you change a user file, remove the added directive to undo it. A system-wide edit affects every build on the host and needs the appropriate administrative approval, since there is no reset operation that can reconstruct a vendor policy after a careless overwrite. Prefer a temporary environment override while you are still investigating.

Done means