文章目录
- eBPF摸着石头过河之一行代码8个报错
- 0x01.call unknown#179/invalid func unknown#179/invalid func unknown#179
- 0x02 R0 invalid mem access 'map_value_or_null'/libbpf: prog 'handle_tp_enter_kill': failed to load: -13
- 0x03 infinite loop detected at insn 85/libbpf: prog 'handle_tp_enter_kill': failed to load: -22
- 0x04 invalid indirect access to stack R2 off=-4 size=8/libbpf: prog 'handle_tp_enter_kill': failed to load: -13
- 0x05 Validating my_function() func#1.../Global function my_function() doesn't return scalar. Only those are supported.
eBPF摸着石头过河之一行代码8个报错
最近在进行eBPF的学习,学习过程中难免一步一个深渊,为了给大家一些排错参考,留下点笔记,权当抛砖引玉。
本文可能根据情况适时更新。
如果你也遇到一些棘手问题欢迎一块交流。
个人开发系统为Ubuntu22.04,内核版本Linux 5.15。
文章中不只是给出解决办法(没有解决办法的会给出原因)包含报错信息、报错现场代码以及分析和解决过程。
因为大家代码不同,给出这些信息时希望朋友们在遇到相似报错的情况下,能有自己解决问题的能力或者提供一个解决思路。网上很多文章在解决代码报错时都是只给出解决方法,但是没有分析方法,这样不利于成长。授人以鱼不如授人以渔。
0x01.call unknown#179/invalid func unknown#179/invalid func unknown#179
背景介绍
在使用libpf进行eBPF开发过程中,程序编译通过,但是加载却出错。
出错代码如下:
u64 address = 0;
bpf_kallsyms_lookup_name("selinux_xfrm_state_free", strlen("selinux_xfrm_state_free") + 1, 0, &address);
本意是想通过该函数获取内核函数selinux_xfrm_state_free
的地址,结果eBPF程序却报错了。
报错信息
19: (85) call unknown#179
invalid func unknown#179
processed 19 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1
-- END PROG LOAD LOG --
libbpf: prog 'do_unlinkat': failed to load: -22
libbpf: failed to load object 'kprobe_bpf'
libbpf: failed to load BPF skeleton 'kprobe_bpf': -22
原因分析
其中,179是libbpf中help function bpf_kallsyms_lookup_name
的序号。
可以看一下其声明
/*
* bpf_kallsyms_lookup_name
*
* Get the address of a kernel symbol, returned in *res*. *res* is
* set to 0 if the symbol is not found.
*
* Returns
* On success, zero. On error, a negative value.
*
* **-EINVAL** if *flags* is not zero.
*
* **-EINVAL** if string *name* is not the same size as *name_sz*.
*
* **-ENOENT** if symbol is not found.
*
* **-EPERM** if caller does not have permission to obtain kernel address.
*/
static long (*bpf_kallsyms_lookup_name)(const char *name, int name_sz, int flags, __u64
*res) = (void *) 179;
根据报错提示信息,是eBPF在加载过程中不认识这个函数,于是到/proc/kallsyms
文件里看一眼。
# cat /proc/kallsyms |grep .*kallsyms_lookup_name
ffffffffb3f8b5e0 T module_kallsyms_lookup_name
ffffffffb3f8bfd0 T kallsyms_lookup_name
果然没有!!!于是我就慌了~~
如果我的开发环境没有该函数,意味着如果要使用该函数,就只能替换新内核了。
然后查了查Linux各个版本内核源码(该函数被定义在kernel/bpf/syscall.c
文件里)。
结果这个函数是在Linux 5.16的版本定义的,而我的是Linux 5.15,我勒个大去~~
以下为该函数在Linux5.16中的实现
BPF_CALL_4(bpf_kallsyms_lookup_name, const char *, name, int, name_sz, int, flags, u64 *, res){
if (flags)
return -EINVAL;
if (name_sz <= 1 || name[name_sz - 1])
return -EINVAL;
if (!bpf_dump_raw_ok(current_cred()))
return -EPERM;
*res = kallsyms_lookup_name(name);
return *res ? 0 : -ENOENT;
}
MMP!这么方便且有用又nb的函数为啥实现得这么晚!!!
解决办法
- 升级内核到Linux5.16或以上;
- 通过应用层读取
/proc/kallsyms
文件来获取符号地址给到内核eBPF; - 编写LKM,然后导出同名函数;
0x02 R0 invalid mem access ‘map_value_or_null’/libbpf: prog ‘handle_tp_enter_kill’: failed to load: -13
报错信息
; xx = bpf_map_lookup_elem(&aa, &i);
64: (18) r1 = 0xffff8dc8e5366400
66: (85) call bpf_map_lookup_elem#1
; xx = bpf_map_lookup_elem(&aa, &i);
67: (18) r1 = 0xffff9addc0568000
69: (7b) *(u64 *)(r1 +0) = r0
R0=map_value_or_null(id=5,off=0,ks=8,vs=72,imm=0) R1_w=map_value(id=0,off=0,ks=4,vs=8,imm=0) R6=inv1111 R7=invP0 R8=inv1 R9=inv(id=0,smax_value=9223372032559808512,umax_value=18446744069414584320,var_off=(0x0; 0xffffffff00000000),s32_min_value=0,s32_max_value=0,u32_max_value=0) R10=fp0 fp-8=mmmmmmmm fp-16=mmmm?mmm fp-24=mmmmmmmm fp-32=mmmmmmmm fp-40=mmmmmmmm
70: (b7) r1 = 686134
; bpf_printk("%-18s:addr:0x%-16x\n", xx->name, xx->addr);
71: (63) *(u32 *)(r10 -24) = r1
72: (18) r1 = 0x312d2578303a7264
74: (7b) *(u64 *)(r10 -32) = r1
75: (18) r1 = 0x64613a7338312d25
77: (7b) *(u64 *)(r10 -40) = r1
78: (79) r4 = *(u64 *)(r0 +64)
R0 invalid mem access 'map_value_or_null'
processed 69 insns (limit 1000000) max_states_per_insn 0 total_states 4 peak_states 4 mark_read 2
-- END PROG LOAD LOG --
libbpf: prog 'handle_tp_enter_kill': failed to load: -13
libbpf: failed to load object 'inline_check_bpf'
libbpf: failed to load BPF skeleton 'inline_check_bpf': -13
报错代码行
xx = bpf_map_lookup_elem(&aa, &i);
bpf_printk("%-18s:addr:0x%-16x\n", xx->name, xx->addr);
原因分析
根据提示,在源码bpf_printk("%-18s:addr:0x%-16x\n", xx->name, xx->addr);
这一行,可能会出现’map_value_or_null’。这里面要打印的也就是xx指针指向的内容。
所以推测这里是校验器发现我们的代码里没有进行空指针检查。
代码修复
加个判断就OK了。
xx = bpf_map_lookup_elem(&aa, &i);
if (xx)
bpf_printk("%-18s:addr:0x%-16x\n", xx->name, xx->addr);
0x03 infinite loop detected at insn 85/libbpf: prog ‘handle_tp_enter_kill’: failed to load: -22
报错信息
82: (b7) r2 = 20
83: (bf) r3 = r0
84: (85) call bpf_trace_printk#6
last_idx 84 first_idx 73
regs=4 stack=0 before 83: (bf) r3 = r0
regs=4 stack=0 before 82: (b7) r2 = 20
; for (; j < 3; ++j)
infinite loop detected at insn 85
processed 99 insns (limit 1000000) max_states_per_insn 1 total_states 6 peak_states 6 mark_read 2
-- END PROG LOAD LOG --
libbpf: prog 'handle_tp_enter_kill': failed to load: -22
libbpf: failed to load object 'inline_check_bpf'
libbpf: failed to load BPF skeleton 'inline_check_bpf': -22
报错代码
int j = 0;
for (; j < 3; ++j) // 报错指向这里
{
xx = bpf_map_lookup_elem(&aa, &j);
if (xx)
bpf_printk("%-18s:addr:0x%-16x\n", xx->name, xx->addr);
else
bpf_printk("xx = NULL\n");
}
分析
意思是检测了无限循环(infinite loop detected),但我这里明明只循环3次。
通过查阅资料发现eBPF不支持循环,如果一定要是要使用参考这篇文章:《[eBPF的使用限制 | REXROCK](https://rexrock.github.io/post/ebpf1/#:~:text=eBPF程序中不能使用循环语句,如果非要使用循环,则必须通过编译选项" %23pragma clang,loop unroll (full) "让编译器在编译过程中将循环展开。)》
我摘抄一些
eBPF程序中不能使用循环语句,如果非要使用循环,则必须通过编译选项“#pragma clang loop unroll(full)”让编译器在编译过程中将循环展开。
此外必须要注意的一点是,循环中的语句必须是单一且独立的块,如下:
static __always_inline int search_service_ip(int i, __u32 ip) {
...
return 0;
}
static __always_inline int is_service_ip(__u32 ip) {
#pragma clang loop unroll(full)
for(int i = 0; i < 32; i++) {
switch(search_service_ip(i, ip)) {
case 0:
continue;
case 1:
return 1;
default:
break;
}
}
return 0;
}
解决方法
没有什么好的解决办法。
循环次数不多的情况下,就手动展开。我尝试过用goto语句代替循环,行不通(没试过用递归代替循环)。
或者你的代码满足“单一且独立的块”,则可以通过内联的方式让编译器展开。
如果你有什么好的方法,还请留言~~~
0x04 invalid indirect access to stack R2 off=-4 size=8/libbpf: prog ‘handle_tp_enter_kill’: failed to load: -13
报错信息
; xx = bpf_map_lookup_elem(&aa, &i);
63: (18) r1 = 0xffff8dc8cbbc9400
65: (85) call bpf_map_lookup_elem#1
invalid indirect access to stack R2 off=-4 size=8
processed 59 insns (limit 1000000) max_states_per_insn 0 total_states 3 peak_states 3 mark_read 2
-- END PROG LOAD LOG --
libbpf: prog 'handle_tp_enter_kill': failed to load: -13
libbpf: failed to load object 'inline_check_bpf'
libbpf: failed to load BPF skeleton 'inline_check_bpf': -13
Failed to open and load BPF skeleton
### 报错原因以及解决
对map变量的访问错误。
可能的原因:
- map变量定义的
max_entries
过小; - map变量的索引值类型不对,比如:
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
,,,,
__type(key, unsigned long);
__type(value, xx);
} my_pid_map SEC(".maps");
int key = 0; // 此处变量key类型错误 正确为 unsigned long key = 0;
bpf_map_lookup_elem(&aa, &key);
0x05 Validating my_function() func#1…/Global function my_function() doesn’t return scalar. Only those are supported.
报错信息
Validating my_function() func#1...
Global function my_function() doesn't return scalar. Only those are supported.
processed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'handle_tp_enter_kill': failed to load: -22
分析与解决
emmmm… 貌似是自定义的全局函数(我这里为my_function)没有相应的return语句,或者return类型不对。