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更加容易。

1.问题产生(字符串存放)

基于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来拷贝所有字节。

2.遇到的新问题(查询map)

我仿照之前用户态程序*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编程经验和模板元编程经验,只能给出这样的答案了(后面再继续研究吧)

3.效果

8ce75e8832e27622e2cc8e5ecc21fa2b.jpeg

4.更改后的代码

  • bpf_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"

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);
}
  • bpf_test.c
#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");