当前位置: 首页 > news >正文

eBPF tail-calls示例 - liyan

eBPF程序是事件驱动的,这就意味着当目标事件触发后,程序才能执行。考虑这样一个场景:有几个不同的BPF程序均挂载在相同的hook点上,而执行需要保持一定的顺序。这时就需要借助tail calls的功能来实现。

一、tail calls 与 bpf2bpf calls的对比

首先要说明的是,将不同的逻辑分支都放到一个bpf程序里是很难进行的,因为bpf程序存在严格的限制:比如512B的执行栈。处理逻辑复杂,往往意味着需要使用的结构体就多,很容易就超出了512B的限制,编译时会报类似如下的错误:

cd uretprobe && go generate
./uretprobe.c:24:15: error: Looks like the BPF stack limit of 512 bytes is exceeded. Please move large on stack variables into BPF per-cpu array map.struct event event = {};^
./uretprobe.c:24:15: error: Looks like the BPF stack limit of 512 bytes is exceeded. Please move large on stack variables into BPF per-cpu array map.

对于使用而言,tail calls从一定程度上规避这个问题:使用bpf_tail_call跳转(注意,跳转callee函数执行完成后,不会继续执行caller剩余的逻辑,而是直接退出)的目标函数,函数内部的栈资源限制计算是独立的,会覆盖调用caller的栈帧。而常规的bpf2bpf call,调用的callee执行完成后,会继续执行caller里的代码。而且,512B的限制会对caller callee整体生效。如,下述的bpf2bpf call是会报错的:

struct event {u32 pid;      // 4Bu8 line[256]; // 256B
};// linux-4.16以前,需要这样声明。4.16新增了真正意义上的函数调用而非inline处理。
// static __always_inline void send_event(ctx) {
static void send_event(struct pt_regs *ctx) {struct event event = {};event.pid          = bpf_get_current_pid_tgid();event.line[0]      = 49;bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
}SEC("uretprobe/bash_readline")
int uretprobe_bash_readline(struct pt_regs *ctx) {struct event event = {};event.pid          = bpf_get_current_pid_tgid();event.line[0]      = 49;send_event(ctx); // 这里发起了一个bpf2bpf call// 如果将line的长度调小,程序能够正常执行。send_event后会继续执行。bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));return 0;
}

这里算是笔者目前感知到的主要差异。tail calls的特性在linux-4.2的版本就上线了。在centos-8版本的系统上运行没有问题。

二、tail calls 的一个示例

这里附上执行效果和一段示例。笔者构建的场景是使用uretprobe/bash_readline作为hook点,依据返回字符串长度的奇偶性来触发不同的bpf function,分别输出不同的事件。实现效果如下。

$ sudo ./uretprobe
2023/08/26 14:34:39 Listening for events..
2023/08/26 14:34:45 /bin/bash:readline return value: ll
2023/08/26 14:34:45 get even event
2023/08/26 14:35:01 /bin/bash:readline return value: ls -l
2023/08/26 14:35:01 get odd event

bpf代码为:

char __license[] SEC("license") = "Dual MIT/GPL";
struct event {u32 pid;u8 line[256];u8 mark;
};struct {__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} events SEC(".maps");// Force emitting struct event into the ELF.
const struct event *unused __attribute__((unused));// 通过bpf-tail-call只能调用同类型的bpf函数
SEC("uretprobe/bash_readline_odd")
int uretprobe_bash_readline_odd(struct pt_regs *ctx){struct event event = {};event.pid = bpf_get_current_pid_tgid();event.line[0] = 49;event.mark = 5;bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));return 0;
}// 通过bpf-tail-call只能调用同类型的bpf函数
SEC("uretprobe/bash_readline_even")
int uretprobe_bash_readline_even(struct pt_regs *ctx){struct event event = {};event.pid = bpf_get_current_pid_tgid();event.mark = 8;event.line[0] = 50;bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));return 0;
}struct{__uint(type, BPF_MAP_TYPE_PROG_ARRAY);__uint(key_size, sizeof(u32));__uint(value_size, sizeof(u32));__uint(max_entries, 1024);__array(values, int (void*));
} tail_jmp_table SEC(".maps") = {.values = {// 这里的id是可以在用户态通过map update来更新的。由此可以延伸出其他有意思的功能。这里从实际需求直接固定值了。[135] = (void*)&uretprobe_bash_readline_odd,[146] = (void*)&uretprobe_bash_readline_even,},
};SEC("uretprobe/bash_readline")
int uretprobe_bash_readline(struct pt_regs *ctx) {struct event event = {};event.pid = bpf_get_current_pid_tgid();bpf_probe_read(&event.line, sizeof(event.line), (void *)PT_REGS_RC(ctx));event.mark = 3;bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));u8 line_length=0;for(line_length=0; line_length<80; line_length++){if(event.line[line_length] == 0){break;}}if (line_length % 2 == 0){// 偶数调用 uretprobe_bash_readline_evenbpf_tail_call(ctx, &tail_jmp_table, 146);}else{// 奇数调用 uretprobe_bash_readline_oddbpf_tail_call(ctx, &tail_jmp_table, 135);}return 0;
}

以上。周末愉快~

三、参考文章

[1] BPF Architecture
[2] bpf-helpers
[3] 在 ebpf/libbpf 程序中使用尾调用(tail calls)
[4] kernel version

http://www.jsqmd.com/news/417096/

相关文章:

  • 视频创作者最易踩的5个版权“深坑”,你中了几个
  • ebpf 采集ebpf 采集tag+tcp五元组 - liyan
  • 正则替换拷贝
  • emacs-若干语言 lsp 配置备注 - liyan
  • Linux sed 命令
  • 【面板数据】更新-省级产业结构高级化及合理化数据-含代码(2000-2024年)
  • AgentRun 实践指南:Agent 的宝藏工具——All-In-One Sandbox
  • Emacs 字符操作快捷键 - liyan
  • 全国艺术留学推荐,看看满足条件后哪个学校和中介通过率更高 - mypinpai
  • win10 安装ffmpeg
  • 浙江杭泰产品质量与种类情况,在多地服务的费用贵吗 - 工业推荐榜
  • Gemini 3.1 Flash Image (Nano Banana 2) API 评测:从参数到落地,我替你踩了坑 - 147API
  • 2026年化工生产用氨水采购指南:脱硫/电子级/食品级氨水专业供应商推荐 - 品牌推荐官
  • 岱宇国际在上海的口碑排名,看看其技术实力、品牌知名度和用户体验 - myqiye
  • 分析2026年宁德性价比高的全屋定制,生产厂合作案例多的排名 - 工业品牌热点
  • Rust枚举OptionT
  • 2026年GEO营销风向标:国内领先的GEO整合营销服务商排名及TOP 3选型指南 - 资讯焦点
  • 2026年最新喷胶厂商实力排行榜:基于环保性能与市场口碑的五大公司权威推荐榜单 - 十大品牌榜
  • 2026年全国聚丙烯纤维厂家权威榜单 靠谱优质实力强 抗裂增强适配多工程场景 - 深度智识库
  • 暑期亲子草原游,呼和浩特哪家旅行社有牧民体验?手把手教你选对呼和浩特亲子草原游,3步识别真动手、真牧户、真安全 - 资讯焦点
  • project管理工具哪个好?2026年project管理工具推荐与排名,解决定制化与安全痛点 - 十大品牌推荐
  • 2026实验室排风厂家五大推荐:迅领实验室领衔,打造安全高效实验环境 - 深度智识库
  • js--28
  • project管理软件哪个好?2026年project管理软件推荐与排名,解决复杂项目与效能度量核心痛点 - 十大品牌推荐
  • 计算机毕业设计springboot高校学生社团管理系统 基于SpringBoot框架的大学生社团活动管理平台设计与实现 高校学生组织数字化运营系统——以社团管理为核心的信息化解决方案
  • 2026清洁度分析仪源头厂家最新排名,西恩士这些企业值得关注 - 工业干货社
  • 聚焦2026国内诚信的间歇式智适应动力模块生产商,压力有关型动力模块/分布式动力模块,间歇式智适应动力模块供应商推荐 - 品牌推荐师
  • 2026年2月广信区门窗店推荐,门窗隔热条材质优劣解析 - 品牌鉴赏师
  • FastAPI + Ollama 实战:搭一个能查天气的AI助手
  • 跨组织协同如何选型?2026年project管理工具推荐与评价,聚焦集成与扩展痛点 - 十大品牌推荐