* [PATCHES 00/31] pahole: Bug fixes and small improvements
@ 2026-07-29 19:07 Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 01/31] cmake: Update minimum required version from 3.5 to 3.10 Arnaldo Carvalho de Melo
` (30 more replies)
0 siblings, 31 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
Hi,
Here are 31 patches fixing pre-existing bugs found via coverage
analysis, AI-assisted review (sashiko), and manual testing. No new
features.
Critical kernel build path fix:
Multi-dimensional arrays (e.g. int[2][3]) were incorrectly collapsed
into flat arrays (int[6]) in BTF encoding. The encoder wrote only
the total element count; the loader read only the first dimension.
Both now preserve the full dimension chain using chained
BTF_KIND_ARRAY nodes, matching bpftool and kernel verifier format.
Before: pahole -F btf showed int a[3][4] as int a[12]
After: int a[2][3][4] encodes as three chained BTF_KIND_ARRAY nodes
and round-trips correctly
Bug fixes found via coverage analysis:
- Fix interior pointer free and missing NULL check in btf_encoder
- Fix missing list head initialization in type__clone_members
- Fix instance memory leak on 14 early returns in
prototype__stdio_fprintf_value
- Fix error path resource leaks in ctf_loader and libctf
- Fix early cleanup crashes in btf_encoder__new/delete
- Fix dangling stack pointer: allocate type_dcu via dwarf_cu__new
- Fix annotation failure leaks in variable and typedef creation
- Fix --errno typo that decrements instead of negating
- Fix heap buffer overflow in languages__parse realloc
- Fix parse_btf_features("all") being a silent no-op
- Fix --fixup_silly_bitfields condition check
- Fix data race in tag__init() decl_file string cache
- Fix variable shadowing in __cus__find_struct_by_name
- Fix phantom holes when class__find_holes ran before byte sizes
were cached (eliminates 36 false "BRAIN FART ALERT" on Firefox)
- Add elf_strptr NULL checks and fix kfunc bounds in btf_encoder
- Use btf_encoder__tag_type() for all type ID computations
Other improvements:
- Fix -Wsign-compare warnings across the codebase
- cmake: Update minimum required version from 3.5 to 3.10
- Skip inline expansions during BTF encoding (performance)
- Use fseek for seekable files in --prettify and --seek_bytes
- Guard pipe_seek() against negative offsets
- Fall back to GNU objcopy when llvm-objcopy is not available
- Add exec_objcopy() shell-injection-safe helper
- Skip libdw__lock when elfutils >= 0.194 is built thread-safe
- Replace stale FIXME/XXX comments with explanations
- Remove 11 dead functions found via coverage analysis
- Mark file-local functions as static
There are several other series that will result in way more coverage
and regression tests and will add support for more rust DWARF tags,
DWARF partial units that will allow us to build the kernel with most
of the CONFIG_DEBUG_ DWARF options (DWARF5, etc), only split DWARF
(skeletons) is left for after these series are applied.
The end result for tests/tests is this, that runs in parallel and mimics
the 'perf test' output:
⬢ [acme@toolbx pahole]$ ./build-and-test-cmd.sh
<SNIP>
Testing pahole version:
v1.31-215-gcd4504f549af6fe7
Verbose mode enabled - showing diagnostic information:
Architecture: x86_64
CPUs: 32
Memory: 60.4 GB
Swap: 8.0 GB
Parallelism: unlimited (all tests run in parallel)
Tip: On low-memory systems, use -j to limit parallelism (e.g., -j 4 or -j 1)
Test artifacts: /tmp/pahole-tests/
Compiler: gcc (GCC) 16.1.1 20260515 (Red Hat 16.1.1-2)
libc: ldd (GNU libc) 2.43
bpftool: bpftool v7.6.0
VMLINUX: not set
PERF_BIN: not set
PERF_SRC_DIR: not set
1: dwarves_emit.c: _Atomic type compile emission. : Ok
2: _Atomic type display and --skip_emitting_atomic_typedefs. : Ok
3: Auxiliary tools: pglobal, prefcnt, dtagnames. : Ok
4: Bitfield layout and data member filtering. : Ok
5: dwarf_loader coverage: bitfields, templates, enums, inlines. : Ok
6: Bitfield typedef recoding (tag__recode_dwarf_bitfield). : Ok
7: DW_FORM_block byte order conversion. : Skip
8: BTF arena type tag encoding for kfuncs. : Ok
9: BTF multi-dimensional array encoding and round-trip. : Ok
10: BTF bitfield encoding and loading round-trip. : Ok
11: BTF VAR and DATASEC encoding for global variables. : Ok
12: BTF deduplication of array types across CUs. : Ok
13: Distilled base BTF generation. : Skip
14: BTF encoding options. : Ok
15: BTF encoder coverage. : Ok
16: BTF encoder comprehensive feature coverage. : Ok
17: BTF_KIND_FWD encoding and loading round-trip. : Ok
18: BTF encoding with invalid symbol names. : Ok
21: BTF FLOAT and ENUM64 type encoding. : Ok
22: Check BTF type tag order. : Ok
23: BTF encoder verbose logging coverage. : Ok
24: BTF encoding into ELF via pahole -J (btf_encoder__write_elf). : Ok
25: BTF true_signature: clang optimized aggregate parameters (2 structs: Ok
26: BTF true_signature: clang optimized aggregate parameters (2 structs: Ok
27: BTF true_signature: clang optimized with large union parameter : Ok
28: BTF true_signature: clang optimized parameters (scalar) : Ok
29: BTF true_signature: clang optimized with stack parameters (9 params: Ok
30: BTF true_signature: clang optimized with stack parameters (non-stat: Ok
31: Class name list from file. : Ok
32: CLI display and filtering options. : Ok
33: codiff struct comparison. : Ok
34: codiff coverage: terse, functions, verbose, multi-CU. : Ok
35: codiff member change coverage. : Ok
36: codiff multi-CU: __cus__find_cu_by_name coverage. : Ok
37: codiff --terse (-t) type change reporting. : Ok
38: Compile emission pipeline. : Ok
39: Compilable output and type filtering. : Ok
40: Recursive container search and verbose counts. : Ok
41: Type containment and pointer search. : Ok
42: C++ advanced DWARF tag coverage. : Ok
43: C++ cleanup: delete_tags coverage for lexblock, parameter packs. : Ok
44: C++ inheritance and namespace printing. : Ok
45: C++ value parameter pack and shadow definition disambiguation. : Ok
46: C++ namespace and using-declaration display. : Ok
47: C++ variadic template parameter pack handling. : Ok
48: C++ template pretty printing round-trip. : Ok
49: C++ template template parameter pretty printing round-trip. : Ok
51: Class name filtering. : Ok
52: Default BTF on a system without BTF. : Ok
53: Display format options. : Ok
54: dwarf_loader.c edge cases: bitfield recode, inlining, call sites. : Ok
55: dwarves.c core API coverage. : Ok
56: DWZ alternate debug file type resolution. : Ok
57: Legacy atomic_ base type emission via --compile (hand-crafted DWARF: Ok
58: Atomic typedef emission. : Ok
59: dwarves_emit.c / dwarves_reorganize.c coverage. : Ok
60: DW_TAG_atomic_type member handling via --compile. : Ok
61: Type emission (--compile) and atomic typedefs. : Ok
62: Enumerator search. : Ok
63: Expand pointers option. : Ok
64: Type expansion and anonymous struct options. : Ok
65: Flexible arrays accounting. : Ok
66: dwarves_fprintf.c coverage: fn-ptr members, labels, C++ types. : Ok
67: Validation of GCC optimized parameters in default BTF. : Ok
68: Validation of BTF encoding of true_signatures. : Skip (no optimizations applied.)
69: Compare parallel vs merged CU loading for inter-CU type references.: Ok
70: Multi-file loading (cus__load_files). : Ok
71: Version output. : Ok
72: Struct packing and reorganization. : Ok
73: pahole coverage: sort, word_size unions, prettify bitfields. : Ok
74: Format coverage: count, skip, structs, hex, contains, first_obj_onl: Ok
75: Header, range and seek_bytes prettify paths. : Ok
76: pahole.c scattered option paths. : Ok
77: pdwtags DWARF tag display. : Ok
78: Check that pfunct can print btf_decl_tags read from BTF. : Ok
79: pfunct --class and --expand_types. : Ok
80: pfunct coverage: symtab, class, expand_types, compile. : Ok
81: pfunct function statistics. : Ok
82: pglobal basic: variables, functions, static exclusion. : Ok
83: prefcnt.c: reference counting coverage. : Ok
84: prettify_bitfield : Ok
85: Pretty printing of files using DWARF type information. : Ok
86: Prototype expression parsing and prettify. : Ok
87: Cacheline boundary display with large structs. : Ok
88: Sizes and holes display. : Ok
89: Small files coverage: elf_symtab, gobuffer, dutil, pglobal. : Ok
90: Sort with multi-CU deduplication. : Ok
91: Sort output and separator. : Ok
92: Statistics and filtering options. : Ok
93: Typedef chain display. : Ok
94: Word size LP resizing. : Ok
95: Union word_size resize and packable hex. : Ok
96: Validation of BTF encoding of functions. : Ok
50: ctracer: struct method tracing generation. : Ok
20: Split BTF encoding (vmlinux base + kernel module). : Ok
19: BTF encoding with kernel vmlinux. : Ok
98: Parallel reproducible DWARF Loading/Serial BTF encoding. : Ok
97: BTF encoding on vmlinux. : Ok
Saved timing data to .test-times (98 tests)
⬢ [acme@toolbx pahole]$
Cheers,
- Arnaldo
CMakeLists.txt | 2 +-
btf_encoder.c | 550 +++++++++++++------
btf_loader.c | 60 ++-
btfdiff | 1 -
codiff.c | 8 +-
ctracer.c | 2 +-
ctf_encoder.c | 2 +-
ctf_loader.c | 16 +-
dutil.c | 46 ++
dutil.h | 1 +
dwarf_loader.c | 122 ++--
dwarves.c | 95 +---
dwarves.h | 21 +-
dwarves_emit.c | 2 +-
dwarves_fprintf.c | 74 +--
gobuffer.c | 49 --
gobuffer.h | 5 -
libctf.c | 14 +-
pahole.c | 63 ++-
pfunct.c | 14 +-
tests/btf_arrays.sh | 158 ++++++
21 files changed, 864 insertions(+), 441 deletions(-)
Arnaldo Carvalho de Melo (31):
cmake: Update minimum required version from 3.5 to 3.10
Fix -Wsign-compare warnings across the codebase
btf_encoder: Fix interior pointer free and missing NULL check
dwarves: Fix missing list head initialization in type__clone_members
pahole: Fix instance memory leak on early returns in prototype__stdio_fprintf_value
ctf_loader, libctf: Fix error path resource leaks
dwarves: Don't search for holes before member byte sizes are cached
dwarf_loader: Allocate type_dcu via dwarf_cu__new to fix dangling stack pointer
dwarf_loader: Fix annotation failure leaks in variable and typedef creation
btf_encoder: Use btf_encoder__tag_type() for all type ID computations
pahole: Fix --errno typo that decrements instead of negating
dwarves: Fix heap buffer overflow in languages__parse realloc
btf_encoder: Fix early cleanup crashes in btf_encoder__new/delete
btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds
dwarf_loader: Fix --fixup_silly_bitfields condition check
dwarf_loader: Skip libdw__lock when elfutils is built thread-safe
dwarf_loader: Fix data race in tag__init() decl_file string cache
pahole: Fix parse_btf_features("all") being a silent no-op
dwarves: Fix variable shadowing in __cus__find_struct_by_name()
dutil: Add exec_objcopy() shell-injection-safe helper
btf_encoder: Fall back to objcopy when llvm-objcopy is not available
dwarf_loader, btf_loader: Replace stale FIXME/XXX comments with explanations
pahole: Skip inline expansions during BTF encoding
pahole: Use fseek for seekable files in --prettify and --seek_bytes
pahole: Guard pipe_seek() against negative offsets
gobuffer: Remove 5 dead functions found via coverage analysis
dwarves: Remove 6 dead functions found via coverage analysis
pfunct, dwarves_fprintf: Mark file-local functions as static
btf_encoder: Fix multi-dimensional array encoding
btf_loader: Fix multi-dimensional array loading
btfdiff: Remove --flat_arrays now that pahole encodes multi dim arrays
in BTF
CMakeLists.txt | 2 +-
btf_encoder.c | 406 +++++++++++++++++++++++++++++++++++--------
btf_loader.c | 53 ++++--
btfdiff | 1 -
codiff.c | 3 +
ctf_loader.c | 8 +-
ctracer.c | 1 +
dutil.c | 72 ++++++++
dutil.h | 3 +
dwarf_loader.c | 157 ++++++++++-------
dwarves.c | 111 ++----------
dwarves.h | 13 --
dwarves_fprintf.c | 5 +-
dwarves_reorganize.c | 6 +-
gobuffer.c | 74 --------
gobuffer.h | 8 -
libctf.c | 10 +-
pahole.c | 177 +++++++++++++------
pfunct.c | 4 +-
tests/btf_arrays.sh | 158 +++++++++++++++++
20 files changed, 861 insertions(+), 411 deletions(-)
create mode 100755 tests/btf_arrays.sh
--
2.55.0
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 01/31] cmake: Update minimum required version from 3.5 to 3.10
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 02/31] Fix -Wsign-compare warnings across the codebase Arnaldo Carvalho de Melo
` (29 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
CMake warns that compatibility with versions < 3.10 will be removed from
a future version. Update cmake_minimum_required to 3.10 to eliminate the
deprecation warning during build.
CMake 3.10 was released in November 2017 and is widely available across
all currently supported Linux distributions.
Before: CMake Deprecation Warning about VERSION < 3.10 on every build
After: No deprecation warning
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a6c329f88e343e66..dc1d742b07531194 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.10)
project(pahole C)
cmake_policy(SET CMP0005 NEW)
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 02/31] Fix -Wsign-compare warnings across the codebase
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 01/31] cmake: Update minimum required version from 3.5 to 3.10 Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 03/31] btf_encoder: Fix interior pointer free and missing NULL check Arnaldo Carvalho de Melo
` (28 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Fix all 15 signed/unsigned comparison warnings triggered by
-Wsign-compare:
pahole.c: use size_t for ARRAY_SIZE() loop iterators in
init_btf_features(), find_btf_feature(),
show_supported_btf_features() and btf_features__enable_default(),
moving declarations into the for statements.
btf_encoder.c: use size_t for ARRAY_SIZE() loops in
should_skip_decl() and btf_encoder__should_skip_kfunc(), use
size_t for the offset in is_sym_kfunc_set(), and unsigned int
for the loop counter matching ranges_cnt.
btf_loader.c: cast bitfield arithmetic to size_t in ternary
where the other branch is pos->byte_size (size_t).
dwarf_loader.c: cast int8_t bitfield_offset to uint32_t before
comparing against uint32_t bit_size — by this point the value
has already been corrected to non-negative.
dwarves.c: use uint16_t for byte_hole_size to match the int
hole field it compares against, and add an id >= 0 guard before
the ARRAY_SIZE() comparison in lang__int2str().
dwarves_fprintf.c: use uint64_t for loop variable matching
attributes->cnt type.
dwarves_reorganize.c: use int for remainder/inc (bounded by
addr_size), and cast byte_size comparison to size_t where the
hole value is known non-negative.
Fixes: 7bc9b9975545ab53 ("pahole: Add --btf_features support")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 12 +++++-------
btf_loader.c | 2 +-
dwarf_loader.c | 2 +-
dwarves.c | 4 ++--
dwarves_fprintf.c | 3 +--
dwarves_reorganize.c | 6 +++---
pahole.c | 14 ++++----------
7 files changed, 17 insertions(+), 26 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 993a61cf320c6d92..9f8cd279fa92af1c 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1211,14 +1211,13 @@ static struct btf_encoder_func_state *btf_encoder__alloc_func_state(struct btf_e
return state;
}
-static bool str_contains_suffix(const char *str, const char * const *suffixes, int nr_suffixes)
+static bool str_contains_suffix(const char *str, const char * const *suffixes, size_t nr_suffixes)
{
const char *suffix = strchr(str, '.');
- int i;
if (!suffix)
return false;
- for (i = 0; i < nr_suffixes; i++) {
+ for (size_t i = 0; i < nr_suffixes; i++) {
if (strstr(suffix, suffixes[i]))
return true;
}
@@ -2087,7 +2086,7 @@ static int is_sym_kfunc_set(GElf_Sym *sym, const char *name, Elf_Data *idlist, s
{
void *ptr = idlist->d_buf;
struct btf_id_set8 *set;
- int off;
+ size_t off;
/* kfuncs are only found in BTF_SET8's */
if (!strstarts(name, BTF_ID_SET8_PFX))
@@ -2282,7 +2281,7 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
ptrdiff_t off;
GElf_Sym sym;
bool found;
- int j;
+ unsigned int j;
if (!gelf_getsym(symbols, i, &sym)) {
elf_error("Failed to get ELF symbol(%d)", i);
@@ -2568,12 +2567,11 @@ static bool filter_variable_name(const char *name)
X("__func_stack_frame_non_standard_")
#undef X
};
- int i;
if (*name != '_')
return false;
- for (i = 0; i < ARRAY_SIZE(skip); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(skip); i++) {
if (strncmp(name, skip[i].s, skip[i].len) == 0)
return true;
}
diff --git a/btf_loader.c b/btf_loader.c
index 92d4832c47af21a0..caead39775da54da 100644
--- a/btf_loader.c
+++ b/btf_loader.c
@@ -744,7 +744,7 @@ static int class__fixup_btf_bitfields(const struct conf_load *conf, struct tag *
*/
smallest_offset = pos->byte_offset;
smallest_offset += pos->bitfield_size ?
- (pos->bitfield_offset + pos->bitfield_size + 7) / 8 :
+ (size_t)(pos->bitfield_offset + pos->bitfield_size + 7) / 8 :
pos->byte_size;
}
diff --git a/dwarf_loader.c b/dwarf_loader.c
index a4cd91178bccde99..14c90080f538e10d 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -3786,7 +3786,7 @@ static int class_member__cache_byte_size(struct tag *tag, struct cu *cu,
/* align on underlying base type natural alignment boundary */
member->bitfield_offset += (member->byte_offset % member->byte_size) * 8;
member->byte_offset = member->bit_offset / member->bit_size * member->bit_size / 8;
- if (member->bitfield_offset >= member->bit_size) {
+ if ((uint32_t)member->bitfield_offset >= member->bit_size) {
member->bitfield_offset -= member->bit_size;
member->byte_offset += member->byte_size;
}
diff --git a/dwarves.c b/dwarves.c
index ef93239d26827711..f05c4d3b4b669335 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1577,7 +1577,7 @@ const struct class_member *class__find_bit_hole(const struct class *class,
const uint16_t bit_hole_size)
{
struct class_member *pos;
- const size_t byte_hole_size = bit_hole_size / 8;
+ const uint16_t byte_hole_size = bit_hole_size / 8;
type__for_each_data_member(&class->type, pos)
if (pos == trailer)
@@ -2449,7 +2449,7 @@ const char *lang__int2str(int id)
{
const char *lang = NULL;
- if (id < ARRAY_SIZE(languages))
+ if (id >= 0 && (size_t)id < ARRAY_SIZE(languages))
lang = languages[id];
else if (id == DW_LANG_Mips_Assembler)
return "asm";
diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c
index ab1c381db64651c6..e44ac05c3b9e7e73 100644
--- a/dwarves_fprintf.c
+++ b/dwarves_fprintf.c
@@ -1206,10 +1206,9 @@ const char *function__prototype(const struct function *func,
static size_t tag__attributes_fprintf(const struct tag *tag, FILE *fp)
{
size_t printed = 0;
- int i;
if (tag->attributes)
- for (i = 0; i < tag->attributes->cnt; ++i)
+ for (uint64_t i = 0; i < tag->attributes->cnt; ++i)
printed += fprintf(fp, "%s ", tag->attributes->values[i]);
return printed;
diff --git a/dwarves_reorganize.c b/dwarves_reorganize.c
index 14f5e8228fa8a95d..50652fefcf14cbb2 100644
--- a/dwarves_reorganize.c
+++ b/dwarves_reorganize.c
@@ -82,7 +82,7 @@ void class__fixup_alignment(struct class *class, const struct cu *cu)
class->type.size -= dec;
class__subtract_offsets_from(class, pos, dec);
} else for (power2 = cu->addr_size; power2 >= 2; power2 /= 2) {
- const size_t remainder = pos->byte_offset % power2;
+ const int remainder = pos->byte_offset % power2;
if (pos->byte_size == power2) {
if (remainder == 0) /* perfectly aligned */
@@ -95,7 +95,7 @@ void class__fixup_alignment(struct class *class, const struct cu *cu)
pos->bit_offset -= remainder * 8;
class__subtract_offsets_from(class, pos, remainder);
} else {
- const size_t inc = power2 - remainder;
+ const int inc = power2 - remainder;
if (last_member->hole == 0)
++class->nr_holes;
@@ -811,7 +811,7 @@ restart:
if (class->padding > 0 &&
member != last_member &&
last_member->byte_size != 0 &&
- last_member->byte_size <= member->hole) {
+ last_member->byte_size <= (size_t)member->hole) {
if (class__move_member(class, member, last_member, cu, 1, verbose, fp))
goto restart;
}
diff --git a/pahole.c b/pahole.c
index 033baedcc602cc75..390d5f2dd20e4dfd 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1249,8 +1249,6 @@ bool set_btf_features_initial;
static void init_btf_features(void)
{
- int i;
-
/* Only set initial values once, as multiple --btf_features=
* may be specified on command-line, and setting values
* again could clobber values. The aim is to enable
@@ -1258,16 +1256,14 @@ static void init_btf_features(void)
*/
if (set_btf_features_initial)
return;
- for (i = 0; i < ARRAY_SIZE(btf_features); i++)
+ for (size_t i = 0; i < ARRAY_SIZE(btf_features); i++)
*btf_features[i].conf_value = btf_features[i].initial_value;
set_btf_features_initial = true;
}
static struct btf_feature *find_btf_feature(char *name)
{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(btf_features); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(btf_features); i++) {
if (strcmp(name, btf_features[i].name) == 0)
return &btf_features[i];
}
@@ -1285,9 +1281,7 @@ static void enable_btf_feature(struct btf_feature *feature)
static void show_supported_btf_features(FILE *output)
{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(btf_features); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(btf_features); i++) {
if (btf_features[i].feature_check && !btf_features[i].feature_check())
continue;
if (i > 0)
@@ -1299,7 +1293,7 @@ static void show_supported_btf_features(FILE *output)
static void btf_features__enable_default(void)
{
- for (int i = 0; i < ARRAY_SIZE(btf_features); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(btf_features); i++) {
if (btf_features[i].default_enabled)
enable_btf_feature(&btf_features[i]);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 03/31] btf_encoder: Fix interior pointer free and missing NULL check
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 01/31] cmake: Update minimum required version from 3.5 to 3.10 Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 02/31] Fix -Wsign-compare warnings across the codebase Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 04/31] dwarves: Fix missing list head initialization in type__clone_members Arnaldo Carvalho de Melo
` (27 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Two bugs in btf_encoder:
1. btf_encoder__save_func() called free(state) on error, but state is
an interior pointer into func_states.array (returned by
btf_encoder__alloc_func_state as &array[cnt++]), not a standalone
malloc'd block. Calling free() on it is undefined behavior that
corrupts the heap allocator's metadata.
Fix: clear the slot with memset and decrement func_states.cnt to
release it back. This is safe because no further func_state
allocations happen between the alloc at the top of save_func and
the error path, so state is always the last element.
2. btf_encoder__new() allocated func_states.array with zalloc() but
did not check for NULL. A failed allocation would cause a NULL
pointer dereference in btf_encoder__alloc_func_state().
Fix: add NULL check, goto out_delete on failure.
Fixes: 4bff1141bb48ae58 ("btf_encoder: Record BTF-centric function state instead of DWARF-centric")
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 9f8cd279fa92af1c..5d0901da73b618d7 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -1393,9 +1393,21 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
}
return 0;
out:
+ /*
+ * state is an interior pointer into func_states.array (returned by
+ * btf_encoder__alloc_func_state), not a standalone allocation.
+ * Calling free(state) here was heap corruption.
+ *
+ * Since no further func_state allocations happen between the alloc
+ * at the top of this function and this error path, state is always
+ * the last element (index cnt-1), so decrementing cnt releases it.
+ * If this invariant ever changes (e.g. nested alloc calls are added),
+ * this cleanup must be revised.
+ */
zfree(&state->annots);
zfree(&state->parms);
- free(state);
+ memset(state, 0, sizeof(*state));
+ encoder->func_states.cnt--;
return err;
}
@@ -2814,6 +2826,8 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
/* Start with funcs->cnt. The array may grow in btf_encoder__alloc_func_state() */
encoder->func_states.array = zalloc(sizeof(*encoder->func_states.array) * funcs->cnt);
+ if (encoder->func_states.array == NULL && funcs->cnt > 0)
+ goto out_delete;
encoder->func_states.cap = funcs->cnt;
encoder->func_states.cnt = 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 04/31] dwarves: Fix missing list head initialization in type__clone_members
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (2 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 03/31] btf_encoder: Fix interior pointer free and missing NULL check Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 05/31] pahole: Fix instance memory leak on early returns in prototype__stdio_fprintf_value Arnaldo Carvalho de Melo
` (26 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
After class__clone() does memcpy(class, from, sizeof(*class)), the
clone's list_head fields still point into the source type's linked
list chains. type__clone_members() reinitializes namespace.tags and
variant_parts, but misses type_enum, template_type_params, and
template_value_params.
If the source type has non-empty lists for any of these, subsequent
list operations on the clone (e.g. during --reorganize) corrupt both
the clone's and the source's lists, leading to crashes or infinite
loops.
Add the three missing INIT_LIST_HEAD calls, matching the initialization
done in __type__init().
Fixes: 8a1e0238ee4005a6 ("dwarf_loader: Initial support for DW_TAG_template_type_param tag")
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/dwarves.c b/dwarves.c
index f05c4d3b4b669335..95a8dcd66f861be9 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1382,6 +1382,10 @@ static int type__clone_members(struct type *type, const struct type *from, struc
type->nr_members = type->nr_static_members = 0;
INIT_LIST_HEAD(&type->namespace.tags);
+ INIT_LIST_HEAD(&type->namespace.annots);
+ INIT_LIST_HEAD(&type->type_enum);
+ INIT_LIST_HEAD(&type->template_type_params);
+ INIT_LIST_HEAD(&type->template_value_params);
type__for_each_member(from, pos) {
struct class_member *clone = class_member__clone(pos, cu);
@@ -1400,6 +1404,8 @@ struct class *class__clone(const struct class *from, const char *new_class_name,
if (class != NULL) {
memcpy(class, from, sizeof(*class));
+ INIT_LIST_HEAD(&class->vtable);
+ class->nr_vtable_entries = 0;
if (new_class_name != NULL) {
class->type.namespace.name = strdup(new_class_name);
if (class->type.namespace.name == NULL) {
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 05/31] pahole: Fix instance memory leak on early returns in prototype__stdio_fprintf_value
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (3 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 04/31] dwarves: Fix missing list head initialization in type__clone_members Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 06/31] ctf_loader, libctf: Fix error path resource leaks Arnaldo Carvalho de Melo
` (25 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
prototype__stdio_fprintf_value() allocates an instance buffer via
malloc() but has 14 early return statements between the allocation
and the free(instance) at the out: label. None of these early returns
free instance, leaking memory on every error path during --prettify
binary record processing.
Change all bare returns after the successful malloc to 'goto out',
setting printed to the error value before jumping. The out: label
already does free(instance) and return printed.
The return -ENOMEM when instance == NULL (malloc failure) is kept
as-is since there is nothing to free.
Fixes: fdfc64ec44f4ad53 ("pahole: Introduce --range")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 45 ++++++++++++++++++++++++++++-----------------
1 file changed, 28 insertions(+), 17 deletions(-)
diff --git a/pahole.c b/pahole.c
index 390d5f2dd20e4dfd..0096dfa34e5047c8 100644
--- a/pahole.c
+++ b/pahole.c
@@ -2584,9 +2584,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
return -ENOMEM;
if (type__instance_read_once(header, input) < 0) {
- int err = --errno;
+ printed = --errno;
fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
- return err;
+ goto out;
}
if (conf.range || prototype->range) {
@@ -2598,14 +2598,16 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
fprintf(stderr, "pahole: --header_type=%s not found\n", conf.header_type);
else
fprintf(stderr, "pahole: range (%s) requires --header\n", range);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
char *member_name = NULL;
if (asprintf(&member_name, "%s.%s", range, "offset") == -1) {
fprintf(stderr, "pahole: not enough memory for range=%s\n", range);
- return -ENOMEM;
+ printed = -ENOMEM;
+ goto out;
}
int64_t value = type_instance__int_value(header, member_name);
@@ -2614,7 +2616,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
fprintf(stderr, "pahole: couldn't read the '%s' member of '%s' for evaluating range=%s\n",
member_name, conf.header_type, range);
free(member_name);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
seek_bytes = value;
@@ -2628,7 +2631,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (seek_bytes < total_read_bytes) {
fprintf(stderr, "pahole: can't go back in input, already read %" PRIu64 " bytes, can't go to position %#" PRIx64 "\n",
total_read_bytes, seek_bytes);
- return -ENOMEM;
+ printed = -ENOMEM;
+ goto out;
}
if (global_verbose) {
@@ -2640,7 +2644,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (asprintf(&member_name, "%s.%s", range, "size") == -1) {
fprintf(stderr, "pahole: not enough memory for range=%s\n", range);
- return -ENOMEM;
+ printed = -ENOMEM;
+ goto out;
}
value = type_instance__int_value(header, member_name);
@@ -2649,7 +2654,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
fprintf(stderr, "pahole: couldn't read the '%s' member of '%s' for evaluating range=%s\n",
member_name, conf.header_type, range);
free(member_name);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
size_bytes = value;
@@ -2661,9 +2667,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
free(member_name);
if (pipe_seek(input, seek_bytes) < 0) {
- int err = --errno;
+ printed = --errno;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
- return err;
+ goto out;
}
goto do_read;
@@ -2676,7 +2682,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (!header) {
fprintf(stderr, "pahole: --seek_bytes (%s) makes reference to --header but it wasn't specified\n",
conf.seek_bytes);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
const char *member_name = conf.seek_bytes + sizeof("$header.") - 1;
@@ -2684,7 +2691,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (value < 0) {
fprintf(stderr, "pahole: couldn't read the '%s' member of '%s' for evaluating --seek_bytes=%s\n",
member_name, conf.header_type, conf.seek_bytes);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
seek_bytes = value;
@@ -2696,7 +2704,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (seek_bytes < header->type->size) {
fprintf(stderr, "pahole: seek bytes evaluated from --seek_bytes=%s is less than the header type size\n",
conf.seek_bytes);
- return -EINVAL;
+ printed = -EINVAL;
+ goto out;
}
} else {
seek_bytes = strtol(conf.seek_bytes, NULL, 0);
@@ -2709,9 +2718,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
}
if (pipe_seek(input, seek_bytes) < 0) {
- int err = --errno;
+ printed = --errno;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
- return err;
+ goto out;
}
}
@@ -2720,7 +2729,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (!header) {
fprintf(stderr, "pahole: --size_bytes (%s) makes reference to --header but it wasn't specified\n",
conf.size_bytes);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
const char *member_name = conf.size_bytes + sizeof("$header.") - 1;
@@ -2728,7 +2738,8 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (value < 0) {
fprintf(stderr, "pahole: couldn't read the '%s' member of '%s' for evaluating --size_bytes=%s\n",
member_name, conf.header_type, conf.size_bytes);
- return -ESRCH;
+ printed = -ESRCH;
+ goto out;
}
size_bytes = value;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 06/31] ctf_loader, libctf: Fix error path resource leaks
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (4 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 05/31] pahole: Fix instance memory leak on early returns in prototype__stdio_fprintf_value Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 07/31] dwarves: Don't search for holes before member byte sizes are cached Arnaldo Carvalho de Melo
` (24 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Three resource leaks in CTF loading error paths:
1. ctf__load_file(): if cu__new() returns NULL, the ctf state allocated
by ctf__new() is leaked. Add ctf__delete(state) before returning.
2. ctf__load_file(): if ctf__load() fails, both cu and state are
leaked. Since cu->priv = state and cu->dfops = &ctf__ops are
already set, cu__delete(cu) will invoke ctf__cu_delete() which
calls ctf__delete(), cleaning up both.
3. ctf__decompress(): if inflate() fails after inflateInit() succeeded,
the zlib internal decompression state is leaked because the err:
path only frees the output buffer without calling inflateEnd().
Add inflateEnd(&state) before the goto.
Fixes: 2dfa5fe6eab0c157 ("[DWARVES]: Initial CTF support")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
ctf_loader.c | 8 ++++++--
libctf.c | 1 +
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/ctf_loader.c b/ctf_loader.c
index 501c4abe859c0d52..39aef17893da5314 100644
--- a/ctf_loader.c
+++ b/ctf_loader.c
@@ -704,8 +704,10 @@ int ctf__load_file(struct cus *cus, struct conf_load *conf,
return -1;
struct cu *cu = cu__new(filename, state->wordsize, NULL, 0, filename, false);
- if (cu == NULL)
+ if (cu == NULL) {
+ ctf__delete(state);
return -1;
+ }
cu->language = LANG_C;
cu->uses_global_strings = false;
@@ -713,8 +715,10 @@ int ctf__load_file(struct cus *cus, struct conf_load *conf,
cu->dfops = &ctf__ops;
cu->priv = state;
state->priv = cu;
- if (ctf__load(state) != 0)
+ if (ctf__load(state) != 0) {
+ cu__delete(cu);
return -1;
+ }
err = ctf__load_sections(state, cu);
diff --git a/libctf.c b/libctf.c
index 0641b10586527a6e..8e31e3d550ebd51a 100644
--- a/libctf.c
+++ b/libctf.c
@@ -104,6 +104,7 @@ static int ctf__decompress(struct ctf *ctf, void *orig_buf, size_t orig_size)
if (inflate(&state, Z_FINISH) != Z_STREAM_END) {
err_str = "struct ctf decompression inflate failure.";
+ inflateEnd(&state);
goto err;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 07/31] dwarves: Don't search for holes before member byte sizes are cached
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (5 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 06/31] ctf_loader, libctf: Fix error path resource leaks Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 08/31] dwarf_loader: Allocate type_dcu via dwarf_cu__new to fix dangling stack pointer Arnaldo Carvalho de Melo
` (23 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
cus__add() called cu__find_class_holes() which ran class__find_holes()
on all structs before cu__finalize() had cached member byte_sizes via
class_member__cache_byte_size(). In the merge path, cus__add() is
called during the CU-processing loop, but cu__finalize() runs after
the loop completes. With uncached byte_sizes (still 0), every member
appeared zero-sized, producing phantom holes between contiguous members.
The holes_searched flag then prevented re-computation when
class__find_holes() was called again during printing.
This caused 36 false "BRAIN FART ALERT!" warnings and phantom "XXX 8
bytes hole, try to pack" messages when processing Firefox debug info,
all on Rust vtable/trait types where members are contiguous at 8-byte
intervals.
Before (Firefox, 152.0-1.fc44.x86_64):
$ pahole -F dwarf firefox-*.debug 2>&1 | grep -c "BRAIN FART"
36
After:
$ pahole -F dwarf firefox-*.debug 2>&1 | grep -c "BRAIN FART"
0
All consumers already call class__find_holes() lazily, guarded by the
holes_searched flag, so the eager call in cus__add() was redundant.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
codiff.c | 3 +++
ctracer.c | 1 +
dwarves.c | 11 -----------
3 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/codiff.c b/codiff.c
index 9e5c56546c314231..72a2c4d720f7ac78 100644
--- a/codiff.c
+++ b/codiff.c
@@ -286,6 +286,9 @@ static void diff_struct(const struct cu *new_cu, struct class *structure,
assert(class__is_struct(new_structure));
+ class__find_holes(structure);
+ class__find_holes(new_structure);
+
diff = class__size(structure) != class__size(new_structure) ||
class__nr_members(structure) != class__nr_members(new_structure) ||
check_print_members_changes(structure, cu,
diff --git a/ctracer.c b/ctracer.c
index 6894766abc236b45..f3f7f937bb981a58 100644
--- a/ctracer.c
+++ b/ctracer.c
@@ -354,6 +354,7 @@ static struct class *class__clone_base_types(const struct tag *tag,
if (clone == NULL)
return NULL;
+ class__find_holes(clone);
type__for_each_data_member_safe(&clone->type, pos, next) {
struct tag *member_type = cu__type(cu, pos->tag.type);
diff --git a/dwarves.c b/dwarves.c
index 95a8dcd66f861be9..57a4bd063cc9413d 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -506,15 +506,6 @@ reevaluate:
return result;
}
-static void cu__find_class_holes(struct cu *cu)
-{
- uint32_t id;
- struct class *pos;
-
- cu__for_each_struct(cu, id, pos)
- class__find_holes(pos);
-}
-
struct cus {
uint32_t nr_entries;
struct list_head cus;
@@ -567,8 +558,6 @@ void cus__add(struct cus *cus, struct cu *cu)
cus__lock(cus);
__cus__add(cus, cu);
cus__unlock(cus);
-
- cu__find_class_holes(cu);
}
static void ptr_table__init(struct ptr_table *pt)
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 08/31] dwarf_loader: Allocate type_dcu via dwarf_cu__new to fix dangling stack pointer
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (6 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 07/31] dwarves: Don't search for holes before member byte sizes are cached Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 09/31] dwarf_loader: Fix annotation failure leaks in variable and typedef creation Arnaldo Carvalho de Melo
` (22 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
In cus__load_module(), type_dcu was declared on the stack and passed
to __cus__load_debug_types(), which stores the pointer in type_cu->priv.
After cus__load_module() returns, type_cu->priv points to a dead stack
frame. When dwarf_cu__delete() later calls cu__free(cu, cu->priv), it
passes an invalid stack address to free(), causing heap corruption or a
crash.
Move the allocation into __cus__load_debug_types() using dwarf_cu__new(),
which allocates through cu__zalloc() — respecting the CU's obstack
setting so that the type_dcu lifetime is tied to the type_cu that owns
it and cleanup goes through the normal dwarf_cu__delete() path.
Fixes: 2938a70e1e2a73ff ("Add support for .debug_types sections.")
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 41 +++++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 14c90080f538e10d..b87519fa73405ff7 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -3889,7 +3889,7 @@ static int cu__set_common(struct cu *cu, struct conf_load *conf,
static int __cus__load_debug_types(struct cus *cus, struct conf_load *conf, Dwfl_Module *mod, Dwarf *dw, Elf *elf,
const char *filename, const unsigned char *build_id,
- int build_id_len, struct cu **cup, struct dwarf_cu *dcup)
+ int build_id_len, struct cu **cup, struct dwarf_cu **dcup)
{
Dwarf_Off off = 0, noff, type_off;
size_t cuhl;
@@ -3897,6 +3897,7 @@ static int __cus__load_debug_types(struct cus *cus, struct conf_load *conf, Dwfl
uint64_t signature;
*cup = NULL;
+ *dcup = NULL;
while (dwarf_next_unit(dw, off, &noff, &cuhl, NULL, NULL, &pointer_size,
&offset_size, &signature, &type_off)
@@ -3904,6 +3905,7 @@ static int __cus__load_debug_types(struct cus *cus, struct conf_load *conf, Dwfl
if (*cup == NULL) {
struct cu *cu;
+ struct dwarf_cu *dcu;
cu = cu__new("", pointer_size, build_id,
build_id_len, filename, conf->use_obstack);
@@ -3913,17 +3915,18 @@ static int __cus__load_debug_types(struct cus *cus, struct conf_load *conf, Dwfl
return DWARF_CB_ABORT;
}
- if (dwarf_cu__init(dcup, cu) != 0) {
+ dcu = dwarf_cu__new(cu);
+ if (dcu == NULL) {
cu__delete(cu);
return DWARF_CB_ABORT;
}
- dcup->cu = cu;
/* Funny hack. */
- dcup->type_unit = dcup;
- cu->priv = dcup;
+ dcu->type_unit = dcu;
+ cu->priv = dcu;
cu->dfops = &dwarf__ops;
*cup = cu;
+ *dcup = dcu;
cus__add(cus, cu);
}
@@ -4488,27 +4491,30 @@ static int cus__load_module(struct cus *cus, struct conf_load *conf,
int build_id_len = 0;
#endif
struct cu *type_cu;
- struct dwarf_cu type_dcu;
+ struct dwarf_cu *type_dcu;
int type_lsk = LSK__KEEPIT;
int lsk_worker_status = LSK__ABORT;
int res = __cus__load_debug_types(cus, conf, mod, dw, elf, filename, build_id, build_id_len, &type_cu, &type_dcu);
- if (res != 0) {
+ if (res != 0)
return res;
- }
if (type_cu != NULL) {
cu__finalize(type_cu, cus, conf);
- type_lsk = cus__steal_now(cus, type_cu, conf);
- if (type_lsk == LSK__DELETE) {
+ if (conf && conf->steal)
+ type_lsk = conf->steal(type_cu, conf);
+ /* Defer cu__delete() for LSK__DELETE until after main
+ * CUs are processed — they need type_dcu alive for
+ * DW_FORM_ref_sig8 resolution. Remove from the list
+ * now so the consumer doesn't see it twice. */
+ if (type_lsk == LSK__DELETE)
cus__remove(cus, type_cu);
- }
}
if (cus__merging_cu(dw, elf)) {
res = cus__merge_and_process_cu(cus, conf, mod, dw, elf, filename,
build_id, build_id_len,
- type_cu ? &type_dcu : NULL);
+ type_cu ? type_dcu : NULL);
} else {
struct dwarf_cus dcus = {
.off = 0,
@@ -4518,7 +4524,7 @@ static int cus__load_module(struct cus *cus, struct conf_load *conf,
.dw = dw,
.elf = elf,
.filename = filename,
- .type_dcu = type_cu ? &type_dcu : NULL,
+ .type_dcu = type_cu ? type_dcu : NULL,
.build_id = build_id,
.build_id_len = build_id_len,
.nr_cus_created = 0,
@@ -4527,12 +4533,15 @@ static int cus__load_module(struct cus *cus, struct conf_load *conf,
lsk_worker_status = dcus.lsk_status;
}
+ /* Deferred type CU cleanup: now that main CUs have finished
+ * resolving DW_FORM_ref_sig8 refs through type_dcu, it is
+ * safe to free the type CU. */
+ if (type_cu != NULL && type_lsk == LSK__DELETE)
+ cu__delete(type_cu);
+
if (res && lsk_worker_status == LSK__ABORT)
return res;
- if (type_lsk == LSK__DELETE)
- cu__delete(type_cu);
-
return DWARF_CB_OK;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 09/31] dwarf_loader: Fix annotation failure leaks in variable and typedef creation
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (7 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 08/31] dwarf_loader: Allocate type_dcu via dwarf_cu__new to fix dangling stack pointer Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 10/31] btf_encoder: Use btf_encoder__tag_type() for all type ID computations Arnaldo Carvalho de Melo
` (21 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
die__create_new_variable() and die__create_new_typedef() leak their
allocated objects when add_child_llvm_annotations() fails: the
allocation succeeds but the error path returns NULL without freeing it.
Split the compound NULL-or-annotation-failure check in
die__create_new_variable() into separate checks, and add explicit
cleanup (tag__delete / type__delete) before returning NULL on
annotation failure in both functions.
Fixes: aa8c494e65a77fa5 ("dwarf_loader: Parse DWARF tag DW_TAG_LLVM_annotation")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index b87519fa73405ff7..ddf7cdcfd467beb4 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -2078,8 +2078,10 @@ static struct tag *die__create_new_typedef(Dwarf_Die *die, struct cu *cu, struct
if (tdef == NULL)
return NULL;
- if (add_child_llvm_annotations(die, -1, conf, &tdef->namespace.annots))
+ if (add_child_llvm_annotations(die, -1, conf, &tdef->namespace.annots)) {
+ type__delete(tdef, cu);
return NULL;
+ }
return &tdef->namespace.tag;
}
@@ -2190,8 +2192,13 @@ static struct tag *die__create_new_variable(Dwarf_Die *die, struct cu *cu, struc
{
struct variable *var = variable__new(die, cu, conf, top_level);
- if (var == NULL || add_child_llvm_annotations(die, -1, conf, &var->annots))
+ if (var == NULL)
+ return NULL;
+
+ if (add_child_llvm_annotations(die, -1, conf, &var->annots)) {
+ tag__delete(&var->ip.tag, cu);
return NULL;
+ }
return &var->ip.tag;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 10/31] btf_encoder: Use btf_encoder__tag_type() for all type ID computations
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (8 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 09/31] dwarf_loader: Fix annotation failure leaks in variable and typedef creation Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 11/31] pahole: Fix --errno typo that decrements instead of negating Arnaldo Carvalho de Melo
` (20 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Replace inline "encoder->type_id_off + tag_type" patterns with
btf_encoder__tag_type(), which already encapsulates both the zero-check
and the offset addition. Some call sites had ternary guards
(tag_type == 0 ? 0 : ...) that are now handled inside the function.
Two sites that use nr_entries or core_id as table indices rather than
type references are left unchanged — they receive different treatment
in a subsequent commit.
No functional change.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 5d0901da73b618d7..0fbd7e3420b0457f 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -865,7 +865,7 @@ static int32_t btf_encoder__add_func_proto_for_ftype(struct btf_encoder *encoder
ftype__for_each_parameter(ftype, param) {
name = parameter__name(param);
- type_id = param->tag.type == 0 ? 0 : encoder->type_id_off + param->tag.type;
+ type_id = btf_encoder__tag_type(encoder, param->tag.type);
++param_idx;
if (btf_encoder__add_func_param(encoder, name, type_id, param_idx == nr_params))
return -1;
@@ -1284,7 +1284,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->addr = function__addr(fn);
state->elf = func;
state->nr_parms = ftype->nr_parms + (ftype->unspec_parms ? 1 : 0);
- state->ret_type_id = ftype->tag.type == 0 ? 0 : encoder->type_id_off + ftype->tag.type;
+ state->ret_type_id = btf_encoder__tag_type(encoder, ftype->tag.type);
if (state->nr_parms > 0) {
state->parms = zalloc(state->nr_parms * sizeof(*state->parms));
if (!state->parms) {
@@ -1363,8 +1363,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
goto out;
}
state->parms[param_idx].name_off = str_off;
- state->parms[param_idx].type_id = param->tag.type == 0 ? 0 :
- encoder->type_id_off + param->tag.type;
+ state->parms[param_idx].type_id = btf_encoder__tag_type(encoder, param->tag.type);
param_idx++;
}
if (ftype->unspec_parms)
@@ -1849,11 +1848,13 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct
* is required.
*/
name = class_member__name(pos);
- if (btf_encoder__add_field(encoder, name, encoder->type_id_off + pos->tag.type,
+ if (btf_encoder__add_field(encoder, name, btf_encoder__tag_type(encoder, pos->tag.type),
pos->bitfield_size, pos->bit_offset))
return -1;
}
+
+
return type_id;
}
@@ -1894,7 +1895,7 @@ static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
struct conf_load *conf_load)
{
/* single out type 0 as it represents special type "void" */
- uint32_t ref_type_id = tag->type == 0 ? 0 : encoder->type_id_off + tag->type;
+ uint32_t ref_type_id = btf_encoder__tag_type(encoder, tag->type);
struct base_type *bt;
const char *name;
@@ -2720,7 +2721,7 @@ static int btf_encoder__encode_cu_variables(struct btf_encoder *encoder)
continue;
}
- type = var->ip.tag.type + encoder->type_id_off;
+ type = btf_encoder__tag_type(encoder, var->ip.tag.type);
linkage = var->external ? BTF_VAR_GLOBAL_ALLOCATED : BTF_VAR_STATIC;
if (encoder->verbose) {
@@ -2983,7 +2984,7 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
type_id_t id;
if (cu__find_base_type_by_name(cu, "int", &id)) {
encoder->has_index_type = true;
- encoder->array_index_id = encoder->type_id_off + id;
+ encoder->array_index_id = btf_encoder__tag_type(encoder, id);
} else {
encoder->has_index_type = false;
encoder->array_index_id = encoder->type_id_off + cu->types_table.nr_entries;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 11/31] pahole: Fix --errno typo that decrements instead of negating
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (9 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 10/31] btf_encoder: Use btf_encoder__tag_type() for all type ID computations Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 12/31] dwarves: Fix heap buffer overflow in languages__parse realloc Arnaldo Carvalho de Melo
` (19 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
prototype__stdio_fprintf_value() uses --errno in three error paths,
which decrements the global errno value instead of negating it with
-errno. This causes the function to return a wrong (often positive)
value on error, making callers think the operation succeeded.
Before: --errno with errno=5 returns 4 (positive, looks like success)
After: -errno with errno=5 returns -5 (negative, correctly signals error)
Fixes: 9310b04854a8b709 ("pahole: Add support for referencing header variables when pretty printing")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pahole.c b/pahole.c
index 0096dfa34e5047c8..6ba3f578c28570a1 100644
--- a/pahole.c
+++ b/pahole.c
@@ -2584,7 +2584,7 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
return -ENOMEM;
if (type__instance_read_once(header, input) < 0) {
- printed = --errno;
+ printed = -errno;
fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
goto out;
}
@@ -2667,7 +2667,7 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
free(member_name);
if (pipe_seek(input, seek_bytes) < 0) {
- printed = --errno;
+ printed = -errno;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
goto out;
}
@@ -2718,7 +2718,7 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
}
if (pipe_seek(input, seek_bytes) < 0) {
- printed = --errno;
+ printed = -errno;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
goto out;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 12/31] dwarves: Fix heap buffer overflow in languages__parse realloc
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (10 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 11/31] pahole: Fix --errno typo that decrements instead of negating Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 13/31] btf_encoder: Fix early cleanup crashes in btf_encoder__new/delete Arnaldo Carvalho de Melo
` (18 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The realloc() call in languages__parse() passes nr_allocated as the
size in bytes, but the array holds int elements. When nr_allocated
doubles past 4 (the initial allocation), the buffer is undersized by
a factor of sizeof(int), causing subsequent writes to corrupt heap
memory.
Before: realloc(entries, nr_allocated) — allocates nr_allocated bytes
After: realloc(entries, nr_allocated * sizeof(int)) — allocates correct size
With 5+ comma-separated --lang arguments, this would write 4*sizeof(int)
bytes past the allocated buffer.
Fixes: 8fc09fd3315ce934 ("core: Adopt the languages__parse(), languages__in() and 'struct languages' from pahole")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dwarves.c b/dwarves.c
index 57a4bd063cc9413d..5c9d83a6dee897c2 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -2499,7 +2499,7 @@ int languages__parse(struct languages *languages, const char *tool)
if (languages->nr_entries >= nr_allocated) {
nr_allocated *= 2;
- int *entries = realloc(languages->entries, nr_allocated);
+ int *entries = realloc(languages->entries, nr_allocated * sizeof(int));
if (entries == NULL)
goto out_enomem;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 13/31] btf_encoder: Fix early cleanup crashes in btf_encoder__new/delete
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (11 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 12/31] dwarves: Fix heap buffer overflow in languages__parse realloc Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 14/31] btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds Arnaldo Carvalho de Melo
` (17 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Fix three issues in btf_encoder initialization and cleanup:
1. INIT_LIST_HEAD(&encoder->elf_functions_list) was done after two
goto out_delete paths (strdup and btf__new failures). Since the
encoder is zalloc'd, elf_functions_list.next is NULL, so
btf_encoder__delete() → elf_functions_list__clear() → list_for_each_safe
dereferences NULL. Move the INIT_LIST_HEAD to the start.
2. btf_encoder__delete() iterates secinfo[] unconditionally, but if
the calloc for secinfo fails, the pointer is NULL while seccnt is
already set, causing a NULL dereference. Add a NULL guard.
3. Add NULL check for elf_strptr() return value before strcmp() in
btf_encoder__write_elf(), preventing a crash on malformed ELF files.
Before: NULL deref on strdup failure + secinfo alloc failure + bad ELF
After: clean error paths in all cases
Fixes: 99e78ff34f8171f9 ("btf_encoder: Introduce elf_functions_list")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 0fbd7e3420b0457f..5c12e79f5ef648ba 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -2029,7 +2029,7 @@ static int btf_encoder__write_elf(struct btf_encoder *encoder, const struct btf
if (shdr == NULL)
continue;
char *secname = elf_strptr(elf, strndx, shdr->sh_name);
- if (strcmp(secname, btf_secname) == 0) {
+ if (secname != NULL && strcmp(secname, btf_secname) == 0) {
btf_data = elf_getdata(scn, btf_data);
break;
}
@@ -2780,6 +2780,7 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
struct elf_functions *funcs = NULL;
if (encoder) {
+ INIT_LIST_HEAD(&encoder->elf_functions_list);
encoder->cu = cu;
encoder->raw_output = detached_filename != NULL;
encoder->source_filename = strdup(cu->filename);
@@ -2818,7 +2819,6 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
if (conf_load->encode_btf_global_vars)
encoder->encode_vars |= BTF_VAR_GLOBAL;
- INIT_LIST_HEAD(&encoder->elf_functions_list);
funcs = btf_encoder__elf_functions(encoder);
if (!funcs)
goto out_delete;
@@ -2912,9 +2912,11 @@ void btf_encoder__delete(struct btf_encoder *encoder)
if (encoder == NULL)
return;
- for (shndx = 0; shndx < encoder->seccnt; shndx++)
- __gobuffer__delete(&encoder->secinfo[shndx].secinfo);
- free(encoder->secinfo);
+ if (encoder->secinfo) {
+ for (shndx = 0; shndx < encoder->seccnt; shndx++)
+ __gobuffer__delete(&encoder->secinfo[shndx].secinfo);
+ free(encoder->secinfo);
+ }
zfree(&encoder->filename);
zfree(&encoder->source_filename);
btf__free(encoder->btf);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 14/31] btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (12 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 13/31] btf_encoder: Fix early cleanup crashes in btf_encoder__new/delete Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 15/31] dwarf_loader: Fix --fixup_silly_bitfields condition check Arnaldo Carvalho de Melo
` (16 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Several elf_strptr() calls in btf_encoder and libctf lacked NULL checks,
which would cause segfaults on malformed ELF files:
1. btf_encoder__new(): strcmp(secname, PERCPU_SECTION) crashes if
elf_section_by_idx() returns a valid section but elf_strptr() fails
internally.
2. btf_encoder__collect_kfuncs(): two elf_strptr() calls for symbol
names passed to strstarts()/get_func_name() without NULL guards.
3. libctf.c ctf__encode(): strcmp(secname, ".SUNW_ctf") without
NULL check.
Also fix is_sym_kfunc_set() bounds check: the original `off >= d_size`
only verified the start offset, but accessing set->flags could read
past the buffer. Changed to `off + sizeof(*set) > d_size` and added
an `off < 0` guard to prevent signed-to-unsigned wraparound when the
symbol address is below the section base.
Before: malformed ELF with invalid string table causes SIGSEGV
After: gracefully skips bad entries
Fixes: 72e88f29c6f7e142 ("pahole: Inject kfunc decl tags into BTF")
Fixes: ff34e733a0c23bf4 ("btf_encoder: Allow encoding VARs from many sections")
Fixes: dcef613288086156 ("libctf: give up "for now" on using libelf to add a section to an existing file")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 10 ++++++----
libctf.c | 2 +-
pahole.c | 14 +++++++++++---
3 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 5c12e79f5ef648ba..c7b71b5b741bfa6f 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -2099,14 +2099,14 @@ static int is_sym_kfunc_set(GElf_Sym *sym, const char *name, Elf_Data *idlist, s
{
void *ptr = idlist->d_buf;
struct btf_id_set8 *set;
- size_t off;
+ ptrdiff_t off;
/* kfuncs are only found in BTF_SET8's */
if (!strstarts(name, BTF_ID_SET8_PFX))
return false;
off = sym->st_value - idlist_addr;
- if (off >= idlist->d_size) {
+ if (off < 0 || (size_t)off + sizeof(*set) > idlist->d_size) {
fprintf(stderr, "%s: symbol '%s' out of bounds\n", __func__, name);
return false;
}
@@ -2276,7 +2276,7 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
continue;
name = elf_strptr(elf, strtabidx, sym.st_name);
- if (!is_sym_kfunc_set(&sym, name, idlist, idlist_addr))
+ if (name == NULL || !is_sym_kfunc_set(&sym, name, idlist, idlist_addr))
continue;
range.start = sym.st_value;
@@ -2305,6 +2305,8 @@ static int btf_encoder__collect_kfuncs(struct btf_encoder *encoder)
continue;
name = elf_strptr(elf, strtabidx, sym.st_name);
+ if (name == NULL)
+ continue;
func = get_func_name(name);
if (!func)
continue;
@@ -2879,7 +2881,7 @@ struct btf_encoder *btf_encoder__new(struct cu *cu, const char *detached_filenam
if (encoder->encode_vars & BTF_VAR_GLOBAL)
encoder->secinfo[shndx].include = true;
- if (strcmp(secname, PERCPU_SECTION) == 0) {
+ if (secname != NULL && strcmp(secname, PERCPU_SECTION) == 0) {
found_percpu = true;
if (encoder->encode_vars & BTF_VAR_PERCPU)
encoder->secinfo[shndx].include = true;
diff --git a/libctf.c b/libctf.c
index 8e31e3d550ebd51a..72f9949a2d25b3d9 100644
--- a/libctf.c
+++ b/libctf.c
@@ -674,7 +674,7 @@ int ctf__encode(struct ctf *ctf, uint8_t flags)
if (shdr == NULL)
continue;
char *secname = elf_strptr(elf, strndx, shdr->sh_name);
- if (strcmp(secname, ".SUNW_ctf") == 0) {
+ if (secname != NULL && strcmp(secname, ".SUNW_ctf") == 0) {
data = elf_getdata(scn, data);
goto out_update;
}
diff --git a/pahole.c b/pahole.c
index 6ba3f578c28570a1..0e2acc35d6d679f0 100644
--- a/pahole.c
+++ b/pahole.c
@@ -2361,6 +2361,11 @@ static int pipe_seek(FILE *fp, off_t offset)
chunk = offset;
}
+ /* On EOF (not I/O error), clear errno so callers don't
+ * pick up a stale value from an earlier successful fread. */
+ if (!ferror(fp))
+ errno = 0;
+
return offset == 0 ? 0 : -1;
}
@@ -2583,8 +2588,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
if (instance == NULL)
return -ENOMEM;
+ errno = 0;
if (type__instance_read_once(header, input) < 0) {
- printed = -errno;
+ printed = errno ? -errno : -EIO;
fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
goto out;
}
@@ -2666,8 +2672,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
free(member_name);
+ errno = 0;
if (pipe_seek(input, seek_bytes) < 0) {
- printed = -errno;
+ printed = errno ? -errno : -EIO;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
goto out;
}
@@ -2717,8 +2724,9 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
seek_bytes -= ftell(input);
}
+ errno = 0;
if (pipe_seek(input, seek_bytes) < 0) {
- printed = -errno;
+ printed = errno ? -errno : -EIO;
fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
goto out;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 15/31] dwarf_loader: Fix --fixup_silly_bitfields condition check
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (13 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 14/31] btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 16/31] dwarf_loader: Skip libdw__lock when elfutils is built thread-safe Arnaldo Carvalho de Melo
` (15 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo, Andrii Nakryiko
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The condition for detecting silly bitfields (bitfields that use all bits
of their underlying type, e.g. "unsigned int x:32") was inverted since
commit 03d9b6ebcac7422f ("dwarf_loader: Fix bitfield fixup logic for
DWARF").
The check compared byte_size == 8 * bitfield_size, but byte_size is in
bytes and bitfield_size is in bits. For a "unsigned char val:8" silly
bitfield: byte_size=1, bitfield_size=8, so 1 == 8*8 = 64 was always
false.
Fix by multiplying byte_size by 8 to convert to bits before comparing:
byte_size * 8 == bitfield_size.
Before:
$ pahole --fixup_silly_bitfields -C silly test.o
unsigned int reserved:32; /* not fixed */
unsigned char flags:8; /* not fixed */
After:
$ pahole --fixup_silly_bitfields -C silly test.o
unsigned int reserved; /* fixed */
unsigned char flags; /* fixed */
Fixes: 03d9b6ebcac7422f ("dwarf_loader: Fix bitfield fixup logic for DWARF")
Cc: Andrii Nakryiko <andriin@fb.com>
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index ddf7cdcfd467beb4..ce0bead0e76e1876 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -3798,10 +3798,14 @@ static int class_member__cache_byte_size(struct tag *tag, struct cu *cu,
member->byte_offset += member->byte_size;
}
+ /* A "silly" bitfield uses all bits of its underlying type
+ * (e.g. unsigned char val:8). Convert it to a plain field,
+ * but only when byte-aligned — in packed structs a full-size
+ * bitfield can start at a non-zero bit offset. */
if (conf_load && conf_load->fixup_silly_bitfields &&
- member->byte_size == 8 * member->bitfield_size) {
+ member->byte_size * 8 == member->bitfield_size &&
+ member->bitfield_offset == 0) {
member->bitfield_size = 0;
- member->bitfield_offset = 0;
}
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 16/31] dwarf_loader: Skip libdw__lock when elfutils is built thread-safe
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (14 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 15/31] dwarf_loader: Fix --fixup_silly_bitfields condition check Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 17/31] dwarf_loader: Fix data race in tag__init() decl_file string cache Arnaldo Carvalho de Melo
` (14 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo, Eduard Zingerman,
Aaron Merey
From: Arnaldo Carvalho de Melo <acme@redhat.com>
pahole serializes dwarf_getlocation(), dwarf_decl_file(), and
__dwarf_getlocations() calls behind libdw__lock because libdw's
internal caches (tsearch trees) are not thread-safe by default.
This mutex was added in commits 1caed1c443d4 ("Add a lock around
dwarf_decl_file() and dwarf_decl_line() calls") and 65b7fd68ccbb
("Use libdw__lock for dwarf_getlocation(s)") to work around
concurrency bugs in libdw.
Since then, elfutils has fixed the underlying issues:
- 0.194: eu_tsearch with proper locking (ec21fbb47)
- 0.194: rwlock replaced with __atomic builtins (680eb3021)
- 0.195: additional last_abbrev_offset protection (8fa2cd1d3)
When elfutils is compiled with --enable-thread-safety it defines
_ELFUTILS_THREAD_SAFE in <elfutils/version.h>. Use this to skip
the pahole-side mutex entirely, eliminating the serialization
overhead.
Profiling shows pthread_rwlock_tryrdlock + unlock from libdw's
internal locking accounts for ~15.6% of serial encoding time.
That overhead also disappears when elfutils is built thread-safe
(rwlock replaced with lock-free atomics in 0.194).
Before (serial, elfutils without --enable-thread-safety):
15.6% in pthread_rwlock_{tryrdlock,unlock} (libdw internal)
Plus serialization from pahole's libdw__lock mutex
After (serial, elfutils with --enable-thread-safety):
Both overheads eliminated
Cc: Alan Maguire <alan.maguire@oracle.com>
Cc: Eduard Zingerman <eddyz87@gmail.com>
Cc: Aaron Merey <amerey@redhat.com>
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index ce0bead0e76e1876..759883cad3fce693 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -57,7 +57,25 @@
#define EM_RISCV 243
#endif
+/*
+ * libdw internal caches (tsearch trees for locations, decl file/line, etc.)
+ * are not thread-safe unless elfutils was compiled with --enable-thread-safety.
+ * When that option is active, elfutils >= 0.194 uses eu_tsearch with proper
+ * locking and >= 0.194 also replaces the abbrev rwlock with __atomic builtins,
+ * eliminating the overhead that rwlock_tryrdlock imposed even in serial mode.
+ *
+ * _ELFUTILS_THREAD_SAFE is defined in <elfutils/version.h> when the library
+ * was built thread-safe. When present we skip our own mutex; when absent we
+ * serialize the racy calls ourselves.
+ */
+#ifdef _ELFUTILS_THREAD_SAFE
+static inline void libdw__lock_lock(void) { }
+static inline void libdw__lock_unlock(void) { }
+#else
static pthread_mutex_t libdw__lock = PTHREAD_MUTEX_INITIALIZER;
+static inline void libdw__lock_lock(void) { pthread_mutex_lock(&libdw__lock); }
+static inline void libdw__lock_unlock(void) { pthread_mutex_unlock(&libdw__lock); }
+#endif
static uint32_t hashtags__bits = 12;
static uint32_t max_hashtags__bits = 21;
@@ -453,11 +471,7 @@ static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen)
int ret = 1;
if (dwarf_attr(die, DW_AT_location, &attr) != NULL) {
- /* use libdw__lock as dwarf_getlocation(s) has concurrency
- * issues when libdw is not compiled with experimental
- * --enable-thread-safety
- */
- pthread_mutex_lock(&libdw__lock);
+ libdw__lock_lock();
if (dwarf_getlocation(&attr, expr, exprlen) == 0) {
/* DW_OP_addrx needs additional lookup for real addr. */
if (*exprlen != 0 && expr[0]->atom == DW_OP_addrx) {
@@ -471,7 +485,7 @@ static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen)
}
ret = 0;
}
- pthread_mutex_unlock(&libdw__lock);
+ libdw__lock_unlock();
}
return ret;
@@ -516,7 +530,7 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
tag->attributes = NULL;
if (cu->extra_dbg_info) {
- pthread_mutex_lock(&libdw__lock);
+ libdw__lock_lock();
int32_t decl_line;
const char *decl_file = dwarf_decl_file(die);
@@ -531,7 +545,7 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
dwarf_decl_line(die, &decl_line);
dtag->decl_line = decl_line;
- pthread_mutex_unlock(&libdw__lock);
+ libdw__lock_unlock();
}
INIT_LIST_HEAD(&tag->node);
@@ -1470,7 +1484,7 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
ptrdiff_t offset = 0;
int loc_num = -1;
- pthread_mutex_lock(&libdw__lock);
+ libdw__lock_lock();
while ((offset = __dwarf_getlocations(attr, offset, &base, &start, &end, &expr, &exprlen)) > 0) {
bool had_stack_value;
@@ -1515,7 +1529,7 @@ static void parameter__decode_location(Dwarf_Attribute *attr, struct conf_load *
break;
}
}
- pthread_mutex_unlock(&libdw__lock);
+ libdw__lock_unlock();
parameter__finish_piece_decode(parm, die, conf, cu);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 17/31] dwarf_loader: Fix data race in tag__init() decl_file string cache
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (15 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 16/31] dwarf_loader: Skip libdw__lock when elfutils is built thread-safe Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 18/31] pahole: Fix parse_btf_features("all") being a silent no-op Arnaldo Carvalho de Melo
` (13 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The static variables last_decl_file and last_decl_file_ptr cache the
most recent dwarf_decl_file() result to avoid redundant strdup() calls
when consecutive DIEs share the same declaration file.
However, multiple worker threads call tag__init() concurrently, and
this cache is a pahole-internal optimization unrelated to libdw thread
safety. When _ELFUTILS_THREAD_SAFE makes libdw__lock a no-op, threads
race on these statics — one thread can see another's stale pointer,
skipping the strdup and assigning an incorrect (or freed) filename.
Make them __thread so each worker gets its own cache. The caching
still works well since DWARF CUs are processed per-thread and DIEs
within a CU tend to share the same source file:
Before (__thread): j=1: 6.63s, j=4: 2.32s, j=32: 2.13s
After (__thread): same — no measurable overhead from TLS access
Fixes: a93160df5335ed2e ("dwarf_loader: Make dwarf_tag->decl_file a real string")
Reported-by: Sashiko:gemini-3-1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarf_loader.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 759883cad3fce693..04dffae3504efa06 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -534,7 +534,14 @@ static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die)
int32_t decl_line;
const char *decl_file = dwarf_decl_file(die);
- static const char *last_decl_file, *last_decl_file_ptr;
+ /*
+ * Per-thread string dedup cache: avoids strdup() when
+ * consecutive DIEs share the same decl_file pointer.
+ * Must be __thread, not plain static, because multiple
+ * worker threads call tag__init() concurrently and a
+ * shared cache would race regardless of libdw locking.
+ */
+ static __thread const char *last_decl_file, *last_decl_file_ptr;
if (decl_file != last_decl_file_ptr) {
last_decl_file = decl_file ? strdup(decl_file) : NULL;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 18/31] pahole: Fix parse_btf_features("all") being a silent no-op
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (16 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 17/31] dwarf_loader: Fix data race in tag__init() decl_file string cache Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 19/31] dwarves: Fix variable shadowing in __cus__find_struct_by_name() Arnaldo Carvalho de Melo
` (12 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
"all" was not recognized as a feature name, so
parse_btf_features("all") silently did nothing — no features were
enabled and no error was reported.
Add btf_features__enable_all() that enables every feature in the
btf_features[] table, and wire it into parse_btf_features() both as
a standalone value and as a token in comma-separated lists.
Before: --btf_features=all silently enabled nothing.
After: --btf_features=all enables all features in the table.
Fixes: 7bc9b9975545ab53 ("pahole: Add --btf_features support")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/pahole.c b/pahole.c
index 0e2acc35d6d679f0..a6012e6a157293e9 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1299,6 +1299,22 @@ static void btf_features__enable_default(void)
}
}
+static void btf_features__enable_all(void)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(btf_features); i++) {
+ /* distilled_base requires a --btf_base argument to
+ * provide the base BTF that btf__distill_base() will
+ * split against. Enabling it unconditionally causes
+ * btf_encoder__encode() to call btf__distill_base()
+ * on standalone objects like vmlinux, which returns
+ * -EINVAL and makes pahole fail fatally. Users who
+ * want distilled_base must request it explicitly. */
+ if (btf_features[i].conf_value == &conf_load.btf_gen_distilled_base)
+ continue;
+ enable_btf_feature(&btf_features[i]);
+ }
+}
+
/* Translate --btf_features=feature1[,feature2] into conf_load values.
* Explicitly ignores unrecognized features to allow future specification
* of new opt-in features.
@@ -1315,6 +1331,11 @@ static void parse_btf_features(const char *features, bool strict)
return;
}
+ if (strcmp(features, "all") == 0) {
+ btf_features__enable_all();
+ return;
+ }
+
// Adding extra features to the set of standard features.
if (strstarts(features, "+")) {
btf_features__enable_default();
@@ -1332,6 +1353,8 @@ static void parse_btf_features(const char *features, bool strict)
*/
if (strcmp(feature_name, "default") == 0) {
btf_features__enable_default();
+ } else if (strcmp(feature_name, "all") == 0) {
+ btf_features__enable_all();
} else if (strict) {
fprintf(stderr, "Feature '%s' in '%s' is not supported. Supported BTF features are:\n",
feature_name, features);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 19/31] dwarves: Fix variable shadowing in __cus__find_struct_by_name()
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (17 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 18/31] pahole: Fix parse_btf_features("all") being a silent no-op Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 20/31] dutil: Add exec_objcopy() shell-injection-safe helper Arnaldo Carvalho de Melo
` (11 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The inner loop declared 'struct tag *tag' which shadowed the outer
'struct tag *tag = NULL' at function scope. When the inner lookup
succeeded and we broke out of the loop, the function returned the
outer (still NULL) tag instead of the found one.
This broke any tool that loads CUs without a steal callback and then
calls cus__find_struct_by_name() — notably ctracer. Tools like pahole
and pfunct were unaffected because they use a steal callback to process
each CU as it arrives, calling per-CU cu__find_struct_by_name() which
has no shadowing issue.
Before: ctracer loads 3291 CUs, cus__find_struct_by_name() returns NULL
After: ctracer loads 3291 CUs, cus__find_struct_by_name() finds the struct
Fixes: fb99cad539e58638 ("dwarf_loader: Parallel DWARF loading")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dwarves.c b/dwarves.c
index 5c9d83a6dee897c2..94bab301d598695d 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -1049,7 +1049,8 @@ static struct tag *__cus__find_struct_by_name(struct cus *cus, struct cu **cu, c
cus__lock(cus);
list_for_each_entry(pos, &cus->cus, node) {
- struct tag *tag = __cu__find_struct_by_name(pos, name, include_decls, unions, id);
+ /* Don't shadow the outer 'tag' — we need to return it */
+ tag = __cu__find_struct_by_name(pos, name, include_decls, unions, id);
if (tag != NULL) {
if (cu != NULL)
*cu = pos;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 20/31] dutil: Add exec_objcopy() shell-injection-safe helper
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (18 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 19/31] dwarves: Fix variable shadowing in __cus__find_struct_by_name() Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 21/31] btf_encoder: Fall back to objcopy when llvm-objcopy is not available Arnaldo Carvalho de Melo
` (10 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Add exec_objcopy() that runs objcopy via posix_spawnp() instead of
system(), eliminating shell injection risk from filenames containing
shell metacharacters.
Signal handling follows POSIX system() semantics: SIGINT/SIGQUIT
ignored in parent during wait, SIGCHLD blocked, default dispositions
restored in child.
No callers yet — the next commit converts btf_encoder and libctf.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dutil.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dutil.h | 3 +++
2 files changed, 75 insertions(+)
diff --git a/dutil.c b/dutil.c
index 14f134016add1f0c..38101f184faf58ee 100644
--- a/dutil.c
+++ b/dutil.c
@@ -9,9 +9,15 @@
#include <ctype.h>
#include <errno.h>
+#include <signal.h>
+#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+extern char **environ;
void *zalloc(size_t size)
{
@@ -234,3 +240,69 @@ char *strlwr(char *s)
return s;
}
+
+int exec_objcopy(const char *objcopy, const char *add_section,
+ const char *filename)
+{
+ sigset_t mask, orig;
+ struct sigaction ign, old_int, old_quit;
+ posix_spawnattr_t attr;
+ sigset_t child_default;
+ pid_t pid;
+ int rc;
+
+ char *argv[] = {
+ (char *)objcopy,
+ "--add-section", (char *)add_section,
+ "--", (char *)filename,
+ NULL
+ };
+
+ /* Ignore SIGINT/SIGQUIT in parent while waiting, per POSIX system() */
+ ign.sa_handler = SIG_IGN;
+ sigemptyset(&ign.sa_mask);
+ ign.sa_flags = 0;
+ sigaction(SIGINT, &ign, &old_int);
+ sigaction(SIGQUIT, &ign, &old_quit);
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ pthread_sigmask(SIG_BLOCK, &mask, &orig);
+
+ posix_spawnattr_init(&attr);
+ /* Restore default signal disposition and mask in child */
+ sigemptyset(&child_default);
+ sigaddset(&child_default, SIGINT);
+ sigaddset(&child_default, SIGQUIT);
+ posix_spawnattr_setsigdefault(&attr, &child_default);
+ posix_spawnattr_setsigmask(&attr, &orig);
+ posix_spawnattr_setflags(&attr,
+ POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);
+
+ rc = posix_spawnp(&pid, objcopy, NULL, &attr, argv, environ);
+ posix_spawnattr_destroy(&attr);
+
+ if (rc != 0) {
+ sigaction(SIGINT, &old_int, NULL);
+ sigaction(SIGQUIT, &old_quit, NULL);
+ pthread_sigmask(SIG_SETMASK, &orig, NULL);
+ return rc == ENOENT ? -ENOENT : -1;
+ }
+
+ int status;
+
+ while (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ status = -1;
+ break;
+ }
+ }
+
+ sigaction(SIGINT, &old_int, NULL);
+ sigaction(SIGQUIT, &old_quit, NULL);
+ pthread_sigmask(SIG_SETMASK, &orig, NULL);
+
+ if (status == -1 || !WIFEXITED(status))
+ return -1;
+
+ return WEXITSTATUS(status) == 0 ? 0 : -1;
+}
diff --git a/dutil.h b/dutil.h
index be02c68cf4a7ff22..603556fa03085abf 100644
--- a/dutil.h
+++ b/dutil.h
@@ -351,6 +351,9 @@ static inline int elf_getshdrstrndx(Elf *elf, size_t *dst)
char *strlwr(char *s);
+int exec_objcopy(const char *objcopy, const char *add_section,
+ const char *filename);
+
void __zfree(void **ptr);
#define zfree(ptr) __zfree((void **)(ptr))
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 21/31] btf_encoder: Fall back to objcopy when llvm-objcopy is not available
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (19 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 20/31] dutil: Add exec_objcopy() shell-injection-safe helper Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 22/31] dwarf_loader, btf_loader: Replace stale FIXME/XXX comments with explanations Arnaldo Carvalho de Melo
` (9 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Switch btf_encoder and libctf from system() to exec_objcopy() (previous
commit). Probe LLVM_OBJCOPY and OBJCOPY environment variables, falling
back to llvm-objcopy then objcopy path search.
Before: hard failure if llvm-objcopy not in PATH
After: tries llvm-objcopy, falls back to objcopy, respects env vars
Reported-by: Sashiko:gemini-3-1-pro-preview # Running on a local machine
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 44 +++++++++++++++++++++++++++++++++-----------
libctf.c | 7 +++----
2 files changed, 36 insertions(+), 15 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index c7b71b5b741bfa6f..c7ebaec4829d121a 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -2049,13 +2049,19 @@ static int btf_encoder__write_elf(struct btf_encoder *encoder, const struct btf
else
elf_error("elf_update failed");
} else {
- const char *llvm_objcopy;
+ const char *objcopy;
char tmp_fn[PATH_MAX];
- char cmd[PATH_MAX * 2];
+ char add_section[PATH_MAX + 64];
- llvm_objcopy = getenv("LLVM_OBJCOPY");
- if (!llvm_objcopy)
- llvm_objcopy = "llvm-objcopy";
+ /*
+ * The kernel build uses $(OBJCOPY) --add-section for
+ * BTF since v5.2 (2019), supporting both GNU objcopy
+ * and llvm-objcopy interchangeably. Prefer
+ * llvm-objcopy, fall back to objcopy.
+ */
+ objcopy = getenv("LLVM_OBJCOPY");
+ if (!objcopy)
+ objcopy = getenv("OBJCOPY");
/* Use objcopy to add a .BTF section */
snprintf(tmp_fn, sizeof(tmp_fn), "%s.btf", filename);
@@ -2073,14 +2079,30 @@ static int btf_encoder__write_elf(struct btf_encoder *encoder, const struct btf
goto unlink;
}
- snprintf(cmd, sizeof(cmd), "%s --add-section %s=%s %s",
- llvm_objcopy, btf_secname, tmp_fn, filename);
- if (system(cmd)) {
- fprintf(stderr, "%s: failed to add %s section to '%s': %d!\n",
- __func__, btf_secname, filename, errno);
- goto unlink;
+ snprintf(add_section, sizeof(add_section), "%s=%s",
+ btf_secname, tmp_fn);
+
+ if (!objcopy) {
+ int rc = exec_objcopy("llvm-objcopy",
+ add_section, filename);
+ if (rc == 0)
+ goto success;
+
+ if (rc != -ENOENT) {
+ fprintf(stderr, "%s: failed to add %s section to '%s'\n",
+ __func__, btf_secname, filename);
+ goto unlink;
+ }
+
+ objcopy = "objcopy";
}
+ if (exec_objcopy(objcopy, add_section, filename)) {
+ fprintf(stderr, "%s: failed to add %s section to '%s'\n",
+ __func__, btf_secname, filename);
+ goto unlink;
+ }
+ success:
err = 0;
unlink:
unlink(tmp_fn);
diff --git a/libctf.c b/libctf.c
index 72f9949a2d25b3d9..51f75fbd66463506 100644
--- a/libctf.c
+++ b/libctf.c
@@ -746,10 +746,9 @@ found_SUNW_ctf_str:
if (close(fd) < 0)
goto out_unlink;
- char cmd[PATH_MAX * 2];
- snprintf(cmd, sizeof(cmd), "objcopy --add-section .SUNW_ctf=%s %s",
- pathname, ctf->filename);
- if (system(cmd) == 0)
+ char add_section[PATH_MAX + 32];
+ snprintf(add_section, sizeof(add_section), ".SUNW_ctf=%s", pathname);
+ if (exec_objcopy("objcopy", add_section, ctf->filename) == 0)
err = 0;
out_unlink:
unlink(pathname);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 22/31] dwarf_loader, btf_loader: Replace stale FIXME/XXX comments with explanations
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (20 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 21/31] btf_encoder: Fall back to objcopy when llvm-objcopy is not available Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 23/31] pahole: Skip inline expansions during BTF encoding Arnaldo Carvalho de Melo
` (8 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Several FIXME and XXX comments referenced issues that were already
resolved or described intentional behavior that was never going to
change. Replace them with proper explanations of why the code
behaves the way it does:
- Call site tags (DW_TAG_call_site): useful for debuggers, not for
pahole's type reconstruction.
- Formal parameters in inline expansions: duplicates of the abstract
origin's parameters, not needed for type reconstruction.
- Template value parameters on ftypes: already attached to the
ftype's template value param list and used by class__fprintf.
- DW_TAG_dwarf_procedure: compiler-internal DWARF expressions with
no type information to extract.
- BTF_KIND_DATASEC: describes runtime variable placement in ELF
sections, intentionally ignored for type reconstruction.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_loader.c | 5 +++--
dwarf_loader.c | 52 ++++++++++++++++++++++----------------------------
2 files changed, 26 insertions(+), 31 deletions(-)
diff --git a/btf_loader.c b/btf_loader.c
index caead39775da54da..5857c3b6d2fc4b2b 100644
--- a/btf_loader.c
+++ b/btf_loader.c
@@ -428,8 +428,9 @@ static int create_new_datasec(struct cu *cu __maybe_unused, const struct btf_typ
//cu__add_tag_with_id(cu, &datasec->tag, id);
/*
- * FIXME: this will not be used to reconstruct some original C code,
- * its about runtime placement of variables so just ignore this for now
+ * BTF_KIND_DATASEC describes runtime variable placement in ELF
+ * sections, not C type information. Not needed for pahole's
+ * type reconstruction, so intentionally ignored.
*/
return 0;
}
diff --git a/dwarf_loader.c b/dwarf_loader.c
index 04dffae3504efa06..61d55bd70d643731 100644
--- a/dwarf_loader.c
+++ b/dwarf_loader.c
@@ -2529,13 +2529,10 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
case DW_TAG_GNU_call_site:
case DW_TAG_GNU_call_site_parameter:
/*
- * FIXME: read http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open
- * and write proper support.
- *
- * From a quick read there is not much we can use in
- * the existing dwarves tools, so just stop warning the user,
- * developers will find these notes if wanting to use in a
- * new tool.
+ * Call site tags describe interprocedural call
+ * metadata (callee, parameters, return values).
+ * Useful for debuggers but not for pahole's type
+ * reconstruction. Silently skip them.
*/
continue;
case DW_TAG_lexical_block:
@@ -2544,15 +2541,10 @@ static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblo
continue;
case DW_TAG_formal_parameter:
/*
- * FIXME:
- * So far DW_TAG_inline_routine had just an
- * abstract origin, but starting with
- * /usr/lib/openoffice.org/basis3.0/program/libdbalx.so
- * I realized it really has to be handled as a
- * DW_TAG_function... Lets just get the types
- * for 1.8, then fix this properly.
- *
- * cu__tag_not_handled(cu, die);
+ * Inline expansions can have their own formal
+ * parameter children duplicating the abstract
+ * origin's parameters. These are not needed
+ * for type reconstruction — skip them.
*/
continue;
case DW_TAG_inlined_subroutine:
@@ -2635,13 +2627,10 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
case DW_TAG_GNU_call_site:
case DW_TAG_GNU_call_site_parameter:
/*
- * XXX: read http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open
- * and write proper support.
- *
- * From a quick read there is not much we can use in
- * the existing dwarves tools, so just stop warning the user,
- * developers will find these notes if wanting to use in a
- * new tool.
+ * Call site tags describe interprocedural call
+ * metadata (callee, parameters, return values).
+ * Useful for debuggers but not for pahole's type
+ * reconstruction. Silently skip them.
*/
continue;
case DW_TAG_dwarf_procedure:
@@ -2678,9 +2667,10 @@ static int die__process_function(Dwarf_Die *die, struct ftype *ftype,
continue;
}
case DW_TAG_template_value_parameter: {
- /* FIXME: probably we'll have to attach this as a list of
- * template parameters to use at class__fprintf time...
- * See die__process_class */
+ /*
+ * Attached to the ftype's template value param list,
+ * used by class__fprintf for C++ template display.
+ */
struct template_value_param *tvparm = template_value_param__new(die, cu, conf);
if (tvparm == NULL)
@@ -2868,10 +2858,14 @@ static int die__process_unit(Dwarf_Die *die, struct cu *cu, struct conf_load *co
return -ENOMEM;
if (tag == &unsupported_tag) {
- // XXX special case DW_TAG_dwarf_procedure, appears when looking at a recent ~/bin/perf
- // Investigate later how to properly support this...
+ /*
+ * DW_TAG_dwarf_procedure: compiler-internal
+ * DWARF expressions, no type info to extract.
+ * DW_TAG_label: skipped via conf->ignore_labels.
+ * DW_TAG_GNU_annotation: handled elsewhere.
+ */
if (dwarf_tag(die) != DW_TAG_dwarf_procedure &&
- dwarf_tag(die) != DW_TAG_label && // conf->ignore_labels == true, see die__process_tag()
+ dwarf_tag(die) != DW_TAG_label &&
dwarf_tag(die) != DW_TAG_GNU_annotation)
tag__print_not_supported(die);
continue;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 23/31] pahole: Skip inline expansions during BTF encoding
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (21 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 22/31] dwarf_loader, btf_loader: Replace stale FIXME/XXX comments with explanations Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 24/31] pahole: Use fseek for seekable files in --prettify and --seek_bytes Arnaldo Carvalho de Melo
` (7 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Enable ignore_inline_expansions for BTF encoding (-J). Inline
expansions (DW_TAG_inlined_subroutine) describe code-flow inlining
decisions — the BTF encoder never uses them (it encodes types, not
code structure).
Skipping them avoids allocating and recursively processing one of
the most numerous DWARF tag types in a vmlinux, reducing memory
usage and DWARF processing time for BTF generation.
This mirrors the existing ignore_labels optimization that was
already enabled for BTF encoding.
In a vmlinux with 548,620 DW_TAG_inlined_subroutine DIEs (the 6th
most common tag type):
Before (processing inline expansions):
$ time pahole -J --btf_encode_detached /tmp/t.btf vmlinux
7.41s, 1057 MB max RSS
After (skipping inline expansions):
$ time pahole -J --btf_encode_detached /tmp/t.btf vmlinux
7.06s, 1042 MB max RSS
~5% faster BTF encoding, 15 MB less peak memory. The savings
scale with the inline expansion count — heavily-inlined codebases
like the kernel benefit the most.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/pahole.c b/pahole.c
index a6012e6a157293e9..65becc938cb0fc01 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1891,9 +1891,7 @@ static error_t pahole__options_parser(int key, char *arg,
case 'J': btf_encode = 1;
conf_load.get_addr_info = true;
conf_load.ignore_alignment_attr = true;
- // XXX for now, test this more thoroughly
- // We may have some references from formal parameters, etc, (abstract_origin)
- // conf_load.ignore_inline_expansions = true;
+ conf_load.ignore_inline_expansions = true;
conf_load.ignore_labels = true;
conf_load.use_obstack = true;
no_bitfield_type_recode = true; break;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 24/31] pahole: Use fseek for seekable files in --prettify and --seek_bytes
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (22 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 23/31] pahole: Skip inline expansions during BTF encoding Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 25/31] pahole: Guard pipe_seek() against negative offsets Arnaldo Carvalho de Melo
` (6 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
pipe_seek() simulates forward seeking by reading and discarding data
in 4096-byte chunks. This was the only seek strategy because the
input could be stdin. But when --prettify_input_filename is a regular
file (the common case), the FILE pointer is seekable.
Use fseeko() first for both --range and --seek_bytes paths. If it
fails (ESPIPE on pipes/stdin), fall back to pipe_seek(). This gives:
- O(1) seeks for regular files instead of reading N bytes through
4096-byte chunks
- Backward seeks now work for regular files where --range offsets
point before the current read position (previously errored with
"can't go back in input")
- No behavior change for pipe/stdin input
Also remove a misleading FIXME on the structures__add find-or-insert
pattern — the code was already correct and the allocation is needed
for deduplication.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 69 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 39 insertions(+), 30 deletions(-)
diff --git a/pahole.c b/pahole.c
index 65becc938cb0fc01..cceaf9030d442248 100644
--- a/pahole.c
+++ b/pahole.c
@@ -1042,7 +1042,7 @@ static int type__print_containers(struct type *type, struct cu *cu, uint32_t con
return 0;
if (ident == 0) {
- bool existing_entry; // FIXME: This should really just search, no need to try to add it.
+ bool existing_entry;
struct structure *str = structures__add(type__class(type), cu, 0, &existing_entry);
if (str == NULL) {
fprintf(stderr, "pahole: insufficient memory for "
@@ -2651,23 +2651,35 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
free(member_name);
- off_t total_read_bytes = ftell(input);
-
- // Since we're reading input, we need to account for what we already read
- // FIXME: we now have a FILE pointer that _may_ be stdin, but not necessarily
- if (seek_bytes < total_read_bytes) {
- fprintf(stderr, "pahole: can't go back in input, already read %" PRIu64 " bytes, can't go to position %#" PRIx64 "\n",
- total_read_bytes, seek_bytes);
- printed = -ENOMEM;
- goto out;
- }
-
if (global_verbose) {
fprintf(output, "pahole: range.seek_bytes evaluated from range=%s is %#" PRIx64 " \n",
range, seek_bytes);
}
- seek_bytes -= total_read_bytes;
+ /*
+ * Use fseek() for seekable files (regular files opened
+ * by path). Fall back to forward-reading pipe_seek()
+ * for non-seekable streams (stdin, pipes).
+ */
+ if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
+ off_t total_read_bytes = ftell(input);
+
+ if (seek_bytes < total_read_bytes) {
+ fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRIu64 " bytes, target %#" PRIx64 "\n",
+ total_read_bytes, seek_bytes);
+ printed = -ESPIPE;
+ goto out;
+ }
+
+ seek_bytes -= total_read_bytes;
+
+ errno = 0;
+ if (pipe_seek(input, seek_bytes) < 0) {
+ printed = errno ? -errno : -EIO;
+ fprintf(stderr, "Couldn't seek to offset %" PRIu64 " for range=%s\n", seek_bytes, range);
+ goto out;
+ }
+ }
if (asprintf(&member_name, "%s.%s", range, "size") == -1) {
fprintf(stderr, "pahole: not enough memory for range=%s\n", range);
@@ -2693,13 +2705,6 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
free(member_name);
- errno = 0;
- if (pipe_seek(input, seek_bytes) < 0) {
- printed = errno ? -errno : -EIO;
- fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
- goto out;
- }
-
goto do_read;
}
@@ -2740,16 +2745,20 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
}
- if (header) {
- // Since we're reading input, we need to account for already read header:
- seek_bytes -= ftell(input);
- }
-
- errno = 0;
- if (pipe_seek(input, seek_bytes) < 0) {
- printed = errno ? -errno : -EIO;
- fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 "\n", conf.seek_bytes, seek_bytes);
- goto out;
+ /*
+ * Use fseek() for seekable files, fall back to
+ * forward-reading pipe_seek() for pipes/stdin.
+ */
+ if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
+ if (header)
+ seek_bytes -= ftell(input);
+
+ errno = 0;
+ if (pipe_seek(input, seek_bytes) < 0) {
+ printed = errno ? -errno : -EIO;
+ fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 ")\n", conf.seek_bytes, seek_bytes);
+ goto out;
+ }
}
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 25/31] pahole: Guard pipe_seek() against negative offsets
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (23 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 24/31] pahole: Use fseek for seekable files in --prettify and --seek_bytes Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 26/31] gobuffer: Remove 5 dead functions found via coverage analysis Arnaldo Carvalho de Melo
` (5 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
pipe_seek() simulates forward seeking on non-seekable streams by
reading and discarding chunks into a 4096-byte stack buffer. When
called with a negative offset, 'chunk' gets a negative value that is
implicitly cast to a huge size_t by fread(), causing a stack buffer
overflow.
This can happen when a user passes a negative --seek_bytes value:
fseeko() fails (SEEK_SET rejects negatives), then the fallback calls
pipe_seek() with the unchecked negative value.
Guard against this by rejecting negative offsets at the top of
pipe_seek(), and short-circuit zero offsets as a no-op.
Before:
$ echo | pahole --seek_bytes -1 --prettify - -C task_struct vmlinux
(stack buffer overflow / crash)
After:
$ echo | pahole --seek_bytes -1 --prettify - -C task_struct vmlinux
Couldn't --seek_bytes -1 (18446744073709551615)
Uncovered during code coverage analysis work.
Fixes: fe284221448c950d ("pahole: Introduce --seek_bytes")
Reported-by: Sashiko:gemini-3.1-pro-preview
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
pahole.c | 58 ++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 44 insertions(+), 14 deletions(-)
diff --git a/pahole.c b/pahole.c
index cceaf9030d442248..5cf92833e6e84df9 100644
--- a/pahole.c
+++ b/pahole.c
@@ -18,6 +18,7 @@
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <bpf/btf.h>
#include "bpf/libbpf.h"
@@ -2371,6 +2372,13 @@ static int pipe_seek(FILE *fp, off_t offset)
char bf[4096];
int chunk = sizeof(bf);
+ /* Negative offset would wrap chunk to a huge size_t in fread() */
+ if (offset < 0)
+ return -1;
+
+ if (offset == 0)
+ return 0;
+
if (chunk > offset)
chunk = offset;
@@ -2605,16 +2613,22 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
uint64_t size_bytes = ULLONG_MAX;
uint32_t count = 0;
uint32_t skip = conf.skip;
+ /* Bytes consumed from input so far, for absolute→relative seek on pipes */
+ off_t consumed = 0;
+ int64_t header_bytes;
if (instance == NULL)
return -ENOMEM;
+ /* Keep consumed in sync with bytes actually fread'd from input */
errno = 0;
- if (type__instance_read_once(header, input) < 0) {
+ header_bytes = type__instance_read_once(header, input);
+ if (header_bytes < 0) {
printed = errno ? -errno : -EIO;
fprintf(stderr, "pahole: --header (%s) type couldn't be read\n", conf.header_type);
goto out;
}
+ consumed = header_bytes;
if (conf.range || prototype->range) {
off_t seek_bytes;
@@ -2662,21 +2676,25 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
* for non-seekable streams (stdin, pipes).
*/
if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
- off_t total_read_bytes = ftell(input);
-
- if (seek_bytes < total_read_bytes) {
- fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRIu64 " bytes, target %#" PRIx64 "\n",
- total_read_bytes, seek_bytes);
+ if (seek_bytes < consumed) {
+ fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRId64 " bytes, target %#" PRIx64 "\n",
+ (int64_t)consumed, seek_bytes);
printed = -ESPIPE;
goto out;
}
- seek_bytes -= total_read_bytes;
-
errno = 0;
- if (pipe_seek(input, seek_bytes) < 0) {
+ if (pipe_seek(input, seek_bytes - consumed) < 0) {
printed = errno ? -errno : -EIO;
- fprintf(stderr, "Couldn't seek to offset %" PRIu64 " for range=%s\n", seek_bytes, range);
+ fprintf(stderr, "Couldn't seek to offset %" PRId64 " for range=%s\n", (int64_t)seek_bytes, range);
+ goto out;
+ }
+ } else {
+ struct stat sb;
+ if (fstat(fileno(input), &sb) == 0 && S_ISREG(sb.st_mode) && seek_bytes >= sb.st_size) {
+ fprintf(stderr, "pahole: seek offset %" PRId64 " is beyond file size %" PRId64 " for range=%s\n",
+ (int64_t)seek_bytes, (int64_t)sb.st_size, range);
+ printed = -EINVAL;
goto out;
}
}
@@ -2750,13 +2768,25 @@ static int prototype__stdio_fprintf_value(struct prototype *prototype, struct ty
* forward-reading pipe_seek() for pipes/stdin.
*/
if (fseeko(input, seek_bytes, SEEK_SET) != 0) {
- if (header)
- seek_bytes -= ftell(input);
+ if (seek_bytes < consumed) {
+ fprintf(stderr, "pahole: can't seek backward in non-seekable input, already read %" PRId64 " bytes, target %#" PRIx64 "\n",
+ (int64_t)consumed, seek_bytes);
+ printed = -ESPIPE;
+ goto out;
+ }
errno = 0;
- if (pipe_seek(input, seek_bytes) < 0) {
+ if (pipe_seek(input, seek_bytes - consumed) < 0) {
printed = errno ? -errno : -EIO;
- fprintf(stderr, "Couldn't --seek_bytes %s (%" PRIu64 ")\n", conf.seek_bytes, seek_bytes);
+ fprintf(stderr, "Couldn't --seek_bytes %s (%" PRId64 ")\n", conf.seek_bytes, (int64_t)seek_bytes);
+ goto out;
+ }
+ } else {
+ struct stat sb;
+ if (fstat(fileno(input), &sb) == 0 && S_ISREG(sb.st_mode) && seek_bytes >= sb.st_size) {
+ fprintf(stderr, "pahole: --seek_bytes %" PRId64 " is beyond file size %" PRId64 "\n",
+ (int64_t)seek_bytes, (int64_t)sb.st_size);
+ printed = -EINVAL;
goto out;
}
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 26/31] gobuffer: Remove 5 dead functions found via coverage analysis
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (24 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 25/31] pahole: Guard pipe_seek() against negative offsets Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 27/31] dwarves: Remove 6 " Arnaldo Carvalho de Melo
` (4 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Coverage analysis showed that 5 of 10 gobuffer functions have zero
callers anywhere in the codebase:
gobuffer__new: never called, dead since creation in be30be91
("gobuffer: Move the non string specific bits",
2008)
gobuffer__init: only caller was gobuffer__new
gobuffer__delete: never called (only __gobuffer__delete is used)
gobuffer__compress: last caller removed in 29fce8dc ("strings: use
BTF's string APIs for strings management", 2020)
gobuffer__sort: added in 2046cc50 for kfunc sorting (2024),
caller removed in c5677178 ("btf_encoder:
Refactor btf_encoder__tag_kfuncs()", 2025)
Removing them also drops the stdio.h and zlib.h includes and the
GOBUFFER__ZCHUNK constant that only gobuffer__compress used.
Before: 10 functions, 152 lines, 29% line coverage.
After: 5 functions, 77 lines, 55% line coverage.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
gobuffer.c | 74 ------------------------------------------------------
gobuffer.h | 8 ------
2 files changed, 82 deletions(-)
diff --git a/gobuffer.c b/gobuffer.c
index c439c48bb775593f..beeb677cfc3d7617 100644
--- a/gobuffer.c
+++ b/gobuffer.c
@@ -11,33 +11,12 @@
#include <search.h>
#include <stdint.h>
#include <stdlib.h>
-#include <stdio.h>
#include <string.h>
-#include <zlib.h>
#include <errno.h>
#include "dutil.h"
#define GOBUFFER__BCHUNK (8 * 1024)
-#define GOBUFFER__ZCHUNK (8 * 1024)
-
-void gobuffer__init(struct gobuffer *gb)
-{
- gb->entries = NULL;
- gb->nr_entries = gb->allocated_size = 0;
- /* 0 == NULL */
- gb->index = 1;
-}
-
-struct gobuffer *gobuffer__new(void)
-{
- struct gobuffer *gb = malloc(sizeof(*gb));
-
- if (gb != NULL)
- gobuffer__init(gb);
-
- return gb;
-}
void __gobuffer__delete(struct gobuffer *gb)
{
@@ -47,12 +26,6 @@ void __gobuffer__delete(struct gobuffer *gb)
zfree(&gb->entries);
}
-void gobuffer__delete(struct gobuffer *gb)
-{
- __gobuffer__delete(gb);
- free(gb);
-}
-
void *gobuffer__ptr(const struct gobuffer *gb, unsigned int s)
{
return s ? gb->entries + s : NULL;
@@ -102,50 +75,3 @@ void gobuffer__copy(const struct gobuffer *gb, void *dest)
}
}
-void gobuffer__sort(struct gobuffer *gb, unsigned int size, int (*compar)(const void *, const void *))
-{
- qsort(gb->entries, gb->nr_entries, size, compar);
-}
-
-const void *gobuffer__compress(struct gobuffer *gb, unsigned int *size)
-{
- z_stream z = {
- .zalloc = Z_NULL,
- .zfree = Z_NULL,
- .opaque = Z_NULL,
- .avail_in = gobuffer__size(gb),
- .next_in = (Bytef *)(gobuffer__entries(gb) ? : ""),
- };
- void *bf = NULL;
- unsigned int bf_size = 0;
-
- if (deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK)
- goto out_free;
-
- do {
- const unsigned int new_bf_size = bf_size + GOBUFFER__ZCHUNK;
- void *nbf = realloc(bf, new_bf_size);
-
- if (nbf == NULL)
- goto out_close_and_free;
-
- bf = nbf;
- z.avail_out = GOBUFFER__ZCHUNK;
- z.next_out = (Bytef *)bf + bf_size;
- bf_size = new_bf_size;
- if (deflate(&z, Z_FINISH) == Z_STREAM_ERROR)
- goto out_close_and_free;
- } while (z.avail_out == 0);
-
- deflateEnd(&z);
- *size = bf_size - z.avail_out;
-out:
- return bf;
-
-out_close_and_free:
- deflateEnd(&z);
-out_free:
- free(bf);
- bf = NULL;
- goto out;
-}
diff --git a/gobuffer.h b/gobuffer.h
index cd218b629898307f..f9c461fec87c172f 100644
--- a/gobuffer.h
+++ b/gobuffer.h
@@ -13,16 +13,10 @@ struct gobuffer {
unsigned int allocated_size;
};
-struct gobuffer *gobuffer__new(void);
-
-void gobuffer__init(struct gobuffer *gb);
-void gobuffer__delete(struct gobuffer *gb);
void __gobuffer__delete(struct gobuffer *gb);
void gobuffer__copy(const struct gobuffer *gb, void *dest);
-void gobuffer__sort(struct gobuffer *gb, unsigned int size, int (*compar)(const void *, const void *));
-
int gobuffer__add(struct gobuffer *gb, const void *s, unsigned int len);
int gobuffer__allocate(struct gobuffer *gb, unsigned int len);
@@ -43,6 +37,4 @@ static inline unsigned int gobuffer__size(const struct gobuffer *gb)
void *gobuffer__ptr(const struct gobuffer *gb, unsigned int s);
-const void *gobuffer__compress(struct gobuffer *gb, unsigned int *size);
-
#endif /* _GOBUFFER_H_ */
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 27/31] dwarves: Remove 6 dead functions found via coverage analysis
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (25 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 26/31] gobuffer: Remove 5 dead functions found via coverage analysis Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 28/31] pfunct, dwarves_fprintf: Mark file-local functions as static Arnaldo Carvalho de Melo
` (3 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Coverage analysis showed that 6 non-static functions in dwarves.c have
zero callers anywhere in the codebase:
base_type__language_defined: introduced in 51643cb290f9 ("core:
Introduce base_type__language_defined()", 2023) for use in
dwarves_emit.c, but that code path was superseded before
reaching the main branch — never called on this lineage.
cus__find_type_by_name: introduced in 7c12b234ee30 ("dwarves:
Introduce cus__find_type_by_name()", 2020), last callers
removed in 22f93766cf02 ("pahole: Support multiple types
for pretty printing", 2020).
cu__find_struct_or_union_by_name: introduced in da632a36862c
("dwarves: Introduce {cu,cus}__find_struct_or_union_by_name()
methods", 2018), last caller removed in 3c1a9a3329d9
("pahole: Make --contains look for more than just unions,
structs", 2020). The struct-only variant cu__find_struct_by_name
is used instead.
cus__find_struct_or_union_by_name: multi-CU wrapper introduced
alongside cu__find_struct_or_union_by_name in da632a36862c
(2018), never had any callers.
cus__find_cu_by_name: locking wrapper introduced in 874e750fb8bd
("core: Move cus__find_pair() from codiff to the core", 2021).
The static __cus__find_cu_by_name it wraps is still used by
cus__find_pair, but the public wrapper was never called.
class__find_bit_hole: introduced in 82892cd9dd97 ("[CLASSES]:
First stab at implementing class__find_bit_hole", 2006), last
caller removed in 57fce1596b9c ("[PAHOLE]: Use class__reorganize
in class__packable", 2007).
Removes the functions from dwarves.c and their declarations from
dwarves.h.
Before: 174 functions, 2027 lines, 63.6% line coverage.
After: 168 functions, 1975 lines, 65.3% line coverage.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves.c | 87 +------------------------------------------------------
dwarves.h | 13 ---------
2 files changed, 1 insertion(+), 99 deletions(-)
diff --git a/dwarves.c b/dwarves.c
index 94bab301d598695d..1748889bc051fcb4 100644
--- a/dwarves.c
+++ b/dwarves.c
@@ -311,29 +311,6 @@ static struct ase_type_name_to_size {
{ .name = NULL },
};
-bool base_type__language_defined(struct base_type *bt)
-{
- int i = 0;
- char bf[64];
- const char *name;
-
- if (bt->name_has_encoding)
- name = bt->name;
- else
- name = base_type__name(bt, bf, sizeof(bf));
-
- while (base_type_name_to_size_table[i].name != NULL) {
- if (bt->name_has_encoding) {
- if (strcmp(base_type_name_to_size_table[i].name, bt->name) == 0)
- return true;
- } else if (strcmp(base_type_name_to_size_table[i].name, name) == 0)
- return true;
- ++i;
- }
-
- return false;
-}
-
size_t base_type__name_to_size(struct base_type *bt, struct cu *cu)
{
int i = 0;
@@ -974,27 +951,7 @@ found:
return pos;
}
-struct tag *cus__find_type_by_name(struct cus *cus, struct cu **cu, const char *name,
- const int include_decls, type_id_t *id)
-{
- struct cu *pos;
- struct tag *tag = NULL;
-
- cus__lock(cus);
-
- list_for_each_entry(pos, &cus->cus, node) {
- tag = cu__find_type_by_name(pos, name, include_decls, id);
- if (tag != NULL) {
- if (cu != NULL)
- *cu = pos;
- break;
- }
- }
-
- cus__unlock(cus);
-
- return tag;
-}
+/* cus__find_type_by_name removed: dead code, no callers */
static struct tag *__cu__find_struct_by_name(const struct cu *cu, const char *name,
const int include_decls, bool unions, type_id_t *idp)
@@ -1034,12 +991,6 @@ struct tag *cu__find_struct_by_name(const struct cu *cu, const char *name,
return __cu__find_struct_by_name(cu, name, include_decls, false, idp);
}
-struct tag *cu__find_struct_or_union_by_name(const struct cu *cu, const char *name,
- const int include_decls, type_id_t *idp)
-{
- return __cu__find_struct_by_name(cu, name, include_decls, true, idp);
-}
-
static struct tag *__cus__find_struct_by_name(struct cus *cus, struct cu **cu, const char *name,
const int include_decls, bool unions, type_id_t *id)
{
@@ -1069,12 +1020,6 @@ struct tag *cus__find_struct_by_name(struct cus *cus, struct cu **cu, const char
return __cus__find_struct_by_name(cus, cu, name, include_decls, false, idp);
}
-struct tag *cus__find_struct_or_union_by_name(struct cus *cus, struct cu **cu, const char *name,
- const int include_decls, type_id_t *idp)
-{
- return __cus__find_struct_by_name(cus, cu, name, include_decls, true, idp);
-}
-
struct function *cu__find_function_at_addr(const struct cu *cu,
uint64_t addr)
{
@@ -1135,19 +1080,6 @@ out:
return pos;
}
-struct cu *cus__find_cu_by_name(struct cus *cus, const char *name)
-{
- struct cu *pos;
-
- cus__lock(cus);
-
- pos = __cus__find_cu_by_name(cus, name);
-
- cus__unlock(cus);
-
- return pos;
-}
-
struct cu *cus__find_pair(struct cus *cus, const char *name)
{
struct cu *cu;
@@ -1568,23 +1500,6 @@ bool class__has_flexible_array(struct class *class, const struct cu *cu)
return class->has_flexible_array;
}
-const struct class_member *class__find_bit_hole(const struct class *class,
- const struct class_member *trailer,
- const uint16_t bit_hole_size)
-{
- struct class_member *pos;
- const uint16_t byte_hole_size = bit_hole_size / 8;
-
- type__for_each_data_member(&class->type, pos)
- if (pos == trailer)
- break;
- else if (pos->hole >= byte_hole_size ||
- pos->bit_hole >= bit_hole_size)
- return pos;
-
- return NULL;
-}
-
void class__find_holes(struct class *class)
{
const struct type *ctype = &class->type;
diff --git a/dwarves.h b/dwarves.h
index 8f1640e4247d5115..99e9d183c853ea8b 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -196,17 +196,12 @@ void cus__remove(struct cus *cus, struct cu *cu);
void cus__print_error_msg(const char *progname, const struct cus *cus,
const char *filename, const int err);
struct cu *cus__find_pair(struct cus *cus, const char *name);
-struct cu *cus__find_cu_by_name(struct cus *cus, const char *name);
struct tag *cus__find_struct_by_name(struct cus *cus, struct cu **cu,
const char *name, const int include_decls,
type_id_t *id);
-struct tag *cus__find_struct_or_union_by_name(struct cus *cus, struct cu **cu,
- const char *name, const int include_decls, type_id_t *id);
void *cu__tag_alloc(struct cu *cu, size_t size);
void cu__tag_free(struct cu *cu, struct tag *tag);
struct tag *cu__find_type_by_name(const struct cu *cu, const char *name, const int include_decls, type_id_t *idp);
-struct tag *cus__find_type_by_name(struct cus *cus, struct cu **cu, const char *name,
- const int include_decls, type_id_t *id);
struct function *cus__find_function_at_addr(struct cus *cus, uint64_t addr, struct cu **cu);
void cus__for_each_cu(struct cus *cus, int (*iterator)(struct cu *cu, void *cookie),
void *cookie,
@@ -510,8 +505,6 @@ struct tag *cu__tag(const struct cu *cu, const uint32_t id);
struct tag *cu__type(const struct cu *cu, const type_id_t id);
struct tag *cu__find_struct_by_name(const struct cu *cu, const char *name,
const int include_decls, type_id_t *id);
-struct tag *cu__find_struct_or_union_by_name(const struct cu *cu, const char *name,
- const int include_decls, type_id_t *id);
bool cu__same_build_id(const struct cu *cu, const struct cu *other);
void cu__account_inline_expansions(struct cu *cu);
int cu__for_all_tags(struct cu *cu,
@@ -1530,10 +1523,6 @@ static inline int class__is_declaration(const struct class *cls)
return cls->type.declaration;
}
-const struct class_member *class__find_bit_hole(const struct class *cls,
- const struct class_member *trailer,
- const uint16_t bit_hole_size);
-
#define class__for_each_member_from(cls, from, pos) \
pos = list_prepare_entry(from, class__tags(cls), tag.node); \
list_for_each_entry_from(pos, class__tags(cls), tag.node) \
@@ -1605,8 +1594,6 @@ const char *base_type__name(const struct base_type *btype, char *bf, size_t len)
size_t base_type__name_to_size(struct base_type *btype, struct cu *cu);
-bool base_type__language_defined(struct base_type *bt);
-
struct array_type {
struct tag tag;
uint32_t *nr_entries;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 28/31] pfunct, dwarves_fprintf: Mark file-local functions as static
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (26 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 27/31] dwarves: Remove 6 " Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 29/31] btf_encoder: Fix multi-dimensional array encoding Arnaldo Carvalho de Melo
` (2 subsequent siblings)
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Coverage analysis found 3 functions that were never declared in any
header and are only called within their own translation unit, yet
lacked the 'static' qualifier:
pfunct.c:
elf_symtab__show: introduced in 640bfd9679b4 ("pfunct: Introduce
--symtab", 2009), always local to the pfunct
executable.
elf_symtabs__show: introduced in the same commit, wrapper that
iterates elf_symtab__show over multiple files.
dwarves_fprintf.c:
filename__read_int: introduced in 9f9588dc2bd8 ("fprintf: Add
alternative method for reading the data cacheline
size", 2021), only compiled on platforms without
_SC_LEVEL1_DCACHE_LINESIZE, only called by
cacheline_size() in the same file.
None were exported from shared libraries (pfunct is an executable;
filename__read_int is behind an #ifndef that evaluates to false on
Linux). Adding 'static' lets the compiler warn if they ever become
truly dead and enables better inlining/optimization.
Before: 3 functions global without external callers
After: 3 functions properly marked static
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
dwarves_fprintf.c | 2 +-
pfunct.c | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c
index e44ac05c3b9e7e73..d7edb0cb14a5b803 100644
--- a/dwarves_fprintf.c
+++ b/dwarves_fprintf.c
@@ -2242,7 +2242,7 @@ void cus__print_error_msg(const char *progname, const struct cus *cus,
}
#ifndef _SC_LEVEL1_DCACHE_LINESIZE
-int filename__read_int(const char *filename, int *value)
+static int filename__read_int(const char *filename, int *value)
{
char line[64];
int fd = open(filename, O_RDONLY), err = -1;
diff --git a/pfunct.c b/pfunct.c
index 5a6dd59a1c7a30b1..237bd54ab9319b0f 100644
--- a/pfunct.c
+++ b/pfunct.c
@@ -426,7 +426,7 @@ static int cu_function_iterator(struct cu *cu, void *cookie __maybe_unused)
return 0;
}
-int elf_symtab__show(char *filename)
+static int elf_symtab__show(char *filename)
{
int fd = open(filename, O_RDONLY), err = -1;
if (fd < 0)
@@ -494,7 +494,7 @@ out_close:
return err;
}
-int elf_symtabs__show(char *filenames[])
+static int elf_symtabs__show(char *filenames[])
{
int i = 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 29/31] btf_encoder: Fix multi-dimensional array encoding
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (27 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 28/31] pfunct, dwarves_fprintf: Mark file-local functions as static Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 30/31] btf_loader: Fix multi-dimensional array loading Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 31/31] btfdiff: Remove --flat_arrays now that pahole encodes multi dim arrays in BTF Arnaldo Carvalho de Melo
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Before this fix, DW_TAG_array_type with more than one dimension was
encoded as a single BTF_KIND_ARRAY using array_type__nelems() which
returns the FLAT total element count. For int a[3][4] that produced:
ARRAY(type=int, nelems=12) ← wrong, flat count
BTF encodes each dimension as a separate chained BTF_KIND_ARRAY:
inner: ARRAY(type=int, nelems=4)
outer: ARRAY(type=inner, nelems=3)
The encoder now loops from innermost to outermost dimension, emitting
one BTF_KIND_ARRAY per dimension, matching the BTF format used by
bpftool and the kernel verifier.
Because one DWARF type now maps to D BTF types (one per dimension),
the simple linear formula type_id_off + core_id no longer gives the
correct BTF id for any type that appears after a multi-dimensional
array. A pre-scan pass (btf_encoder__precompute_btf_ids) runs before
the encoding loop and fills btf_id_map[] with the correct expected BTF
id for every DWARF core_id, accounting for:
- NULL holes in types_table (type_id_null_adj)
- Skipped types (DW_TAG_unspecified_type → encode_tag returns 0)
- Extra BTF slots consumed by multi-dim arrays
btf_encoder__tag_type() consults the map first so that cross-references
from struct members, pointers, and typedefs resolve to the right BTF id
even before the referenced type has been encoded. The map is
authoritative whenever it covers a given id — a 0-sentinel entry
correctly resolves to BTF void rather than falling through to the
linear formula.
Audited: DW_TAG_unspecified_type is the sole return-0 path in
encode_tag; all callees (add_base_type, add_ref_type, add_struct_type,
add_enum_type, add_func_proto, add_array) return >= 1 or negative.
The drift check is updated to compare the actual returned BTF id against
the pre-computed map entry rather than the old linear formula.
Before: pahole -F btf showed int a[3][4] as int a[4][3] or int a[12]
After: int a[2][3][4] encodes as three chained BTF_KIND_ARRAY nodes
and round-trips correctly to int a[2][3][4]
And these all match:
⬢ [acme@toolbx pahole]$ pahole -F btf -C lru_gen_folio vmlinux.btf
struct lru_gen_folio {
long unsigned int max_seq; /* 0 8 */
long unsigned int min_seq[2]; /* 8 16 */
long unsigned int timestamps[4]; /* 24 32 */
struct list_head folios[4][2][5]; /* 56 640 */
/* --- cacheline 10 boundary (640 bytes) was 56 bytes ago --- */
long int nr_pages[4][2][5]; /* 696 320 */
/* --- cacheline 15 boundary (960 bytes) was 56 bytes ago --- */
long unsigned int avg_refaulted[2][4]; /* 1016 64 */
/* --- cacheline 16 boundary (1024 bytes) was 56 bytes ago --- */
long unsigned int avg_total[2][4]; /* 1080 64 */
/* --- cacheline 17 boundary (1088 bytes) was 56 bytes ago --- */
long unsigned int protected[1][2][4]; /* 1144 64 */
/* --- cacheline 18 boundary (1152 bytes) was 56 bytes ago --- */
atomic_long_t evicted[1][2][4]; /* 1208 64 */
/* --- cacheline 19 boundary (1216 bytes) was 56 bytes ago --- */
atomic_long_t refaulted[1][2][4]; /* 1272 64 */
/* --- cacheline 20 boundary (1280 bytes) was 56 bytes ago --- */
bool enabled; /* 1336 1 */
u8 gen; /* 1337 1 */
u8 seg; /* 1338 1 */
/* XXX 5 bytes hole, try to pack */
/* --- cacheline 21 boundary (1344 bytes) --- */
struct hlist_nulls_node list; /* 1344 16 */
/* size: 1360, cachelines: 22, members: 14 */
/* sum members: 1355, holes: 1, sum holes: 5 */
/* last cacheline: 16 bytes */
};
⬢ [acme@toolbx pahole]$ pahole -F dwarf -C lru_gen_folio vmlinux
struct lru_gen_folio {
long unsigned int max_seq; /* 0 8 */
long unsigned int min_seq[2]; /* 8 16 */
long unsigned int timestamps[4]; /* 24 32 */
struct list_head folios[4][2][5]; /* 56 640 */
/* --- cacheline 10 boundary (640 bytes) was 56 bytes ago --- */
long int nr_pages[4][2][5]; /* 696 320 */
/* --- cacheline 15 boundary (960 bytes) was 56 bytes ago --- */
long unsigned int avg_refaulted[2][4]; /* 1016 64 */
/* --- cacheline 16 boundary (1024 bytes) was 56 bytes ago --- */
long unsigned int avg_total[2][4]; /* 1080 64 */
/* --- cacheline 17 boundary (1088 bytes) was 56 bytes ago --- */
long unsigned int protected[1][2][4]; /* 1144 64 */
/* --- cacheline 18 boundary (1152 bytes) was 56 bytes ago --- */
atomic_long_t evicted[1][2][4]; /* 1208 64 */
/* --- cacheline 19 boundary (1216 bytes) was 56 bytes ago --- */
atomic_long_t refaulted[1][2][4]; /* 1272 64 */
/* --- cacheline 20 boundary (1280 bytes) was 56 bytes ago --- */
bool enabled; /* 1336 1 */
u8 gen; /* 1337 1 */
u8 seg; /* 1338 1 */
/* XXX 5 bytes hole, try to pack */
/* --- cacheline 21 boundary (1344 bytes) --- */
struct hlist_nulls_node list; /* 1344 16 */
/* size: 1360, cachelines: 22, members: 14 */
/* sum members: 1355, holes: 1, sum holes: 5 */
/* last cacheline: 16 bytes */
};
⬢ [acme@toolbx pahole]$
And:
⬢ [acme@toolbx pahole]$ bpftool btf dump file vmlinux.btf format c | grep lru_gen_folio -A20
struct lru_gen_folio {
long unsigned int max_seq;
long unsigned int min_seq[2];
long unsigned int timestamps[4];
struct list_head folios[4][2][5];
long int nr_pages[4][2][5];
long unsigned int avg_refaulted[2][4];
long unsigned int avg_total[2][4];
long unsigned int protected[1][2][4];
atomic_long_t evicted[1][2][4];
atomic_long_t refaulted[1][2][4];
bool enabled;
u8 gen;
u8 seg;
struct hlist_nulls_node list;
};
⬢ [acme@toolbx pahole]$ bpftool btf dump file vmlinux.btf format raw | grep lru_gen_folio -A20
[867] STRUCT 'lru_gen_folio' size=1360 vlen=14
'max_seq' type_id=1 bits_offset=0
'min_seq' type_id=4 bits_offset=64
'timestamps' type_id=721 bits_offset=192
'folios' type_id=870 bits_offset=448
'nr_pages' type_id=873 bits_offset=5568
'avg_refaulted' type_id=874 bits_offset=8128
'avg_total' type_id=874 bits_offset=8640
'protected' type_id=875 bits_offset=9152
'evicted' type_id=878 bits_offset=9664
'refaulted' type_id=878 bits_offset=10176
'enabled' type_id=71 bits_offset=10688
'gen' type_id=29 bits_offset=10696
'seg' type_id=29 bits_offset=10704
'list' type_id=695 bits_offset=10752
[868] ARRAY '(anon)' type_id=97 index_type_id=21 nr_elems=5
[869] ARRAY '(anon)' type_id=868 index_type_id=21 nr_elems=2
[870] ARRAY '(anon)' type_id=869 index_type_id=21 nr_elems=4
[871] ARRAY '(anon)' type_id=44 index_type_id=21 nr_elems=5
[872] ARRAY '(anon)' type_id=871 index_type_id=21 nr_elems=2
[873] ARRAY '(anon)' type_id=872 index_type_id=21 nr_elems=4
Fixes: 68645f7facc2eb69 ("btf: Add BTF support")
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_encoder.c | 299 +++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 260 insertions(+), 39 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index c7ebaec4829d121a..20d8317a909f775f 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -152,6 +152,27 @@ struct btf_encoder {
encode_attributes,
true_signature;
uint32_t array_index_id;
+ uint32_t *type_id_null_adj;
+ /*
+ * Maps DWARF core_id → actual BTF type id. Populated by
+ * btf_encoder__precompute_btf_ids() before encoding begins.
+ *
+ * Entry value 0 means "skipped" (e.g. DW_TAG_unspecified_type);
+ * this is safe as a sentinel because BTF type id 0 is void and
+ * the first real type id is always >= 1.
+ *
+ * tag__nr_btf_ids() is the single source of truth for how many
+ * BTF ids each DWARF tag consumes; both precompute and encode
+ * use it so the mapping stays consistent.
+ */
+ uint32_t *btf_id_map;
+ uint32_t btf_id_map_sz;
+ /*
+ * Final extra/skipped counts from btf_encoder__precompute_btf_ids(),
+ * used to compute the fallback array_index_id when "int" is absent.
+ */
+ uint32_t btf_id_extra;
+ uint32_t btf_id_skipped;
struct elf_secinfo *secinfo;
size_t seccnt;
int encode_vars;
@@ -742,7 +763,15 @@ static int32_t btf_encoder__tag_type(struct btf_encoder *encoder, uint32_t tag_t
if (tag_type == 0)
return 0;
- return encoder->type_id_off + tag_type;
+ /* Map is authoritative when it covers this id: entry 0 means
+ * skipped (DW_TAG_unspecified_type, dwz-pruned) → void. */
+ if (encoder->btf_id_map && tag_type < encoder->btf_id_map_sz)
+ return encoder->btf_id_map[tag_type];
+
+ /* Fallback for map-absent CUs (nr == 0) or out-of-range ids */
+ uint32_t adj = encoder->type_id_null_adj ? encoder->type_id_null_adj[tag_type] : 0;
+
+ return encoder->type_id_off + tag_type - adj;
}
static int btf__tag_bpf_arena_ptr(struct btf *btf, int ptr_id)
@@ -1812,20 +1841,6 @@ static void dump_invalid_symbol(const char *msg, const char *sym,
fprintf(stderr, "PAHOLE: Error: Use '--btf_encode_force' to ignore such symbols and force emit the btf.\n");
}
-static int tag__check_id_drift(struct btf_encoder *encoder, const struct tag *tag,
- uint32_t core_id, uint32_t btf_type_id)
-{
- if (btf_type_id != (core_id + encoder->type_id_off)) {
- fprintf(stderr,
- "%s: %s id drift, core_id: %u, btf_type_id: %u, type_id_off: %u\n",
- __func__, dwarf_tag_name(tag->tag),
- core_id, btf_type_id, encoder->type_id_off);
- return -1;
- }
-
- return 0;
-}
-
static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct tag *tag)
{
struct type *type = tag__type(tag);
@@ -1858,18 +1873,6 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct
return type_id;
}
-static uint32_t array_type__nelems(struct tag *tag)
-{
- int i;
- uint32_t nelem = 1;
- struct array_type *array = tag__array_type(tag);
-
- for (i = array->dimensions - 1; i >= 0; --i)
- nelem *= array->nr_entries[i];
-
- return nelem;
-}
-
static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct tag *tag,
struct conf_load *conf_load)
{
@@ -1891,7 +1894,28 @@ static int32_t btf_encoder__add_enum_type(struct btf_encoder *encoder, struct ta
return type_id;
}
+/*
+ * How many BTF type IDs will encoding this tag consume?
+ * 0 — skipped (DW_TAG_unspecified_type)
+ * 1 — the common case
+ * D — multi-dimensional arrays (one BTF_KIND_ARRAY per dimension)
+ *
+ * Both btf_encoder__precompute_btf_ids() and btf_encoder__encode_tag()
+ * must agree on this; keeping the logic in one place prevents drift.
+ */
+static int tag__nr_btf_ids(const struct tag *tag)
+{
+ if (tag->tag == DW_TAG_unspecified_type)
+ return 0;
+ if (tag->tag == DW_TAG_array_type) {
+ int dims = tag__array_type(tag)->dimensions;
+ return dims < 1 ? 1 : dims;
+ }
+ return 1;
+}
+
static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
+ const struct cu *cu __maybe_unused,
struct conf_load *conf_load)
{
/* single out type 0 as it represents special type "void" */
@@ -1899,6 +1923,11 @@ static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
struct base_type *bt;
const char *name;
+ /*
+ * Any case that returns 0 (skip) must have a matching entry in
+ * tag__nr_btf_ids(); the precompute map depends on agreement.
+ * The drift check will catch mismatches at runtime.
+ */
switch (tag->tag) {
case DW_TAG_base_type:
bt = tag__base_type(tag);
@@ -1927,10 +1956,35 @@ static int btf_encoder__encode_tag(struct btf_encoder *encoder, struct tag *tag,
return btf_encoder__add_ref_type(encoder, BTF_KIND_FWD, 0, name, tag->tag == DW_TAG_union_type);
else
return btf_encoder__add_struct_type(encoder, tag);
- case DW_TAG_array_type:
- /* TODO: Encode one dimension at a time. */
+ case DW_TAG_array_type: {
+ /*
+ * BTF represents each array dimension as a separate chained
+ * BTF_KIND_ARRAY node. Emit from innermost to outermost so
+ * that each outer node can reference the one just emitted.
+ * For int a[3][4] (dimensions=2, nr_entries=[3,4]):
+ * i=1: ARRAY(type=int, nelems=4) → id_inner
+ * i=0: ARRAY(type=id_inner, nelems=3) → returned id
+ */
+ struct array_type *at = tag__array_type(tag);
+ int32_t id = ref_type_id;
+ int i;
+
encoder->need_index_type = true;
- return btf_encoder__add_array(encoder, ref_type_id, encoder->array_index_id, array_type__nelems(tag));
+ /*
+ * Malformed DWARF may produce 0 dimensions; emit one
+ * BTF_KIND_ARRAY with 0 elements rather than returning the
+ * bare element type (which would trigger an ID drift error).
+ */
+ if (at->dimensions == 0) {
+ return btf_encoder__add_array(encoder, id, encoder->array_index_id, 0);
+ }
+ for (i = at->dimensions - 1; i >= 0; --i) {
+ id = btf_encoder__add_array(encoder, id, encoder->array_index_id, at->nr_entries[i]);
+ if (id < 0)
+ return id;
+ }
+ return id;
+ }
case DW_TAG_enumeration_type:
return btf_encoder__add_enum_type(encoder, tag, conf_load);
case DW_TAG_subroutine_type:
@@ -2985,10 +3039,80 @@ static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
return false;
}
+/*
+ * Pre-compute the actual BTF type id for every DWARF core_id.
+ *
+ * The linear formula type_id_off + core_id - null_adj holds only for a
+ * strict 1-to-1 DWARF→BTF mapping. Two things break it:
+ *
+ * • Skipped types (DW_TAG_unspecified_type → encode_tag returns 0): each
+ * skipped entry shifts all subsequent BTF ids down by 1.
+ *
+ * • Multi-dimensional arrays: one BTF_KIND_ARRAY is emitted per dimension,
+ * so a D-dimensional array occupies D consecutive BTF ids. The "name" of
+ * the type (the outermost array) lands at base + D - 1, and each inner
+ * array consumed an extra slot, shifting all later ids up.
+ *
+ * By scanning the types table once before encoding we can fill btf_id_map[]
+ * with the correct expected id for every core_id. btf_encoder__tag_type()
+ * then reads the map so that any type reference (struct member, pointer base,
+ * typedef chain, …) resolves to the right BTF id even when the referenced
+ * type hasn't been encoded yet.
+ *
+ * Assumes every non-skipped tag appends exactly nr_ids fresh BTF types;
+ * no in-CU dedup occurs (dedup is a separate libbpf btf__dedup() pass).
+ */
+static int btf_encoder__precompute_btf_ids(struct btf_encoder *encoder, struct cu *cu)
+{
+ uint32_t nr = cu->types_table.nr_entries;
+ uint32_t cid, extra = 0, skipped = 0;
+
+ if (!nr)
+ return 0;
+
+ encoder->btf_id_map = calloc(nr, sizeof(*encoder->btf_id_map));
+ if (!encoder->btf_id_map) {
+ fprintf(stderr, "btf_encoder: out of memory for btf_id_map (%u entries)\n", nr);
+ return -ENOMEM;
+ }
+ encoder->btf_id_map_sz = nr;
+
+ /* Mirror cu__for_each_type: valid core_ids are 1 .. nr-1 */
+ for (cid = 1; cid < nr; cid++) {
+ struct tag *t = cu->types_table.entries[cid];
+ uint32_t adj, base;
+ int nr_ids;
+
+ if (!t)
+ continue;
+
+ nr_ids = tag__nr_btf_ids(t);
+ if (nr_ids == 0) {
+ skipped++;
+ continue;
+ }
+
+ adj = encoder->type_id_null_adj ? encoder->type_id_null_adj[cid] : 0;
+ base = encoder->type_id_off + cid - adj - skipped + extra;
+
+ /*
+ * Multi-dim arrays consume nr_ids consecutive BTF slots;
+ * the outermost (the type other entries reference) is the
+ * last one emitted: base + nr_ids - 1.
+ */
+ encoder->btf_id_map[cid] = base + (nr_ids - 1);
+ extra += nr_ids - 1;
+ }
+
+ encoder->btf_id_extra = extra;
+ encoder->btf_id_skipped = skipped;
+ return 0;
+}
+
int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct conf_load *conf_load)
{
struct llvm_annotation *annot;
- int btf_type_id, tag_type_id, skipped_types = 0;
+ int btf_type_id, tag_type_id;
struct elf_functions *funcs;
uint32_t core_id;
struct function *fn;
@@ -3005,6 +3129,50 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
encoder->type_id_off = btf__type_cnt(encoder->btf) - 1;
+ /*
+ * NULL holes in types_table occur only with dwz alternate debug
+ * files: dwarf_cu__prune_unreferenced_alt_pus() NULLs entries for
+ * imported partial unit types not referenced by this CU. This
+ * does not happen in the common kernel BTF case.
+ *
+ * Build a prefix-sum so btf_encoder__tag_type() can translate
+ * types_table indices (small_ids) to dense BTF type IDs.
+ */
+ encoder->type_id_null_adj = NULL;
+ if (cu->types_table.nr_entries > 0) {
+ uint32_t nr = cu->types_table.nr_entries;
+ uint32_t *adj = calloc(nr + 1, sizeof(*adj));
+
+ if (adj) {
+ uint32_t nulls = 0;
+
+ for (uint32_t i = 1; i < nr; i++) {
+ if (cu->types_table.entries[i] == NULL)
+ nulls++;
+ adj[i] = nulls;
+ }
+ adj[nr] = nulls;
+ if (nulls > 0)
+ encoder->type_id_null_adj = adj;
+ else
+ free(adj);
+ } else {
+ /* Without the adjustment table, NULL holes would cause
+ * wrong BTF type IDs — fail rather than silently corrupt */
+ for (uint32_t i = 1; i < nr; i++) {
+ if (cu->types_table.entries[i] == NULL) {
+ fprintf(stderr, "btf_encoder: out of memory for type_id_null_adj (%u entries)\n", nr);
+ err = -ENOMEM;
+ goto out;
+ }
+ }
+ }
+ }
+
+ err = btf_encoder__precompute_btf_ids(encoder, cu);
+ if (err)
+ goto out;
+
if (!encoder->has_index_type) {
/* cu__find_base_type_by_name() takes "type_id_t *id" */
type_id_t id;
@@ -3012,24 +3180,58 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
encoder->has_index_type = true;
encoder->array_index_id = btf_encoder__tag_type(encoder, id);
} else {
+ uint32_t nr = cu->types_table.nr_entries;
+ uint32_t adj = encoder->type_id_null_adj ? encoder->type_id_null_adj[nr] : 0;
+
encoder->has_index_type = false;
- encoder->array_index_id = encoder->type_id_off + cu->types_table.nr_entries;
+ /*
+ * Point past all encoded CU types. Must account for
+ * NULL holes (adj), skipped DW_TAG_unspecified_type
+ * entries, and extra BTF slots from multi-dim arrays.
+ */
+ encoder->array_index_id = encoder->type_id_off + nr - adj
+ - encoder->btf_id_skipped
+ + encoder->btf_id_extra;
}
}
cu__for_each_type(cu, core_id, pos) {
- btf_type_id = btf_encoder__encode_tag(encoder, pos, conf_load);
+ btf_type_id = btf_encoder__encode_tag(encoder, pos, cu, conf_load);
- if (btf_type_id == 0) {
- ++skipped_types;
+ if (btf_type_id == 0)
continue;
- }
- if (btf_type_id < 0 ||
- tag__check_id_drift(encoder, pos, core_id, btf_type_id + skipped_types)) {
+ /* Verify actual BTF id matches the pre-computed map entry. */
+ if (btf_type_id < 0) {
+ fprintf(stderr,
+ "%s: error encoding %s (core_id %u): %d\n",
+ __func__, dwarf_tag_name(pos->tag), core_id,
+ btf_type_id);
err = -1;
goto out;
}
+ if (encoder->btf_id_map && core_id < encoder->btf_id_map_sz) {
+ uint32_t expected = encoder->btf_id_map[core_id];
+
+ /* Inverse check: precompute expected skip but encode emitted */
+ if (!expected) {
+ fprintf(stderr,
+ "%s: %s unexpected emit, core_id: %u, btf_type_id: %u "
+ "(precompute predicted skip)\n",
+ __func__, dwarf_tag_name(pos->tag), core_id,
+ (uint32_t)btf_type_id);
+ err = -1;
+ goto out;
+ }
+ if ((uint32_t)btf_type_id != expected) {
+ fprintf(stderr,
+ "%s: %s id drift, core_id: %u, expected: %u, got: %u\n",
+ __func__, dwarf_tag_name(pos->tag), core_id,
+ expected, (uint32_t)btf_type_id);
+ err = -1;
+ goto out;
+ }
+ }
}
if (encoder->need_index_type && !encoder->has_index_type) {
@@ -3060,7 +3262,15 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
continue;
}
- btf_type_id = encoder->type_id_off + core_id;
+ if (!encoder->btf_id_map ||
+ core_id >= encoder->btf_id_map_sz ||
+ !encoder->btf_id_map[core_id]) {
+ if (encoder->verbose)
+ fprintf(stderr, "BTF: skipping annotations on unmapped %s (core_id %u)\n",
+ tag_name, core_id);
+ continue;
+ }
+ btf_type_id = (int)encoder->btf_id_map[core_id];
ns = tag__namespace(pos);
list_for_each_entry(annot, &ns->annots, node) {
tag_type_id = btf_encoder__add_decl_tag(encoder, annot->value, btf_type_id, annot->component_idx);
@@ -3122,6 +3332,17 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
if (!err)
err = LSK__DELETE;
out:
+ /*
+ * Safe to free here: btf_encoder__save_func() stores
+ * already-translated BTF type IDs, so the deferred
+ * btf_encoder__add_saved_funcs() path never calls
+ * btf_encoder__tag_type() after this point.
+ */
+ zfree(&encoder->type_id_null_adj);
+ zfree(&encoder->btf_id_map);
+ encoder->btf_id_map_sz = 0;
+ encoder->btf_id_extra = 0;
+ encoder->btf_id_skipped = 0;
encoder->cu = NULL;
return err;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 30/31] btf_loader: Fix multi-dimensional array loading
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (28 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 29/31] btf_encoder: Fix multi-dimensional array encoding Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 31/31] btfdiff: Remove --flat_arrays now that pahole encodes multi dim arrays in BTF Arnaldo Carvalho de Melo
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
create_new_array() had a FIXME — it loaded every BTF_KIND_ARRAY as a
1D array (dimensions=1, nr_entries=[nelems]) and lost the chaining
that encodes multi-dim arrays.
BTF chains dimensions through the element type field:
inner: ARRAY { type=int, nelems=4 } ← int[4]
outer: ARRAY { type=inner, nelems=3 } ← int[3][4]
When the outer node is loaded, the inner has already been loaded (BTF
types are processed in id order, and inner always has a lower id).
Detect this by checking whether cu__type(cu, ap->type) is a
DW_TAG_array_type, and if so, absorb its dimensions:
array->dimensions = inner->dimensions + 1
array->nr_entries[] = [ap->nelems, inner->nr_entries...]
array->tag.type = inner->tag.type ← skip to base element type
Note: cu__type() (types_table) must be used, not cu__tag() (tags_table);
DW_TAG_array_type entries are stored in the types table.
Before: int a[3][4] round-tripped as int a[4][3] (reversed dimensions,
because each BTF_KIND_ARRAY was 1D and the display chain printed
dimensions from innermost to outermost)
After: int a[3][4] and int a[2][3][4] round-trip correctly via BTF
Fixes: 472256d3c57bef53 ("btf_loader: Introduce a loader for the BTF format")
Assisted-by: Claude:claude-sonnet-4-6
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btf_loader.c | 46 ++++++++++---
tests/btf_arrays.sh | 158 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 194 insertions(+), 10 deletions(-)
create mode 100755 tests/btf_arrays.sh
diff --git a/btf_loader.c b/btf_loader.c
index 5857c3b6d2fc4b2b..66ffd1a0b5ad73a3 100644
--- a/btf_loader.c
+++ b/btf_loader.c
@@ -200,20 +200,46 @@ static int create_new_array(struct cu *cu, const struct btf_type *tp, uint32_t i
if (array == NULL)
return -ENOMEM;
- /* FIXME: where to get the number of dimensions?
- * it it flattened? */
- array->dimensions = 1;
- array->nr_entries = malloc(sizeof(uint32_t));
+ /*
+ * BTF encodes each dimension of a multi-dimensional array as a
+ * separate BTF_KIND_ARRAY node chained via the element type field.
+ * For example, int a[3][4] becomes:
+ * inner: BTF_KIND_ARRAY { type=int, nelems=4 }
+ * outer: BTF_KIND_ARRAY { type=inner, nelems=3 }
+ *
+ * Reconstruct the pahole multi-dim representation (one array_type
+ * with dimensions[] and nr_entries[]) by absorbing any inner
+ * BTF_KIND_ARRAY that was already loaded.
+ */
+ struct tag *elem_tag = cu__type(cu, ap->type);
- if (array->nr_entries == NULL) {
- free(array);
- return -ENOMEM;
+ if (elem_tag && elem_tag->tag == DW_TAG_array_type &&
+ tag__array_type(elem_tag)->dimensions < UINT8_MAX) {
+ struct array_type *inner = tag__array_type(elem_tag);
+
+ array->dimensions = inner->dimensions + 1;
+ array->nr_entries = malloc(array->dimensions * sizeof(uint32_t));
+ if (array->nr_entries == NULL) {
+ free(array);
+ return -ENOMEM;
+ }
+ array->nr_entries[0] = ap->nelems;
+ memcpy(&array->nr_entries[1], inner->nr_entries,
+ inner->dimensions * sizeof(uint32_t));
+ /* point directly to the base element type, skipping the inner array node */
+ array->tag.type = inner->tag.type;
+ } else {
+ array->dimensions = 1;
+ array->nr_entries = malloc(sizeof(uint32_t));
+ if (array->nr_entries == NULL) {
+ free(array);
+ return -ENOMEM;
+ }
+ array->nr_entries[0] = ap->nelems;
+ array->tag.type = ap->type;
}
- array->nr_entries[0] = ap->nelems;
array->tag.tag = DW_TAG_array_type;
- array->tag.type = ap->type;
-
cu__add_tag_with_id(cu, &array->tag, id);
return 0;
diff --git a/tests/btf_arrays.sh b/tests/btf_arrays.sh
new file mode 100755
index 0000000000000000..42979156b24c041c
--- /dev/null
+++ b/tests/btf_arrays.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright © 2026 Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+#
+# Test BTF multi-dimensional array encoding and loading round-trip.
+#
+# BTF represents each dimension of a multi-dimensional array as a separate
+# chained BTF_KIND_ARRAY node. For int a[3][4]:
+# inner: ARRAY { type=int, nelems=4 }
+# outer: ARRAY { type=inner, nelems=3 }
+#
+# The encoder (btf_encoder.c) must emit one BTF_KIND_ARRAY per dimension,
+# from innermost to outermost. The loader (btf_loader.c) must collapse
+# the chain back into pahole's flat multi-dim array_type.
+#
+# Tests:
+# 1. BTF dump has the right number of chained ARRAY nodes for each dim
+# 2. pahole -F btf output matches DWARF output for all array members
+# 3. Array sizes are preserved across the encode→load round-trip
+
+. "$(dirname "$0")/test_lib.sh"
+
+outdir=$(make_tmpdir)
+trap cleanup EXIT
+
+title_log "BTF multi-dimensional array encoding and round-trip."
+
+CC=${CC:-gcc}
+if ! command -v ${CC%% *} > /dev/null 2>&1; then
+ info_log "skip: $CC not available"
+ test_skip
+fi
+
+if ! command -v bpftool > /dev/null 2>&1; then
+ info_log "skip: bpftool not available"
+ test_skip
+fi
+
+src=$(make_tmpsrc)
+obj=$(make_tmpobj)
+
+cat > "$src" << 'EOF'
+struct multidim {
+ int a1d[5];
+ int a2d[3][4];
+ int a3d[2][3][4];
+ char s[8];
+};
+
+struct multidim g;
+EOF
+
+$CC -g -c -o "$obj" "$src" 2>/dev/null
+if [ $? -ne 0 ]; then
+ error_log "FAIL: compilation failed"
+ test_fail
+fi
+
+# --- BTF encoding ---
+
+btf="$outdir/arrays.btf"
+pahole --btf_encode_detached="$btf" "$obj" 2>/dev/null
+if [ $? -ne 0 ] || [ ! -s "$btf" ]; then
+ error_log "FAIL: pahole --btf_encode_detached failed"
+ test_fail
+fi
+
+dump=$(bpftool btf dump file "$btf" 2>/dev/null)
+if [ -z "$dump" ]; then
+ error_log "FAIL: bpftool btf dump produced no output"
+ test_fail
+fi
+
+# --- Check 1: chained ARRAY nodes for int[3][4] ---
+# After btf__dedup the chain int[4] → int[3][4] must be present.
+
+inner4_id=$(echo "$dump" | grep "ARRAY.*nr_elems=4" | head -1 | sed 's/\[\([0-9]*\)\].*/\1/')
+if [ -z "$inner4_id" ]; then
+ error_log "FAIL: no ARRAY with nr_elems=4 found for int[3][4] inner dim"
+ test_fail
+fi
+info_log "inner ARRAY nr_elems=4 at type_id=$inner4_id: ok"
+
+# There must be an ARRAY referencing inner4_id with nr_elems=3 (outer of int[3][4])
+if ! echo "$dump" | grep -q "ARRAY.*type_id=${inner4_id}.*nr_elems=3"; then
+ error_log "FAIL: no outer ARRAY(type=${inner4_id}, nr_elems=3) for int[3][4]"
+ test_fail
+fi
+info_log "outer ARRAY nr_elems=3 referencing inner: ok"
+
+# --- Check 2: struct member type_ids must differ between a2d and a3d ---
+# If they were the same, a3d would not add the extra outer dimension.
+
+a2d_type=$(echo "$dump" | grep "'a2d'" | sed "s/.*type_id=\([0-9]*\).*/\1/")
+a3d_type=$(echo "$dump" | grep "'a3d'" | sed "s/.*type_id=\([0-9]*\).*/\1/")
+
+if [ -z "$a2d_type" ] || [ -z "$a3d_type" ]; then
+ error_log "FAIL: could not extract type_ids for a2d ($a2d_type) or a3d ($a3d_type)"
+ test_fail
+fi
+if [ "$a2d_type" = "$a3d_type" ]; then
+ error_log "FAIL: a2d and a3d share the same type_id ($a2d_type); a3d not encoded with extra dim"
+ test_fail
+fi
+info_log "a2d type_id=$a2d_type, a3d type_id=$a3d_type (distinct): ok"
+
+# --- Check 3: round-trip via pahole -F btf ---
+# In-place encoding then reload via BTF frontend must match DWARF output.
+
+pahole -J "$obj" 2>/dev/null
+if [ $? -ne 0 ]; then
+ error_log "FAIL: pahole -J in-place encoding failed"
+ test_fail
+fi
+
+btf_out=$(pahole -F btf -C multidim "$obj" 2>/dev/null)
+
+if [ -z "$btf_out" ]; then
+ error_log "FAIL: pahole -F btf -C multidim produced no output"
+ test_fail
+fi
+
+# 1D array preserved
+if ! echo "$btf_out" | grep -q "a1d\[5\]"; then
+ error_log "FAIL: round-trip: a1d[5] not found in BTF output"
+ test_fail
+fi
+info_log "a1d[5] preserved: ok"
+
+# 2D array — exact dimension order must match DWARF
+if ! echo "$btf_out" | grep -q "a2d\[3\]\[4\]"; then
+ error_log "FAIL: round-trip: a2d[3][4] not found in BTF output"
+ test_fail
+fi
+info_log "a2d[3][4] preserved: ok"
+
+# 3D array — exact dimension order must match DWARF
+if ! echo "$btf_out" | grep -q "a3d\[2\]\[3\]\[4\]"; then
+ error_log "FAIL: round-trip: a3d[2][3][4] not found in BTF output"
+ test_fail
+fi
+info_log "a3d[2][3][4] preserved: ok"
+
+# char array preserved
+if ! echo "$btf_out" | grep -q "s\[8\]"; then
+ error_log "FAIL: round-trip: s[8] not found in BTF output"
+ test_fail
+fi
+info_log "s[8] preserved: ok"
+
+# --- Check 4: struct size preserved ---
+if ! echo "$btf_out" | grep -q "size: 172"; then
+ error_log "FAIL: struct multidim size=172 not preserved after BTF round-trip"
+ test_fail
+fi
+info_log "struct multidim size=172 preserved: ok"
+
+test_pass
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 31/31] btfdiff: Remove --flat_arrays now that pahole encodes multi dim arrays in BTF
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
` (29 preceding siblings ...)
2026-07-29 19:07 ` [PATCH 30/31] btf_loader: Fix multi-dimensional array loading Arnaldo Carvalho de Melo
@ 2026-07-29 19:07 ` Arnaldo Carvalho de Melo
30 siblings, 0 replies; 32+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-07-29 19:07 UTC (permalink / raw)
To: Alan Maguire
Cc: Jiri Olsa, Clark Williams, dwarves, bpf, Andrii Nakryiko,
Yonghong Song, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
With it, i.e. flattening DWARF arrays we get:
acme@number:~/git/build/v7.2-rc3+$ btfdiff vmlinux
--- /tmp/btfdiff.dwarf.sXVla7 2026-07-16 18:53:09.744346444 -0300
+++ /tmp/btfdiff.btf.uzBSIz 2026-07-16 18:53:04.975270734 -0300
@@ -21905,7 +21905,7 @@ struct amd_iommu {
/* typedef u32 -> __u32 */ unsigned int stored_addr_lo; /* 336 4 */
/* typedef u32 -> __u32 */ unsigned int stored_addr_hi; /* 340 4 */
- /* typedef u32 -> __u32 */ unsigned int stored_l1[108]; /* 344 432 */
+ /* typedef u32 -> __u32 */ unsigned int stored_l1[6][18]; /* 344 432 */
/* --- cacheline 12 boundary (768 bytes) was 8 bytes ago --- */
/* typedef u32 -> __u32 */ unsigned int stored_l2[131]; /* 776 524 */
/* --- cacheline 20 boundary (1280 bytes) was 20 bytes ago --- */
@@ -22489,7 +22489,7 @@ struct amd_mdb_pcie {
/* --- cacheline 6 boundary (384 bytes) --- */
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 384 32 */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 416 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 416 48 */
} presets; /* 384 80 */
/* --- cacheline 7 boundary (448 bytes) was 16 bytes ago --- */
struct pci_config_window * cfg; /* 464 8 */
@@ -29007,7 +29007,7 @@ struct audit_aux_data_pids {
} bpf; /* 296 4 */
} target_ref[16]; /* 272 512 */
/* --- cacheline 12 boundary (768 bytes) was 16 bytes ago --- */
- char target_comm[256]; /* 784 256 */
+ char target_comm[16][16]; /* 784 256 */
/* --- cacheline 16 boundary (1024 bytes) was 16 bytes ago --- */
int pid_count; /* 1040 4 */
@@ -30613,7 +30613,7 @@ struct automaton_nomiss {
char * event_names[7]; /* 40 56 */
/* --- cacheline 1 boundary (64 bytes) was 32 bytes ago --- */
char * env_names[3]; /* 96 24 */
- unsigned char function[35]; /* 120 35 */
+ unsigned char function[5][7]; /* 120 35 */
/* --- cacheline 2 boundary (128 bytes) was 27 bytes ago --- */
unsigned char initial_state; /* 155 1 */
/* typedef bool */ _Bool final_states[5]; /* 156 5 */
@@ -30627,7 +30627,7 @@ struct automaton_stall {
char * event_names[4]; /* 24 32 */
char * env_names[1]; /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
- unsigned char function[12]; /* 64 12 */
+ unsigned char function[3][4]; /* 64 12 */
unsigned char initial_state; /* 76 1 */
/* typedef bool */ _Bool final_states[3]; /* 77 3 */
@@ -30637,7 +30637,7 @@ struct automaton_stall {
struct automaton_wip {
char * state_names[2]; /* 0 16 */
char * event_names[3]; /* 16 24 */
- unsigned char function[6]; /* 40 6 */
+ unsigned char function[2][3]; /* 40 6 */
unsigned char initial_state; /* 46 1 */
/* typedef bool */ _Bool final_states[2]; /* 47 2 */
@@ -30648,7 +30648,7 @@ struct automaton_wip {
struct automaton_wwnr {
char * state_names[2]; /* 0 16 */
char * event_names[3]; /* 16 24 */
- unsigned char function[6]; /* 40 6 */
+ unsigned char function[2][3]; /* 40 6 */
unsigned char initial_state; /* 46 1 */
/* typedef bool */ _Bool final_states[2]; /* 47 2 */
@@ -136221,7 +136221,7 @@ struct dw_pcie {
/* --- cacheline 6 boundary (384 bytes) --- */
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 384 32 */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 416 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 416 48 */
} presets; /* 384 80 */
/* --- cacheline 7 boundary (448 bytes) was 16 bytes ago --- */
struct pci_config_window * cfg; /* 464 8 */
@@ -136582,7 +136582,7 @@ struct dw_pcie_rp {
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 288 32 */
/* --- cacheline 5 boundary (320 bytes) --- */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 320 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 320 48 */
} presets; /* 288 80 */
struct pci_config_window * cfg; /* 368 8 */
/* typedef bool */ _Bool ecam_enabled; /* 376 1 */
@@ -173280,7 +173280,7 @@ struct fu740_pcie {
/* --- cacheline 6 boundary (384 bytes) --- */
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 384 32 */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 416 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 416 48 */
} presets; /* 384 80 */
/* --- cacheline 7 boundary (448 bytes) was 16 bytes ago --- */
struct pci_config_window * cfg; /* 464 8 */
@@ -191392,10 +191392,10 @@ struct hugetlb_cgroup {
/* --- cacheline 832 boundary (53248 bytes) --- */
/* typedef atomic_long_t -> atomic64_t */ struct {
/* typedef s64 -> __s64 */ long long int counter; /* 53248 8 */
- } events[2]; /* 53248 16 */
+ } events[2][1]; /* 53248 16 */
/* typedef atomic_long_t -> atomic64_t */ struct {
/* typedef s64 -> __s64 */ long long int counter; /* 53264 8 */
- } events_local[2]; /* 53264 16 */
+ } events_local[2][1]; /* 53264 16 */
struct cgroup_file {
struct kernfs_node * kn; /* 53280 8 */
long unsigned int notified_at; /* 53288 8 */
@@ -234492,7 +234492,7 @@ struct intel_pcie {
/* --- cacheline 6 boundary (384 bytes) --- */
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 384 32 */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 416 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 416 48 */
} presets; /* 384 80 */
/* --- cacheline 7 boundary (448 bytes) was 16 bytes ago --- */
struct pci_config_window * cfg; /* 464 8 */
@@ -263257,7 +263257,7 @@ struct keembay_pcie {
/* --- cacheline 6 boundary (384 bytes) --- */
struct pci_eq_presets {
/* typedef u16 -> __u16 */ short unsigned int eq_presets_8gts[16]; /* 384 32 */
- /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[48]; /* 416 48 */
+ /* typedef u8 -> __u8 */ unsigned char eq_presets_Ngts[3][16]; /* 416 48 */
} presets; /* 384 80 */
/* --- cacheline 7 boundary (448 bytes) was 16 bytes ago --- */
struct pci_config_window * cfg; /* 464 8 */
<Big SNIP>
@@ -617956,14 +617956,14 @@ struct xz_dec_microlzma {
struct lzma_len_dec {
/* typedef uint16_t -> u16 -> __u16 */ short unsigned int choice; /* 2824 2 */
/* typedef uint16_t -> u16 -> __u16 */ short unsigned int choice2; /* 2826 2 */
- /* typedef uint16_t -> u16 -> __u16 */ short unsigned int low[128]; /* 2828 256 */
+ /* typedef uint16_t -> u16 -> __u16 */ short unsigned int low[16][8]; /* 2828 256 */
/* --- cacheline 48 boundary (3072 bytes) was 12 bytes ago --- */
- /* typedef uint16_t -> u16 -> __u16 */ short unsigned int mid[128]; /* 3084 256 */
+ /* typedef uint16_t -> u16 -> __u16 */ short unsigned int mid[16][8]; /* 3084 256 */
/* --- cacheline 52 boundary (3328 bytes) was 12 bytes ago --- */
/* typedef uint16_t -> u16 -> __u16 */ short unsigned int high[256]; /* 3340 512 */
} rep_len_dec; /* 2824 1028 */
/* --- cacheline 60 boundary (3840 bytes) was 12 bytes ago --- */
- /* typedef uint16_t -> u16 -> __u16 */ short unsigned int literal[12288]; /* 3852 24576 */
+ /* typedef uint16_t -> u16 -> __u16 */ short unsigned int literal[16][768]; /* 3852 24576 */
} lzma; /* 124 28304 */
/* --- cacheline 444 boundary (28416 bytes) was 12 bytes ago --- */
struct {
Now, removing --flat_arrays makes it clean, meaning both DWARF and BTF
loaders support multi dimensional arrays and so does the BTF encoder.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
btfdiff | 1 -
1 file changed, 1 deletion(-)
diff --git a/btfdiff b/btfdiff
index 46348f46a08c50ab..18794dfd3e1562d4 100755
--- a/btfdiff
+++ b/btfdiff
@@ -24,7 +24,6 @@ dwarf_output=$(mktemp /tmp/btfdiff.dwarf.XXXXXX)
pahole_bin=${PAHOLE-"pahole"}
${pahole_bin} -E -F dwarf \
- --flat_arrays \
--sort \
--jobs \
--suppress_aligned_attribute \
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread
end of thread, other threads:[~2026-07-29 19:08 UTC | newest]
Thread overview: 32+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 19:07 [PATCHES 00/31] pahole: Bug fixes and small improvements Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 01/31] cmake: Update minimum required version from 3.5 to 3.10 Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 02/31] Fix -Wsign-compare warnings across the codebase Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 03/31] btf_encoder: Fix interior pointer free and missing NULL check Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 04/31] dwarves: Fix missing list head initialization in type__clone_members Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 05/31] pahole: Fix instance memory leak on early returns in prototype__stdio_fprintf_value Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 06/31] ctf_loader, libctf: Fix error path resource leaks Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 07/31] dwarves: Don't search for holes before member byte sizes are cached Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 08/31] dwarf_loader: Allocate type_dcu via dwarf_cu__new to fix dangling stack pointer Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 09/31] dwarf_loader: Fix annotation failure leaks in variable and typedef creation Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 10/31] btf_encoder: Use btf_encoder__tag_type() for all type ID computations Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 11/31] pahole: Fix --errno typo that decrements instead of negating Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 12/31] dwarves: Fix heap buffer overflow in languages__parse realloc Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 13/31] btf_encoder: Fix early cleanup crashes in btf_encoder__new/delete Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 14/31] btf_encoder, libctf: Add elf_strptr NULL checks and fix kfunc bounds Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 15/31] dwarf_loader: Fix --fixup_silly_bitfields condition check Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 16/31] dwarf_loader: Skip libdw__lock when elfutils is built thread-safe Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 17/31] dwarf_loader: Fix data race in tag__init() decl_file string cache Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 18/31] pahole: Fix parse_btf_features("all") being a silent no-op Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 19/31] dwarves: Fix variable shadowing in __cus__find_struct_by_name() Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 20/31] dutil: Add exec_objcopy() shell-injection-safe helper Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 21/31] btf_encoder: Fall back to objcopy when llvm-objcopy is not available Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 22/31] dwarf_loader, btf_loader: Replace stale FIXME/XXX comments with explanations Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 23/31] pahole: Skip inline expansions during BTF encoding Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 24/31] pahole: Use fseek for seekable files in --prettify and --seek_bytes Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 25/31] pahole: Guard pipe_seek() against negative offsets Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 26/31] gobuffer: Remove 5 dead functions found via coverage analysis Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 27/31] dwarves: Remove 6 " Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 28/31] pfunct, dwarves_fprintf: Mark file-local functions as static Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 29/31] btf_encoder: Fix multi-dimensional array encoding Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 30/31] btf_loader: Fix multi-dimensional array loading Arnaldo Carvalho de Melo
2026-07-29 19:07 ` [PATCH 31/31] btfdiff: Remove --flat_arrays now that pahole encodes multi dim arrays in BTF Arnaldo Carvalho de Melo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox