记一次ebpf的for循环问题

记一次ebpf的for循环问题

起因

一段BCC开发的uprobe代码中包含一个for循环,在redhat 4.18上可以运行,在redhat 3.10上无法正常运行。
在kernel verifier部分检测出存在for循环。
Code:

1
2
3
4
5
6
7
8
9
10
11
for(u64 i = 0; i < (MAX_STACK_SIZE - 1); i++){
if(i >= stack_size) break;
lock = GET_LOCK_PTR_FROM_STATS_PTR_DEADLOCK(start_ptr);
wait_lock = GET_WAIT_LOCK_PTR_FROM_STATS_PTR_DEADLOCK(start_ptr);
get_dead_lock_data_from_lock_ptr(lock,&s_stats_item.lock);
get_dead_lock_data_from_lock_ptr(wait_lock,&s_stats_item.wait_lock);
bpf_probe_read_user(&s_stats_item.heap_no,sizeof(u64),GET_HEAP_NO_PTR_FROM_STATS_PTR_DEADLOCK(start_ptr));
dead_lock_checker_stack.update(&end,&s_stats_item);
end = (end + 1)%BPF_HASH_NORMAL;
start_ptr += sizeof(COM_STATS_T_PTR);
}
1
back-edge from insn 1024 to 1003 

原因分析

为什么在4.18上可以运行,但在3.10上无法运行?

高版本kernel中允许ebpf代码中存在有限for循环,而3.10中不允许任何循环的存在

该如何解决3.10无法运行问题?

让for循环在编译阶段展开

尝试使用 #pragma unroll 以及 ebpf::BPF init添加 编译参数-funroll-loops均未解决问题

尝试从BCC中获取更多的信息,debug模式重编BCC并安装 (非源码安装可以直接安装debug-info)

再次运行包含for循环的代码,发现报错如下:

1
loop not unrolled: the optimizer was unable to perform the requested transformation; the transformation might be disabled or specified as part of an unsupported transformation ordering

无论是通过添加伪编译指令 #pragma unroll 以及 增加编译参数 -funroll-loops 实际上并没有展开循环.

实际上,上述两种方式均是建议clang编译器进行编译优化展开循环,但由于展开后的字节码大小过大,编译器拒绝了我的展开建议.

解决方案

使用#pragma unroll(16)指定clang展开for循环的次数

建议

  • for循环在编译阶段就要确定是有限for循环
  • 循环体内代码尽量精简(避免声明局部变量等),减少单次循环的指令数量

记一次ebpf的for循环问题
https://zongjiangu.github.io/2023/03/13/记一次ebpf的for循环问题/
作者
zongjiangU
发布于
2023年3月13日
许可协议