bpftrace¶
bpftrace 仍处于开发阶段,有不少问题需要解决,也有不少功能等待实现。
《BPF Performance Tools》一书详细介绍了 bpftrace 的工作原理,本篇笔记在此基础上完成。
程序入口与核心数据结构¶
main.cpp
初始化BPFtrace
和PassManager
。PassManager
添加CreateClangParsePass
步骤。CreateClangParsePass
调用ClangParser::parse
执行 Clang 解析逻辑。ClangParser::parse
完成实际的 C 代码解析工作。
class BPFtrace
¶
- 动态跟踪功能:
- 支持动态添加和移除探针(
add_probe
、attach_probe
)。 - 提供符号解析功能(如
resolve_ksym
、resolve_usym
),用于将地址转换为可读的函数名。 -
支持堆栈跟踪(
get_stack
)和时间戳解析(resolve_timestamp
)。 -
输出与控制:
- 管理输出内容(如
print_map
、print_benchmark_results
)。 -
支持多种输出格式(如 PCAP 文件写入
write_pcaps
)。 -
资源管理:
- 管理 BPF 程序和映射(
BpfBytecode
、BpfMap
)。 -
支持多核 CPU 环境(
ncpus_
、max_cpu_id_
)。 -
调试与配置:
- 提供调试模式(
DebugStage
)和日志输出控制(bt_verbose
、bt_quiet
)。 - 支持安全模式(
safe_mode_
)和自定义配置(Config
)。
bpftrace 语言编译¶
研究 clang 解析器是如何工作的,这也将对我们编写 bpftrace 程序有用。
Pass 的执行方式¶
看看编译相关的数据结构:
class Program : public Node {
// 存储和操作程序的各个组成部分,包括配置、导入、宏、函数和探针等
std::string c_definitions;
Config *config = nullptr;
ImportList imports;
MapDeclList map_decls;
MacroList macros;
SubprogList functions;
ProbeList probes;
}
class ASTContext : public ast::State<"ast"> {
Program *root = nullptr;
}
main()
auto flags = extra_flags(bpftrace, args.include_dirs, args.include_files);
ast = ast::ASTContext(args.filename, buf.str());
ast::PassManager pm;
pm.put(ast);
pm.put(bpftrace);
pm.add(printPass(name));
//... 添加各个 Pass
ast::AllParsePasses(std::move(flags));
passes.emplace_back(CreateClangParsePass(std::move(extra_flags)));
auto pmresult = pm.run();
foreach([&](auto &pass) { return pass.run(ctx); })
return fn_(ctx);
于是我们回头去找 fn_
是什么。以 ClangParsePass
为例:
ast::Pass CreateClangParsePass(std::vector<std::string> &&extra_flags)
{
return ast::Pass::create("ClangParser",
[extra_flags = std::move(extra_flags)](
ast::ASTContext &ast,
BPFtrace &b) -> Result<CDefinitions> {
ClangParser parser;
if (!parser.parse(ast.root, b, extra_flags)) {
return make_error<ClangParseError>();
}
return std::move(parser.definitions);
});
}
static Pass create(const std::string &name, Func func)
{
std::function fn(func);
return createFn(name, fn);
}
请记住这个 lambda 函数被存放在 std::function fn
中,接下来我们看看它是如何存放的:
static Pass createFn(const std::string &name,
std::function<Result<Return>(Inputs &...)> fn)
{
return createImpl(name, fn);
}
static Pass createImpl(const std::string &name,
std::function<Result<Return>(Inputs &...)> fn)
{
return Pass(
name,
std::move(inputs),
std::move(outputs),
[fn](PassContext &ctx) -> Result<> {
auto args = std::make_tuple(std::ref(ctx.get<Inputs>())...);
auto result = std::apply(fn, args);
});
}
using Fn = std::function<Result<>(PassContext &ctx)>;
Pass(std::string name,
std::vector<int> &&inputs,
std::vector<int> &&outputs,
Fn fn)
: name_(std::move(name)),
inputs_(std::move(inputs)),
outputs_(std::move(outputs)),
fn_(std::move(fn)) {}
至此,我们终于明白究竟什么东西被存放在 class Pass
的 fn_
字段:createImpl
中那个接受 PassContext
的 lambda 函数,它捕获了传入的 fn
,将其 std::apply
到 args
上。这个被调用的 fn
,又是最开头创建的 lambda 函数,它将 ClangParser.parse()
作用到 ast.root
和 BPFtrace b
上。
src/ast/passes/clang_parser.cpp
¶
总体来说,ClangParser
负责处理类型定义。它获取 BTF 和系统中的头文件信息,使用 libclang 翻译为 AST,然后使用 visit_children()
分类存放下面的信息:
definitions.macros
:存储宏定义。definitions.enums
和definitions.enum_defs
:存储枚举信息。bpftrace.structs
:存储结构体字段信息。
// When the imported definitions are parsed with clang, relevant C definitions
// are centralized here to be consumed by later passes.
class CDefinitions : public ast::State<"C-definitions"> {
public:
std::map<std::string, std::string> macros;
std::map<std::string, std::tuple<uint64_t, std::string>> enums;
std::map<std::string, std::map<uint64_t, std::string>> enum_defs;
};
class ClangParser {
public:
bool parse(ast::Program *program,
BPFtrace &bpftrace,
std::vector<std::string> extra_flags = {});
// Moved out by the pass.
CDefinitions definitions;
};
class BPFtrace : public ast::State<"bpftrace"> {
StructManager structs;
}
graph TD
A[开始解析] --> B[初始化输入和参数]
B --> C{是否有BTF数据?}
C -->|是| D[设置BTF相关参数]
C -->|否| E[跳过BTF处理]
D --> F[初始解析]
F --> G{是否有重定义错误?}
G -->|是| H[标记BTF冲突]
G -->|否| I[解析不完整类型]
I --> J[解析未知typedef]
J --> K{是否有重定义错误?}
K -->|是| H
K -->|否| L[最终解析]
H --> M[移除BTF参数并使用用户定义类型]
M --> L
E --> L
L --> N{解析成功?}
N -->|是| O[遍历AST并返回成功]
N -->|否| P[返回失败]
linux/type.h
错误分析¶
首先还原错误路径。__aligned_u64
等类型定义在 linux/type.h
中:
在 clang_parser.c
中的 ClangParser::parse()
中,注意到存在 BTF 时,会通过添加宏定义阻止 linux/type.h
中的类型定义:
if (bpftrace.has_btf_data()) {
// We set these args early because some systems may not have
// <linux/types.h> (containers) and fully rely on BTF.
// Prevent BTF generated header from redefining stuff found
// in <linux/types.h>
args.push_back("-D_LINUX_TYPES_H");
}
BTF 生成的头文件 __btf_generated_header.h
中没有发现 __aligned_u64
的定义。bpftrace/src/stdlib/include/linux/types.h
是有定义的,并且头文件保护符用的是 _UAPI_LINUX_TYPES_H
,应该不受上面宏参数的影响。
我想看看预处理器的结果。于是我在 clang_parseTranslationUnit2()
的调用前输出其参数,再用命令行 -E
输出预处理结果:
#!/usr/bin/env bash
clang -E definitions.h \
-isystem \
/bpftrace/include \
-isystem \
/usr/lib64/clang/17/include \
-isystem \
/usr/local/include \
-isystem \
/usr/include \
-isystem \
/usr/include/x86_64-linux-gnu \
-nostdinc \
-isystem \
/virtual/lib/clang/include \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/source/arch/x86/include \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/build/arch/x86/include/generated \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/source/include \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/build/include \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/source/arch/x86/include/uapi \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/build/arch/x86/include/generated/uapi \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/source/include/uapi \
-I/lib/modules/6.6.92-34.1.tl4.x86_64/build/include/generated/uapi \
-include \
/lib/modules/6.6.92-34.1.tl4.x86_64/source/include/linux/kconfig.h \
-D__KERNEL__ \
-D__BPF_TRACING__ \
-D__HAVE_BUILTIN_BSWAP16__ \
-D__HAVE_BUILTIN_BSWAP32__ \
-D__HAVE_BUILTIN_BSWAP64__ \
-DKBUILD_MODNAME="bpftrace" \
-D_LINUX_TYPES_H \
-DBPFTRACE_HAVE_BTF
我惊讶地发现,原来是 linux/type.h
只匹配到源码树中的,而非 /bpftrace/include
中的:
$ grep linux/type output.c
# 1 "/lib/modules/6.6.92-34.1.tl4.x86_64/source/include/linux/types.h" 1 3
# 1 "/lib/modules/6.6.92-34.1.tl4.x86_64/source/include/linux/types.h" 1 3
# 1 "/lib/modules/6.6.92-34.1.tl4.x86_64/source/include/linux/types.h" 1 3
难怪 __aligned_u64
没有被定义。
查阅了一段时间的资料,似乎没有明确的 Include 顺序说明。有人说 Clang 按照 GCC 的顺序,先 -I
再 -isystem
,也有人驳斥这种说法。相关资料罗列如下:
- Clang++ include path order - Developers - The Stan Forums
- What's going on with clang's include priorities? - Stack Overflow
将 /bpftrace/includ
的 -isystem
修改为 -I
后,__aligned_u64
被定义了。然而还缺少 bool
、u64
、u32
等类型的定义。索性把所有 -isystem
全部修改为 -I
,问题居然全部解决了!原 Issue 提出者的命令也没有类型缺失问题了。
$ build/src/bpftrace -e 'tracepoint:sched:sched_switch { $task = (struct task_struct *)curtask; if ($task->state & 0x02) { @[kstack] = count(); } }' --include linux/sched.h --include sys/types.h
stdin:1:76-89: ERROR: Struct/union of type 'struct task_struct' does not contain a field named 'state'
tracepoint:sched:sched_switch { $task = (struct task_struct *)curtask; if ($task->state & 0x02) { @[kstack] = count(); } }
梳理一下传给 Clang 的参数:
-
源码中定义的:
-
通过
query_clang_include_dirs()
查找 Clang 自带的: -
Arch include 目录:
-
其余
extra_flags
又来自哪里呢?来自前面看到的extra_flags(bpftrace, args.include_dirs, args.include_files);
:get_kernel_dirs
:BPFTRACE_KERNEL_SOURCE
、BPFTRACE_KERNEL_BUILD
或 将路径指向/lib/modules/$(uname -r)/build
及/source
-
get_kernel_cflags
弄了一堆参数:cflags.emplace_back("-nostdinc"); cflags.emplace_back("-isystem"); cflags.emplace_back("/virtual/lib/clang/include"); // see linux/Makefile for $(LINUXINCLUDE) + $(USERINCLUDE) cflags.push_back("-I" + ksrc + "/arch/" + arch + "/include"); cflags.push_back("-I" + kobj + "/arch/" + arch + "/include/generated"); cflags.push_back("-I" + ksrc + "/include"); cflags.push_back("-I" + kobj + "/include"); cflags.push_back("-I" + ksrc + "/arch/" + arch + "/include/uapi"); cflags.push_back("-I" + kobj + "/arch/" + arch + "/include/generated/uapi"); cflags.push_back("-I" + ksrc + "/include/uapi"); cflags.push_back("-I" + kobj + "/include/generated/uapi"); cflags.emplace_back("-include"); cflags.push_back(ksrc + "/include/linux/kconfig.h"); cflags.emplace_back("-D__KERNEL__"); cflags.emplace_back("-D__BPF_TRACING__"); cflags.emplace_back("-D__HAVE_BUILTIN_BSWAP16__"); cflags.emplace_back("-D__HAVE_BUILTIN_BSWAP32__"); cflags.emplace_back("-D__HAVE_BUILTIN_BSWAP64__"); cflags.emplace_back("-DKBUILD_MODNAME=\"bpftrace\"");
-
保存为
CXUnsavedFile
的所有文件:未标明的都是bpftrace/src/stdlib/include
下的