LibBpfLoader: Failed to create maps: (ret=-38)

Bpfloader 失败时的log,当打出此log时,kernel会反复重启:

 [   85.639342] logd: logdr: UID=2000 GID=2000 PID=228 b tail=0 logMask=99 pid=0s
04-15 07:57:02.099     0     0 I SELinux : Permission bpf in class capability2 not defined in policy.
04-15 07:57:02.116     0     0 I SELinux : Permission bpf in class cap2_userns not defined in policy.
04-15 07:57:07.262   212   212 W bpfloader: Failed to create directory: /sys/fs/bpf/tethering/, ret: No such file or directory
04-15 07:57:07.269   212   212 D LibBpfLoader: Loading critical for tethering ELF object /apex/com.android.tethering/etc/bpf/offload.o with license Apache 2.0
04-15 07:57:07.269   212   212 I LibBpfLoader: Section bpfloader_min_ver value is 0 [0x0]
04-15 07:57:07.269   212   212 I LibBpfLoader: Section bpfloader_max_ver value is 65536 [0x10000]
04-15 07:57:07.269   212   212 I LibBpfLoader: Section size_of_bpf_map_def value is 48 [0x30]
04-15 07:57:07.269   212   212 I LibBpfLoader: Section size_of_bpf_prog_def valu e is 28 [0x1c]
04-15 07:57:07.269   212   212 I LibBpfLoader: BpfLoader version 0x00002 processing ELF object /apex/com.android.tethering/etc/bpf/offload.o with ver [0x00000,0x10000)
04-15 07:57:07.358   212   212 D LibBpfLoader: bpf_create_map name tether_error_map, ret: -1
04-15 07:57:07.358   212   212 E LibBpfLoader: Failed to create maps: (ret=-38) in /apex/com.android.tethering/etc/bpf/offload.o

报错原因分析

分析上面的log,可以大致从上述log看出,应该有一个应用程序 LibBpfLoader,它会负责加载sys/fs/bpf路径下面的bpf文件,每个bpf文件是xxx.o格式,实质上是一个ELF格式
而在android 12中,LibBpfLoader对应的源代码实现,位于如下目录:android/system/bpf/bpfloader/ BpfLoader.cpp, 它里面有一个main()函数入口,总体流程如下:
main() // android/system/bpf/bpfloader/ BpfLoader.cpp
  -》loadAllElfObjects(location.dir, location.prefix) b //加载指定路径 location.dir 下面的 xxx.o 文件,每个 xxx.o 文件是一个ELF格式的bpf文件
    -》opendir(progDir)
    -》readdir(dir)
      -》android::bpf::loadProg(progPath.c_str(), &critical, prefix) //加载这个xxx.o 文件
      -》readSectionUint(“bpfloader_min_ver”, elfFile, DEFAULT_BPFLOADER_MIN_VER); //读取一个ELF文件的bpfloader_min_ver 版本号
      -》readSectionUint(“bpfloader_max_ver”, elfFile, DEFAULT_BPFLOADER_MAX_VER); //读取一个ELF文件的 bpfloader_max_ver 版本号
      -》readSectionUint(“size_of_bpf_map_def”, elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF); //读取一个ELF文件的size_of_bpf_map_def 段
      -》readSectionUint(“size_of_bpf_prog_def”, elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF); //读取一个ELF文件的size_of_bpf_prog_def 端
      -》readCodeSections(elfFile, cs, sizeOfBpfProgDef)
      -》createMaps(elfPath, elfFile, mapFds, prefix, sizeOfBpfMapDef) //这个函数里面,将会出错
        -》readSectionByName(“maps”, elfFile, mdData)
        -》getSectionSymNames(elfFile, “maps”, mapNames)
        -》bpf_create_map(type, mapNames[i].c_str(), md[i].key_size, md[i].value_size, md[i].max_entries, md[i].map_flags)
         -》syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); //通过系统调用号 __NR_bpf调用到bpf相关的syscall,因为没有找到对应的syscall,所以返回 -38
        -》ALOGD(“bpf_create_map name %s, ret: %d\n”, mapNames[i].c_str(), fd.get()); //看上面的log,这里的打印,是能对应上的
      -》ALOGE(“Failed to create maps: (ret=%d) in %s\n”, ret, elfPath); // ret = -38,而-38在kernel中对应的对应错误号如下,就是 Invalid system call number 无效的syscall number

/*
 * This error code is special: arch syscall entry code will return
 * -ENOSYS if users try to call a syscall that doesn't exist.  To keep
 * failures of syscalls that really do exist distinguishable from
 * failures due to attempts to use a nonexistent syscall, syscall
 * implementations should refrain from returning -ENOSYS.
 */
#define	      ENOSYS		38	//无效的syscall number

分析到这里,再结合上面出错的log,基本可以判断是因为LibBpfLoader调用到bpf_create_map时,bpf_create_map又调用syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)),而 __NR_bpf 在kernel中并没有实现,所以,返回错误 -38, 即无效的syscall number

#define __NR_bpf 280 

__SYSCALL(__NR_bpf, sys_bpf)

接下来,验证这个猜测,首先看下linux中syscall的调用流程如下:
do_el0_svc(struct pt_regs *regs)
 -》el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table);
 -》invoke_syscall(regs, scno, sc_nr, syscall_table);

static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
			   unsigned int sc_nr,
			   const syscall_fn_t syscall_table[])
{
	long ret;

	add_random_kstack_offset();

	if (scno < sc_nr) { 
		syscall_fn_t syscall_fn;
		syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
		ret = __invoke_syscall(regs, syscall_fn);
	} else {
		ret = do_ni_syscall(regs, scno);
	}

if (ret == -38) {
		printk("=>[kinzho01]---[%-32s]---[%-5d] syscall number is :%d,error = -38 \r\n", __FUNCTION__, __LINE__, scno);  //这里加一句打印,当系统调用返回 -38 时,打出当前的调用号,如果刚好就是 280,证明出错的就是 BPF 的syscall numer
	}

	syscall_set_return_value(current, regs, 0, ret);  //当调用一个没有实现的NR syscall时,将会返回-NOSYS, 也就是 -38
}

/*
 * Non-implemented system calls get redirected here.
 */
asmlinkage long sys_ni_syscall(void)
{
	return -ENOSYS;  //返回 -38
}

上机测试后,发现确实打印出如下这句话,表明 返回出错号 -38 时,调用号是 280,而 280 刚好就是 BPF 的syscall number, 因为kernel中有如下定义:
04-15 09:37:23.332 0 0 W : =>[kinzho01]—[invoke_syscall ]—[58 ] syscall number is :280, error = -38 //280刚好就是 __NR_bpf,对应上了,#define __NR_bpf 280

结论

要想让 Android里面的LibBpfLoader 能够进行基本的初始化不报错,至少需要打开如下2个配置:

  • CONFIG_BPF=y //在kernel/Makefile中: obj-$(CONFIG_BPF) += bpf/
    ,可以看到,当这个配置打开时,kernel才会编译 bpf/目录
  • CONFIG_BPF_SYSCALL=y // 在kernel/bpf/Makefile,
    obj-$(CONFIG_BPF_SYSCALL) += syscall.o,可以看到,当这个配置打开时,kernel才会编译
    bpf/syscall.c
  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值