eBPF摸着石头过河之一行代码8个报错

本文记录了在学习eBPF过程中遇到的八个错误,包括callunknown#179、R0invalidmemaccess、infiniteloopdetected、invalidindirectaccesstostack和函数返回类型错误等问题。错误涉及内核函数查找、内存访问、循环限制等,每个错误都提供了原因分析和解决策略,如升级内核、添加空指针检查、避免循环等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的函数为啥实现得这么晚!!!

解决办法

  1. 升级内核到Linux5.16或以上;
  2. 通过应用层读取/proc/kallsyms文件来获取符号地址给到内核eBPF;
  3. 编写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变量的访问错误。

可能的原因:

  1. map变量定义的max_entries过小;
  2. 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类型不对。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值