首页 > 编程语言 >AOSP平台编写Android-ebpf程序(tracepoint)的一些map定义和使用问题,导致map和prog无法产生的原因。

AOSP平台编写Android-ebpf程序(tracepoint)的一些map定义和使用问题,导致map和prog无法产生的原因。

时间:2024-03-19 21:02:52浏览次数:15  
标签:info map bpf ebpf pid AOSP uint64 prev

 前言

本片文章并不主要讲解在AOSP平台ebpf程序的整个编写流程,只是一些的map的定义使用问题,如有需要可查看,aosp平台的整个下载流程,以及简单的程序的编译和如何push到手机运行,这位up是我在ebpf领域探索的领路人,本站ID:LiujiaHuan13,如果有需要up本人后面会考虑写一篇aosp程序书写和编译的具体流程。

背景

由于Android本身的安全性要求对于ebpf程序的限制比较高,一些小的问题都会导致内核态程序无法加载(无法加载对应的map和prog),因此本文总结了一些导致map无法产生的原因。本文主要使用tracepoint进行挂载sched/sched_switch(进程切换函数)的例子来进行说明。

1.map无法产生的原因 

一般来说map无法产生,就是因为map的类型定义的不对的原因,可能是HASH或者ARRAY类型的问题,也有可能是map的key键和value值的问题等等,因此我们将之分为两类并举出一些可能的例子。这里我们选择使用tracepoint定义了五个map用以说明:

DEFINE_BPF_MAP(cpu_id_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(cpu_nextpid_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(cpu_prevpid_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(rb_test_map, HASH, uint64_t, process_info, 2048);
DEFINE_BPF_MAP(comm_name_map, ARRAY, int, names, 1024);

 前三个map都是ARRAY,键为int类型,值为uint64_t类型,分别用来传递cpu_id(cpu号),next_pid(上cpu进程id),prev_pid(下cpu进程id)。

第四个map我们定义为HASH类型,键为uint64_t类型,值是一个process_info结构体,process_info的内容如下:

typedef struct process_info {
	uint32_t tid;
	uint32_t pid; // 进程的PID
    uint64_t cpu_id;
	uint64_t start_t; //进程开始的时间
	uint64_t used_t; //已经使用的CPU时间
	// uint64_t total_t; //每个cpu占用的总时间
	// uint64_t occ; //占用率
	// char comm[TASK_COMM_LEN];
    uint64_t prev_pid;
    uint64_t next_pid;
}process_info ;

第五个map我们定义为ARRAY类型,键值为int和names结构体(用来传递字符串类型char) ,names内容如下:

typedef struct names{
    char name[16];
}names;

 这里的定义都是正确的,程序能够正常运行,产生的map和prog如下:

运行结果如下:

下面我们对map进行一些修改来看看可能导致map无法产生的情况。

1.1map的类型定义错误

我们这里将第四个map(rb_test_map)的类型修改为ARRAY类型:

DEFINE_BPF_MAP(rb_test_map, ARRAY, uint64_t, process_info, 2048);

我们重启手机之后发现只产生了三个map,且prog没有产生:

由于map的产生是根据内核态定义的顺序来的,所以就是到第四个map出现了问题,也就是我们刚刚修改的map,这里的原因暂且不清楚,之前猜测可能是因为大小超出了限制的原因,然而实际上修改为1024也不能产生,然而time_in_state中其实用到了ARRAY来传递结构体的:

现在猜测可能是因为array是连续的数组,而hash是一个散列表可能是索引错误的原因,这里后面在做尝试吧。目前测试的结果,只要是使用结构体作value值的话,且map在内核态进行了lookup操作,那么map使用ARRAY类型就会导致map无法产生,但是如果是只进行了update操作没有lookup的话就可以使用ARRAY类型也可以使用HASH,就比如第五个map用来传递comm的结构体类型。

 我们定义的第五个map是用来传递comm值的,而他实际上也是在使用一个结构体来传递的,这原因具体可以看这篇文章解决Android-ebpf的MAP保存字符串的问题 - 知乎 (zhihu.com)

但我们并没有使用它来进行lookup而只是进行了update,具体代码如下:

        names pidCommon={};
        memcpy(pidCommon.name, args->next_comm, 16);
        bpf_comm_name_map_update_elem(&key, &pidCommon, BPF_ANY);

而他使用HASH或者 ARRAY都可以

前三个单独传递一个int值,或者u64类型的值的map使用ARRAY和HASH类型的map都是可以运行的且运行没有问题,只有结构体这里会出现问题。

1.2key和value使用结构体时的错误

这里的原因,就可能是map的键也使用了结构体的原因,使用AARAY可能无法产生map,而使用HASH后程序无法运行,后面会写一篇将使用结构体传递数据的文章讲到。

2.prog无法产生的原因及Aborted (core dumped)问题

prog无法产生的原因有很多,主要是main函数逻辑错误,或者lookup,update等bpf相关函数使用错误的问题,或是由于程序的加载顺序的原因,map没有成功产生,导致之后的prog肯定也无法产生。

将Aborted (core dumped)和这个放一块将也是因为,出现这个错误的原因主要就是main函数内部逻辑错误或是别的错误的问题,注意这里是指map和prog成功产生,且在用户态正常读取了map和attach了tracepoint的prog之后的情况,如果上面有map和prog报错负值就有可能是用户态的问题了。

2.1没有进行错误处理

这个是最容易出现的问题,因为我们再其他平台编写ebpf程序的安全性要求比较低,可能常常在使用lookup函数查找map时没有加错误处理,而这在Android系统会导致prog直接无法产生,因为Android不允许你在找不到东西的时候程序直接崩掉导致影响到系统本身,所以就需要你手动处理点东西了,也就是说我们只需要在使用lookup函数时加上简单的错误处理就行了。如:

struct process_info *prev_info = bpf_rb_test_map_lookup_elem(&prev_pid);
    if(!prev_info){
        return 0;
    }

而还有我个人认为比较好用的方式,就是加个初始化,这是从系统自带的程序time_in_state学来的方法,time_in_state的源码如下:

time_key_t key = {.uid = uid, .bucket = freq_idx / FREQS_PER_ENTRY};
    tis_val_t* val = bpf_uid_time_in_state_map_lookup_elem(&key);
    if (!val) {
        tis_val_t zero_val = {.ar = {0}};
        bpf_uid_time_in_state_map_update_elem(&key, &zero_val, BPF_NOEXIST);
        val = bpf_uid_time_in_state_map_lookup_elem(&key);
    }
    if (val) val->ar[freq_idx % FREQS_PER_ENTRY] += delta;

我们可以看到,他在从map找不到东西时,就定义了一个空的对象,然后通过update函数使用相同的键值传入到map中,这样就保证了下一次读值时一定有东西(相当于初始化了一个值),因为我们第一次要的数据本身就是空值在其他平台肯呢个系统本身会处理这块,但在Android就需要我们手动来了,同样的我们的代码就可以修改为:

process_info *prev_info = bpf_rb_test_map_lookup_elem(&prev_pid);
    if(!prev_info){
        process_info zero_info = {};
        bpf_rb_test_map_update_elem(&prev_pid, &zero_info, BPF_ANY);
        prev_info = bpf_rb_test_map_lookup_elem(&prev_pid);
    }//这里的错误处理必须要加上!!!
    if(prev_info){
        //这里加上你要处理数据的逻辑,读取新的数据,或是更新等
    }

2.2Aborted (core dumped)

Aborted: 表示程序执行过程中发生了一个致命错误,导致程序被终止。

(core dumped): 表示在程序终止时,系统生成了一个核心转储文件(core dump file)。核心转储文件包含程序崩溃时的内存映像,它可以帮助开发者在发生错误时分析问题。核心转储文件通常命名为 core,可以用于调试程序。

内存错误: 访问无效的内存地址,使用已释放的内存等。

无限递归: 一个无限递归可能导致栈溢出,最终导致程序终止。

除零错误: 在程序中进行除零操作。

使用未初始化的变量: 试图使用未初始化的变量。

系统限制: 操作系统可能对程序的资源使用施加了一些限制,例如堆栈大小、文件描述符等。

上面这是chat给出的原因,这个是我们经常会遇到的问题,就是map和prog都产生成功但是运行用户态程序时,就是无法运行,这里就是经典的用户态程序主函数内部的问题,这里我们有一个要注意的点可能你之前在编写了一个基础并且push到手机成功加载生成了map和prog,而之后你又在这基础上进行了修改,而push到手机后bpfloader没有报错,且map和prog都还在但就是程序无法运行,可能也会报bad file的错误,这里可能就是map本身可能有错但是因为只是手动热加载(bpfloader)之前的正确产生的map和prog没有删除,这里只需要重启一下手机,错误的map和prog就会被删除,根据问题修改就行了。

下面举例两个可能的原因:

2.2.1函数内部原因

我在使用结构体传递数据时之前是这样写的:

因为up之前是主要在bootstrap框架下编写ebpf程序(偏向于正常的linux的ebpf程序的编写),用的是perf_buffer或者ring_buffer来传递数据,并且在那边对于map的理解是需要使用look_up来给读取的数据赋一个内存,使其能在后面传递数据,而在aosp平台下,我们读取数据是直接从map里来读取的,这里可能是因为lookup在这里根本没有必要的原因,因为我们只需要传递数据,所以只需要定义一个空的结构体,将数值全部赋入然后直接使用update更新进map就行了(up后面会写一篇文章来讲在aosp平台用户态和内核态之间的数据传递以及使用结构体来传递数据),如下:

process_info prev_space = {};
		prev_space.cpu_id = cpu_id;
		prev_space.pid = pid;
		prev_space.tid = tid;
		prev_space.used_t = prev_info->used_t;
		bpf_rb_test_map_update_elem(&zero, &prev_space, BPF_ANY);

 这里可能是因为跟上面map一样的问题,AARAY和HASH的使用问题可以上去看看。

2.2.2用户态原因

我们再写用户态读取map的地址时把名字写错了,这个虽然听起来是很简单的错误,但往往是最容易发生的,在刚开始写aosp平台的ebpf程序时极容易发生的问题,up本人就错过好几次,举个例子如我们再sys/fs/bpf下查找我们的map如下:

而我们再写用户态程序时map名字没打对:

多打了一个map,所以运行的结果就是Aborted (core dumped),但是上面如果你写了报错提示就会在map那里得到一个负值,这样我们就很容易发现错误了,所以up建议用户态如下书写。

3.用户态书写建议

const char *tp_prog_path = "/sys/fs/bpf/prog_xintest_tracepoint_sched_sched_switch";
  const char *tp_map_path1 = "/sys/fs/bpf/map_xintest_cpu_id_map";
  const char *tp_map_path2 = "/sys/fs/bpf/map_xintest_cpu_nextpid_map";
  const char *tp_map_path3 = "/sys/fs/bpf/map_xintest_cpu_prevpid_map";
  // const char *tp_map_path4 = "/sys/fs/bpf/map_xintest_rb_test_map";
  const char *tp_map_path5 = "/sys/fs/bpf/map_xintest_comm_name_map";
  // Attach tracepoint and wait for 4 seconds
  int mProgFd = bpf_obj_get(tp_prog_path);
  printf("bpf prog fd:%d\n",mProgFd);
  int mMapFd1 = bpf_obj_get(tp_map_path1);
  printf("bpf map1 fd:%d\n",mMapFd1);
  int mMapFd2 = bpf_obj_get(tp_map_path2);
  printf("bpf map2 fd:%d\n",mMapFd2);
  int mMapFd3 = bpf_obj_get(tp_map_path3);
  printf("bpf map3 fd:%d\n",mMapFd3);
  // int mMapFd4 = bpf_obj_get(tp_map_path4);
  // printf("bpf map4 fd:%d\n",mMapFd4);
  int mMapFd5 = bpf_obj_get(tp_map_path5);
  printf("bpf map5 fd:%d\n",mMapFd5);
  int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
  printf("attach tracepoint ret:%d\n",ret);

最好是将每个map和prog先get到一个int值里,然后在下面紧接着打印这个int值,如果为负值就证明这一步出现了错误,我们就能即使进行对应的处理了。

代码

xintest.c

#include <linux/bpf.h> 
#include <stdbool.h> 
#include <stdint.h> 
#include <bpf_helpers.h>
#include <string.h>
#include <linux/ptrace.h>
#ifndef memcpy
# define memcpy(dest, src, n)   __builtin_memcpy((dest), (src), (n))
#endif

typedef struct names{
    char name[16];
}names;

typedef struct process_info {
	uint32_t tid;
	uint32_t pid; // 进程的PID
    uint64_t cpu_id;
	uint64_t start_t; //进程开始的时间
	uint64_t used_t; //已经使用的CPU时间
	// uint64_t total_t; //每个cpu占用的总时间
	// uint64_t occ; //占用率
	// char comm[TASK_COMM_LEN];
    uint64_t prev_pid;
    uint64_t next_pid;
}process_info ;

struct switch_args {
    unsigned long long ignore;
    char prev_comm[16];
    int prev_pid;
    int prev_prio;
    long long prev_state;
    char next_comm[16];
    int next_pid;
    int next_prio;
};

DEFINE_BPF_MAP(cpu_id_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(cpu_nextpid_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(cpu_prevpid_map, ARRAY, int, uint64_t, 1024);
DEFINE_BPF_MAP(rb_test_map, HASH, uint64_t, process_info, 2048);
DEFINE_BPF_MAP(comm_name_map, ARRAY, int, names, 1024);

DEFINE_BPF_PROG("tracepoint/sched/sched_switch",AID_ROOT,AID_NET_ADMIN,tp_sched_switch)(struct switch_args* args) {
    int key;
    uint64_t used;
    uint64_t prev_pid, next_pid;
	next_pid = args->next_pid;
    prev_pid = args->prev_pid;
    key = bpf_get_smp_processor_id(); 
    process_info *prev_info = bpf_rb_test_map_lookup_elem(&prev_pid);
    if(!prev_info){
        process_info zero_info = {};
        bpf_rb_test_map_update_elem(&prev_pid, &zero_info, BPF_ANY);
        prev_info = bpf_rb_test_map_lookup_elem(&prev_pid);
    }//这里的错误处理必须要加上!!!
    if(prev_info){
        used = bpf_ktime_get_ns();
        prev_info->used_t = used;
        prev_info->cpu_id = key;
        prev_info->next_pid = args->next_pid;
        prev_info->prev_pid = args->prev_pid;
        names pidCommon={};
        memcpy(pidCommon.name, args->next_comm, 16);
        bpf_comm_name_map_update_elem(&key, &pidCommon, BPF_ANY);
        bpf_cpu_id_map_update_elem(&key, &used, BPF_ANY);
        bpf_cpu_nextpid_map_update_elem(&key, &next_pid, BPF_ANY);
        bpf_cpu_prevpid_map_update_elem(&key, &prev_pid, BPF_ANY);
        bpf_rb_test_map_update_elem(&next_pid, prev_info, BPF_ANY);
    }
    return 0; 
}
LICENSE("Apache 2.0");
//LICENSE("GPL");

xintest_cli.cpp 

#include <android-base/macros.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <bpf/BpfMap.h>
#include <bpf/BpfUtils.h>
#include <libbpf_android.h>
#include "libbpf.h"
#define TASK_COMM_LEN 16
//using namespace android::bpf;

typedef struct cpu_info {
	int cpu_id;
	uint64_t process_id;
}cpu_info ;

typedef struct names{
    char name[16];
}names;

typedef struct process_info {
	uint32_t tid;
	uint32_t pid; // 进程的PID
  uint64_t cpu_id;
	uint64_t start_t; //进程开始的时间
	uint64_t used_t; //已经使用的CPU时间
	// uint64_t total_t; //每个cpu占用的总时间
	// uint64_t occ; //占用率
	// char comm[TASK_COMM_LEN];
  uint64_t prev_pid;
  uint64_t next_pid;
}process_info ;



int main()
{
  int cpu_no = 0;
  const char *tp_prog_path = "/sys/fs/bpf/prog_xintest_tracepoint_sched_sched_switch";
  const char *tp_map_path1 = "/sys/fs/bpf/map_xintest_cpu_id_map";
  const char *tp_map_path2 = "/sys/fs/bpf/map_xintest_cpu_nextpid_map";
  const char *tp_map_path3 = "/sys/fs/bpf/map_xintest_cpu_prevpid_map";
  // const char *tp_map_path4 = "/sys/fs/bpf/map_xintest_rb_test_map";
  const char *tp_map_path5 = "/sys/fs/bpf/map_xintest_comm_name_map";
  // Attach tracepoint and wait for 4 seconds
  int mProgFd = bpf_obj_get(tp_prog_path);
  printf("bpf prog fd:%d\n",mProgFd);
  int mMapFd1 = bpf_obj_get(tp_map_path1);
  printf("bpf map1 fd:%d\n",mMapFd1);
  int mMapFd2 = bpf_obj_get(tp_map_path2);
  printf("bpf map2 fd:%d\n",mMapFd2);
  int mMapFd3 = bpf_obj_get(tp_map_path3);
  printf("bpf map3 fd:%d\n",mMapFd3);
  // int mMapFd4 = bpf_obj_get(tp_map_path4);
  // printf("bpf map4 fd:%d\n",mMapFd4);
  int mMapFd5 = bpf_obj_get(tp_map_path5);
  printf("bpf map5 fd:%d\n",mMapFd5);
  int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
  printf("attach tracepoint ret:%d\n",ret);
  sleep(4);
  android::bpf::BpfMap<int, uint64_t> myMap1(tp_map_path1); 
  android::bpf::BpfMap<int, uint64_t> myMap2(tp_map_path2); 
  android::bpf::BpfMap<int, uint64_t> myMap3(tp_map_path3);
  // android::bpf::BpfMap< uint64_t, process_info> myMap4(tp_map_path4);
  android::bpf::BpfMap< int, names> myMap5(tp_map_path5);  
while(1)
{
  usleep(40000);
  process_info md = {};
  md.cpu_id = *myMap1.readValue(0);
  md.next_pid = *myMap2.readValue(0);
  md.prev_pid = *myMap3.readValue(0);
  // process_info data = myMap4.readValue(0).value();
  names pidname = myMap5.readValue(0).value();
  
  printf(" time on CPU %d is %llu,nextpid:%llu,prevpid:%llu,pidname:%s\n", cpu_no, md.cpu_id, md.next_pid, md.prev_pid, pidname.name);
  // printf(" datacpu: %llu,datanextpid: %llu,dataprevpid: %llu\n",  data.cpu_id, data.next_pid, data.prev_pid);
}
return 0;
}

另言

up在后面会写一篇文章来讲在aosp平台用户态和内核态之间的数据传递以及使用结构体来传递数据,里面会大概讲到用户态和内核态文件的基本书写。除此之外运行可能还会遇到badfile的问题,之后遇到了会讲到。

标签:info,map,bpf,ebpf,pid,AOSP,uint64,prev
From: https://blog.csdn.net/m0_68715848/article/details/136821780

相关文章

  • MyBatis3源码深度解析(十六)SqlSession的创建与执行(三)Mapper方法的调用过程
    文章目录前言5.9Mapper方法的调用过程5.10小结前言上一节【MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取】已经知道,调用SqlSession对象的getMapper(Class)方法,传入指定的Mapper接口对应的Class对象,即可获得一个动态......
  • 从 Linux 内核角度探秘 JDK MappedByteBuffer
    本文涉及到的内核源码版本为:5.4,JVM源码为:OpenJDK17,RocketMQ源码版本为:5.1.1在之前的文章《一步一图带你深入剖析JDKNIOByteBuffer在不同字节序下的设计与实现》中,笔者为大家详细剖析了JDKBuffer的整个设计体系,从总体上来讲,JDKNIO为每一种Java基本类型定义了对......
  • react使用map循环渲染dom时,增加或删减数组,但想保持其余的dom与数据不发生改变
     核心思路:dom渲染与key值有关系,如果想实现上述需求,则需要关注改变前后的循环项的key值是否发生改变currentCabinet?.map((item,index)=><BaseInfokey={`currentCabinet${item?.ciId}`}sceneKey={sceneKey}currentCabinet={item}/>)如以上示例,以ciId为key值,可以保证即......
  • golang sync.Map之如何设计一个并发安全的读写分离结构?
    在golang中,想要并发安全的操作map,可以使用sync.Map结构,sync.Map是一个适合读多写少的数据结构,今天我们来看看它的设计思想,来看看为什么说它适合读多写少的场景。如下,是golang中sync.Map的数据结构,其中属性read是只读的map,dirty是负责写入的map,sync.Map中的键值对value......
  • QSignalMapper的使用和使用场景
    QSignalMapper的使用和使用场景 目录QSignalMapper的使用和使用场景常见场景下面是参考。可看可不看这篇写的不错,搬运为Markdown了可以看一下参考 QSignalMapper的使用和使用场景QSignalMapper类收集了一系列的无参信号,然后使用相对于信号发送者来说的整数......
  • Semaphore源码解析
    Semaphorehttps://www.bilibili.com/video/BV1Ae411C7xr/publicclassSemaphoreimplementsjava.io.Serializable同Reetrantlock在Sync继承AQSabstractstaticclassSyncextendsAbstractQueuedSynchronizer可以指定Sync是否是公平锁,默认非公平permits为设置AQS内stat......
  • Go04-数组+切片+map
    Go4-数组+切片+map1.数组的定义、赋值、访问和遍历//1数组用来存放多个同一类型的数据,在Go中数组是值类型。//2数组的定义、赋值和遍历。//定义数字。vararrs[2]int//给数组元素赋值。arrs[0]=0arrs[1]=2//02fori:=0;i<len(arrs);i++{fmt.P......
  • 【STL】 C++常用容器介绍系列(一)----(map、set、stack)
    目录一、map系列1、map介绍2、unordered_map介绍3、map和unordered_map的选择二、set系列1、set介绍2、unordered_set介绍3、set和unordered_set的选择三、如何遍历和查询map和set1、map的遍历2、map的查询3、set的遍历4、set的查询四、stack介绍和操作stack的方......
  • 实现 React-redux(一) connect 和 mapStateToProps
    1.结合context和storeimportReact,{Component}from'react';importPropTypesfrom'prop-types'functioncreateStore(reducer){letstate=nullconstlisteners=[]constsubscribe=(listener)=>listeners.push(listener)con......
  • 实现 React-redux(二) mapDispatchToProps
     App.js:importReact,{Component}from'react';importPropTypesfrom'prop-types'importHeaderfrom'./Header'functioncreateStore(reducer){letstate=nullconstlisteners=[]constsubscribe=(listener)=>l......