use pahole to encode BTF and pfunct to check that - DWARF functions that made it into BTF match signatures - for functions we say we skipped, we did indeed skip them in BTF encoding; and - it was correct to skip these functions It is not possible to check everything, as pfunct does not print "."-suffixed optimized representations, and it does not analyze location calling conventions. As a starting point we check for - functions encoded in BTF match signatures with DWARF - functions with multiple incompatible return values - functions with incompatible parameter count/type This test automates manual validation of encoding/skipping, and as such will be useful to ensure regressions are not introduced, for comparing gcc/LLVM-based DWARF representations etc. The test takes ~5 minutes to run in all; output looks like this: $ vmlinux=~/kbuild/bpf-next/vmlinux bash ./btf_functions.sh Validation of BTF encoding of functions; this may take some time... Matched 55969 functions exactly. Matched 239 functions with inlines. Matched 1 functions with multiple const/non-const instances. Ok Validation of skipped function logic... Validating skipped functions are absent from BTF... Skipped encoding 748 functions in BTF. Ok Validating skipped functions have incompatible return values... Found 8 functions with multiple incompatible return values. Ok Validating skipped functions have incompatible params/counts... Found 116 instances with multiple instances with incompatible parameters. Found 2 instances where inline functions were not inlined and had incompatible parameters. Found 351 instances where the function name suggests optimizations led to inconsistent parameters. Ok Suggested-by: Jiri Olsa <olsajiri@xxxxxxxxx> Signed-off-by: Alan Maguire <alan.maguire@xxxxxxxxxx> --- tests/btf_functions.sh | 192 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100755 tests/btf_functions.sh diff --git a/tests/btf_functions.sh b/tests/btf_functions.sh new file mode 100755 index 0000000..4509407 --- /dev/null +++ b/tests/btf_functions.sh @@ -0,0 +1,192 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (c) 2024, Oracle and/or its affiliates. +# +# Examine functions - especially those for which we skipped BTF encoding - +# to validate that they were indeed skipped for BTF encoding, and that they +# also should have been. +# + +outdir= + +fail() +{ + # Do not remove test dir; might be useful for analysis + trap - EXIT + if [[ -d "$outdir" ]]; then + echo "Test data is in $outdir" + fi + exit 1 +} + +cleanup() +{ + rm ${outdir}/* + rmdir $outdir +} + +vmlinux=${vmlinux:-$1} + +if [ -z "$vmlinux" ] ; then + vmlinux=$(pahole --running_kernel_vmlinux) + if [ -z "$vmlinux" ] ; then + echo "Please specify a vmlinux file to operate on" + exit 1 + fi +fi + +if [ ! -f "$vmlinux" ] ; then + echo "$vmlinux file not available, please specify another" + exit 1 +fi + +outdir=$(mktemp -d /tmp/btf_functions.sh.XXXXXX) + +trap cleanup EXIT + +test -n "$VERBOSE" && printf "Encoding..." + +pahole --btf_features=default --btf_encode_detached=$outdir/vmlinux.btf --verbose $vmlinux |grep "skipping BTF encoding of function" > ${outdir}/skipped_fns + +test -n "$VERBOSE" && printf "done.\n" + +echo "Validation of BTF encoding of functions; this may take some time..." + +funcs=$(pfunct --format_path=btf $outdir/vmlinux.btf |sort) + +# all functions from DWARF; some inline functions are not inlined so include them too +pfunct --all --no_parm_names --format_path=dwarf $vmlinux | \ + awk '{ print $0}'|sort|uniq > $outdir/dwarf.funcs +# all functions from BTF (removing bpf_kfunc prefix where found) +pfunct --all --no_parm_names --format_path=btf $outdir/vmlinux.btf |\ + awk '{ gsub("^bpf_kfunc ",""); print $0}'|sort|uniq > $outdir/btf.funcs + +exact=0 +inline=0 +const_insensitive=0 + +for f in $funcs ; do + btf=$(grep " $f(" $outdir/btf.funcs) + # look for non-inline match first + dwarf=$(grep " $f(" $outdir/dwarf.funcs |egrep -v "^inline ") + if [[ "$btf" != "$dwarf" ]]; then + # function might be declared inline in DWARF. + inline_dwarf=$(grep " $f(" $outdir/dwarf.funcs |grep "^inline ") + if [[ "inline $btf" != "$inline_dwarf" ]]; then + # some functions have multiple instances in DWARF where one has + # const param(s) and another does not (see errpos()). We do not + # mark these functions inconsistent as though they technically + # have different prototypes, the data itself is not different. + btf_noconst=$(echo $btf | awk '{gsub("const ",""); print $0 }') + dwarf_noconst=$(echo $dwarf | awk '{gsub("const ",""); print $0 }') + if [[ "$dwarf_noconst" =~ "$btf_noconst" ]]; then + const_insensitive=$((const_insensitive+1)) + else + echo "ERROR: mismatch for '$f()' : BTF '$btf' not found; DWARF '$dwarf'" + fail + fi + else + inline=$((inline+1)) + fi + else + exact=$((exact+1)) + fi +done + +echo "Matched $exact functions exactly." +echo "Matched $inline functions with inlines." +echo "Matched $const_insensitive functions with multiple const/non-const instances." +echo "Ok" + +echo "Validation of skipped function logic..." + +skipped_cnt=$(wc -l ${outdir}/skipped_fns | awk '{ print $1}') + +if [[ "$skipped_cnt" == "0" ]]; then + echo "No skipped functions. Done." + exit 0 +fi + +echo "Validating skipped functions are absent from BTF..." + +skipped_fns=$(awk '{print $1}' $outdir/skipped_fns) +for s in $skipped_fns ; do + # Ensure the skipped function are not in BTF + inbtf=$(grep " $s(" $outdir/btf.funcs) + if [[ -n "$inbtf" ]]; then + echo "ERROR: '${s}()' was added incorrectly to BTF: '$inbtf'" + fail + fi +done + +echo "Skipped encoding $skipped_cnt functions in BTF." +echo "Ok" + +echo "Validating skipped functions have incompatible return values..." + +return_mismatches=$(awk '/return type mismatch/ { print $1 }' $outdir/skipped_fns) +return_count=0 + +for r in $return_mismatches ; do + # Ensure there are multiple instances with incompatible return values + grep " $r(" $outdir/dwarf.funcs | \ + awk -v FN=$r '{i = index($0, FN); if (i>0) print substr($0, 0, i-1) }' \ + | uniq > ${outdir}/retvals.$r + test -n "$VERBOSE" && echo "'${r}()' has return values:" + test -n "$VERBOSE" && cat ${outdir}/retvals.$r + cnt=$(wc -l ${outdir}/retvals.$r | awk '{ print $1 }') + if [[ $cnt -lt 2 ]]; then + echo "ERROR: '${r}()' has only one return value; it should not be reported as having incompatible return values" + fail + fi + return_count=$((return_count+1)) +done + +echo "Found $return_count functions with multiple incompatible return values." +echo "Ok" + +echo "Validating skipped functions have incompatible params/counts..." + +param_mismatches=$(awk '/due to param / { print $1 }' $outdir/skipped_fns) + +multiple=0 +multiple_inline=0 +optimized=0 +warnings=0 + +for p in $param_mismatches ; do + skipmsg=$(awk -v FN=$p '{ if ($1 == FN) print $0 }' $outdir/skipped_fns) + altname=$(echo $skipmsg | awk '{ i=index($2,")"); print substr($2,2,i-2); }') + if [[ "$altname" != "$p" ]]; then + test -n "$VERBOSE" && echo "skipping optimized function $p ($altname); pfunct may not reflect late optimizations." + optimized=$((optimized+1)) + continue + fi + # Ensure there are multiple instances with incompatible params + grep " $p(" $outdir/dwarf.funcs | uniq > ${outdir}/protos.$p + test -n "$VERBOSE" && echo "'${p}()' has prototypes:" + test -n "$VERBOSE" && cat ${outdir}/protos.$p + cnt=$(wc -l ${outdir}/protos.$p | awk '{ print $1 }') + if [[ $cnt -lt 2 ]]; then + # function may be inlined in multiple sites with different protos + inlined=$(grep inline ${outdir}/protos.$p) + if [[ -n "$inlined" ]]; then + multiple_inline=$((multiple_inline+1)) + else + echo "WARN: '${p}()' has only one prototype; if it was subject to late optimization, pfunct may not reflect inconsistencies pahole found." + echo "Full skip message from pahole: $skipmsg" + warnings=$((warnings+1)) + fi + else + multiple=$((multiple+1)) + fi +done + +echo "Found $multiple instances with multiple instances with incompatible parameters." +echo "Found $multiple_inline instances where inline functions were not inlined and had incompatible parameters." +echo "Found $optimized instances where the function name suggests optimizations led to inconsistent parameters." +echo "Found $warnings instances where pfunct did not notice inconsistencies." +echo "Ok" + +exit 0 -- 2.43.5