dg-publish: "true"
0.关于环境:
环境:Android T, libbpf_android, libbpf, libbpf_bcc
libbpf_android:这个库是专门为Android平台开发的eBPF用户态库,它提供了一系列工具和API,使得在Android上进行eBPF程序的编译、加载和执行变得非常容易。此外,该库还提供了针对Android特定问题的eBPF示例程序和文档。
libbpf:这个库是一个通用的eBPF库,它可以在Linux内核版本4.1及以上的任何系统上使用。它提供了一些API和工具用于编译、加载和管理eBPF程序。此外,它还支持BCC (BPF Compiler Collection) 工具集,用于生成高级eBPF程序。
libbpf_bcc:这个库是基于libbpf和BCC的,它允许用户更方便地编写和运行高级eBPF程序。BCC是一个基于Python的eBPF程序开发工具集,它包括一组高级的eBPF程序示例、Python API以及一些工具来编译、运行和调试eBPF程序。而libbpf_bcc则提供了C++接口,使得在C++应用程序中使用BCC更加容易。
在基于AOSP+WSL2+platform-tools搭建安卓开发ebpf环境 - JiaHuann's Blog.中,我们用到了一个简单的ebpf样例程序,只建立了一些简单的MAP去导出switch_args中的一些简单数据。
那么自然而然的我们想要保存struct switch_args
中的next_common[16]
这个结构体,因为我们不仅需要知道下一个pid是啥,我们更想知道pid对应的进程名字,最起码阅读性更强对吧?
那么问题来了,我在尝试建立map为char [16]
的map时
DEFINE_BPF_MAP(cpu_pidname_map, ARRAY,sizeof(char[16]), names, 1024);
总是会出现宏展开错误,具体需要研究bpf_helper.h
中的实现,挺复杂的似乎
bpf_helper.h
该文件通常被用于与libbpf
库结合使用,从而实现用户空间程序和内核空间程序之间的交互。
很纳闷,由于我阅读源码能力(macro)有限,于是我换了个思路(后面一定会返回来研究的):
typedef struct names{
char name[16];
}names;
DEFINE_BPF_MAP(cpu_pidname_map, ARRAY, int, names, 1024);
DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_NET_ADMIN, tp_sched_switch) (struct switch_args* args) {
names pidCommon={};
memcpy(pidCommon.name, args->next_comm, 16);
// pidCommon.name[15] = '\n';
key = bpf_get_smp_processor_id();
bpf_cpu_pidname_map_update_elem(&key, &pidCommon, BPF_ANY);
return 0;
}
这样就把next_comm成功的更新到map里了,这里使用memcpy来拷贝所有字节。
我仿照之前用户态程序
*myMap1.readValue(0)
去获得结构体,发现无论如何都拿不到,经过大量资料查找也没有解决问题,于是我只能选择看源码。
我们在用户态程序不是每次都要用android::bpf::BpfMap<int, names> myMap2(cpu_pidname_path);
于是在libbpf_android中的BpfMap.h
中找到定义,并且发现BpfMap
对象的readValue
方法
base::Result<Value> readValue(const Key key) const {
Value value;
if (findMapEntry(mMapFd, &key, &value)) {
return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
}
return value;
}
返回的是一个base::Result<Value>
对象,定位base::Result的定义:android_base/result.h
看到using Result = android::base::expected<T, ResultError<E, include_message>>
再次定位expected定义android_base/expected.h
看到最终expected对象定义
class _NODISCARD_ expected {
public:
using value_type = T;
using error_type = E;
using unexpected_type = unexpected<E>;
template<class U>
using rebind = expected<U, error_type>;
findMapEntry(mMapFd, &key, &value)
的实现被包裹在syscall中frameworks/libs/net/common/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) {
return bpf(BPF_MAP_LOOKUP_ELEM, {
.map_fd = BPF_FD_TO_U32(map_fd),
.key = ptr_to_u64(key),
.value = ptr_to_u64(value),
});
}
这里会来到标准的bpf调用,然后会把查找到的value地址传给调用,内核会根据地址写我们user态的value内容。
*myMap0.readValue(0)
实际上是将base::Result<value>
类型对象解引用为(隐式转换)简单类型。
需要注意的是,如果base::Result对象中包含有错误信息,则解引用可能会导致未定义行为或抛出异常。因此,在使用解引用运算符之前,最好先检查base::Result对象的状态,以确保它已成功读取所需的值。
但是一旦value是结构体就不能简单的解引用(隐式转换)获取普通类型,所以我们需要用到对象的.value()
方法去获得到Value value本身,这个时候就可以
names pidCommon = myMap2.readValue(0).value();
至于为什么不能用·*myMap0.readValue(0)
直接获取结构体本身,由于我个人没有太多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"
typedef struct names{
char name[16];
}names;
typedef 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;
}switch_t;
int main() {
// Attach tracepoint and wait for 4 seconds
constexpr const char tp_prog_path[] = "/sys/fs/bpf/prog_bpf_test1_tracepoint_sched_sched_switch";
constexpr const char cpu_pid_path[] = "/sys/fs/bpf/map_bpf_test1_cpu_pid_map";
constexpr const char cpu_pidprio_path[] = "/sys/fs/bpf/map_bpf_test1_cpu_pidprio_map";
constexpr const char cpu_pidname_path[] = "/sys/fs/bpf/map_bpf_test1_cpu_pidname_map";
//int tp_cpuFreqFd = bpf_obj_get(tp_prog_power_cpu_frequency);
int mProgFd = bpf_obj_get(tp_prog_path);
bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
sleep(1);
android::bpf::BpfMap<int, int> myMap0(cpu_pid_path);
android::bpf::BpfMap<int, int> myMap1(cpu_pidprio_path);
android::bpf::BpfMap<int, names> myMap2(cpu_pidname_path);
while(1) {
usleep(40000);
names pidCommon = myMap2.readValue(0).value();
// Read the map to find the last PID that ran on CPU 0
// android::bpf::BpfMap<int, int> myMap(mMapFd);
printf("last PID running on CPU: %d is %d\n", 0, *myMap0.readValue(0));
printf("next pid's prio on %d is: %d\n", 0, *myMap1.readValue(0));
printf("next pid's name is %d is: %s\n", 0, pidCommon.name);
}
exit(0);
}
#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>
#ifndef memcpy
# define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n))
#endif
typedef struct names{
char name[16];
}names;
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;
};
//定义map,这里的定义会影响后面函数的名字,函数名字需要和map是对应的。
DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);
DEFINE_BPF_MAP(cpu_pidprio_map, ARRAY, int, uint32_t, 1024);
DEFINE_BPF_MAP(cpu_pidname_map, ARRAY, int, names, 1024);
// SEC("tracepoint/sched/sched_switch")
DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_NET_ADMIN, tp_sched_switch) (struct switch_args* args) {
// int tp_sched_switch(struct switch_args* args) {
int key;
uint32_t val;
uint32_t nextPrio;
names pidCommon={};
memcpy(pidCommon.name, args->next_comm, 16);
// pidCommon.name[15] = '\n';
key = bpf_get_smp_processor_id();
val = args->next_pid;
nextPrio = args->next_prio;
bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
bpf_cpu_pidprio_map_update_elem(&key, &nextPrio, BPF_ANY);
bpf_cpu_pidname_map_update_elem(&key, &pidCommon, BPF_ANY);
return 0;
}
// char _license[] SEC("license") = "GPL";
LICENSE("Apache 2.0");