* [PATCH v3 0/3] btf_encoder: do not encode functions consuming packed structs on stack
@ 2025-07-07 14:02 Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2025-07-07 14:02 UTC (permalink / raw)
To: dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf,
Alexis Lothoré (eBPF Foundation)
Hello,
this is the v3 of the packed-struct-passed-on-stack series. This
revision follows on Ihor's comments on v2.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- add uncertain param loc logic to saved_functions_combine to
deduplicate functions
- remove unneeded call to class__infer_holes
- bring a userspace binary instead of a OoT kernel module for testing
- consolidate paths used in the new test
- Link to v2: https://lore.kernel.org/r/20250703-btf_skip_structs_on_stack-v2-0-4767e3ba10c9@bootlin.com
Changes in v2:
- infer structs attributes
- skip function encoded if some consumed struct (passed on stack) is
marked as packed
- add some tests in btf_functions.sh
- drop RFC prefix
- Link to v1: https://lore.kernel.org/r/20250618-btf_skip_structs_on_stack-v1-1-e70be639cc53@bootlin.com
---
Alexis Lothoré (eBPF Foundation) (3):
btf_encoder: skip functions consuming packed structs passed by value on stack
tests: add some tests validating skipped functions due to uncertain arg location
gitignore: ignore all the test kmod build-related files
.gitignore | 3 ++
btf_encoder.c | 53 +++++++++++++++++++++++++++--
dwarves.h | 1 +
tests/bin/Makefile | 10 ++++++
tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
tests/btf_functions.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 221 insertions(+), 3 deletions(-)
---
base-commit: 042d73962d35fdd1466e056f1ea14590b1cdbb9b
change-id: 20250617-btf_skip_structs_on_stack-006adf457d50
Best regards,
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value on stack
2025-07-07 14:02 [PATCH v3 0/3] btf_encoder: do not encode functions consuming packed structs on stack Alexis Lothoré (eBPF Foundation)
@ 2025-07-07 14:02 ` Alexis Lothoré (eBPF Foundation)
2025-07-07 17:05 ` Alexei Starovoitov
` (3 more replies)
2025-07-07 14:02 ` [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 3/3] gitignore: ignore all the test kmod build-related files Alexis Lothoré (eBPF Foundation)
2 siblings, 4 replies; 16+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2025-07-07 14:02 UTC (permalink / raw)
To: dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf,
Alexis Lothoré (eBPF Foundation)
Most ABIs allow functions to receive structs passed by value, if they
fit in a register or a pair of registers, depending on the exact ABI.
However, when there is a struct passed by value but all registers are
already used for parameters passing, the struct is still passed by value
but on the stack. This becomes an issue if the passed struct is defined
with some attributes like __attribute__((packed)) or
__attribute__((aligned(X)), as its location on the stack is altered, but
this change is not reflected in dwarf information. The corresponding BTF
data generated from this can lead to incorrect BPF trampolines
generation (eg to attach bpf tracing programs to kernel functions) in
the Linux kernel.
Prevent those wrong cases by not encoding functions consuming structs
passed by value on stack, when those structs do not have the expected
alignment due to some attribute usage.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- remove unneeded class__find_holes (already done by
class__infer_packed_attributes)
- add uncertain parm loc in saved_functions_combine
Changes in v2:
- do not deny any struct passed by value, only those passed on stack AND
with some attribute alteration
- use the existing class__infer_packed_attributes to deduce is a struct
is "altered". As a consequence, move the function filtering from
parameter__new to btf_encoder__encode_cu, to make sure that all the
needed data has been parsed from debug info
---
btf_encoder.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
dwarves.h | 1 +
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/btf_encoder.c b/btf_encoder.c
index 0bc23349b5d740c3ddab8208b2e15cdbdd139b9d..3f040fe03d7a208aa742914513bacde9782aabcf 100644
--- a/btf_encoder.c
+++ b/btf_encoder.c
@@ -87,6 +87,7 @@ struct btf_encoder_func_state {
uint8_t optimized_parms:1;
uint8_t unexpected_reg:1;
uint8_t inconsistent_proto:1;
+ uint8_t uncertain_parm_loc:1;
int ret_type_id;
struct btf_encoder_func_parm *parms;
struct btf_encoder_func_annot *annots;
@@ -1203,6 +1204,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
state->inconsistent_proto = ftype->inconsistent_proto;
state->unexpected_reg = ftype->unexpected_reg;
state->optimized_parms = ftype->optimized_parms;
+ state->uncertain_parm_loc = ftype->uncertain_parm_loc;
ftype__for_each_parameter(ftype, param) {
const char *name = parameter__name(param) ?: "";
@@ -1365,7 +1367,7 @@ static int saved_functions_cmp(const void *_a, const void *_b)
static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b)
{
- uint8_t optimized, unexpected, inconsistent;
+ uint8_t optimized, unexpected, inconsistent, uncertain_parm_loc;
int ret;
ret = strncmp(a->elf->name, b->elf->name,
@@ -1375,11 +1377,13 @@ static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_
optimized = a->optimized_parms | b->optimized_parms;
unexpected = a->unexpected_reg | b->unexpected_reg;
inconsistent = a->inconsistent_proto | b->inconsistent_proto;
+ uncertain_parm_loc = a->uncertain_parm_loc | b->uncertain_parm_loc;
if (!unexpected && !inconsistent && !funcs__match(a, b))
inconsistent = 1;
a->optimized_parms = b->optimized_parms = optimized;
a->unexpected_reg = b->unexpected_reg = unexpected;
a->inconsistent_proto = b->inconsistent_proto = inconsistent;
+ a->uncertain_parm_loc = b->uncertain_parm_loc = uncertain_parm_loc;
return 0;
}
@@ -1430,9 +1434,15 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
/* do not exclude functions with optimized-out parameters; they
* may still be _called_ with the right parameter values, they
* just do not _use_ them. Only exclude functions with
- * unexpected register use or multiple inconsistent prototypes.
+ * unexpected register use, multiple inconsistent prototypes or
+ * uncertain parameters location
*/
- add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto;
+ add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto && !state->uncertain_parm_loc;
+
+ if (state->uncertain_parm_loc)
+ btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
+ "uncertain parameter location\n",
+ 0, 0);
if (add_to_btf) {
err = btf_encoder__add_func(state->encoder, state);
@@ -2553,6 +2563,38 @@ void btf_encoder__delete(struct btf_encoder *encoder)
free(encoder);
}
+static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
+{
+ struct parameter *param;
+ int param_idx = 0;
+
+ if (ftype->nr_parms < cu->nr_register_params)
+ return false;
+
+ ftype__for_each_parameter(ftype, param) {
+ if (param_idx++ < cu->nr_register_params)
+ continue;
+
+ struct tag *type = cu__type(cu, param->tag.type);
+
+ if (type == NULL || !tag__is_struct(type))
+ continue;
+
+ struct type *ctype = tag__type(type);
+ if (ctype->namespace.name == 0)
+ continue;
+
+ struct class *class = tag__class(type);
+
+ class__infer_packed_attributes(class, cu);
+
+ if (class->is_packed)
+ return true;
+ }
+
+ return false;
+}
+
int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct conf_load *conf_load)
{
struct llvm_annotation *annot;
@@ -2647,6 +2689,8 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
* Skip functions that:
* - are marked as declarations
* - do not have full argument names
+ * - have arguments with uncertain locations, e.g packed
+ * structs passed by value on stack
* - are not in ftrace list (if it's available)
* - are not external (in case ftrace filter is not available)
*/
@@ -2693,6 +2737,9 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
if (!func)
continue;
+ if (ftype__has_uncertain_arg_loc(cu, &fn->proto))
+ fn->proto.uncertain_parm_loc = 1;
+
err = btf_encoder__save_func(encoder, fn, func);
if (err)
goto out;
diff --git a/dwarves.h b/dwarves.h
index 36c689847ebf29a1ab9936f9d0f928dd46514547..d689aee5910f4b40dc13b3e9dc596dfbe6a2c3d0 100644
--- a/dwarves.h
+++ b/dwarves.h
@@ -1021,6 +1021,7 @@ struct ftype {
uint8_t unexpected_reg:1;
uint8_t processed:1;
uint8_t inconsistent_proto:1;
+ uint8_t uncertain_parm_loc:1;
struct list_head template_type_params;
struct list_head template_value_params;
struct template_parameter_pack *template_parameter_pack;
--
2.50.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-07 14:02 [PATCH v3 0/3] btf_encoder: do not encode functions consuming packed structs on stack Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
@ 2025-07-07 14:02 ` Alexis Lothoré (eBPF Foundation)
2025-07-07 14:14 ` Alexis Lothoré
2025-08-05 15:09 ` Alan Maguire
2025-07-07 14:02 ` [PATCH v3 3/3] gitignore: ignore all the test kmod build-related files Alexis Lothoré (eBPF Foundation)
2 siblings, 2 replies; 16+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2025-07-07 14:02 UTC (permalink / raw)
To: dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf,
Alexis Lothoré (eBPF Foundation)
Add a small binary representing specific cases likely absent from
standard vmlinux or kernel modules files. As a starter, the introduced
binary exposes a few functions consuming structs passed by value, some
passed by register, some passed on the stack:
int main(void);
int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
void *, char, short int, struct test_bin_struct_packed);
int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
void *, char, short int, struct test_bin_struct);
int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
int test_bin_func_ok(int, void *, char, short int);
Then enrich btf_functions.sh to make it perform the following steps:
- build the binary
- generate BTF info and pfunct listing, both with dwarf and the
generated BTF
- check that any function encoded in BTF is found in DWARF
- check that any function announced as skipped is indeed absent from BTF
- check that any skipped function has been skipped due to uncertain
parameter location
Example of the new test execution:
Encoding...Matched 4 functions exactly.
Ok
Validation of skipped function logic...
Skipped encoding 1 functions in BTF.
Ok
Validating skipped functions have uncertain parameter location...
pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
Found 1 legitimately skipped function due to uncertain loc
Ok
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- bring a userspace binary instead of an OoT kernel module
- remove test dependency to a kernel directory being provided
- improve test dir detection
Changes in v2:
- new patch
---
tests/bin/Makefile | 10 ++++++
tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
tests/btf_functions.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 167 insertions(+)
diff --git a/tests/bin/Makefile b/tests/bin/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..70bcf57ac4744f30fe03ea12908e42c69390f14a
--- /dev/null
+++ b/tests/bin/Makefile
@@ -0,0 +1,10 @@
+CC=${CROSS_COMPILE}gcc
+
+test_bin: test_bin.c
+ ${CC} $^ -Wall -Wextra -Werror -g -o $@
+
+clean:
+ rm -rf test_bin
+
+.PHONY: clean
+
diff --git a/tests/bin/test_bin.c b/tests/bin/test_bin.c
new file mode 100644
index 0000000000000000000000000000000000000000..ca6a4852cc511243925db905e55e040519af9cfd
--- /dev/null
+++ b/tests/bin/test_bin.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+
+#define noinline __attribute__((noinline))
+#define __packed __attribute__((__packed__))
+
+struct test_bin_struct {
+ char a;
+ short b;
+ int c;
+ unsigned long long d;
+};
+
+struct test_bin_struct_packed {
+ char a;
+ short b;
+ int c;
+ unsigned long long d;
+}__packed;
+
+int test_bin_func_ok(int a, void *b, char c, short d);
+int test_bin_func_struct_ok(int a, void *b, char c, struct test_bin_struct d);
+int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d, int e,
+ void *f, char g, short h,
+ struct test_bin_struct i);
+int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d, int e,
+ void *f, char g, short h,
+ struct test_bin_struct_packed i);
+
+noinline int test_bin_func_ok(int a, void *b, char c, short d)
+{
+ return a + (long)b + c + d;
+}
+
+noinline int test_bin_func_struct_ok(int a, void *b, char c,
+ struct test_bin_struct d)
+{
+ return a + (long)b + c + d.a + d.b + d.c + d.d;
+}
+
+noinline int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d,
+ int e, void *f, char g, short h,
+ struct test_bin_struct i)
+{
+ return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
+}
+
+noinline int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d,
+ int e, void *f, char g, short h,
+ struct test_bin_struct_packed i)
+{
+ return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
+}
+
+int main()
+{
+ struct test_bin_struct test;
+ struct test_bin_struct_packed test_bis;
+
+ test_bin_func_ok(0, NULL, 0, 0);
+ test_bin_func_struct_ok(0, NULL, 0, test);
+ test_bin_func_struct_on_stack_ok(0, NULL, 0, 0, 0, NULL, 0, 0, test);
+ test_bin_func_struct_on_stack_ko(0, NULL, 0, 0, 0, NULL, 0, 0, test_bis);
+ return 0;
+}
+
diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
index c92e5ae906f90badfede86eb530108894fbc8c93..fb62b0b56662bb2ae58f7adc0a022c400cba5e0f 100755
--- a/tests/btf_functions.sh
+++ b/tests/btf_functions.sh
@@ -193,4 +193,95 @@ if [[ -n "$VERBOSE" ]]; then
fi
echo "Ok"
+# Some specific cases can not be tested directly with a standard kernel.
+# We can use the small binary in bin/ to test those cases, like packed
+# structs passed on the stack.
+
+echo -n "Validation of BTF encoding corner cases with test_bin functions; this may take some time: "
+
+test -n "$VERBOSE" && printf "\nBuilding test_bin..."
+tests_dir=$(realpath $(dirname $0))
+make -C ${tests_dir}/bin
+
+test -n "$VERBOSE" && printf "\nEncoding..."
+pahole --btf_features=default --lang_exclude=rust --btf_encode_detached=$outdir/test_bin.btf \
+ --verbose ${tests_dir}/bin/test_bin | grep "skipping BTF encoding of function" \
+ > ${outdir}/test_bin_skipped_fns
+
+funcs=$(pfunct --format_path=btf $outdir/test_bin.btd 2>/dev/null|sort)
+pfunct --all --no_parm_names --format_path=dwarf bin/test_bin | \
+ sort|uniq > $outdir/test_bin_dwarf.funcs
+pfunct --all --no_parm_names --format_path=btf $outdir/test_bin.btf 2>/dev/null|\
+ awk '{ gsub("^(bpf_kfunc |bpf_fastcall )+",""); print $0}'|sort|uniq > $outdir/test_bin_btf.funcs
+
+exact=0
+while IFS= read -r btf ; do
+ # Matching process can be kept simpler as the tested binary is
+ # specifically tailored for tests
+ dwarf=$(grep -F "$btf" $outdir/test_bin_dwarf.funcs)
+ if [[ "$btf" != "$dwarf" ]]; then
+ echo "ERROR: mismatch : BTF '$btf' not found; DWARF '$dwarf'"
+ fail
+ else
+ exact=$((exact+1))
+ fi
+done < $outdir/test_bin_btf.funcs
+
+if [[ -n "$VERBOSE" ]]; then
+ echo "Matched $exact functions exactly."
+ echo "Ok"
+ echo "Validation of skipped function logic..."
+fi
+
+skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
+if [[ "$skipped_cnt" == "0" ]]; then
+ echo "No skipped functions. Done."
+ exit 0
+fi
+
+skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
+for s in $skipped_fns ; do
+ # Ensure the skipped function are not in BTF
+ inbtf=$(grep " $s(" $outdir/test_bin_btf.funcs)
+ if [[ -n "$inbtf" ]]; then
+ echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'"
+ fail
+ fi
+done
+
+if [[ -n "$VERBOSE" ]]; then
+ echo "Skipped encoding $skipped_cnt functions in BTF."
+ echo "Ok"
+ echo "Validating skipped functions have uncertain parameter location..."
+fi
+
+uncertain_loc=$(awk '/due to uncertain parameter location/ { print $1 }' $outdir/test_bin_skipped_fns)
+legitimate_skip=0
+
+for f in $uncertain_loc ; do
+ # Extract parameters types
+ raw_params=$(grep ${f} $outdir/test_bin_dwarf.funcs|sed -n 's/^[^(]*(\([^)]*\)).*/\1/p')
+ IFS=',' read -ra params <<< "${raw_params}"
+ for param in "${params[@]}"
+ do
+ # Search any param that could be a struct
+ struct_type=$(echo ${param}|grep -E '^struct [^*]' | sed -E 's/^struct //')
+ if [ -n "${struct_type}" ]; then
+ # Check with pahole if the struct is detected as
+ # packed
+ if pahole -F dwarf -C "${struct_type}" ${tests_dir}/bin/test_bin|tail -n 2|grep -q __packed__
+ then
+ legitimate_skip=$((legitimate_skip+1))
+ continue 2
+ fi
+ fi
+ done
+ echo "ERROR: '${f}()' should not have been skipped; it has no parameter with uncertain location"
+ fail
+done
+
+if [[ -n "$VERBOSE" ]]; then
+ echo "Found ${legitimate_skip} legitimately skipped function due to uncertain loc"
+fi
+echo "Ok"
exit 0
--
2.50.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v3 3/3] gitignore: ignore all the test kmod build-related files
2025-07-07 14:02 [PATCH v3 0/3] btf_encoder: do not encode functions consuming packed structs on stack Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location Alexis Lothoré (eBPF Foundation)
@ 2025-07-07 14:02 ` Alexis Lothoré (eBPF Foundation)
2 siblings, 0 replies; 16+ messages in thread
From: Alexis Lothoré (eBPF Foundation) @ 2025-07-07 14:02 UTC (permalink / raw)
To: dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf,
Alexis Lothoré (eBPF Foundation)
The kmod module generates quite a lot of intermediate build files, so
ignore those in git.
Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v3:
- update dropped files names, following changes in previous commit
Changes in v2:
- new patch
---
.gitignore | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.gitignore b/.gitignore
index 96e05c7842624067ed5571bccbaae76122a66567..98fdf13b96225697b5d58126af17c92af487ed6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
/build
/config.h
+tests/bin/*
+!tests/bin/test_bin.c
+!tests/bin/Makefile
--
2.50.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-07 14:02 ` [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location Alexis Lothoré (eBPF Foundation)
@ 2025-07-07 14:14 ` Alexis Lothoré
2025-07-07 19:36 ` Ihor Solodrai
2025-08-05 15:09 ` Alan Maguire
1 sibling, 1 reply; 16+ messages in thread
From: Alexis Lothoré @ 2025-07-07 14:14 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation), dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation) wrote:
> Add a small binary representing specific cases likely absent from
> standard vmlinux or kernel modules files. As a starter, the introduced
> binary exposes a few functions consuming structs passed by value, some
> passed by register, some passed on the stack:
>
> int main(void);
> int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
> void *, char, short int, struct test_bin_struct_packed);
> int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
> void *, char, short int, struct test_bin_struct);
> int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
> int test_bin_func_ok(int, void *, char, short int);
>
> Then enrich btf_functions.sh to make it perform the following steps:
> - build the binary
> - generate BTF info and pfunct listing, both with dwarf and the
> generated BTF
> - check that any function encoded in BTF is found in DWARF
> - check that any function announced as skipped is indeed absent from BTF
> - check that any skipped function has been skipped due to uncertain
> parameter location
>
> Example of the new test execution:
> Encoding...Matched 4 functions exactly.
> Ok
> Validation of skipped function logic...
> Skipped encoding 1 functions in BTF.
> Ok
> Validating skipped functions have uncertain parameter location...
> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
A word about this specific error: I may have missed it in the previous
iteration, but I systematically get this error when running the following
command:
$ pahole -C test_bin_struct_packed tests/bin/test_bin
I initially thought that it would be something related to the binary being
a userspace program and not a kernel module, but I observe the following:
- the issue is observed even on a .ko file (tested on the previous series
iteration with kmod.ko)
- the issue does not appear if there is no class filtering (ie the `-C`
arg) provided to pahole
- the issue occurs as well with the packaged pahole version on my host (v1.30)
- the struct layout is still displayed correctly despite the error
A quick bisect shows that the error log has started appearing with
59f5409f1357 ("dwarf_loader: Fix termination on BTF encoding error"). This
commit has "enforced" error propagation if dwfl_getmodules returns
something different than 0 (before, it was propagating an error only if the
error code was negative, but dwfl_getmodules seems to be able to return
values > 0 as well). As is sound unrelated to this series, I pushed this
new revision anyway. [1] seems to hint that the issue is known, but in my
case I don't get any additional log about unhandled DWARF operation. The
issue is pretty repeatable on my side, feel free to ask for any additional
detail or manipulation that could help.
[1] https://lore.kernel.org/dwarves/933e199997949c0ac8a71551830f1e6c98d8bff0@linux.dev/
> Found 1 legitimately skipped function due to uncertain loc
> Ok
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> Changes in v3:
> - bring a userspace binary instead of an OoT kernel module
> - remove test dependency to a kernel directory being provided
> - improve test dir detection
>
> Changes in v2:
> - new patch
> ---
> tests/bin/Makefile | 10 ++++++
> tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
> tests/btf_functions.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 167 insertions(+)
>
> diff --git a/tests/bin/Makefile b/tests/bin/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..70bcf57ac4744f30fe03ea12908e42c69390f14a
> --- /dev/null
> +++ b/tests/bin/Makefile
> @@ -0,0 +1,10 @@
> +CC=${CROSS_COMPILE}gcc
> +
> +test_bin: test_bin.c
> + ${CC} $^ -Wall -Wextra -Werror -g -o $@
> +
> +clean:
> + rm -rf test_bin
> +
> +.PHONY: clean
> +
> diff --git a/tests/bin/test_bin.c b/tests/bin/test_bin.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ca6a4852cc511243925db905e55e040519af9cfd
> --- /dev/null
> +++ b/tests/bin/test_bin.c
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdio.h>
> +
> +#define noinline __attribute__((noinline))
> +#define __packed __attribute__((__packed__))
> +
> +struct test_bin_struct {
> + char a;
> + short b;
> + int c;
> + unsigned long long d;
> +};
> +
> +struct test_bin_struct_packed {
> + char a;
> + short b;
> + int c;
> + unsigned long long d;
> +}__packed;
> +
> +int test_bin_func_ok(int a, void *b, char c, short d);
> +int test_bin_func_struct_ok(int a, void *b, char c, struct test_bin_struct d);
> +int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d, int e,
> + void *f, char g, short h,
> + struct test_bin_struct i);
> +int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d, int e,
> + void *f, char g, short h,
> + struct test_bin_struct_packed i);
> +
> +noinline int test_bin_func_ok(int a, void *b, char c, short d)
> +{
> + return a + (long)b + c + d;
> +}
> +
> +noinline int test_bin_func_struct_ok(int a, void *b, char c,
> + struct test_bin_struct d)
> +{
> + return a + (long)b + c + d.a + d.b + d.c + d.d;
> +}
> +
> +noinline int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d,
> + int e, void *f, char g, short h,
> + struct test_bin_struct i)
> +{
> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
> +}
> +
> +noinline int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d,
> + int e, void *f, char g, short h,
> + struct test_bin_struct_packed i)
> +{
> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
> +}
> +
> +int main()
> +{
> + struct test_bin_struct test;
> + struct test_bin_struct_packed test_bis;
> +
> + test_bin_func_ok(0, NULL, 0, 0);
> + test_bin_func_struct_ok(0, NULL, 0, test);
> + test_bin_func_struct_on_stack_ok(0, NULL, 0, 0, 0, NULL, 0, 0, test);
> + test_bin_func_struct_on_stack_ko(0, NULL, 0, 0, 0, NULL, 0, 0, test_bis);
> + return 0;
> +}
> +
> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
> index c92e5ae906f90badfede86eb530108894fbc8c93..fb62b0b56662bb2ae58f7adc0a022c400cba5e0f 100755
> --- a/tests/btf_functions.sh
> +++ b/tests/btf_functions.sh
> @@ -193,4 +193,95 @@ if [[ -n "$VERBOSE" ]]; then
> fi
> echo "Ok"
>
> +# Some specific cases can not be tested directly with a standard kernel.
> +# We can use the small binary in bin/ to test those cases, like packed
> +# structs passed on the stack.
> +
> +echo -n "Validation of BTF encoding corner cases with test_bin functions; this may take some time: "
> +
> +test -n "$VERBOSE" && printf "\nBuilding test_bin..."
> +tests_dir=$(realpath $(dirname $0))
> +make -C ${tests_dir}/bin
> +
> +test -n "$VERBOSE" && printf "\nEncoding..."
> +pahole --btf_features=default --lang_exclude=rust --btf_encode_detached=$outdir/test_bin.btf \
> + --verbose ${tests_dir}/bin/test_bin | grep "skipping BTF encoding of function" \
> + > ${outdir}/test_bin_skipped_fns
> +
> +funcs=$(pfunct --format_path=btf $outdir/test_bin.btd 2>/dev/null|sort)
> +pfunct --all --no_parm_names --format_path=dwarf bin/test_bin | \
> + sort|uniq > $outdir/test_bin_dwarf.funcs
> +pfunct --all --no_parm_names --format_path=btf $outdir/test_bin.btf 2>/dev/null|\
> + awk '{ gsub("^(bpf_kfunc |bpf_fastcall )+",""); print $0}'|sort|uniq > $outdir/test_bin_btf.funcs
> +
> +exact=0
> +while IFS= read -r btf ; do
> + # Matching process can be kept simpler as the tested binary is
> + # specifically tailored for tests
> + dwarf=$(grep -F "$btf" $outdir/test_bin_dwarf.funcs)
> + if [[ "$btf" != "$dwarf" ]]; then
> + echo "ERROR: mismatch : BTF '$btf' not found; DWARF '$dwarf'"
> + fail
> + else
> + exact=$((exact+1))
> + fi
> +done < $outdir/test_bin_btf.funcs
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Matched $exact functions exactly."
> + echo "Ok"
> + echo "Validation of skipped function logic..."
> +fi
> +
> +skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
> +if [[ "$skipped_cnt" == "0" ]]; then
> + echo "No skipped functions. Done."
> + exit 0
> +fi
> +
> +skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
> +for s in $skipped_fns ; do
> + # Ensure the skipped function are not in BTF
> + inbtf=$(grep " $s(" $outdir/test_bin_btf.funcs)
> + if [[ -n "$inbtf" ]]; then
> + echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'"
> + fail
> + fi
> +done
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Skipped encoding $skipped_cnt functions in BTF."
> + echo "Ok"
> + echo "Validating skipped functions have uncertain parameter location..."
> +fi
> +
> +uncertain_loc=$(awk '/due to uncertain parameter location/ { print $1 }' $outdir/test_bin_skipped_fns)
> +legitimate_skip=0
> +
> +for f in $uncertain_loc ; do
> + # Extract parameters types
> + raw_params=$(grep ${f} $outdir/test_bin_dwarf.funcs|sed -n 's/^[^(]*(\([^)]*\)).*/\1/p')
> + IFS=',' read -ra params <<< "${raw_params}"
> + for param in "${params[@]}"
> + do
> + # Search any param that could be a struct
> + struct_type=$(echo ${param}|grep -E '^struct [^*]' | sed -E 's/^struct //')
> + if [ -n "${struct_type}" ]; then
> + # Check with pahole if the struct is detected as
> + # packed
> + if pahole -F dwarf -C "${struct_type}" ${tests_dir}/bin/test_bin|tail -n 2|grep -q __packed__
> + then
> + legitimate_skip=$((legitimate_skip+1))
> + continue 2
> + fi
> + fi
> + done
> + echo "ERROR: '${f}()' should not have been skipped; it has no parameter with uncertain location"
> + fail
> +done
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Found ${legitimate_skip} legitimately skipped function due to uncertain loc"
> +fi
> +echo "Ok"
> exit 0
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value on stack
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
@ 2025-07-07 17:05 ` Alexei Starovoitov
2025-07-07 17:45 ` Ihor Solodrai
` (2 subsequent siblings)
3 siblings, 0 replies; 16+ messages in thread
From: Alexei Starovoitov @ 2025-07-07 17:05 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation)
Cc: dwarves, bpf, Alan Maguire, Arnaldo Carvalho de Melo,
Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet, ebpf
On Mon, Jul 7, 2025 at 7:02 AM Alexis Lothoré (eBPF Foundation)
<alexis.lothore@bootlin.com> wrote:
>
> Most ABIs allow functions to receive structs passed by value, if they
> fit in a register or a pair of registers, depending on the exact ABI.
> However, when there is a struct passed by value but all registers are
> already used for parameters passing, the struct is still passed by value
> but on the stack. This becomes an issue if the passed struct is defined
> with some attributes like __attribute__((packed)) or
> __attribute__((aligned(X)), as its location on the stack is altered, but
> this change is not reflected in dwarf information. The corresponding BTF
> data generated from this can lead to incorrect BPF trampolines
> generation (eg to attach bpf tracing programs to kernel functions) in
> the Linux kernel.
>
> Prevent those wrong cases by not encoding functions consuming structs
> passed by value on stack, when those structs do not have the expected
> alignment due to some attribute usage.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
...
> +static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
> +{
> + struct parameter *param;
> + int param_idx = 0;
> +
> + if (ftype->nr_parms < cu->nr_register_params)
> + return false;
> +
> + ftype__for_each_parameter(ftype, param) {
> + if (param_idx++ < cu->nr_register_params)
> + continue;
> +
> + struct tag *type = cu__type(cu, param->tag.type);
> +
> + if (type == NULL || !tag__is_struct(type))
> + continue;
> +
> + struct type *ctype = tag__type(type);
> + if (ctype->namespace.name == 0)
> + continue;
> +
> + struct class *class = tag__class(type);
> +
> + class__infer_packed_attributes(class, cu);
> +
> + if (class->is_packed)
> + return true;
> + }
> +
> + return false;
> +}
> +
The logic looks good to me.
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value on stack
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
2025-07-07 17:05 ` Alexei Starovoitov
@ 2025-07-07 17:45 ` Ihor Solodrai
2025-08-04 7:13 ` Alexis Lothoré
2025-08-04 9:58 ` Jiri Olsa
3 siblings, 0 replies; 16+ messages in thread
From: Ihor Solodrai @ 2025-07-07 17:45 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation), dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
On 7/7/25 7:02 AM, Alexis Lothoré (eBPF Foundation) wrote:
> Most ABIs allow functions to receive structs passed by value, if they
> fit in a register or a pair of registers, depending on the exact ABI.
> However, when there is a struct passed by value but all registers are
> already used for parameters passing, the struct is still passed by value
> but on the stack. This becomes an issue if the passed struct is defined
> with some attributes like __attribute__((packed)) or
> __attribute__((aligned(X)), as its location on the stack is altered, but
> this change is not reflected in dwarf information. The corresponding BTF
> data generated from this can lead to incorrect BPF trampolines
> generation (eg to attach bpf tracing programs to kernel functions) in
> the Linux kernel.
>
> Prevent those wrong cases by not encoding functions consuming structs
> passed by value on stack, when those structs do not have the expected
> alignment due to some attribute usage.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> Changes in v3:
> - remove unneeded class__find_holes (already done by
> class__infer_packed_attributes)
> - add uncertain parm loc in saved_functions_combine
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Thank you!
> Changes in v2:
> - do not deny any struct passed by value, only those passed on stack AND
> with some attribute alteration
> - use the existing class__infer_packed_attributes to deduce is a struct
> is "altered". As a consequence, move the function filtering from
> parameter__new to btf_encoder__encode_cu, to make sure that all the
> needed data has been parsed from debug info
> ---
> btf_encoder.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
> dwarves.h | 1 +
> 2 files changed, 51 insertions(+), 3 deletions(-)
>
> diff --git a/btf_encoder.c b/btf_encoder.c
> index 0bc23349b5d740c3ddab8208b2e15cdbdd139b9d..3f040fe03d7a208aa742914513bacde9782aabcf 100644
> --- a/btf_encoder.c
> +++ b/btf_encoder.c
> @@ -87,6 +87,7 @@ struct btf_encoder_func_state {
> uint8_t optimized_parms:1;
> uint8_t unexpected_reg:1;
> uint8_t inconsistent_proto:1;
> + uint8_t uncertain_parm_loc:1;
> int ret_type_id;
> struct btf_encoder_func_parm *parms;
> struct btf_encoder_func_annot *annots;
> @@ -1203,6 +1204,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
> state->inconsistent_proto = ftype->inconsistent_proto;
> state->unexpected_reg = ftype->unexpected_reg;
> state->optimized_parms = ftype->optimized_parms;
> + state->uncertain_parm_loc = ftype->uncertain_parm_loc;
> ftype__for_each_parameter(ftype, param) {
> const char *name = parameter__name(param) ?: "";
>
> @@ -1365,7 +1367,7 @@ static int saved_functions_cmp(const void *_a, const void *_b)
>
> static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b)
> {
> - uint8_t optimized, unexpected, inconsistent;
> + uint8_t optimized, unexpected, inconsistent, uncertain_parm_loc;
> int ret;
>
> ret = strncmp(a->elf->name, b->elf->name,
> @@ -1375,11 +1377,13 @@ static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_
> optimized = a->optimized_parms | b->optimized_parms;
> unexpected = a->unexpected_reg | b->unexpected_reg;
> inconsistent = a->inconsistent_proto | b->inconsistent_proto;
> + uncertain_parm_loc = a->uncertain_parm_loc | b->uncertain_parm_loc;
> if (!unexpected && !inconsistent && !funcs__match(a, b))
> inconsistent = 1;
> a->optimized_parms = b->optimized_parms = optimized;
> a->unexpected_reg = b->unexpected_reg = unexpected;
> a->inconsistent_proto = b->inconsistent_proto = inconsistent;
> + a->uncertain_parm_loc = b->uncertain_parm_loc = uncertain_parm_loc;
>
> return 0;
> }
> @@ -1430,9 +1434,15 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
> /* do not exclude functions with optimized-out parameters; they
> * may still be _called_ with the right parameter values, they
> * just do not _use_ them. Only exclude functions with
> - * unexpected register use or multiple inconsistent prototypes.
> + * unexpected register use, multiple inconsistent prototypes or
> + * uncertain parameters location
> */
> - add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto;
> + add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto && !state->uncertain_parm_loc;
> +
> + if (state->uncertain_parm_loc)
> + btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
> + "uncertain parameter location\n",
> + 0, 0);
>
> if (add_to_btf) {
> err = btf_encoder__add_func(state->encoder, state);
> @@ -2553,6 +2563,38 @@ void btf_encoder__delete(struct btf_encoder *encoder)
> free(encoder);
> }
>
> +static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
> +{
> + struct parameter *param;
> + int param_idx = 0;
> +
> + if (ftype->nr_parms < cu->nr_register_params)
> + return false;
> +
> + ftype__for_each_parameter(ftype, param) {
> + if (param_idx++ < cu->nr_register_params)
> + continue;
> +
> + struct tag *type = cu__type(cu, param->tag.type);
> +
> + if (type == NULL || !tag__is_struct(type))
> + continue;
> +
> + struct type *ctype = tag__type(type);
> + if (ctype->namespace.name == 0)
> + continue;
> +
> + struct class *class = tag__class(type);
> +
> + class__infer_packed_attributes(class, cu);
> +
> + if (class->is_packed)
> + return true;
> + }
> +
> + return false;
> +}
> +
> int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct conf_load *conf_load)
> {
> struct llvm_annotation *annot;
> @@ -2647,6 +2689,8 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
> * Skip functions that:
> * - are marked as declarations
> * - do not have full argument names
> + * - have arguments with uncertain locations, e.g packed
> + * structs passed by value on stack
> * - are not in ftrace list (if it's available)
> * - are not external (in case ftrace filter is not available)
> */
> @@ -2693,6 +2737,9 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
> if (!func)
> continue;
>
> + if (ftype__has_uncertain_arg_loc(cu, &fn->proto))
> + fn->proto.uncertain_parm_loc = 1;
> +
> err = btf_encoder__save_func(encoder, fn, func);
> if (err)
> goto out;
> diff --git a/dwarves.h b/dwarves.h
> index 36c689847ebf29a1ab9936f9d0f928dd46514547..d689aee5910f4b40dc13b3e9dc596dfbe6a2c3d0 100644
> --- a/dwarves.h
> +++ b/dwarves.h
> @@ -1021,6 +1021,7 @@ struct ftype {
> uint8_t unexpected_reg:1;
> uint8_t processed:1;
> uint8_t inconsistent_proto:1;
> + uint8_t uncertain_parm_loc:1;
> struct list_head template_type_params;
> struct list_head template_value_params;
> struct template_parameter_pack *template_parameter_pack;
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-07 14:14 ` Alexis Lothoré
@ 2025-07-07 19:36 ` Ihor Solodrai
2025-07-09 16:21 ` Alan Maguire
0 siblings, 1 reply; 16+ messages in thread
From: Ihor Solodrai @ 2025-07-07 19:36 UTC (permalink / raw)
To: Alexis Lothoré, dwarves, Alan Maguire,
Arnaldo Carvalho de Melo
Cc: bpf, Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet,
ebpf
On 7/7/25 7:14 AM, Alexis Lothoré wrote:
> On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation) wrote:
>> Add a small binary representing specific cases likely absent from
>> standard vmlinux or kernel modules files. As a starter, the introduced
>> binary exposes a few functions consuming structs passed by value, some
>> passed by register, some passed on the stack:
>>
>> int main(void);
>> int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
>> void *, char, short int, struct test_bin_struct_packed);
>> int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
>> void *, char, short int, struct test_bin_struct);
>> int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
>> int test_bin_func_ok(int, void *, char, short int);
>>
>> Then enrich btf_functions.sh to make it perform the following steps:
>> - build the binary
>> - generate BTF info and pfunct listing, both with dwarf and the
>> generated BTF
>> - check that any function encoded in BTF is found in DWARF
>> - check that any function announced as skipped is indeed absent from BTF
>> - check that any skipped function has been skipped due to uncertain
>> parameter location
>>
>> Example of the new test execution:
>> Encoding...Matched 4 functions exactly.
>> Ok
>> Validation of skipped function logic...
>> Skipped encoding 1 functions in BTF.
>> Ok
>> Validating skipped functions have uncertain parameter location...
>> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
>
> A word about this specific error: I may have missed it in the previous
> iteration, but I systematically get this error when running the following
> command:
> $ pahole -C test_bin_struct_packed tests/bin/test_bin
>
> I initially thought that it would be something related to the binary being
> a userspace program and not a kernel module, but I observe the following:
> - the issue is observed even on a .ko file (tested on the previous series
> iteration with kmod.ko)
> - the issue does not appear if there is no class filtering (ie the `-C`
> arg) provided to pahole
> - the issue occurs as well with the packaged pahole version on my host (v1.30)
> - the struct layout is still displayed correctly despite the error
>
> A quick bisect shows that the error log has started appearing with
> 59f5409f1357 ("dwarf_loader: Fix termination on BTF encoding error"). This
> commit has "enforced" error propagation if dwfl_getmodules returns
> something different than 0 (before, it was propagating an error only if the
> error code was negative, but dwfl_getmodules seems to be able to return
> values > 0 as well). As is sound unrelated to this series, I pushed this
> new revision anyway. [1] seems to hint that the issue is known, but in my
> case I don't get any additional log about unhandled DWARF operation. The
> issue is pretty repeatable on my side, feel free to ask for any additional
> detail or manipulation that could help.
I looked into this...
pahole_stealer may return LSK__STOP_LOADING in normal case, for example
when a class filter is provided [1]:
if (list_empty(&class_names)) {
dump_and_stop:
ret = LSK__STOP_LOADING;
}
And in the dwarf_loader we abort (as with error) in case of
LSK__STOP_LOADING [2]:
if (cus__steal_now(dcus->cus, job->cu, dcus->conf) == LSK__STOP_LOADING)
goto out_abort;
This was not an issue before 59f5409f1357 because of how errors were
propagated to dwfl_getmodules(), as mentioned in the other thread.
I think a proper fix for this is differentiating two variants of
LSK__STOP_LOADING: stop because of an error, and stop because there is
nothing else to do. That would require a bit of refactoring.
Alan, Arnaldo, what do you think?
[1] https://github.com/acmel/dwarves/blob/master/pahole.c#L3390-L3392
[2] https://github.com/acmel/dwarves/blob/master/dwarf_loader.c#L3678-L3679
>
> [1] https://lore.kernel.org/dwarves/933e199997949c0ac8a71551830f1e6c98d8bff0@linux.dev/
>> Found 1 legitimately skipped function due to uncertain loc
>> Ok
>>
>> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>> ---
>> Changes in v3:
>> - bring a userspace binary instead of an OoT kernel module
>> - remove test dependency to a kernel directory being provided
>> - improve test dir detection
>>
>> Changes in v2:
>> - new patch
>> ---
>> tests/bin/Makefile | 10 ++++++
>> tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
>> tests/btf_functions.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 167 insertions(+)
>>
>> diff --git a/tests/bin/Makefile b/tests/bin/Makefile
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..70bcf57ac4744f30fe03ea12908e42c69390f14a
>> --- /dev/null
>> +++ b/tests/bin/Makefile
>> @@ -0,0 +1,10 @@
>> +CC=${CROSS_COMPILE}gcc
>> +
>> +test_bin: test_bin.c
>> + ${CC} $^ -Wall -Wextra -Werror -g -o $@
>> +
>> +clean:
>> + rm -rf test_bin
>> +
>> +.PHONY: clean
>> +
>> diff --git a/tests/bin/test_bin.c b/tests/bin/test_bin.c
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..ca6a4852cc511243925db905e55e040519af9cfd
>> --- /dev/null
>> +++ b/tests/bin/test_bin.c
>> @@ -0,0 +1,66 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <stdio.h>
>> +
>> +#define noinline __attribute__((noinline))
>> +#define __packed __attribute__((__packed__))
>> +
>> +struct test_bin_struct {
>> + char a;
>> + short b;
>> + int c;
>> + unsigned long long d;
>> +};
>> +
>> +struct test_bin_struct_packed {
>> + char a;
>> + short b;
>> + int c;
>> + unsigned long long d;
>> +}__packed;
>> +
>> +int test_bin_func_ok(int a, void *b, char c, short d);
>> +int test_bin_func_struct_ok(int a, void *b, char c, struct test_bin_struct d);
>> +int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d, int e,
>> + void *f, char g, short h,
>> + struct test_bin_struct i);
>> +int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d, int e,
>> + void *f, char g, short h,
>> + struct test_bin_struct_packed i);
>> +
>> +noinline int test_bin_func_ok(int a, void *b, char c, short d)
>> +{
>> + return a + (long)b + c + d;
>> +}
>> +
>> +noinline int test_bin_func_struct_ok(int a, void *b, char c,
>> + struct test_bin_struct d)
>> +{
>> + return a + (long)b + c + d.a + d.b + d.c + d.d;
>> +}
>> +
>> +noinline int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d,
>> + int e, void *f, char g, short h,
>> + struct test_bin_struct i)
>> +{
>> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
>> +}
>> +
>> +noinline int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d,
>> + int e, void *f, char g, short h,
>> + struct test_bin_struct_packed i)
>> +{
>> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
>> +}
>> +
>> +int main()
>> +{
>> + struct test_bin_struct test;
>> + struct test_bin_struct_packed test_bis;
>> +
>> + test_bin_func_ok(0, NULL, 0, 0);
>> + test_bin_func_struct_ok(0, NULL, 0, test);
>> + test_bin_func_struct_on_stack_ok(0, NULL, 0, 0, 0, NULL, 0, 0, test);
>> + test_bin_func_struct_on_stack_ko(0, NULL, 0, 0, 0, NULL, 0, 0, test_bis);
>> + return 0;
>> +}
>> +
>> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
>> index c92e5ae906f90badfede86eb530108894fbc8c93..fb62b0b56662bb2ae58f7adc0a022c400cba5e0f 100755
>> --- a/tests/btf_functions.sh
>> +++ b/tests/btf_functions.sh
>> @@ -193,4 +193,95 @@ if [[ -n "$VERBOSE" ]]; then
>> fi
>> echo "Ok"
>>
>> +# Some specific cases can not be tested directly with a standard kernel.
>> +# We can use the small binary in bin/ to test those cases, like packed
>> +# structs passed on the stack.
>> +
>> +echo -n "Validation of BTF encoding corner cases with test_bin functions; this may take some time: "
>> +
>> +test -n "$VERBOSE" && printf "\nBuilding test_bin..."
>> +tests_dir=$(realpath $(dirname $0))
>> +make -C ${tests_dir}/bin
>> +
>> +test -n "$VERBOSE" && printf "\nEncoding..."
>> +pahole --btf_features=default --lang_exclude=rust --btf_encode_detached=$outdir/test_bin.btf \
>> + --verbose ${tests_dir}/bin/test_bin | grep "skipping BTF encoding of function" \
>> + > ${outdir}/test_bin_skipped_fns
>> +
>> +funcs=$(pfunct --format_path=btf $outdir/test_bin.btd 2>/dev/null|sort)
>> +pfunct --all --no_parm_names --format_path=dwarf bin/test_bin | \
>> + sort|uniq > $outdir/test_bin_dwarf.funcs
>> +pfunct --all --no_parm_names --format_path=btf $outdir/test_bin.btf 2>/dev/null|\
>> + awk '{ gsub("^(bpf_kfunc |bpf_fastcall )+",""); print $0}'|sort|uniq > $outdir/test_bin_btf.funcs
>> +
>> +exact=0
>> +while IFS= read -r btf ; do
>> + # Matching process can be kept simpler as the tested binary is
>> + # specifically tailored for tests
>> + dwarf=$(grep -F "$btf" $outdir/test_bin_dwarf.funcs)
>> + if [[ "$btf" != "$dwarf" ]]; then
>> + echo "ERROR: mismatch : BTF '$btf' not found; DWARF '$dwarf'"
>> + fail
>> + else
>> + exact=$((exact+1))
>> + fi
>> +done < $outdir/test_bin_btf.funcs
>> +
>> +if [[ -n "$VERBOSE" ]]; then
>> + echo "Matched $exact functions exactly."
>> + echo "Ok"
>> + echo "Validation of skipped function logic..."
>> +fi
>> +
>> +skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
>> +if [[ "$skipped_cnt" == "0" ]]; then
>> + echo "No skipped functions. Done."
>> + exit 0
>> +fi
>> +
>> +skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
>> +for s in $skipped_fns ; do
>> + # Ensure the skipped function are not in BTF
>> + inbtf=$(grep " $s(" $outdir/test_bin_btf.funcs)
>> + if [[ -n "$inbtf" ]]; then
>> + echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'"
>> + fail
>> + fi
>> +done
>> +
>> +if [[ -n "$VERBOSE" ]]; then
>> + echo "Skipped encoding $skipped_cnt functions in BTF."
>> + echo "Ok"
>> + echo "Validating skipped functions have uncertain parameter location..."
>> +fi
>> +
>> +uncertain_loc=$(awk '/due to uncertain parameter location/ { print $1 }' $outdir/test_bin_skipped_fns)
>> +legitimate_skip=0
>> +
>> +for f in $uncertain_loc ; do
>> + # Extract parameters types
>> + raw_params=$(grep ${f} $outdir/test_bin_dwarf.funcs|sed -n 's/^[^(]*(\([^)]*\)).*/\1/p')
>> + IFS=',' read -ra params <<< "${raw_params}"
>> + for param in "${params[@]}"
>> + do
>> + # Search any param that could be a struct
>> + struct_type=$(echo ${param}|grep -E '^struct [^*]' | sed -E 's/^struct //')
>> + if [ -n "${struct_type}" ]; then
>> + # Check with pahole if the struct is detected as
>> + # packed
>> + if pahole -F dwarf -C "${struct_type}" ${tests_dir}/bin/test_bin|tail -n 2|grep -q __packed__
>> + then
>> + legitimate_skip=$((legitimate_skip+1))
>> + continue 2
>> + fi
>> + fi
>> + done
>> + echo "ERROR: '${f}()' should not have been skipped; it has no parameter with uncertain location"
>> + fail
>> +done
>> +
>> +if [[ -n "$VERBOSE" ]]; then
>> + echo "Found ${legitimate_skip} legitimately skipped function due to uncertain loc"
>> +fi
>> +echo "Ok"
>> exit 0
>
>
>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-07 19:36 ` Ihor Solodrai
@ 2025-07-09 16:21 ` Alan Maguire
2025-07-15 8:04 ` Alexis Lothoré
0 siblings, 1 reply; 16+ messages in thread
From: Alan Maguire @ 2025-07-09 16:21 UTC (permalink / raw)
To: Ihor Solodrai, Alexis Lothoré, dwarves,
Arnaldo Carvalho de Melo
Cc: bpf, Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet,
ebpf
On 07/07/2025 20:36, Ihor Solodrai wrote:
> On 7/7/25 7:14 AM, Alexis Lothoré wrote:
>> On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation)
>> wrote:
>>> Add a small binary representing specific cases likely absent from
>>> standard vmlinux or kernel modules files. As a starter, the introduced
>>> binary exposes a few functions consuming structs passed by value, some
>>> passed by register, some passed on the stack:
>>>
>>> int main(void);
>>> int test_bin_func_struct_on_stack_ko(int, void *, char, short int,
>>> int, \
>>> void *, char, short int, struct test_bin_struct_packed);
>>> int test_bin_func_struct_on_stack_ok(int, void *, char, short int,
>>> int, \
>>> void *, char, short int, struct test_bin_struct);
>>> int test_bin_func_struct_ok(int, void *, char, struct
>>> test_bin_struct);
>>> int test_bin_func_ok(int, void *, char, short int);
>>>
>>> Then enrich btf_functions.sh to make it perform the following steps:
>>> - build the binary
>>> - generate BTF info and pfunct listing, both with dwarf and the
>>> generated BTF
>>> - check that any function encoded in BTF is found in DWARF
>>> - check that any function announced as skipped is indeed absent from BTF
>>> - check that any skipped function has been skipped due to uncertain
>>> parameter location
>>>
>>> Example of the new test execution:
>>> Encoding...Matched 4 functions exactly.
>>> Ok
>>> Validation of skipped function logic...
>>> Skipped encoding 1 functions in BTF.
>>> Ok
>>> Validating skipped functions have uncertain parameter location...
>>> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
>>
>> A word about this specific error: I may have missed it in the previous
>> iteration, but I systematically get this error when running the following
>> command:
>> $ pahole -C test_bin_struct_packed tests/bin/test_bin
>>
>> I initially thought that it would be something related to the binary
>> being
>> a userspace program and not a kernel module, but I observe the following:
>> - the issue is observed even on a .ko file (tested on the previous series
>> iteration with kmod.ko)
>> - the issue does not appear if there is no class filtering (ie the `-C`
>> arg) provided to pahole
>> - the issue occurs as well with the packaged pahole version on my host
>> (v1.30)
>> - the struct layout is still displayed correctly despite the error
>>
>> A quick bisect shows that the error log has started appearing with
>> 59f5409f1357 ("dwarf_loader: Fix termination on BTF encoding error").
>> This
>> commit has "enforced" error propagation if dwfl_getmodules returns
>> something different than 0 (before, it was propagating an error only
>> if the
>> error code was negative, but dwfl_getmodules seems to be able to return
>> values > 0 as well). As is sound unrelated to this series, I pushed this
>> new revision anyway. [1] seems to hint that the issue is known, but in my
>> case I don't get any additional log about unhandled DWARF operation. The
>> issue is pretty repeatable on my side, feel free to ask for any
>> additional
>> detail or manipulation that could help.
>
> I looked into this...
>
> pahole_stealer may return LSK__STOP_LOADING in normal case, for example
> when a class filter is provided [1]:
>
> if (list_empty(&class_names)) {
> dump_and_stop:
> ret = LSK__STOP_LOADING;
> }
>
> And in the dwarf_loader we abort (as with error) in case of
> LSK__STOP_LOADING [2]:
>
> if (cus__steal_now(dcus->cus, job->cu, dcus->conf) ==
> LSK__STOP_LOADING)
> goto out_abort;
>
> This was not an issue before 59f5409f1357 because of how errors were
> propagated to dwfl_getmodules(), as mentioned in the other thread.
>
> I think a proper fix for this is differentiating two variants of
> LSK__STOP_LOADING: stop because of an error, and stop because there is
> nothing else to do. That would require a bit of refactoring.
>
> Alan, Arnaldo, what do you think?
>
Would it suffice to treat LSK__STOP_LOADING as an error in the BTF
encoding case, and not otherwise? That's a bit of hack; ideally I
suppose we'd introduce LSK__ABORT (like DWARF_CB_ABORT) and use it for
all the failure modes, reserving LSK__STOP_LOADING for cases where we
are done processing rather than we met an error.
> [1] https://github.com/acmel/dwarves/blob/master/pahole.c#L3390-L3392
> [2] https://github.com/acmel/dwarves/blob/master/dwarf_loader.c#L3678-L3679
>
>>
>> [1] https://lore.kernel.org/
>> dwarves/933e199997949c0ac8a71551830f1e6c98d8bff0@linux.dev/
>>> Found 1 legitimately skipped function due to uncertain loc
>>> Ok
>>>
>>> Signed-off-by: Alexis Lothoré (eBPF Foundation)
>>> <alexis.lothore@bootlin.com>
>>> ---
>>> Changes in v3:
>>> - bring a userspace binary instead of an OoT kernel module
>>> - remove test dependency to a kernel directory being provided
>>> - improve test dir detection
>>>
>>> Changes in v2:
>>> - new patch
>>> ---
>>> tests/bin/Makefile | 10 ++++++
>>> tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
>>> tests/btf_functions.sh | 91 +++++++++++++++++++++++++++++++++++++++
>>> +++++++++++
>>> 3 files changed, 167 insertions(+)
>>>
>>> diff --git a/tests/bin/Makefile b/tests/bin/Makefile
>>> new file mode 100644
>>> index
>>> 0000000000000000000000000000000000000000..70bcf57ac4744f30fe03ea12908e42c69390f14a
>>> --- /dev/null
>>> +++ b/tests/bin/Makefile
>>> @@ -0,0 +1,10 @@
>>> +CC=${CROSS_COMPILE}gcc
>>> +
>>> +test_bin: test_bin.c
>>> + ${CC} $^ -Wall -Wextra -Werror -g -o $@
>>> +
>>> +clean:
>>> + rm -rf test_bin
>>> +
>>> +.PHONY: clean
>>> +
>>> diff --git a/tests/bin/test_bin.c b/tests/bin/test_bin.c
>>> new file mode 100644
>>> index
>>> 0000000000000000000000000000000000000000..ca6a4852cc511243925db905e55e040519af9cfd
>>> --- /dev/null
>>> +++ b/tests/bin/test_bin.c
>>> @@ -0,0 +1,66 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +#include <stdio.h>
>>> +
>>> +#define noinline __attribute__((noinline))
>>> +#define __packed __attribute__((__packed__))
>>> +
>>> +struct test_bin_struct {
>>> + char a;
>>> + short b;
>>> + int c;
>>> + unsigned long long d;
>>> +};
>>> +
>>> +struct test_bin_struct_packed {
>>> + char a;
>>> + short b;
>>> + int c;
>>> + unsigned long long d;
>>> +}__packed;
>>> +
>>> +int test_bin_func_ok(int a, void *b, char c, short d);
>>> +int test_bin_func_struct_ok(int a, void *b, char c, struct
>>> test_bin_struct d);
>>> +int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short
>>> d, int e,
>>> + void *f, char g, short h,
>>> + struct test_bin_struct i);
>>> +int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short
>>> d, int e,
>>> + void *f, char g, short h,
>>> + struct test_bin_struct_packed i);
>>> +
>>> +noinline int test_bin_func_ok(int a, void *b, char c, short d)
>>> +{
>>> + return a + (long)b + c + d;
>>> +}
>>> +
>>> +noinline int test_bin_func_struct_ok(int a, void *b, char c,
>>> + struct test_bin_struct d)
>>> +{
>>> + return a + (long)b + c + d.a + d.b + d.c + d.d;
>>> +}
>>> +
>>> +noinline int test_bin_func_struct_on_stack_ok(int a, void *b, char
>>> c, short d,
>>> + int e, void *f, char
>>> g, short h,
>>> + struct
>>> test_bin_struct i)
>>> +{
>>> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b +
>>> i.c + i.d;
>>> +}
>>> +
>>> +noinline int test_bin_func_struct_on_stack_ko(int a, void *b, char
>>> c, short d,
>>> + int e, void *f, char
>>> g, short h,
>>> + struct
>>> test_bin_struct_packed i)
>>> +{
>>> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b +
>>> i.c + i.d;
>>> +}
>>> +
>>> +int main()
>>> +{
>>> + struct test_bin_struct test;
>>> + struct test_bin_struct_packed test_bis;
>>> +
>>> + test_bin_func_ok(0, NULL, 0, 0);
>>> + test_bin_func_struct_ok(0, NULL, 0, test);
>>> + test_bin_func_struct_on_stack_ok(0, NULL, 0, 0, 0, NULL, 0, 0,
>>> test);
>>> + test_bin_func_struct_on_stack_ko(0, NULL, 0, 0, 0, NULL, 0, 0,
>>> test_bis);
>>> + return 0;
>>> +}
>>> +
>>> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
>>> index
>>> c92e5ae906f90badfede86eb530108894fbc8c93..fb62b0b56662bb2ae58f7adc0a022c400cba5e0f 100755
>>> --- a/tests/btf_functions.sh
>>> +++ b/tests/btf_functions.sh
>>> @@ -193,4 +193,95 @@ if [[ -n "$VERBOSE" ]]; then
>>> fi
>>> echo "Ok"
>>> +# Some specific cases can not be tested directly with a standard
>>> kernel.
>>> +# We can use the small binary in bin/ to test those cases, like packed
>>> +# structs passed on the stack.
>>> +
>>> +echo -n "Validation of BTF encoding corner cases with test_bin
>>> functions; this may take some time: "
>>> +
>>> +test -n "$VERBOSE" && printf "\nBuilding test_bin..."
>>> +tests_dir=$(realpath $(dirname $0))
>>> +make -C ${tests_dir}/bin
>>> +
>>> +test -n "$VERBOSE" && printf "\nEncoding..."
>>> +pahole --btf_features=default --lang_exclude=rust --
>>> btf_encode_detached=$outdir/test_bin.btf \
>>> + --verbose ${tests_dir}/bin/test_bin | grep "skipping BTF
>>> encoding of function" \
>>> + > ${outdir}/test_bin_skipped_fns
>>> +
>>> +funcs=$(pfunct --format_path=btf $outdir/test_bin.btd 2>/dev/null|sort)
>>> +pfunct --all --no_parm_names --format_path=dwarf bin/test_bin | \
>>> + sort|uniq > $outdir/test_bin_dwarf.funcs
>>> +pfunct --all --no_parm_names --format_path=btf $outdir/test_bin.btf
>>> 2>/dev/null|\
>>> + awk '{ gsub("^(bpf_kfunc |bpf_fastcall )+",""); print $0}'|sort|
>>> uniq > $outdir/test_bin_btf.funcs
>>> +
>>> +exact=0
>>> +while IFS= read -r btf ; do
>>> + # Matching process can be kept simpler as the tested binary is
>>> + # specifically tailored for tests
>>> + dwarf=$(grep -F "$btf" $outdir/test_bin_dwarf.funcs)
>>> + if [[ "$btf" != "$dwarf" ]]; then
>>> + echo "ERROR: mismatch : BTF '$btf' not found; DWARF '$dwarf'"
>>> + fail
>>> + else
>>> + exact=$((exact+1))
>>> + fi
>>> +done < $outdir/test_bin_btf.funcs
>>> +
>>> +if [[ -n "$VERBOSE" ]]; then
>>> + echo "Matched $exact functions exactly."
>>> + echo "Ok"
>>> + echo "Validation of skipped function logic..."
>>> +fi
>>> +
>>> +skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
>>> +if [[ "$skipped_cnt" == "0" ]]; then
>>> + echo "No skipped functions. Done."
>>> + exit 0
>>> +fi
>>> +
>>> +skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
>>> +for s in $skipped_fns ; do
>>> + # Ensure the skipped function are not in BTF
>>> + inbtf=$(grep " $s(" $outdir/test_bin_btf.funcs)
>>> + if [[ -n "$inbtf" ]]; then
>>> + echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'"
>>> + fail
>>> + fi
>>> +done
>>> +
>>> +if [[ -n "$VERBOSE" ]]; then
>>> + echo "Skipped encoding $skipped_cnt functions in BTF."
>>> + echo "Ok"
>>> + echo "Validating skipped functions have uncertain parameter
>>> location..."
>>> +fi
>>> +
>>> +uncertain_loc=$(awk '/due to uncertain parameter location/ { print
>>> $1 }' $outdir/test_bin_skipped_fns)
>>> +legitimate_skip=0
>>> +
>>> +for f in $uncertain_loc ; do
>>> + # Extract parameters types
>>> + raw_params=$(grep ${f} $outdir/test_bin_dwarf.funcs|sed -n 's/
>>> ^[^(]*(\([^)]*\)).*/\1/p')
>>> + IFS=',' read -ra params <<< "${raw_params}"
>>> + for param in "${params[@]}"
>>> + do
>>> + # Search any param that could be a struct
>>> + struct_type=$(echo ${param}|grep -E '^struct [^*]' | sed -E
>>> 's/^struct //')
>>> + if [ -n "${struct_type}" ]; then
>>> + # Check with pahole if the struct is detected as
>>> + # packed
>>> + if pahole -F dwarf -C "${struct_type}" ${tests_dir}/bin/
>>> test_bin|tail -n 2|grep -q __packed__
>>> + then
>>> + legitimate_skip=$((legitimate_skip+1))
>>> + continue 2
>>> + fi
>>> + fi
>>> + done
>>> + echo "ERROR: '${f}()' should not have been skipped; it has no
>>> parameter with uncertain location"
>>> + fail
>>> +done
>>> +
>>> +if [[ -n "$VERBOSE" ]]; then
>>> + echo "Found ${legitimate_skip} legitimately skipped function due
>>> to uncertain loc"
>>> +fi
>>> +echo "Ok"
>>> exit 0
>>
>>
>>
>>
>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-09 16:21 ` Alan Maguire
@ 2025-07-15 8:04 ` Alexis Lothoré
2025-07-15 15:36 ` Ihor Solodrai
0 siblings, 1 reply; 16+ messages in thread
From: Alexis Lothoré @ 2025-07-15 8:04 UTC (permalink / raw)
To: Alan Maguire, Ihor Solodrai, dwarves, Arnaldo Carvalho de Melo
Cc: bpf, Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet,
ebpf
On Wed Jul 9, 2025 at 6:21 PM CEST, Alan Maguire wrote:
> On 07/07/2025 20:36, Ihor Solodrai wrote:
>> On 7/7/25 7:14 AM, Alexis Lothoré wrote:
>>> On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation)
>>> wrote:
[...]
>> I think a proper fix for this is differentiating two variants of
>> LSK__STOP_LOADING: stop because of an error, and stop because there is
>> nothing else to do. That would require a bit of refactoring.
>>
>> Alan, Arnaldo, what do you think?
>>
>
> Would it suffice to treat LSK__STOP_LOADING as an error in the BTF
> encoding case, and not otherwise? That's a bit of hack; ideally I
> suppose we'd introduce LSK__ABORT (like DWARF_CB_ABORT) and use it for
> all the failure modes, reserving LSK__STOP_LOADING for cases where we
> are done processing rather than we met an error.
Ihor, Alan, is anyone one of you planning to work on it ? If not, do you
want me take a look and implement one of the solution suggested above ? I
guess it's best to aim for Alan's second suggestion first (introducing a
new LSK enum to represent a failure), otherwise the simpler solution
distinguishing reasons for LSK__STOP_LOADING.
Alexis
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-15 8:04 ` Alexis Lothoré
@ 2025-07-15 15:36 ` Ihor Solodrai
0 siblings, 0 replies; 16+ messages in thread
From: Ihor Solodrai @ 2025-07-15 15:36 UTC (permalink / raw)
To: Alexis Lothoré, Alan Maguire, dwarves,
Arnaldo Carvalho de Melo
Cc: bpf, Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet,
ebpf
On 7/15/25 1:04 AM, Alexis Lothoré wrote:
> On Wed Jul 9, 2025 at 6:21 PM CEST, Alan Maguire wrote:
>> On 07/07/2025 20:36, Ihor Solodrai wrote:
>>> On 7/7/25 7:14 AM, Alexis Lothoré wrote:
>>>> On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation)
>>>> wrote:
>
> [...]
>
>>> I think a proper fix for this is differentiating two variants of
>>> LSK__STOP_LOADING: stop because of an error, and stop because there is
>>> nothing else to do. That would require a bit of refactoring.
>>>
>>> Alan, Arnaldo, what do you think?
>>>
>>
>> Would it suffice to treat LSK__STOP_LOADING as an error in the BTF
>> encoding case, and not otherwise? That's a bit of hack; ideally I
>> suppose we'd introduce LSK__ABORT (like DWARF_CB_ABORT) and use it for
>> all the failure modes, reserving LSK__STOP_LOADING for cases where we
>> are done processing rather than we met an error.
>
> Ihor, Alan, is anyone one of you planning to work on it ? If not, do you
> want me take a look and implement one of the solution suggested above ? I
> guess it's best to aim for Alan's second suggestion first (introducing a
> new LSK enum to represent a failure), otherwise the simpler solution
> distinguishing reasons for LSK__STOP_LOADING.
If you're willing to work on this, please go ahead.
It's not directly related to this series though, so maybe a separate
patch.
>
> Alexis
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value on stack
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
2025-07-07 17:05 ` Alexei Starovoitov
2025-07-07 17:45 ` Ihor Solodrai
@ 2025-08-04 7:13 ` Alexis Lothoré
2025-08-04 9:58 ` Jiri Olsa
3 siblings, 0 replies; 16+ messages in thread
From: Alexis Lothoré @ 2025-08-04 7:13 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation), dwarves
Cc: bpf, Alan Maguire, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
Hi,
On Mon Jul 7, 2025 at 4:02 PM CEST, Alexis Lothoré (eBPF Foundation) wrote:
> Most ABIs allow functions to receive structs passed by value, if they
> fit in a register or a pair of registers, depending on the exact ABI.
> However, when there is a struct passed by value but all registers are
> already used for parameters passing, the struct is still passed by value
> but on the stack. This becomes an issue if the passed struct is defined
> with some attributes like __attribute__((packed)) or
> __attribute__((aligned(X)), as its location on the stack is altered, but
> this change is not reflected in dwarf information. The corresponding BTF
> data generated from this can lead to incorrect BPF trampolines
> generation (eg to attach bpf tracing programs to kernel functions) in
> the Linux kernel.
>
> Prevent those wrong cases by not encoding functions consuming structs
> passed by value on stack, when those structs do not have the expected
> alignment due to some attribute usage.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
Gentle ping/follow-up on this series. Most of the discussions on this
revision were about an unrelated bug that has been submitted and merged in
[1], aside from that Alexei and Ihor provided some positive feedback on the
current revision. Any additional feedback on this ?
[1] https://lore.kernel.org/dwarves/20250731-lsk__abort-v3-1-40f79e168198@bootlin.com/
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value on stack
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
` (2 preceding siblings ...)
2025-08-04 7:13 ` Alexis Lothoré
@ 2025-08-04 9:58 ` Jiri Olsa
3 siblings, 0 replies; 16+ messages in thread
From: Jiri Olsa @ 2025-08-04 9:58 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation)
Cc: dwarves, bpf, Alan Maguire, Arnaldo Carvalho de Melo,
Alexei Starovoitov, Thomas Petazzoni, Bastien Curutchet, ebpf
On Mon, Jul 07, 2025 at 04:02:03PM +0200, Alexis Lothoré (eBPF Foundation) wrote:
> Most ABIs allow functions to receive structs passed by value, if they
> fit in a register or a pair of registers, depending on the exact ABI.
> However, when there is a struct passed by value but all registers are
> already used for parameters passing, the struct is still passed by value
> but on the stack. This becomes an issue if the passed struct is defined
> with some attributes like __attribute__((packed)) or
> __attribute__((aligned(X)), as its location on the stack is altered, but
> this change is not reflected in dwarf information. The corresponding BTF
> data generated from this can lead to incorrect BPF trampolines
> generation (eg to attach bpf tracing programs to kernel functions) in
> the Linux kernel.
>
> Prevent those wrong cases by not encoding functions consuming structs
> passed by value on stack, when those structs do not have the expected
> alignment due to some attribute usage.
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
> ---
> Changes in v3:
> - remove unneeded class__find_holes (already done by
> class__infer_packed_attributes)
> - add uncertain parm loc in saved_functions_combine
lgtm, no change in functions on my setup
Acked-by: Jiri Olsa <jolsa@kernel.org>
jirka
> Changes in v2:
> - do not deny any struct passed by value, only those passed on stack AND
> with some attribute alteration
> - use the existing class__infer_packed_attributes to deduce is a struct
> is "altered". As a consequence, move the function filtering from
> parameter__new to btf_encoder__encode_cu, to make sure that all the
> needed data has been parsed from debug info
> ---
> btf_encoder.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
> dwarves.h | 1 +
> 2 files changed, 51 insertions(+), 3 deletions(-)
>
> diff --git a/btf_encoder.c b/btf_encoder.c
> index 0bc23349b5d740c3ddab8208b2e15cdbdd139b9d..3f040fe03d7a208aa742914513bacde9782aabcf 100644
> --- a/btf_encoder.c
> +++ b/btf_encoder.c
> @@ -87,6 +87,7 @@ struct btf_encoder_func_state {
> uint8_t optimized_parms:1;
> uint8_t unexpected_reg:1;
> uint8_t inconsistent_proto:1;
> + uint8_t uncertain_parm_loc:1;
> int ret_type_id;
> struct btf_encoder_func_parm *parms;
> struct btf_encoder_func_annot *annots;
> @@ -1203,6 +1204,7 @@ static int32_t btf_encoder__save_func(struct btf_encoder *encoder, struct functi
> state->inconsistent_proto = ftype->inconsistent_proto;
> state->unexpected_reg = ftype->unexpected_reg;
> state->optimized_parms = ftype->optimized_parms;
> + state->uncertain_parm_loc = ftype->uncertain_parm_loc;
> ftype__for_each_parameter(ftype, param) {
> const char *name = parameter__name(param) ?: "";
>
> @@ -1365,7 +1367,7 @@ static int saved_functions_cmp(const void *_a, const void *_b)
>
> static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_encoder_func_state *b)
> {
> - uint8_t optimized, unexpected, inconsistent;
> + uint8_t optimized, unexpected, inconsistent, uncertain_parm_loc;
> int ret;
>
> ret = strncmp(a->elf->name, b->elf->name,
> @@ -1375,11 +1377,13 @@ static int saved_functions_combine(struct btf_encoder_func_state *a, struct btf_
> optimized = a->optimized_parms | b->optimized_parms;
> unexpected = a->unexpected_reg | b->unexpected_reg;
> inconsistent = a->inconsistent_proto | b->inconsistent_proto;
> + uncertain_parm_loc = a->uncertain_parm_loc | b->uncertain_parm_loc;
> if (!unexpected && !inconsistent && !funcs__match(a, b))
> inconsistent = 1;
> a->optimized_parms = b->optimized_parms = optimized;
> a->unexpected_reg = b->unexpected_reg = unexpected;
> a->inconsistent_proto = b->inconsistent_proto = inconsistent;
> + a->uncertain_parm_loc = b->uncertain_parm_loc = uncertain_parm_loc;
>
> return 0;
> }
> @@ -1430,9 +1434,15 @@ static int btf_encoder__add_saved_funcs(struct btf_encoder *encoder, bool skip_e
> /* do not exclude functions with optimized-out parameters; they
> * may still be _called_ with the right parameter values, they
> * just do not _use_ them. Only exclude functions with
> - * unexpected register use or multiple inconsistent prototypes.
> + * unexpected register use, multiple inconsistent prototypes or
> + * uncertain parameters location
> */
> - add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto;
> + add_to_btf |= !state->unexpected_reg && !state->inconsistent_proto && !state->uncertain_parm_loc;
> +
> + if (state->uncertain_parm_loc)
> + btf_encoder__log_func_skip(encoder, saved_fns[i].elf,
> + "uncertain parameter location\n",
> + 0, 0);
>
> if (add_to_btf) {
> err = btf_encoder__add_func(state->encoder, state);
> @@ -2553,6 +2563,38 @@ void btf_encoder__delete(struct btf_encoder *encoder)
> free(encoder);
> }
>
> +static bool ftype__has_uncertain_arg_loc(struct cu *cu, struct ftype *ftype)
> +{
> + struct parameter *param;
> + int param_idx = 0;
> +
> + if (ftype->nr_parms < cu->nr_register_params)
> + return false;
> +
> + ftype__for_each_parameter(ftype, param) {
> + if (param_idx++ < cu->nr_register_params)
> + continue;
> +
> + struct tag *type = cu__type(cu, param->tag.type);
> +
> + if (type == NULL || !tag__is_struct(type))
> + continue;
> +
> + struct type *ctype = tag__type(type);
> + if (ctype->namespace.name == 0)
> + continue;
> +
> + struct class *class = tag__class(type);
> +
> + class__infer_packed_attributes(class, cu);
> +
> + if (class->is_packed)
> + return true;
> + }
> +
> + return false;
> +}
> +
> int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct conf_load *conf_load)
> {
> struct llvm_annotation *annot;
> @@ -2647,6 +2689,8 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
> * Skip functions that:
> * - are marked as declarations
> * - do not have full argument names
> + * - have arguments with uncertain locations, e.g packed
> + * structs passed by value on stack
> * - are not in ftrace list (if it's available)
> * - are not external (in case ftrace filter is not available)
> */
> @@ -2693,6 +2737,9 @@ int btf_encoder__encode_cu(struct btf_encoder *encoder, struct cu *cu, struct co
> if (!func)
> continue;
>
> + if (ftype__has_uncertain_arg_loc(cu, &fn->proto))
> + fn->proto.uncertain_parm_loc = 1;
> +
> err = btf_encoder__save_func(encoder, fn, func);
> if (err)
> goto out;
> diff --git a/dwarves.h b/dwarves.h
> index 36c689847ebf29a1ab9936f9d0f928dd46514547..d689aee5910f4b40dc13b3e9dc596dfbe6a2c3d0 100644
> --- a/dwarves.h
> +++ b/dwarves.h
> @@ -1021,6 +1021,7 @@ struct ftype {
> uint8_t unexpected_reg:1;
> uint8_t processed:1;
> uint8_t inconsistent_proto:1;
> + uint8_t uncertain_parm_loc:1;
> struct list_head template_type_params;
> struct list_head template_value_params;
> struct template_parameter_pack *template_parameter_pack;
>
> --
> 2.50.0
>
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-07-07 14:02 ` [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location Alexis Lothoré (eBPF Foundation)
2025-07-07 14:14 ` Alexis Lothoré
@ 2025-08-05 15:09 ` Alan Maguire
2025-08-05 19:06 ` Alexis Lothoré
1 sibling, 1 reply; 16+ messages in thread
From: Alan Maguire @ 2025-08-05 15:09 UTC (permalink / raw)
To: Alexis Lothoré (eBPF Foundation), dwarves
Cc: bpf, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
On 07/07/2025 15:02, Alexis Lothoré (eBPF Foundation) wrote:
> Add a small binary representing specific cases likely absent from
> standard vmlinux or kernel modules files. As a starter, the introduced
> binary exposes a few functions consuming structs passed by value, some
> passed by register, some passed on the stack:
>
> int main(void);
> int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
> void *, char, short int, struct test_bin_struct_packed);
> int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
> void *, char, short int, struct test_bin_struct);
> int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
> int test_bin_func_ok(int, void *, char, short int);
>
> Then enrich btf_functions.sh to make it perform the following steps:
> - build the binary
> - generate BTF info and pfunct listing, both with dwarf and the
> generated BTF
> - check that any function encoded in BTF is found in DWARF
> - check that any function announced as skipped is indeed absent from BTF
> - check that any skipped function has been skipped due to uncertain
> parameter location
>
> Example of the new test execution:
> Encoding...Matched 4 functions exactly.
> Ok
> Validation of skipped function logic...
> Skipped encoding 1 functions in BTF.
> Ok
> Validating skipped functions have uncertain parameter location...
> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
> Found 1 legitimately skipped function due to uncertain loc
> Ok
>
> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
Thanks for the updated changes+test. I think it'd be good to have this
be less verbose in successful case. Currently I see:
1: Validation of BTF encoding of functions; this may take some time: Ok
Validation of BTF encoding corner cases with test_bin functions; this
may take some time: make: Entering directory
'/home/almagui/src/github/dwarves/tests/bin'
gcc test_bin.c -Wall -Wextra -Werror -g -o test_bin
make: Leaving directory '/home/almagui/src/github/dwarves/tests/bin'
No skipped functions. Done.
Ideally we just want the "Ok" for success in non-vebose mode. I'd
propose making the following changes in order to support that; if these
are okay by you there's no need to send another revision.
diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
index f97bdf5..a4ab67e 100755
--- a/tests/btf_functions.sh
+++ b/tests/btf_functions.sh
@@ -110,7 +110,6 @@ skipped_cnt=$(wc -l ${outdir}/skipped_fns | awk '{
print $1}')
if [[ "$skipped_cnt" == "0" ]]; then
echo "No skipped functions. Done."
- exit 0
fi
skipped_fns=$(awk '{print $1}' $outdir/skipped_fns)
@@ -191,17 +190,16 @@ if [[ -n "$VERBOSE" ]]; then
echo "Found $optimized instances where the function name
suggests optimizations led to inconsistent parameters."
echo "Found $warnings instances where pfunct did not notice
inconsistencies."
fi
-echo "Ok"
# Some specific cases can not be tested directly with a standard kernel.
# We can use the small binary in bin/ to test those cases, like packed
# structs passed on the stack.
-echo -n "Validation of BTF encoding corner cases with test_bin
functions; this may take some time: "
+test -n "$VERBOSE" && echo -n "Validation of BTF encoding corner cases
with test_bin functions; this may take some time: "
test -n "$VERBOSE" && printf "\nBuilding test_bin..."
tests_dir=$(realpath $(dirname $0))
-make -C ${tests_dir}/bin
+make -C ${tests_dir}/bin >/dev/null
test -n "$VERBOSE" && printf "\nEncoding..."
pahole --btf_features=default --lang_exclude=rust
--btf_encode_detached=$outdir/test_bin.btf \
@@ -234,10 +232,6 @@ if [[ -n "$VERBOSE" ]]; then
fi
skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
-if [[ "$skipped_cnt" == "0" ]]; then
- echo "No skipped functions. Done."
- exit 0
-fi
skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
for s in $skipped_fns ; do
> ---
> Changes in v3:
> - bring a userspace binary instead of an OoT kernel module
> - remove test dependency to a kernel directory being provided
> - improve test dir detection
>
> Changes in v2:
> - new patch
> ---
> tests/bin/Makefile | 10 ++++++
> tests/bin/test_bin.c | 66 ++++++++++++++++++++++++++++++++++++
> tests/btf_functions.sh | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 167 insertions(+)
>
> diff --git a/tests/bin/Makefile b/tests/bin/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..70bcf57ac4744f30fe03ea12908e42c69390f14a
> --- /dev/null
> +++ b/tests/bin/Makefile
> @@ -0,0 +1,10 @@
> +CC=${CROSS_COMPILE}gcc
> +
> +test_bin: test_bin.c
> + ${CC} $^ -Wall -Wextra -Werror -g -o $@
> +
> +clean:
> + rm -rf test_bin
> +
> +.PHONY: clean
> +
> diff --git a/tests/bin/test_bin.c b/tests/bin/test_bin.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ca6a4852cc511243925db905e55e040519af9cfd
> --- /dev/null
> +++ b/tests/bin/test_bin.c
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdio.h>
> +
> +#define noinline __attribute__((noinline))
> +#define __packed __attribute__((__packed__))
> +
> +struct test_bin_struct {
> + char a;
> + short b;
> + int c;
> + unsigned long long d;
> +};
> +
> +struct test_bin_struct_packed {
> + char a;
> + short b;
> + int c;
> + unsigned long long d;
> +}__packed;
> +
> +int test_bin_func_ok(int a, void *b, char c, short d);
> +int test_bin_func_struct_ok(int a, void *b, char c, struct test_bin_struct d);
> +int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d, int e,
> + void *f, char g, short h,
> + struct test_bin_struct i);
> +int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d, int e,
> + void *f, char g, short h,
> + struct test_bin_struct_packed i);
> +
> +noinline int test_bin_func_ok(int a, void *b, char c, short d)
> +{
> + return a + (long)b + c + d;
> +}
> +
> +noinline int test_bin_func_struct_ok(int a, void *b, char c,
> + struct test_bin_struct d)
> +{
> + return a + (long)b + c + d.a + d.b + d.c + d.d;
> +}
> +
> +noinline int test_bin_func_struct_on_stack_ok(int a, void *b, char c, short d,
> + int e, void *f, char g, short h,
> + struct test_bin_struct i)
> +{
> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
> +}
> +
> +noinline int test_bin_func_struct_on_stack_ko(int a, void *b, char c, short d,
> + int e, void *f, char g, short h,
> + struct test_bin_struct_packed i)
> +{
> + return a + (long)b + c + d + e + (long)f + g + h + i.a + i.b + i.c + i.d;
> +}
> +
> +int main()
> +{
> + struct test_bin_struct test;
> + struct test_bin_struct_packed test_bis;
> +
> + test_bin_func_ok(0, NULL, 0, 0);
> + test_bin_func_struct_ok(0, NULL, 0, test);
> + test_bin_func_struct_on_stack_ok(0, NULL, 0, 0, 0, NULL, 0, 0, test);
> + test_bin_func_struct_on_stack_ko(0, NULL, 0, 0, 0, NULL, 0, 0, test_bis);
> + return 0;
> +}
> +
> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
> index c92e5ae906f90badfede86eb530108894fbc8c93..fb62b0b56662bb2ae58f7adc0a022c400cba5e0f 100755
> --- a/tests/btf_functions.sh
> +++ b/tests/btf_functions.sh
> @@ -193,4 +193,95 @@ if [[ -n "$VERBOSE" ]]; then
> fi
> echo "Ok"
>
> +# Some specific cases can not be tested directly with a standard kernel.
> +# We can use the small binary in bin/ to test those cases, like packed
> +# structs passed on the stack.
> +
> +echo -n "Validation of BTF encoding corner cases with test_bin functions; this may take some time: "
> +
> +test -n "$VERBOSE" && printf "\nBuilding test_bin..."
> +tests_dir=$(realpath $(dirname $0))
> +make -C ${tests_dir}/bin
> +
> +test -n "$VERBOSE" && printf "\nEncoding..."
> +pahole --btf_features=default --lang_exclude=rust --btf_encode_detached=$outdir/test_bin.btf \
> + --verbose ${tests_dir}/bin/test_bin | grep "skipping BTF encoding of function" \
> + > ${outdir}/test_bin_skipped_fns
> +
> +funcs=$(pfunct --format_path=btf $outdir/test_bin.btd 2>/dev/null|sort)
> +pfunct --all --no_parm_names --format_path=dwarf bin/test_bin | \
> + sort|uniq > $outdir/test_bin_dwarf.funcs
> +pfunct --all --no_parm_names --format_path=btf $outdir/test_bin.btf 2>/dev/null|\
> + awk '{ gsub("^(bpf_kfunc |bpf_fastcall )+",""); print $0}'|sort|uniq > $outdir/test_bin_btf.funcs
> +
> +exact=0
> +while IFS= read -r btf ; do
> + # Matching process can be kept simpler as the tested binary is
> + # specifically tailored for tests
> + dwarf=$(grep -F "$btf" $outdir/test_bin_dwarf.funcs)
> + if [[ "$btf" != "$dwarf" ]]; then
> + echo "ERROR: mismatch : BTF '$btf' not found; DWARF '$dwarf'"
> + fail
> + else
> + exact=$((exact+1))
> + fi
> +done < $outdir/test_bin_btf.funcs
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Matched $exact functions exactly."
> + echo "Ok"
> + echo "Validation of skipped function logic..."
> +fi
> +
> +skipped_cnt=$(wc -l ${outdir}/test_bin_skipped_fns | awk '{ print $1}')
> +if [[ "$skipped_cnt" == "0" ]]; then
> + echo "No skipped functions. Done."
> + exit 0
> +fi
> +
> +skipped_fns=$(awk '{print $1}' $outdir/test_bin_skipped_fns)
> +for s in $skipped_fns ; do
> + # Ensure the skipped function are not in BTF
> + inbtf=$(grep " $s(" $outdir/test_bin_btf.funcs)
> + if [[ -n "$inbtf" ]]; then
> + echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'"
> + fail
> + fi
> +done
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Skipped encoding $skipped_cnt functions in BTF."
> + echo "Ok"
> + echo "Validating skipped functions have uncertain parameter location..."
> +fi
> +
> +uncertain_loc=$(awk '/due to uncertain parameter location/ { print $1 }' $outdir/test_bin_skipped_fns)
> +legitimate_skip=0
> +
> +for f in $uncertain_loc ; do
> + # Extract parameters types
> + raw_params=$(grep ${f} $outdir/test_bin_dwarf.funcs|sed -n 's/^[^(]*(\([^)]*\)).*/\1/p')
> + IFS=',' read -ra params <<< "${raw_params}"
> + for param in "${params[@]}"
> + do
> + # Search any param that could be a struct
> + struct_type=$(echo ${param}|grep -E '^struct [^*]' | sed -E 's/^struct //')
> + if [ -n "${struct_type}" ]; then
> + # Check with pahole if the struct is detected as
> + # packed
> + if pahole -F dwarf -C "${struct_type}" ${tests_dir}/bin/test_bin|tail -n 2|grep -q __packed__
> + then
> + legitimate_skip=$((legitimate_skip+1))
> + continue 2
> + fi
> + fi
> + done
> + echo "ERROR: '${f}()' should not have been skipped; it has no parameter with uncertain location"
> + fail
> +done
> +
> +if [[ -n "$VERBOSE" ]]; then
> + echo "Found ${legitimate_skip} legitimately skipped function due to uncertain loc"
> +fi
> +echo "Ok"
> exit 0
>
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-08-05 15:09 ` Alan Maguire
@ 2025-08-05 19:06 ` Alexis Lothoré
2025-08-06 11:14 ` Alan Maguire
0 siblings, 1 reply; 16+ messages in thread
From: Alexis Lothoré @ 2025-08-05 19:06 UTC (permalink / raw)
To: Alan Maguire, dwarves
Cc: bpf, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
Hi Alan,
On Tue Aug 5, 2025 at 5:09 PM CEST, Alan Maguire wrote:
> On 07/07/2025 15:02, Alexis Lothoré (eBPF Foundation) wrote:
>> Add a small binary representing specific cases likely absent from
>> standard vmlinux or kernel modules files. As a starter, the introduced
>> binary exposes a few functions consuming structs passed by value, some
>> passed by register, some passed on the stack:
>>
>> int main(void);
>> int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
>> void *, char, short int, struct test_bin_struct_packed);
>> int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
>> void *, char, short int, struct test_bin_struct);
>> int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
>> int test_bin_func_ok(int, void *, char, short int);
>>
>> Then enrich btf_functions.sh to make it perform the following steps:
>> - build the binary
>> - generate BTF info and pfunct listing, both with dwarf and the
>> generated BTF
>> - check that any function encoded in BTF is found in DWARF
>> - check that any function announced as skipped is indeed absent from BTF
>> - check that any skipped function has been skipped due to uncertain
>> parameter location
>>
>> Example of the new test execution:
>> Encoding...Matched 4 functions exactly.
>> Ok
>> Validation of skipped function logic...
>> Skipped encoding 1 functions in BTF.
>> Ok
>> Validating skipped functions have uncertain parameter location...
>> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
>> Found 1 legitimately skipped function due to uncertain loc
>> Ok
>>
>> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>
> Thanks for the updated changes+test. I think it'd be good to have this
> be less verbose in successful case. Currently I see:
>
> 1: Validation of BTF encoding of functions; this may take some time: Ok
> Validation of BTF encoding corner cases with test_bin functions; this
> may take some time: make: Entering directory
> '/home/almagui/src/github/dwarves/tests/bin'
> gcc test_bin.c -Wall -Wextra -Werror -g -o test_bin
> make: Leaving directory '/home/almagui/src/github/dwarves/tests/bin'
> No skipped functions. Done.
>
> Ideally we just want the "Ok" for success in non-vebose mode. I'd
> propose making the following changes in order to support that; if these
> are okay by you there's no need to send another revision.
I'm perfeclty fine with the idea, thanks for handling it. Just a
comment/question below
> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
> index f97bdf5..a4ab67e 100755
> --- a/tests/btf_functions.sh
> +++ b/tests/btf_functions.sh
> @@ -110,7 +110,6 @@ skipped_cnt=$(wc -l ${outdir}/skipped_fns | awk '{
> print $1}')
>
> if [[ "$skipped_cnt" == "0" ]]; then
> echo "No skipped functions. Done."
> - exit 0
> fi
Shouldn't we get rid of this whole if block then, similarly to what you
have done with the other one below ?
Thanks,
Alexis
--
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location
2025-08-05 19:06 ` Alexis Lothoré
@ 2025-08-06 11:14 ` Alan Maguire
0 siblings, 0 replies; 16+ messages in thread
From: Alan Maguire @ 2025-08-06 11:14 UTC (permalink / raw)
To: Alexis Lothoré, dwarves
Cc: bpf, Arnaldo Carvalho de Melo, Alexei Starovoitov,
Thomas Petazzoni, Bastien Curutchet, ebpf
On 05/08/2025 20:06, Alexis Lothoré wrote:
> Hi Alan,
>
> On Tue Aug 5, 2025 at 5:09 PM CEST, Alan Maguire wrote:
>> On 07/07/2025 15:02, Alexis Lothoré (eBPF Foundation) wrote:
>>> Add a small binary representing specific cases likely absent from
>>> standard vmlinux or kernel modules files. As a starter, the introduced
>>> binary exposes a few functions consuming structs passed by value, some
>>> passed by register, some passed on the stack:
>>>
>>> int main(void);
>>> int test_bin_func_struct_on_stack_ko(int, void *, char, short int, int, \
>>> void *, char, short int, struct test_bin_struct_packed);
>>> int test_bin_func_struct_on_stack_ok(int, void *, char, short int, int, \
>>> void *, char, short int, struct test_bin_struct);
>>> int test_bin_func_struct_ok(int, void *, char, struct test_bin_struct);
>>> int test_bin_func_ok(int, void *, char, short int);
>>>
>>> Then enrich btf_functions.sh to make it perform the following steps:
>>> - build the binary
>>> - generate BTF info and pfunct listing, both with dwarf and the
>>> generated BTF
>>> - check that any function encoded in BTF is found in DWARF
>>> - check that any function announced as skipped is indeed absent from BTF
>>> - check that any skipped function has been skipped due to uncertain
>>> parameter location
>>>
>>> Example of the new test execution:
>>> Encoding...Matched 4 functions exactly.
>>> Ok
>>> Validation of skipped function logic...
>>> Skipped encoding 1 functions in BTF.
>>> Ok
>>> Validating skipped functions have uncertain parameter location...
>>> pahole: /home/alexis/src/pahole/tests/bin/test_bin: Invalid argument
>>> Found 1 legitimately skipped function due to uncertain loc
>>> Ok
>>>
>>> Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
>>
>> Thanks for the updated changes+test. I think it'd be good to have this
>> be less verbose in successful case. Currently I see:
>>
>> 1: Validation of BTF encoding of functions; this may take some time: Ok
>> Validation of BTF encoding corner cases with test_bin functions; this
>> may take some time: make: Entering directory
>> '/home/almagui/src/github/dwarves/tests/bin'
>> gcc test_bin.c -Wall -Wextra -Werror -g -o test_bin
>> make: Leaving directory '/home/almagui/src/github/dwarves/tests/bin'
>> No skipped functions. Done.
>>
>> Ideally we just want the "Ok" for success in non-vebose mode. I'd
>> propose making the following changes in order to support that; if these
>> are okay by you there's no need to send another revision.
>
> I'm perfeclty fine with the idea, thanks for handling it. Just a
> comment/question below
>
>> diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh
>> index f97bdf5..a4ab67e 100755
>> --- a/tests/btf_functions.sh
>> +++ b/tests/btf_functions.sh
>> @@ -110,7 +110,6 @@ skipped_cnt=$(wc -l ${outdir}/skipped_fns | awk '{
>> print $1}')
>>
>> if [[ "$skipped_cnt" == "0" ]]; then
>> echo "No skipped functions. Done."
>> - exit 0
>> fi
>
> Shouldn't we get rid of this whole if block then, similarly to what you
> have done with the other one below ?
>
yep, good catch, removed that too. Series applied to next branch of
https://git.kernel.org/pub/scm/devel/pahole/pahole.git
Thanks!
Alan
> Thanks,
>
> Alexis
>
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2025-08-06 11:14 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-07 14:02 [PATCH v3 0/3] btf_encoder: do not encode functions consuming packed structs on stack Alexis Lothoré (eBPF Foundation)
2025-07-07 14:02 ` [PATCH v3 1/3] btf_encoder: skip functions consuming packed structs passed by value " Alexis Lothoré (eBPF Foundation)
2025-07-07 17:05 ` Alexei Starovoitov
2025-07-07 17:45 ` Ihor Solodrai
2025-08-04 7:13 ` Alexis Lothoré
2025-08-04 9:58 ` Jiri Olsa
2025-07-07 14:02 ` [PATCH v3 2/3] tests: add some tests validating skipped functions due to uncertain arg location Alexis Lothoré (eBPF Foundation)
2025-07-07 14:14 ` Alexis Lothoré
2025-07-07 19:36 ` Ihor Solodrai
2025-07-09 16:21 ` Alan Maguire
2025-07-15 8:04 ` Alexis Lothoré
2025-07-15 15:36 ` Ihor Solodrai
2025-08-05 15:09 ` Alan Maguire
2025-08-05 19:06 ` Alexis Lothoré
2025-08-06 11:14 ` Alan Maguire
2025-07-07 14:02 ` [PATCH v3 3/3] gitignore: ignore all the test kmod build-related files Alexis Lothoré (eBPF Foundation)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).