blob: e88254afefe636e4051699097352886c8e4d38f3 [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Python script to generate ebpf skeletons from bpf code.
This script compiles the C code with target bpf and then runs bpftool against
the resulting object file to generate bpf skeleton header files that can then be
used by userspace programs to load, attach and communicate with bpf functions.
"""
import argparse
import os
import pathlib
import subprocess
import sys
import typing
def _run_command(command: typing.List[str]) -> subprocess.CompletedProcess:
"""Run a command with default options.
Run a command using subprocess.run with default configuration.
"""
return subprocess.run(
command,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
def do_gen_bpf_skeleton(args):
"""Generate BPF skeletons from C.
Takes a BPF application written in C and generates the BPF object file and
then uses that to generate BPF skeletons using bpftool.
If args.out_min_btf is specified, the BPF object file is also processed to
generate a min CO-RE BTF.
"""
out_header = args.out_header
out_btf = args.out_min_btf
vmlinux_btf = args.vmlinux_btf
source = args.source
arch = args.arch
includes = args.includes
defines = args.defines or []
sysroot = args.sysroot
obj = "_".join(os.path.basename(source).split(".")[:-1]) + ".o"
arch_to_define = {
"amd64": "__TARGET_ARCH_x86",
"amd64-linux": "__TARGET_ARCH_x86",
"arm": "__TARGET_ARCH_arm",
"arm-linux": "__TARGET_ARCH_arm",
"arm64": "__TARGET_ARCH_arm64",
"mips": "__TARGET_ARCH_mips",
"ppc": "__TARGET_ARCH_powerpc",
"ppc64": "__TARGET_ARCH_powerpc",
"ppc64-linux": "__TARGET_ARCH_powerpc",
"x86": "__TARGET_ARCH_x86",
"x86-linux": "__TARGET_ARCH_x86",
}
arch = arch_to_define.get(arch, None)
if arch is None:
print(
f"Unable to map arch={arch} to a sensible"
"__TARGET_ARCH_XXX for bpf compilation."
)
return -1
# Calling bpf-clang is equivalent to "clang --target bpf".
# It may seem odd that the application needs to be compiled with -g but
# then llvm-strip is ran against the resulting object.
# The -g is needed for the bpf application to compile properly but we
# want to reduce the file size by stripping it.
call_bpf_clang = (
["/usr/bin/bpf-clang", "-g", "-O2", f"--sysroot={sysroot}"]
+ [f"-I{x}" for x in includes]
+ [f"-D{x}" for x in defines]
+ [f"-D{arch}", "-c", source, "-o", obj]
)
gen_skeleton = ["/usr/sbin/bpftool", "gen", "skeleton", obj]
strip_dwarf = ["llvm-strip", "-g", obj]
# Compile the BPF C application.
_run_command(call_bpf_clang)
# Strip useless dwarf information.
_run_command(strip_dwarf)
# Use bpftools to generate skeletons from the BPF object files.
bpftool_proc = _run_command(gen_skeleton)
# BPFtools will output the C formatted dump of kernel symbols to stdout.
# Write the contents to file.
with open(out_header, "w", encoding="utf-8") as bpf_skeleton:
bpf_skeleton.write(bpftool_proc.stdout)
# Generate a detached min_core BTF.
if out_btf:
if not vmlinux_btf:
print(
"Need a full vmlinux BTF as input in order to generate a min "
"BTF"
)
return -1
gen_min_core_btf = [
"/usr/sbin/bpftool",
"gen",
"min_core_btf",
vmlinux_btf,
out_btf,
obj,
]
_run_command(gen_min_core_btf)
return 0
def do_gen_vmlinux(args):
"""Generate vmlinux.h for use in BPF programs.
Invokes pahole and bpftool to generate vmlinux.h from vmlinux from the
kernel build. Uses BTF as an intermediate format. The generated BTF is
preserved for possible use in generation of min CO-RE BTFs.
"""
sysroot = args.sysroot
vmlinux_out = args.out_header
btf_out = args.out_btf
gen_detached_btf = [
"/usr/bin/pahole",
"--btf_encode_detached",
btf_out,
f"{sysroot}/usr/lib/debug/boot/vmlinux",
]
gen_vmlinux = [
"/usr/sbin/bpftool",
"btf",
"dump",
"file",
btf_out,
"format",
"c",
]
# First, run pahole to generate a detached vmlinux BTF. This step works
# regardless of whether the vmlinux was built with CONFIG_DEBUG_BTF_INFO.
pathlib.Path(os.path.dirname(btf_out)).mkdir(parents=True, exist_ok=True)
_run_command(gen_detached_btf)
# Then, use the generated BTF (and not vmlinux itself) to generate the
# header.
vmlinux_cmd = _run_command(gen_vmlinux)
with open(f"{vmlinux_out}", "w", encoding="utf-8") as vmlinux:
vmlinux.write(vmlinux_cmd.stdout)
return 0
def main(argv: typing.List[str]) -> int:
"""A command line tool for all things BPF.
A command line tool to help generate C BPF skeletons and to generate
vmlinux.h from kernel build artifacts.
"""
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(help="sub-command help")
gen_skel = subparsers.add_parser("gen_skel")
gen_skel.add_argument(
"--out_header",
required=True,
help="The name of the output header file.",
)
gen_skel.add_argument(
"--source", required=True, help="The bpf source code."
)
gen_skel.add_argument(
"--arch", required=True, help="The target architecture."
)
gen_skel.add_argument(
"--includes",
required=True,
nargs="+",
help="Additional include directories.",
)
gen_skel.add_argument(
"--defines",
required=False,
nargs="*",
help="Additional preprocessor defines.",
)
gen_skel.add_argument(
"--vmlinux_btf",
required=False,
help="The detached full vmlinux BTF file.",
)
gen_skel.add_argument(
"--out_min_btf",
required=False,
help="The name of the output min BTF file.",
)
# We require the board sysroot so that BPF compilations will use board
# libbpf headers.
gen_skel.add_argument(
"--sysroot",
required=True,
help="The path that should be treated as the root directory.",
)
gen_skel.set_defaults(func=do_gen_bpf_skeleton)
gen_vmlinux = subparsers.add_parser("gen_vmlinux")
gen_vmlinux.add_argument(
"--sysroot",
required=True,
help="The path that should be treated as the root directory.",
)
gen_vmlinux.add_argument(
"--out_header",
required=True,
help="The name of the output vmlinux.h file.",
)
gen_vmlinux.add_argument(
"--out_btf",
required=True,
help="The name of the output vmlinux BTF file.",
)
gen_vmlinux.set_defaults(func=do_gen_vmlinux)
args = parser.parse_args(argv)
try:
return args.func(args)
except subprocess.CalledProcessError as error:
print(
f'cmd={" ".join(error.cmd)}\nstderr={error.stderr}\n'
f"stdout={error.stdout}\nretcode={error.returncode}\n"
)
return -1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))