Merge branch 'resolve_btfids-support-for-btf-modifications'

Ihor Solodrai says:

====================
resolve_btfids: Support for BTF modifications

This series changes resolve_btfids and kernel build scripts to enable
BTF transformations in resolve_btfids. Main motivation for enhancing
resolve_btfids is to reduce dependency of the kernel build on pahole
capabilities [1] and enable BTF features and optimizations [2][3]
particular to the kernel.

Patches #1-#4 in the series are non-functional changes in
resolve_btfids.

Patch #5 makes kernel build notice pahole version changes between
builds.

Patch #6 changes minimum version of pahole required for
CONFIG_DEBUG_INFO_BTF to v1.22

Patch #7 makes a small prep change in selftests/bpf build.

The last patch (#8) makes significant changes in resolve_btfids and
introduces scripts/gen-btf.sh. See implementation details in the patch
description.

Successful BPF CI run: https://github.com/kernel-patches/bpf/actions/runs/20378061470

[1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
[2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/
[3] https://lore.kernel.org/bpf/20251119031531.1817099-1-dolinux.peng@gmail.com/
---

v6->v7:
  - documentation edits in patches #5 and #6 (Nicolas)

v6: https://lore.kernel.org/bpf/20251219020006.785065-1-ihor.solodrai@linux.dev/

v5->v6:
  - patch #8: fix double free when btf__distill_base fails (reported by AI)
    https://lore.kernel.org/bpf/e269870b8db409800045ee0061fc02d21721e0efadd99ca83960b48f8db7b3f3@mail.kernel.org/

v5: https://lore.kernel.org/bpf/20251219003147.587098-1-ihor.solodrai@linux.dev/

v4->v5:
  - patch #3: fix an off-by-one bug (reported by AI)
    https://lore.kernel.org/bpf/106b6e71bce75b8f12a85f2f99e75129e67af7287f6d81fa912589ece14044f9@mail.kernel.org/
  - patch #8: cleanup GEN_BTF in Makefile.btf

v4: https://lore.kernel.org/bpf/20251218003314.260269-1-ihor.solodrai@linux.dev/

v3->v4:
  - add patch #4: "resolve_btfids: Always build with -Wall -Werror"
  - add patch #5: "kbuild: Sync kconfig when PAHOLE_VERSION changes" (Alan)
  - fix clang cross-compilation (LKP)
    https://lore.kernel.org/bpf/cecb6351-ea9a-4f8a-863a-82c9ef02f012@linux.dev/
  - remove GEN_BTF env variable (Andrii)
  - nits and cleanup in resolve_btfids/main.c (Andrii, Eduard)
  - nits in a patch bumping minimum pahole version (Andrii, AI)

v3: https://lore.kernel.org/bpf/20251205223046.4155870-1-ihor.solodrai@linux.dev/

v2->v3:
  - add patch #4 bumping minimum pahole version (Andrii, Alan)
  - add patch #5 pre-fixing resolve_btfids test (Donglin)
  - add GEN_BTF var and assemble RESOLVE_BTFIDS_FLAGS in Makefile.btf (Alan)
  - implement --distill_base flag in resolve_btfids, set it depending
    on KBUILD_EXTMOD in Makefile.btf (Eduard)
  - various implementation nits, see the v2 thread for details (Andrii, Eduard)

v2: https://lore.kernel.org/bpf/20251127185242.3954132-1-ihor.solodrai@linux.dev/

v1->v2:
  - gen-btf.sh and other shell script fixes (Donglin)
  - update selftests build (Donglin)
  - generate .BTF.base only when KBUILD_EXTMOD is set (Alan)
  - proper endianness handling for cross-compilation
  - change elf_begin mode from ELF_C_RDWR_MMAP to ELF_C_READ_MMAP_PRIVATE
  - remove compressed_section_fix()
  - nit NULL check in patch #3 (suggested by AI)

v1: https://lore.kernel.org/bpf/20251126012656.3546071-1-ihor.solodrai@linux.dev/
====================

Link: https://patch.msgid.link/20251219181321.1283664-1-ihor.solodrai@linux.dev
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
Andrii Nakryiko 2025-12-19 10:55:40 -08:00
commit 3d60306b7b
17 changed files with 448 additions and 195 deletions

View File

@ -38,7 +38,7 @@ bash 4.2 bash --version
binutils 2.30 ld -v
flex 2.5.35 flex --version
bison 2.0 bison --version
pahole 1.16 pahole --version
pahole 1.22 pahole --version
util-linux 2.10o mount --version
kmod 13 depmod -V
e2fsprogs 1.41.4 e2fsck -V
@ -143,7 +143,7 @@ pahole
Since Linux 5.2, if CONFIG_DEBUG_INFO_BTF is selected, the build system
generates BTF (BPF Type Format) from DWARF in vmlinux, a bit later from kernel
modules as well. This requires pahole v1.16 or later.
modules as well. This requires pahole v1.22 or later.
It is found in the 'dwarves' or 'pahole' distro packages or from
https://fedorapeople.org/~acme/dwarves/.

View File

@ -43,7 +43,6 @@ options should be enabled to use sched_ext:
CONFIG_DEBUG_INFO_BTF=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_PAHOLE_HAS_SPLIT_BTF=y
CONFIG_PAHOLE_HAS_BTF_TAG=y
sched_ext is used only when the BPF scheduler is loaded and running.

View File

@ -4766,6 +4766,7 @@ F: net/sched/act_bpf.c
F: net/sched/cls_bpf.c
F: samples/bpf/
F: scripts/bpf_doc.py
F: scripts/gen-btf.sh
F: scripts/Makefile.btf
F: scripts/pahole-version.sh
F: tools/bpf/

View File

@ -708,11 +708,12 @@ endif
# The expansion should be delayed until arch/$(SRCARCH)/Makefile is included.
# Some architectures define CROSS_COMPILE in arch/$(SRCARCH)/Makefile.
# CC_VERSION_TEXT and RUSTC_VERSION_TEXT are referenced from Kconfig (so they
# need export), and from include/config/auto.conf.cmd to detect the compiler
# upgrade.
# CC_VERSION_TEXT, RUSTC_VERSION_TEXT and PAHOLE_VERSION are referenced from
# Kconfig (so they need export), and from include/config/auto.conf.cmd to
# detect the version changes between builds.
CC_VERSION_TEXT = $(subst $(pound),,$(shell LC_ALL=C $(CC) --version 2>/dev/null | head -n 1))
RUSTC_VERSION_TEXT = $(subst $(pound),,$(shell $(RUSTC) --version 2>/dev/null))
PAHOLE_VERSION = $(shell $(srctree)/scripts/pahole-version.sh $(PAHOLE))
ifneq ($(findstring clang,$(CC_VERSION_TEXT)),)
include $(srctree)/scripts/Makefile.clang
@ -733,7 +734,7 @@ ifdef config-build
# KBUILD_DEFCONFIG may point out an alternative default configuration
# used for 'make defconfig'
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT RUSTC_VERSION_TEXT
export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT RUSTC_VERSION_TEXT PAHOLE_VERSION
config: outputmakefile scripts_basic FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
@ -1921,12 +1922,18 @@ clean: private rm-files := Module.symvers modules.nsdeps compile_commands.json
PHONY += prepare
# now expand this into a simple variable to reduce the cost of shell evaluations
prepare: CC_VERSION_TEXT := $(CC_VERSION_TEXT)
prepare: PAHOLE_VERSION := $(PAHOLE_VERSION)
prepare:
@if [ "$(CC_VERSION_TEXT)" != "$(CONFIG_CC_VERSION_TEXT)" ]; then \
echo >&2 "warning: the compiler differs from the one used to build the kernel"; \
echo >&2 " The kernel was built by: $(CONFIG_CC_VERSION_TEXT)"; \
echo >&2 " You are using: $(CC_VERSION_TEXT)"; \
fi
@if [ "$(PAHOLE_VERSION)" != "$(CONFIG_PAHOLE_VERSION)" ]; then \
echo >&2 "warning: pahole version differs from the one used to build the kernel"; \
echo >&2 " The kernel was built with: $(CONFIG_PAHOLE_VERSION)"; \
echo >&2 " You are using: $(PAHOLE_VERSION)"; \
fi
PHONY += help
help:

View File

@ -171,7 +171,7 @@ config RUSTC_HAS_FILE_AS_C_STR
config PAHOLE_VERSION
int
default $(shell,$(srctree)/scripts/pahole-version.sh $(PAHOLE))
default "$(PAHOLE_VERSION)"
config CONSTRUCTORS
bool

View File

@ -388,18 +388,13 @@ config DEBUG_INFO_BTF
depends on !DEBUG_INFO_SPLIT && !DEBUG_INFO_REDUCED
depends on !GCC_PLUGIN_RANDSTRUCT || COMPILE_TEST
depends on BPF_SYSCALL
depends on PAHOLE_VERSION >= 116
depends on DEBUG_INFO_DWARF4 || PAHOLE_VERSION >= 121
depends on PAHOLE_VERSION >= 122
# pahole uses elfutils, which does not have support for Hexagon relocations
depends on !HEXAGON
help
Generate deduplicated BTF type information from DWARF debug info.
Turning this on requires pahole v1.16 or later (v1.21 or later to
support DWARF 5), which will convert DWARF type info into equivalent
deduplicated BTF type info.
config PAHOLE_HAS_SPLIT_BTF
def_bool PAHOLE_VERSION >= 119
Turning this on requires pahole v1.22 or later, which will convert
DWARF type info into equivalent deduplicated BTF type info.
config PAHOLE_HAS_BTF_TAG
def_bool PAHOLE_VERSION >= 123
@ -421,7 +416,7 @@ config PAHOLE_HAS_LANG_EXCLUDE
config DEBUG_INFO_BTF_MODULES
bool "Generate BTF type information for kernel modules"
default y
depends on DEBUG_INFO_BTF && MODULES && PAHOLE_HAS_SPLIT_BTF
depends on DEBUG_INFO_BTF && MODULES
help
Generate compact split BTF type information for kernel modules.

View File

@ -7,14 +7,7 @@ JOBS := $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))
ifeq ($(call test-le, $(pahole-ver), 125),y)
# pahole 1.18 through 1.21 can't handle zero-sized per-CPU vars
ifeq ($(call test-le, $(pahole-ver), 121),y)
pahole-flags-$(call test-ge, $(pahole-ver), 118) += --skip_encoding_btf_vars
endif
pahole-flags-$(call test-ge, $(pahole-ver), 121) += --btf_gen_floats
pahole-flags-$(call test-ge, $(pahole-ver), 122) += -j$(JOBS)
pahole-flags-y += --btf_gen_floats -j$(JOBS)
pahole-flags-$(call test-ge, $(pahole-ver), 125) += --skip_encoding_btf_inconsistent_proto --btf_gen_optimized
@ -25,13 +18,15 @@ pahole-flags-$(call test-ge, $(pahole-ver), 126) = -j$(JOBS) --btf_features=enc
pahole-flags-$(call test-ge, $(pahole-ver), 130) += --btf_features=attributes
ifneq ($(KBUILD_EXTMOD),)
module-pahole-flags-$(call test-ge, $(pahole-ver), 128) += --btf_features=distilled_base
endif
endif
pahole-flags-$(CONFIG_PAHOLE_HAS_LANG_EXCLUDE) += --lang_exclude=rust
export PAHOLE_FLAGS := $(pahole-flags-y)
export MODULE_PAHOLE_FLAGS := $(module-pahole-flags-y)
resolve-btfids-flags-y :=
resolve-btfids-flags-$(CONFIG_WERROR) += --fatal_warnings
resolve-btfids-flags-$(if $(KBUILD_EXTMOD),y) += --distill_base
resolve-btfids-flags-$(if $(KBUILD_VERBOSE),y) += --verbose
export RESOLVE_BTFIDS_FLAGS := $(resolve-btfids-flags-y)

View File

@ -42,9 +42,8 @@ quiet_cmd_btf_ko = BTF [M] $@
cmd_btf_ko = \
if [ ! -f $(objtree)/vmlinux ]; then \
printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
else \
LLVM_OBJCOPY="$(OBJCOPY)" $(PAHOLE) -J $(PAHOLE_FLAGS) $(MODULE_PAHOLE_FLAGS) --btf_base $(objtree)/vmlinux $@; \
$(RESOLVE_BTFIDS) -b $(objtree)/vmlinux $@; \
else \
$(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \
fi;
# Same as newer-prereqs, but allows to exclude specified extra dependencies

View File

@ -71,7 +71,7 @@ targets += vmlinux.unstripped .vmlinux.export.o
vmlinux.unstripped: scripts/link-vmlinux.sh vmlinux.o .vmlinux.export.o $(KBUILD_LDS) FORCE
+$(call if_changed_dep,link_vmlinux)
ifdef CONFIG_DEBUG_INFO_BTF
vmlinux.unstripped: $(RESOLVE_BTFIDS)
vmlinux.unstripped: $(RESOLVE_BTFIDS) $(srctree)/scripts/gen-btf.sh
endif
ifdef CONFIG_BUILDTIME_TABLE_SORT

157
scripts/gen-btf.sh Executable file
View File

@ -0,0 +1,157 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2025 Meta Platforms, Inc. and affiliates.
#
# This script generates BTF data for the provided ELF file.
#
# Kernel BTF generation involves these conceptual steps:
# 1. pahole generates BTF from DWARF data
# 2. resolve_btfids applies kernel-specific btf2btf
# transformations and computes data for .BTF_ids section
# 3. the result gets linked/objcopied into the target binary
#
# How step (3) should be done differs between vmlinux, and
# kernel modules, which is the primary reason for the existence
# of this script.
#
# For modules the script expects vmlinux passed in as --btf_base.
# Generated .BTF, .BTF.base and .BTF_ids sections become embedded
# into the input ELF file with objcopy.
#
# For vmlinux the input file remains unchanged and two files are produced:
# - ${1}.btf.o ready for linking into vmlinux
# - ${1}.BTF_ids with .BTF_ids data blob
# This output is consumed by scripts/link-vmlinux.sh
set -e
usage()
{
echo "Usage: $0 [--btf_base <file>] <target ELF file>"
exit 1
}
BTF_BASE=""
while [ $# -gt 0 ]; do
case "$1" in
--btf_base)
BTF_BASE="$2"
shift 2
;;
-*)
echo "Unknown option: $1" >&2
usage
;;
*)
break
;;
esac
done
if [ $# -ne 1 ]; then
usage
fi
ELF_FILE="$1"
shift
is_enabled() {
grep -q "^$1=y" ${objtree}/include/config/auto.conf
}
info()
{
printf " %-7s %s\n" "${1}" "${2}"
}
case "${KBUILD_VERBOSE}" in
*1*)
set -x
;;
esac
gen_btf_data()
{
info BTF "${ELF_FILE}"
btf1="${ELF_FILE}.BTF.1"
${PAHOLE} -J ${PAHOLE_FLAGS} \
${BTF_BASE:+--btf_base ${BTF_BASE}} \
--btf_encode_detached=${btf1} \
"${ELF_FILE}"
info BTFIDS "${ELF_FILE}"
${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_FLAGS} \
${BTF_BASE:+--btf_base ${BTF_BASE}} \
--btf ${btf1} "${ELF_FILE}"
}
gen_btf_o()
{
local btf_data=${ELF_FILE}.btf.o
# Create ${btf_data} which contains just .BTF section but no symbols. Add
# SHF_ALLOC because .BTF will be part of the vmlinux image. --strip-all
# deletes all symbols including __start_BTF and __stop_BTF, which will
# be redefined in the linker script.
info OBJCOPY "${btf_data}"
echo "" | ${CC} ${CLANG_FLAGS} -c -x c -o ${btf_data} -
${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF \
--set-section-flags .BTF=alloc,readonly ${btf_data}
${OBJCOPY} --only-section=.BTF --strip-all ${btf_data}
# Change e_type to ET_REL so that it can be used to link final vmlinux.
# GNU ld 2.35+ and lld do not allow an ET_EXEC input.
if is_enabled CONFIG_CPU_BIG_ENDIAN; then
et_rel='\0\1'
else
et_rel='\1\0'
fi
printf "${et_rel}" | dd of="${btf_data}" conv=notrunc bs=1 seek=16 status=none
}
embed_btf_data()
{
info OBJCOPY "${ELF_FILE}.BTF"
${OBJCOPY} --add-section .BTF=${ELF_FILE}.BTF ${ELF_FILE}
# a module might not have a .BTF_ids or .BTF.base section
local btf_base="${ELF_FILE}.BTF.base"
if [ -f "${btf_base}" ]; then
${OBJCOPY} --add-section .BTF.base=${btf_base} ${ELF_FILE}
fi
local btf_ids="${ELF_FILE}.BTF_ids"
if [ -f "${btf_ids}" ]; then
${OBJCOPY} --update-section .BTF_ids=${btf_ids} ${ELF_FILE}
fi
}
cleanup()
{
rm -f "${ELF_FILE}.BTF.1"
rm -f "${ELF_FILE}.BTF"
if [ "${BTFGEN_MODE}" = "module" ]; then
rm -f "${ELF_FILE}.BTF.base"
rm -f "${ELF_FILE}.BTF_ids"
fi
}
trap cleanup EXIT
BTFGEN_MODE="vmlinux"
if [ -n "${BTF_BASE}" ]; then
BTFGEN_MODE="module"
fi
gen_btf_data
case "${BTFGEN_MODE}" in
vmlinux)
gen_btf_o
;;
module)
embed_btf_data
;;
esac
exit 0

View File

@ -106,34 +106,6 @@ vmlinux_link()
${kallsymso} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
}
# generate .BTF typeinfo from DWARF debuginfo
# ${1} - vmlinux image
gen_btf()
{
local btf_data=${1}.btf.o
info BTF "${btf_data}"
LLVM_OBJCOPY="${OBJCOPY}" ${PAHOLE} -J ${PAHOLE_FLAGS} ${1}
# Create ${btf_data} which contains just .BTF section but no symbols. Add
# SHF_ALLOC because .BTF will be part of the vmlinux image. --strip-all
# deletes all symbols including __start_BTF and __stop_BTF, which will
# be redefined in the linker script. Add 2>/dev/null to suppress GNU
# objcopy warnings: "empty loadable segment detected at ..."
${OBJCOPY} --only-section=.BTF --set-section-flags .BTF=alloc,readonly \
--strip-all ${1} "${btf_data}" 2>/dev/null
# Change e_type to ET_REL so that it can be used to link final vmlinux.
# GNU ld 2.35+ and lld do not allow an ET_EXEC input.
if is_enabled CONFIG_CPU_BIG_ENDIAN; then
et_rel='\0\1'
else
et_rel='\1\0'
fi
printf "${et_rel}" | dd of="${btf_data}" conv=notrunc bs=1 seek=16 status=none
btf_vmlinux_bin_o=${btf_data}
}
# Create ${2}.o file with all symbols from the ${1} object file
kallsyms()
{
@ -205,6 +177,7 @@ if is_enabled CONFIG_ARCH_WANTS_PRE_LINK_VMLINUX; then
fi
btf_vmlinux_bin_o=
btfids_vmlinux=
kallsymso=
strip_debug=
generate_map=
@ -232,11 +205,13 @@ if is_enabled CONFIG_KALLSYMS || is_enabled CONFIG_DEBUG_INFO_BTF; then
fi
if is_enabled CONFIG_DEBUG_INFO_BTF; then
if ! gen_btf .tmp_vmlinux1; then
if ! ${srctree}/scripts/gen-btf.sh .tmp_vmlinux1; then
echo >&2 "Failed to generate BTF for vmlinux"
echo >&2 "Try to disable CONFIG_DEBUG_INFO_BTF"
exit 1
fi
btf_vmlinux_bin_o=.tmp_vmlinux1.btf.o
btfids_vmlinux=.tmp_vmlinux1.BTF_ids
fi
if is_enabled CONFIG_KALLSYMS; then
@ -289,14 +264,9 @@ fi
vmlinux_link "${VMLINUX}"
# fill in BTF IDs
if is_enabled CONFIG_DEBUG_INFO_BTF; then
info BTFIDS "${VMLINUX}"
RESOLVE_BTFIDS_ARGS=""
if is_enabled CONFIG_WERROR; then
RESOLVE_BTFIDS_ARGS=" --fatal_warnings "
fi
${RESOLVE_BTFIDS} ${RESOLVE_BTFIDS_ARGS} "${VMLINUX}"
info OBJCOPY ${btfids_vmlinux}
${OBJCOPY} --update-section .BTF_ids=${btfids_vmlinux} ${VMLINUX}
fi
mksysmap "${VMLINUX}" System.map

View File

@ -70,7 +70,8 @@ HOSTCFLAGS_resolve_btfids += -g \
-I$(srctree)/tools/include/uapi \
-I$(LIBBPF_INCLUDE) \
-I$(SUBCMD_INCLUDE) \
$(LIBELF_FLAGS)
$(LIBELF_FLAGS) \
-Wall -Werror
LIBS = $(LIBELF_LIBS) -lz

View File

@ -71,9 +71,11 @@
#include <fcntl.h>
#include <errno.h>
#include <linux/btf_ids.h>
#include <linux/kallsyms.h>
#include <linux/rbtree.h>
#include <linux/zalloc.h>
#include <linux/err.h>
#include <linux/limits.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <subcmd/parse-options.h>
@ -98,6 +100,13 @@
# error "Unknown machine endianness!"
#endif
enum btf_id_kind {
BTF_ID_KIND_NONE,
BTF_ID_KIND_SYM,
BTF_ID_KIND_SET,
BTF_ID_KIND_SET8
};
struct btf_id {
struct rb_node rb_node;
char *name;
@ -105,17 +114,20 @@ struct btf_id {
int id;
int cnt;
};
enum btf_id_kind kind;
int addr_cnt;
bool is_set;
bool is_set8;
Elf64_Addr addr[ADDR_CNT];
};
struct object {
const char *path;
const char *btf;
const char *btf_path;
const char *base_btf_path;
struct btf *btf;
struct btf *base_btf;
bool distill_base;
struct {
int fd;
Elf *elf;
@ -194,8 +206,10 @@ static struct btf_id *btf_id__find(struct rb_root *root, const char *name)
return NULL;
}
static struct btf_id *
btf_id__add(struct rb_root *root, char *name, bool unique)
static struct btf_id *__btf_id__add(struct rb_root *root,
char *name,
enum btf_id_kind kind,
bool unique)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent = NULL;
@ -218,12 +232,23 @@ btf_id__add(struct rb_root *root, char *name, bool unique)
if (id) {
pr_debug("adding symbol %s\n", name);
id->name = name;
id->kind = kind;
rb_link_node(&id->rb_node, parent, p);
rb_insert_color(&id->rb_node, root);
}
return id;
}
static inline struct btf_id *btf_id__add(struct rb_root *root, char *name, enum btf_id_kind kind)
{
return __btf_id__add(root, name, kind, false);
}
static inline struct btf_id *btf_id__add_unique(struct rb_root *root, char *name, enum btf_id_kind kind)
{
return __btf_id__add(root, name, kind, true);
}
static char *get_id(const char *prefix_end)
{
/*
@ -257,22 +282,36 @@ static char *get_id(const char *prefix_end)
return id;
}
static struct btf_id *add_set(struct object *obj, char *name, bool is_set8)
static struct btf_id *add_set(struct object *obj, char *name, enum btf_id_kind kind)
{
int len = strlen(name);
int prefixlen;
char *id;
/*
* __BTF_ID__set__name
* name = ^
* id = ^
*/
char *id = name + (is_set8 ? sizeof(BTF_SET8 "__") : sizeof(BTF_SET "__")) - 1;
int len = strlen(name);
switch (kind) {
case BTF_ID_KIND_SET:
prefixlen = sizeof(BTF_SET "__") - 1;
break;
case BTF_ID_KIND_SET8:
prefixlen = sizeof(BTF_SET8 "__") - 1;
break;
default:
pr_err("Unexpected kind %d passed to %s() for symbol %s\n", kind, __func__, name);
return NULL;
}
id = name + prefixlen;
if (id >= name + len) {
pr_err("FAILED to parse set name: %s\n", name);
return NULL;
}
return btf_id__add(&obj->sets, id, true);
return btf_id__add_unique(&obj->sets, id, kind);
}
static struct btf_id *add_symbol(struct rb_root *root, char *name, size_t size)
@ -285,45 +324,19 @@ static struct btf_id *add_symbol(struct rb_root *root, char *name, size_t size)
return NULL;
}
return btf_id__add(root, id, false);
return btf_id__add(root, id, BTF_ID_KIND_SYM);
}
/* Older libelf.h and glibc elf.h might not yet define the ELF compression types. */
#ifndef SHF_COMPRESSED
#define SHF_COMPRESSED (1 << 11) /* Section with compressed data. */
#endif
/*
* The data of compressed section should be aligned to 4
* (for 32bit) or 8 (for 64 bit) bytes. The binutils ld
* sets sh_addralign to 1, which makes libelf fail with
* misaligned section error during the update:
* FAILED elf_update(WRITE): invalid section alignment
*
* While waiting for ld fix, we fix the compressed sections
* sh_addralign value manualy.
*/
static int compressed_section_fix(Elf *elf, Elf_Scn *scn, GElf_Shdr *sh)
static void bswap_32_data(void *data, u32 nr_bytes)
{
int expected = gelf_getclass(elf) == ELFCLASS32 ? 4 : 8;
u32 cnt, i;
u32 *ptr;
if (!(sh->sh_flags & SHF_COMPRESSED))
return 0;
cnt = nr_bytes / sizeof(u32);
ptr = data;
if (sh->sh_addralign == expected)
return 0;
pr_debug2(" - fixing wrong alignment sh_addralign %u, expected %u\n",
sh->sh_addralign, expected);
sh->sh_addralign = expected;
if (gelf_update_shdr(scn, sh) == 0) {
pr_err("FAILED cannot update section header: %s\n",
elf_errmsg(-1));
return -1;
}
return 0;
for (i = 0; i < cnt; i++)
ptr[i] = bswap_32(ptr[i]);
}
static int elf_collect(struct object *obj)
@ -344,7 +357,7 @@ static int elf_collect(struct object *obj)
elf_version(EV_CURRENT);
elf = elf_begin(fd, ELF_C_RDWR_MMAP, NULL);
elf = elf_begin(fd, ELF_C_READ_MMAP_PRIVATE, NULL);
if (!elf) {
close(fd);
pr_err("FAILED cannot create ELF descriptor: %s\n",
@ -407,21 +420,20 @@ static int elf_collect(struct object *obj)
obj->efile.symbols_shndx = idx;
obj->efile.strtabidx = sh.sh_link;
} else if (!strcmp(name, BTF_IDS_SECTION)) {
/*
* If target endianness differs from host, we need to bswap32
* the .BTF_ids section data on load, because .BTF_ids has
* Elf_Type = ELF_T_BYTE, and so libelf returns data buffer in
* the target endianness. We repeat this on dump.
*/
if (obj->efile.encoding != ELFDATANATIVE) {
pr_debug("bswap_32 .BTF_ids data from target to host endianness\n");
bswap_32_data(data->d_buf, data->d_size);
}
obj->efile.idlist = data;
obj->efile.idlist_shndx = idx;
obj->efile.idlist_addr = sh.sh_addr;
} else if (!strcmp(name, BTF_BASE_ELF_SEC)) {
/* If a .BTF.base section is found, do not resolve
* BTF ids relative to vmlinux; resolve relative
* to the .BTF.base section instead. btf__parse_split()
* will take care of this once the base BTF it is
* passed is NULL.
*/
obj->base_btf_path = NULL;
}
if (compressed_section_fix(elf, scn, &sh))
return -1;
}
return 0;
@ -488,35 +500,31 @@ static int symbols_collect(struct object *obj)
id = add_symbol(&obj->funcs, prefix, sizeof(BTF_FUNC) - 1);
/* set8 */
} else if (!strncmp(prefix, BTF_SET8, sizeof(BTF_SET8) - 1)) {
id = add_set(obj, prefix, true);
id = add_set(obj, prefix, BTF_ID_KIND_SET8);
/*
* SET8 objects store list's count, which is encoded
* in symbol's size, together with 'cnt' field hence
* that - 1.
*/
if (id) {
if (id)
id->cnt = sym.st_size / sizeof(uint64_t) - 1;
id->is_set8 = true;
}
/* set */
} else if (!strncmp(prefix, BTF_SET, sizeof(BTF_SET) - 1)) {
id = add_set(obj, prefix, false);
id = add_set(obj, prefix, BTF_ID_KIND_SET);
/*
* SET objects store list's count, which is encoded
* in symbol's size, together with 'cnt' field hence
* that - 1.
*/
if (id) {
if (id)
id->cnt = sym.st_size / sizeof(int) - 1;
id->is_set = true;
}
} else {
pr_err("FAILED unsupported prefix %s\n", prefix);
return -1;
}
if (!id)
return -ENOMEM;
return -EINVAL;
if (id->addr_cnt >= ADDR_CNT) {
pr_err("FAILED symbol %s crossed the number of allowed lists\n",
@ -529,16 +537,10 @@ static int symbols_collect(struct object *obj)
return 0;
}
static int symbols_resolve(struct object *obj)
static int load_btf(struct object *obj)
{
int nr_typedefs = obj->nr_typedefs;
int nr_structs = obj->nr_structs;
int nr_unions = obj->nr_unions;
int nr_funcs = obj->nr_funcs;
struct btf *base_btf = NULL;
int err, type_id;
struct btf *btf;
__u32 nr_types;
struct btf *base_btf = NULL, *btf = NULL;
int err;
if (obj->base_btf_path) {
base_btf = btf__parse(obj->base_btf_path, NULL);
@ -546,18 +548,54 @@ static int symbols_resolve(struct object *obj)
if (err) {
pr_err("FAILED: load base BTF from %s: %s\n",
obj->base_btf_path, strerror(-err));
return -1;
goto out_err;
}
}
btf = btf__parse_split(obj->btf ?: obj->path, base_btf);
btf = btf__parse_split(obj->btf_path ?: obj->path, base_btf);
err = libbpf_get_error(btf);
if (err) {
pr_err("FAILED: load BTF from %s: %s\n",
obj->btf ?: obj->path, strerror(-err));
goto out;
obj->btf_path ?: obj->path, strerror(-err));
goto out_err;
}
obj->base_btf = base_btf;
obj->btf = btf;
if (obj->base_btf && obj->distill_base) {
err = btf__distill_base(obj->btf, &base_btf, &btf);
if (err) {
pr_err("FAILED to distill base BTF: %s\n", strerror(errno));
goto out_err;
}
btf__free(obj->base_btf);
btf__free(obj->btf);
obj->base_btf = base_btf;
obj->btf = btf;
}
return 0;
out_err:
btf__free(base_btf);
btf__free(btf);
obj->base_btf = NULL;
obj->btf = NULL;
return err;
}
static int symbols_resolve(struct object *obj)
{
int nr_typedefs = obj->nr_typedefs;
int nr_structs = obj->nr_structs;
int nr_unions = obj->nr_unions;
int nr_funcs = obj->nr_funcs;
struct btf *btf = obj->btf;
int err, type_id;
__u32 nr_types;
err = -1;
nr_types = btf__type_cnt(btf);
@ -615,8 +653,6 @@ static int symbols_resolve(struct object *obj)
err = 0;
out:
btf__free(base_btf);
btf__free(btf);
return err;
}
@ -627,7 +663,7 @@ static int id_patch(struct object *obj, struct btf_id *id)
int i;
/* For set, set8, id->id may be 0 */
if (!id->id && !id->is_set && !id->is_set8) {
if (!id->id && id->kind != BTF_ID_KIND_SET && id->kind != BTF_ID_KIND_SET8) {
pr_err("WARN: resolve_btfids: unresolved symbol %s\n", id->name);
warnings++;
}
@ -680,6 +716,7 @@ static int sets_patch(struct object *obj)
{
Elf_Data *data = obj->efile.idlist;
struct rb_node *next;
int cnt;
next = rb_first(&obj->sets);
while (next) {
@ -699,39 +736,28 @@ static int sets_patch(struct object *obj)
return -1;
}
if (id->is_set) {
switch (id->kind) {
case BTF_ID_KIND_SET:
set = data->d_buf + off;
cnt = set->cnt;
qsort(set->ids, set->cnt, sizeof(set->ids[0]), cmp_id);
} else {
break;
case BTF_ID_KIND_SET8:
set8 = data->d_buf + off;
cnt = set8->cnt;
/*
* Make sure id is at the beginning of the pairs
* struct, otherwise the below qsort would not work.
*/
BUILD_BUG_ON((u32 *)set8->pairs != &set8->pairs[0].id);
qsort(set8->pairs, set8->cnt, sizeof(set8->pairs[0]), cmp_id);
/*
* When ELF endianness does not match endianness of the
* host, libelf will do the translation when updating
* the ELF. This, however, corrupts SET8 flags which are
* already in the target endianness. So, let's bswap
* them to the host endianness and libelf will then
* correctly translate everything.
*/
if (obj->efile.encoding != ELFDATANATIVE) {
int i;
set8->flags = bswap_32(set8->flags);
for (i = 0; i < set8->cnt; i++) {
set8->pairs[i].flags =
bswap_32(set8->pairs[i].flags);
}
}
break;
default:
pr_err("Unexpected btf_id_kind %d for set '%s'\n", id->kind, id->name);
return -1;
}
pr_debug("sorting addr %5lu: cnt %6d [%s]\n",
off, id->is_set ? set->cnt : set8->cnt, id->name);
pr_debug("sorting addr %5lu: cnt %6d [%s]\n", off, cnt, id->name);
next = rb_next(next);
}
@ -740,8 +766,6 @@ static int sets_patch(struct object *obj)
static int symbols_patch(struct object *obj)
{
off_t err;
if (__symbols_patch(obj, &obj->structs) ||
__symbols_patch(obj, &obj->unions) ||
__symbols_patch(obj, &obj->typedefs) ||
@ -752,20 +776,90 @@ static int symbols_patch(struct object *obj)
if (sets_patch(obj))
return -1;
/* Set type to ensure endian translation occurs. */
obj->efile.idlist->d_type = ELF_T_WORD;
return 0;
}
elf_flagdata(obj->efile.idlist, ELF_C_SET, ELF_F_DIRTY);
static int dump_raw_data(const char *out_path, const void *data, u32 size)
{
size_t written;
FILE *file;
err = elf_update(obj->efile.elf, ELF_C_WRITE);
if (err < 0) {
pr_err("FAILED elf_update(WRITE): %s\n",
elf_errmsg(-1));
file = fopen(out_path, "wb");
if (!file) {
pr_err("Couldn't open %s for writing\n", out_path);
return -1;
}
pr_debug("update %s for %s\n",
err >= 0 ? "ok" : "failed", obj->path);
return err < 0 ? -1 : 0;
written = fwrite(data, 1, size, file);
if (written != size) {
pr_err("Failed to write data to %s\n", out_path);
fclose(file);
unlink(out_path);
return -1;
}
fclose(file);
pr_debug("Dumped %lu bytes of data to %s\n", size, out_path);
return 0;
}
static int dump_raw_btf_ids(struct object *obj, const char *out_path)
{
Elf_Data *data = obj->efile.idlist;
int err;
if (!data || !data->d_buf) {
pr_debug("%s has no BTF_ids data to dump\n", obj->path);
return 0;
}
/*
* If target endianness differs from host, we need to bswap32 the
* .BTF_ids section data before dumping so that the output is in
* target endianness.
*/
if (obj->efile.encoding != ELFDATANATIVE) {
pr_debug("bswap_32 .BTF_ids data from host to target endianness\n");
bswap_32_data(data->d_buf, data->d_size);
}
err = dump_raw_data(out_path, data->d_buf, data->d_size);
if (err)
return -1;
return 0;
}
static int dump_raw_btf(struct btf *btf, const char *out_path)
{
const void *raw_btf_data;
u32 raw_btf_size;
int err;
raw_btf_data = btf__raw_data(btf, &raw_btf_size);
if (!raw_btf_data) {
pr_err("btf__raw_data() failed\n");
return -1;
}
err = dump_raw_data(out_path, raw_btf_data, raw_btf_size);
if (err)
return -1;
return 0;
}
static inline int make_out_path(char *buf, u32 buf_sz, const char *in_path, const char *suffix)
{
int len = snprintf(buf, buf_sz, "%s%s", in_path, suffix);
if (len < 0 || len >= buf_sz) {
pr_err("Output path is too long: %s%s\n", in_path, suffix);
return -E2BIG;
}
return 0;
}
static const char * const resolve_btfids_usage[] = {
@ -787,15 +881,19 @@ int main(int argc, const char **argv)
.sets = RB_ROOT,
};
bool fatal_warnings = false;
char out_path[PATH_MAX];
struct option btfid_options[] = {
OPT_INCR('v', "verbose", &verbose,
"be more verbose (show errors, etc)"),
OPT_STRING(0, "btf", &obj.btf, "BTF data",
"BTF data"),
OPT_STRING(0, "btf", &obj.btf_path, "file",
"path to a file with input BTF data"),
OPT_STRING('b', "btf_base", &obj.base_btf_path, "file",
"path of file providing base BTF"),
OPT_BOOLEAN(0, "fatal_warnings", &fatal_warnings,
"turn warnings into errors"),
OPT_BOOLEAN(0, "distill_base", &obj.distill_base,
"distill --btf_base and emit .BTF.base section data"),
OPT_END()
};
int err = -1;
@ -807,6 +905,9 @@ int main(int argc, const char **argv)
obj.path = argv[0];
if (load_btf(&obj))
goto out;
if (elf_collect(&obj))
goto out;
@ -816,9 +917,8 @@ int main(int argc, const char **argv)
*/
if (obj.efile.idlist_shndx == -1 ||
obj.efile.symbols_shndx == -1) {
pr_debug("Cannot find .BTF_ids or symbols sections, nothing to do\n");
err = 0;
goto out;
pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
goto dump_btf;
}
if (symbols_collect(&obj))
@ -830,9 +930,29 @@ int main(int argc, const char **argv)
if (symbols_patch(&obj))
goto out;
err = make_out_path(out_path, sizeof(out_path), obj.path, BTF_IDS_SECTION);
err = err ?: dump_raw_btf_ids(&obj, out_path);
if (err)
goto out;
dump_btf:
err = make_out_path(out_path, sizeof(out_path), obj.path, BTF_ELF_SEC);
err = err ?: dump_raw_btf(obj.btf, out_path);
if (err)
goto out;
if (obj.base_btf && obj.distill_base) {
err = make_out_path(out_path, sizeof(out_path), obj.path, BTF_BASE_ELF_SEC);
err = err ?: dump_raw_btf(obj.base_btf, out_path);
if (err)
goto out;
}
if (!(fatal_warnings && warnings))
err = 0;
out:
btf__free(obj.base_btf);
btf__free(obj.btf);
if (obj.efile.elf) {
elf_end(obj.efile.elf);
close(obj.efile.fd);

View File

@ -65,7 +65,6 @@ It's also recommended that you also include the following Kconfig options:
```
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_PAHOLE_HAS_SPLIT_BTF=y
CONFIG_PAHOLE_HAS_BTF_TAG=y
```

View File

@ -45,3 +45,6 @@ xdp_synproxy
xdp_hw_metadata
xdp_features
verification_cert.h
*.BTF
*.BTF_ids
*.BTF.base

View File

@ -4,6 +4,7 @@ include ../../../scripts/Makefile.arch
include ../../../scripts/Makefile.include
CXX ?= $(CROSS_COMPILE)g++
OBJCOPY ?= $(CROSS_COMPILE)objcopy
CURDIR := $(abspath .)
TOOLSDIR := $(abspath ../../..)
@ -643,6 +644,9 @@ $(TRUNNER_TESTS_HDR): $(TRUNNER_TESTS_DIR)/*.c
) > $$@)
endif
$(TRUNNER_OUTPUT)/resolve_btfids.test.o: $(RESOLVE_BTFIDS) $(TRUNNER_OUTPUT)/btf_data.bpf.o
$(TRUNNER_OUTPUT)/resolve_btfids.test.o: private TEST_NEEDS_BTFIDS = 1
# compile individual test files
# Note: we cd into output directory to ensure embedded BPF object is found
$(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)/%.test.o: \
@ -650,6 +654,10 @@ $(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)/%.test.o: \
| $(TRUNNER_OUTPUT)/%.test.d
$$(call msg,TEST-OBJ,$(TRUNNER_BINARY),$$@)
$(Q)cd $$(@D) && $$(CC) -I. $$(CFLAGS) -MMD -MT $$@ -c $(CURDIR)/$$< $$(LDLIBS) -o $$(@F)
$$(if $$(TEST_NEEDS_BTFIDS), \
$$(call msg,BTFIDS,$(TRUNNER_BINARY),$$@) \
$(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@; \
$(OBJCOPY) --update-section .BTF_ids=$$@.BTF_ids $$@)
$(TRUNNER_TEST_OBJS:.o=.d): $(TRUNNER_OUTPUT)/%.test.d: \
$(TRUNNER_TESTS_DIR)/%.c \
@ -695,13 +703,11 @@ $(OUTPUT)/$(TRUNNER_BINARY): | $(TRUNNER_BPF_OBJS)
$(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS) \
$(TRUNNER_EXTRA_OBJS) $$(BPFOBJ) \
$(TRUNNER_LIB_OBJS) \
$(RESOLVE_BTFIDS) \
$(TRUNNER_BPFTOOL) \
$(OUTPUT)/veristat \
| $(TRUNNER_BINARY)-extras
$$(call msg,BINARY,,$$@)
$(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LLVM_LDLIBS) $$(LDFLAGS) $$(LLVM_LDFLAGS) -o $$@
$(Q)$(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@
$(Q)ln -sf $(if $2,..,.)/tools/build/bpftool/$(USE_BOOTSTRAP)bpftool \
$(OUTPUT)/$(if $2,$2/)bpftool
@ -890,6 +896,7 @@ EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \
prog_tests/tests.h map_tests/tests.h verifier/tests.h \
feature bpftool $(TEST_KMOD_TARGETS) \
$(addprefix $(OUTPUT)/,*.o *.d *.skel.h *.lskel.h *.subskel.h \
*.BTF *.BTF_ids *.BTF.base \
no_alu32 cpuv4 bpf_gcc \
liburandom_read.so) \
$(OUTPUT)/FEATURE-DUMP.selftests \

View File

@ -101,9 +101,9 @@ static int resolve_symbols(void)
int type_id;
__u32 nr;
btf = btf__parse_elf("btf_data.bpf.o", NULL);
btf = btf__parse_raw("resolve_btfids.test.o.BTF");
if (CHECK(libbpf_get_error(btf), "resolve",
"Failed to load BTF from btf_data.bpf.o\n"))
"Failed to load BTF from resolve_btfids.test.o.BTF\n"))
return -1;
nr = btf__type_cnt(btf);