Re: pahole treats embedded structures a holes

Arnaldo Carvalho de Melo <[email protected]> Fri, 5 Jun 2026 16:28:18 -0300
Newsgroups org.kernel.vger.dwarves
Message-ID <aiMjUoE_CPn7hJtv@x1>
FYI and documentation. I'll work on looking at the DW_AT_producer tag
for those compiler options to show in the pretty printing output, as
well as checking potential implications for BTF generation, but I think
that one will end up being harmless because of the combination of all
DWARF CUs the types suppressed by CU would eventually be resolved.

- Arnaldo

----- Forwarded message from Arnaldo Carvalho de Melo <[email protected]> -----

Date: Mon, 1 Jun 2026 12:18:58 -0300
From: Arnaldo Carvalho de Melo <[email protected]>
To: Christoph Hellwig <[email protected]>
Subject: Re: pahole treats embedded structures a holes
Message-ID: <ah2i4lHbSgilDxQd@x1>

On Sat, May 30, 2026 at 05:22:20AM +0200, Christoph Hellwig wrote:
> Here is a compressed xfs_buf.o.  I don't think it's the same as before,
> but it shows the same symptoms.

TIL: -femit-struct-debug-baseonly

TLDR; you enabled CONFIG_DEBUG_INFO_REDUCED while distros use:

acme@x1:~/git/linux$ grep CONFIG_DEBUG_INFO_ /lib/modules/`uname -r`/config
# CONFIG_DEBUG_INFO_NONE is not set
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
# CONFIG_DEBUG_INFO_DWARF4 is not set
# CONFIG_DEBUG_INFO_DWARF5 is not set
# CONFIG_DEBUG_INFO_REDUCED is not set
CONFIG_DEBUG_INFO_COMPRESSED_NONE=y
# CONFIG_DEBUG_INFO_COMPRESSED_ZLIB is not set
# CONFIG_DEBUG_INFO_SPLIT is not set
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y
acme@x1:~/git/linux$

I'm adding a TODO entry to notice that and add a comment at that point
stating that the type for a embedded struct wasn't found and that the
user probably is using -femit-struct-debug-baseonly and maybe
CONFIG_DEBUG_INFO_REDUCED.

Please consider replying with this message to the mailing list to leave
this documented in the archives :-)

- Arnaldo

Longer analysis:

acme@x1:~$ pahole -C xfs_buf xfs_buf.o  | head
struct xfs_buf {
	struct rhash_head          b_rhash_head;         /*     0     0 */

	/* XXX 8 bytes hole, try to pack */

	xfs_daddr_t                b_rhash_key;          /*     8     8 */
	int                        b_length;             /*    16     4 */

	/* XXX 4 bytes hole, try to pack */

acme@x1:~$ pahole -C b_rhash_head xfs_buf.o 
pahole: type 'b_rhash_head' not found
acme@x1:~$ pahole -C xfs_buf xfs_buf.o  | head
struct xfs_buf {
	struct rhash_head          b_rhash_head;         /*     0     0 */

	/* XXX 8 bytes hole, try to pack */

	xfs_daddr_t                b_rhash_key;          /*     8     8 */
	int                        b_length;             /*    16     4 */

	/* XXX 4 bytes hole, try to pack */

acme@x1:~$ pahole -C rhash_head xfs_buf.o 
pahole: type 'rhash_head' not found
acme@x1:~$

acme@x1:~$ readelf -wi xfs_buf.o | grep DW_AT_producer
    <d>   DW_AT_producer    : (indirect string, offset: 0x28e6): GNU C11 15.2.0 -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -mno-sse4a -m64 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -march=x86-64 -mtune=generic -mno-red-zone -mcmodel=kernel -mstack-protector-guard-reg=gs -mstack-protector-guard-symbol=__ref_stack_chk_guard -mindirect-branch=thunk-extern -mindirect-branch-register -mindirect-branch-cs-prefix -mfunction-return=thunk-extern -g -O2 -std=gnu11 -fshort-wchar -funsigned-char -fno-common -fno-PIE -fno-strict-aliasing -fms-extensions -fcf-protection=branch -falign-jumps=1 -falign-loops=1 -fno-asynchronous-unwind-tables -fno-jump-tables -fpatchable-function-entry=16,16 -fno-delete-null-pointer-checks -fno-allow-store-data-races -fstack-protector-strong -fomit-frame-pointer -fzero-init-padding-bits=all -fno-stack-clash-protection -fmin-function-alignment=16 -fstrict-flex-arrays=3 -fno-strict-overflow -fstack-check=no -fconserve-stack -fno-builtin-wcslen -fno-var-tracking -femit-struct-debug-baseonly
acme@x1:~$

acme@x1:~/git/linux$ git grep -B4 -A4 -- -femit-struct-debug-baseonly
rust/Makefile-  -mno-pointers-to-nested-functions -mno-string \
rust/Makefile-  -mno-strict-align -mstrict-align -mdirect-extern-access \
rust/Makefile-  -mexplicit-relocs -mno-check-zero-division \
rust/Makefile-  -fconserve-stack -falign-jumps=% -falign-loops=% \
rust/Makefile:  -femit-struct-debug-baseonly -fno-ipa-cp-clone -fno-ipa-sra \
rust/Makefile-  -fno-partial-inlining -fplugin-arg-arm_ssp_per_task_plugin-% \
rust/Makefile-  -fno-reorder-blocks -fno-allow-store-data-races -fasan-shadow-offset=% \
rust/Makefile-  -fzero-call-used-regs=% -fno-stack-clash-protection \
rust/Makefile-  -fno-inline-functions-called-once -fsanitize=bounds-strict \
--
scripts/Makefile.debug-ifdef CONFIG_DEBUG_INFO_REDUCED
scripts/Makefile.debug-DEBUG_CFLAGS     += -fno-var-tracking
scripts/Makefile.debug-DEBUG_RUSTFLAGS  += -Cdebuginfo=1
scripts/Makefile.debug-ifdef CONFIG_CC_IS_GCC
scripts/Makefile.debug:DEBUG_CFLAGS     += -femit-struct-debug-baseonly
scripts/Makefile.debug-endif
scripts/Makefile.debug-else
scripts/Makefile.debug-DEBUG_RUSTFLAGS  += -Cdebuginfo=2
scripts/Makefile.debug-endif
acme@x1:~/git/linux$

----- 8< -----
-femit-struct-debug-baseonly is a GCC (GNU Compiler Collection)
command-line flag used to drastically reduce the size of object files by
limiting the amount of debug information generated for structures,
unions, and classes.

It works by strictly filtering which type definitions are included in
the final DWARF debug symbols based on where those types are
declared.

How it Works

When you compile a source file (e.g., main.c), the compiler normally
emits debug information for all types used or declared within that file,
including those from deeply nested external header files.

With -femit-struct-debug-baseonly enabled, GCC uses aggressive filtering
rules:

Matches Base File Name: Debug information is only generated for
structs/unions/classes if the base name of the file where the type is
declared matches the base name of the file being compiled.

Practical Example:

If you are compiling foo.c, debug symbols will only be generated for
types declared directly inside foo.c and its direct companion header
foo.h. Types declared in other included headers (like standard_lib.h)
are completely ignored.

Key Benefits

Smaller Binaries:

It severely reduces the storage footprint of debug ELF and object files.

Faster Link Times:

Less debug information means linkers have less data to process and
deduplicate.

Faster Debugger Loading:

Debuggers like GDB or IDE symbol loaders can load the project
significantly faster.

Caveats & Constraints

Limited Debugging Visibility:

Because it aggressively strips type data, your debugger might not
recognize or be able to pretty-print structures declared in external
files or libraries during a live debugging session.

Format Restriction: This flag exclusively functions with DWARF debug
output format.

Alternative Flags: It is a stricter, more aggressive variant of
-femit-struct-debug-reduced.

If you require fine-grained control, you can instead use
-femit-struct-debug-detailed.

----- 8< -----

----- End forwarded message -----