Principles of using eBPF in Android and examples of kprobe dmabuf_setup

Table of Contents

eBPF in Android

Android eBPF kprobe dma code

Define a MAP

Define a PROG

bpfprogs/Android.bp

test program

bpfprogs/memstats/Android.bp

bpfprogs/memstats/MemStats.h

bpfprogs/memstats/MemStats.cpp

bpfprogs/memstats/MemStatsMain.cpp

Compile and run

Result analysis

summary


eBPF in Android

Official website: https://source.android.com/docs/core/architecture/kernel/bpf?hl=zh-cn#tracepoints

The official Android website has a more detailed introduction. For official examples of using eBPF in Android, you can refer to the above link.

Android eBPF kprobe dma code

In the source directory /system/bpfprogs, we can develop our own eBPF program.

In the system/bpfprogs source code directory, create a new memStats.c sample program. There are several key points in the sub-program. I will explain them one by one below.

memStats.c

#include <bpf_helpers.h>
#include <sys/types.h>


struct pt_regs {
    unsigned long long regs[31];
    unsigned long long sp;
    unsigned long long pc;
    unsigned long long pstate;
};

#define DMABUF_MAP_SIZE 4096

struct dmabuf_info {
    unsigned long pid;
    uint64_t inode;
    char comm[16];
    uint64_t size;
};

DEFINE_BPF_MAP_GRW(dmabuf_mem_map, HASH, uint64_t, struct dmabuf_info, DMABUF_MAP_SIZE,
                   AID_SYSTEM);

DEFINE_BPF_PROG("kprobe/dmabuf_setup", AID_ROOT, AID_SYSTEM, kp_dmabuf_setup)
(struct pt_regs* regs) {
    const int ALLOW = 1;
    struct dmabuf_info cur_val = { 0 };
    unsigned long long tempAddr;
    size_t size;
    unsigned long inode;

    pid_t pid = (bpf_get_current_pid_tgid() >> 32) & 0xffffffff;

    bpf_probe_read( & amp;size, sizeof(size), (void*)regs->regs[0]); // size = [x0]; dmabuf->size
    bpf_probe_read( & amp;tempAddr, sizeof(tempAddr), ((void*)regs->regs[1] + 32)); // tempAddr = [x1 + 32];
    bpf_probe_read( & amp;inode, sizeof(inode), ((void*)tempAddr + 64)); // inode = [[x1 + 32] + 64];


    cur_val.pid = pid;
    cur_val.size = size;
    cur_val.inode = inode;

    bpf_get_current_comm(cur_val.comm, sizeof(cur_val.comm));
    bpf_dmabuf_mem_map_update_elem( & amp;(cur_val.inode), & amp;cur_val, BPF_ANY);
    return ALLOW;
}

LICENSE("GPL");

Define a MAP

DEFINE_BPF_MAP_GRW(dmabuf_mem_map, HASH, uint64_t, struct dmabuf_info, DMABUF_MAP_SIZE,
                   AID_SYSTEM);

in:

  1. The name of MAP is: dmabuf_mem_map
  2. MAP uses HASH for storage, and ARRAY is also common.
  3. The key type of MAP is uint64_t, and the value type is struct dmabuf_info. This function bpf_dmabuf_mem_map_update_elem(& amp;key, & amp;value, flags) will be used later to map key and value.
  4. The size of MAP DMABUF_MAP_SIZE is set to 4096

  5. Permission is AID_SYSTEM

Define a PROG

DEFINE_BPF_PROG("kprobe/dmabuf_setup", AID_ROOT, AID_SYSTEM, kp_dmabuf_setup)
(struct pt_regs* regs) {
    const int ALLOW = 1;
    struct dmabuf_info cur_val = { 0 };
    unsigned long long tempAddr;
    size_t size;
    unsigned long inode;

    pid_t pid = (bpf_get_current_pid_tgid() >> 32) & 0xffffffff;

    bpf_probe_read( & amp;size, sizeof(size), (void*)regs->regs[0]); // size = [x0]; dmabuf->size
    bpf_probe_read( & amp;tempAddr, sizeof(tempAddr), ((void*)regs->regs[1] + 32)); // tempAddr = [x1 + 32];
    bpf_probe_read( & amp;inode, sizeof(inode), ((void*)tempAddr + 64)); // inode = [[x1 + 32] + 64];


    cur_val.pid = pid;
    cur_val.size = size;
    cur_val.inode = inode;

    bpf_get_current_comm(cur_val.comm, sizeof(cur_val.comm));
    bpf_dmabuf_mem_map_update_elem( & amp;(cur_val.inode), & amp;cur_val, BPF_ANY);
    return ALLOW;
}

in:

  1. pid_t pid = (bpf_get_current_pid_tgid() >> 32) & amp; 0xffffffff; Get the pid number of the process

  2. Three bpf_probe_read(…) obtain the size and inode of the corresponding dmabuf. This is more difficult to understand than that, so let me explain it in detail here. First you need to know about the incoming parameter rules for aarch64 calls:

    How to get the parameters of the corresponding function of kprobe? Parameter 1~parameter 8 in ARM64 are saved to the X0~X7 registers respectively, that is, x0 stores parameter 1..... Please refer to this article for details: Part 16 – Linux ARM Assembly ARM64 Calling Standard – Nuggets

  3. So how is the parameter offset in the register calculated? Mainly obtained through gdb debugging.

How to get the function input variable offset?
 aarch64-linux-android-gdb vmlinux
(gdb) ptype/T struct dma_buf
type = struct dma_buf {
    size_t size;
    struct file *file;
    struct list_head attachments;
    const struct dma_buf_ops *ops;
    struct mutex lock;
    unsigned int vmapping_counter;
    struct iosys_map vmap_ptr;
    const char *exp_name;
    const char *name;
    spinlock_t name_lock;
    struct module *owner;
    struct list_head list_node;
    void *priv;
    struct dma_resv *resv;
    wait_queue_head_t poll;
    struct dma_buf_poll_cb_t cb_in;
    struct dma_buf_poll_cb_t cb_out;
    struct dma_buf_sysfs_entry *sysfs_entry;
    u64 android_kabi_reserved1;
    u64 android_kabi_reserved2;
}
(gdb) print (int) & amp;((struct dma_buf *)0)->name
$2 = 120
(gdb) print (int) & amp;((struct dma_buf *)0)->exp_name
$3 = 112
(gdb) print (int) & amp;((struct dma_buf *)0)->file
$6 = 8

bpf_get_current_comm(cur_val.comm, sizeof(cur_val.comm)); Get the name of the process.

bpf_dmabuf_mem_map_update_elem( & amp;(cur_val.inode), & amp;cur_val, BPF_ANY); Establish the MAP mapping of key and value.

bpfprogs/Android.bp

The following code needs to be added to Android.bp:

bpf {
    name: "memStats.o",
    srcs: ["memStats.c"],
    btf: true,
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

How to write a test program to verify that there are no problems with the above eBPF program?

Test program

Mainly refer to the Android open source code: the implementation part in frameworks/native/services/gpuservice/.

bpfprogs/memstats/Android.bp

// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
    default_applicable_licenses: ["system_bpfprogs_license"],
}
cc_binary {
    name: "memStats",
    srcs: [
        "MemStatsMain.cpp",
        "MemStats.cpp",
    ],
    header_libs: ["bpf_headers"],
    shared_libs: [
        "libbase",
        "libbpf_bcc",
        "libcutils",
        "liblog",
        "libutils",
    ],
    export_header_lib_headers: ["bpf_headers"],
    export_shared_lib_headers: ["libbase"],
    cppflags: [
        "-Wall",
        "-Werror",
        "-Wformat",
        "-Wthread-safety",
        "-Wunused",
        "-Wunreachable-code",
    ],
}

bpfprogs/memstats/MemStats.h

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once
#include <bpf/BpfMap.h>
#include <utils/String16.h>
#include <utils/Vector.h>
#include <functional>

class MemStats {
public:
    MemStats() = default;
    ~MemStats();
    // initialize eBPF program and map
    void initialize();
    bool isInitialized() { return mInitialized.load(); }
private:
    std::atomic<bool> mInitialized = false;
    //tracepoint event category

    //tracepoint event category
    static constexpr char * kMemStatsKprobeTraceGroup[] = {"kprobes"};

    //tracepoint
    static constexpr char * kMemStatsDmabufKprobe[] ={"dmabuf_setup"};

    // pinned bpf c program path in bpf sysfs
    static constexpr char * kMemStatsDmabufProgPath[] =
    {"/sys/fs/bpf/prog_memStats_kprobe_dmabuf_setup"};

    // 30 seconds timeout for trying to attach bpf program to tracepoint
    static constexpr int kWaitTimeout = 30;
};

bpfprogs/memstats/MemStats.cpp

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#undef LOG_TAG
#define LOG_TAG "MemStats"
#define ATRACE_TAG ATRACE_TAG_GRAPHICSS3
#include "MemStats.h"
#include <android-base/stringprintf.h>
#include <libbpf.h>
#include <bpf/WaitForProgsLoaded.h>
#include <log/log.h>
#include <unistd.h>
#include <utils/Timers.h>
#include <utils/Trace.h>
#include <unordered_map>
#include <vector>

#include <android-base/file.h>


MemStats::~MemStats() {

    for (int i = 0; i < sizeof(kMemStatsDmabufKprobe)/sizeof(char *); i + + )
    {
        bpf_detach_tracepoint(kMemStatsKprobeTraceGroup[0], kMemStatsDmabufKprobe[i]);
    }
}

void MemStats::initialize() {
    int count = 0;
    int fd = 0;
    // Make sure bpf programs are loaded
    android::bpf::waitForProgsLoaded();
    errno = 0;

    android::base::WriteStringToFile("p:dmabuf_setup dma_buf_stats_setup","/sys/kernel/debug/tracing/kprobe_events");
    
    for (int i = 0; i < sizeof(kMemStatsDmabufProgPath)/sizeof(char *); i + + )
    {
        fd = android::bpf::retrieveProgram(kMemStatsDmabufProgPath[i]);

        if (fd < 0) {
            ALOGE("Failed to retrieve pinned program from %s [%d(%s)]", kMemStatsDmabufProgPath[i], errno,
                strerror(errno));
        }
        // Attach the program to the tracepoint, and the tracepoint is automatically enabled here.
        errno = 0;
        count = 0;
        while (bpf_attach_tracepoint(fd, kMemStatsKprobeTraceGroup[0], kMemStatsDmabufKprobe[i]) < 0) {
            
            if ( + + count > kWaitTimeout) {
                ALOGE("Failed to attach bpf program to %s/%s tracepoint [%d(%s)]", kMemStatsKprobeTraceGroup[0],
                    kMemStatsDmabufKprobe[i], errno, strerror(errno));
            }
            // Retry until loaded or timeout.
            sleep(1);
        }
    }

    mInitialized.store(true);
}

in:

android::base::WriteStringToFile("p:dmabuf_setup dma_buf_stats_setup","/sys/kernel/debug/tracing/kprobe_events");

Before bpf_attach_tracepoint(…), write “p:dmabuf_setup dma_buf_stats_setup” to the event “/sys/kernel/debug/tracing/kprobe_events”. If you do not use this line of code, you can also use it in the Linux terminal, adb shell; echo ‘p:dmabuf_setup dma_buf_stats_setup’ > /sys/kernel/debug/tracing/kprobe_events

bpfprogs/memstats/MemStatsMain.cpp

#include "MemStats.h"
#include <unistd.h>
int main()
{
    class MemStats *memHandle = new MemStats;

    memHandle->initialize();
    
    while(1)
    {
        sleep(10);
    }

 return 0;
}

Compile and run

  1. makememStats.o
  2. adb push system/etc/bpf/memStats.o /system/etc/bpf/

  3. adb reboot; #After restarting, you can see the generated file map_memStats_dmabuf_mem_map in the /sys/fs/bpf/ directory.

  4. make memStats

  5. adb push memStats /data/local/tmp/

  6. adb shell; /data/local/tmp/memStats

  7. In another Linux terminal: cat /sys/fs/bpf/map_memStats_dmabuf_mem_map

You can see the following information output:

/ # cat /sys/fs/bpf/map_memStats_dmabuf_mem_map
# WARNING!! The output is for debug purpose only
# WARNING!! The output format will change
697: {1512,697,['b','i','n','d','e','r',':', '1','5','1','2','_','3',],188416,}
1344: {1512,1344,['P','r','e','v','i','e','w', '_','4',],22020096,}
752: {1512,752,['P','r','e','v','i','e','w', '_','0',],4096,}
589: {1525,589,['b','i','n','d','e','r',':', '1','5','2','5','_','1',],10522624,}
1175: {1512,1175,['P','r','e','v','i','e','w', '_','2',],462848,}
1498: {1512,1498,['P','r','e','v','i','e','w', '_','4',],4096,}
1539: {1525,1539,['b','i','n','d','e','r',':', '1','5','2','5','_','1',],73728,}
712: {1512,712,['P','r','e','v','i','e','w', '_','2',],4096,}
1469: {1525,1469,['b','i','n','d','e','r',':', '1','5','2','5','_','1',],2506752,}
1520: {2553,1520,['.','v','o','r','b','i','s', '.','d','e','c','o','d','e','r',], 32768,}
745: {1512,745,['P','r','e','v','i','e','w', '_','1',],4096,}
1137: {1512,1137,['P','r','e','v','i','e','w', '_','7',],4096,}
1186: {1512,1186,['b','i','n','d','e','r',':', '1','5','1','2','_','3',],6451200,}
724: {1512,724,['P','r','e','v','i','e','w', '_','5',],4096,}
791: {1512,791,['P','r','e','v','i','e','w', '_','7',],4096,}
1159: {1512,1159,['P','r','e','v','i','e','w', '_','4',],462848,}
810: {1512,810,['P','r','e','v','i','e','w', '_','7',],4096,}
1285: {1512,1285,['P','r','e','v','i','e','w', '_','0',],4096,}
761: {1512,761,['P','r','e','v','i','e','w', '_','8',],4096,}
735: {1512,735,['P','r','e','v','i','e','w', '_','1',],4096,}
630: {1512,630,['b','i','n','d','e','r',':', '1','5','1','2','_','3',],4096,}
1012: {1512,1012,['b','i','n','d','e','r',':', '1','5','1','2','_','3',],45056,}

Result Analysis

Explain the following output, using this as an example: 697: {1512,697,[‘b’,’i’,’n’,’d’,’e’,’ r’,’:’,’1′,’5′,’1′,’2′,’_’,’3′,],188416, }

Among them, 697 is the value of inode, {1512,697,[‘b’,’i’,’n’,’d’,’e’,’r’,\ ‘:’,’1′,’5′,’1′,’2′,’_’,’3′,],188416,} is the struct dmabuf_info structure body output. Specifically, each item in the output is the value in the corresponding structure, as shown below:

struct dmabuf_info {
    unsigned long pid; //1512
    uint64_t inode; //697
    char comm[16]; //['b','i','n','d','e','r',':' ,'1','5','1','2','_','3',]
    uint64_t size; //188416
};

Summary

The above is a specific eBPF example program. Monitor the dma_buf_stats_setup interface in the kernel through kprobe.

kernel_platform/common/drivers/dma-buf/dma-buf-sysfs-stats.c

int dma_buf_stats_setup(struct dma_buf *dmabuf, struct file *file)
172 {
173 struct dma_buf_sysfs_entry *sysfs_entry;
174 int ret;
175
176 if (!dmabuf->exp_name) {
177 pr_err("exporter name must not be empty if stats needed\\
");
178 return -EINVAL;
179 }
180
181 sysfs_entry = kzalloc(sizeof(struct dma_buf_sysfs_entry), GFP_KERNEL);
182 if (!sysfs_entry)
183 return -ENOMEM;
184
185 sysfs_entry->kobj.kset = dma_buf_per_buffer_stats_kset;
186 sysfs_entry->dmabuf = dmabuf;
187
188 dmabuf->sysfs_entry = sysfs_entry;
189
190 /* create the directory for buffer stats */
191 ret = kobject_init_and_add( & amp;sysfs_entry->kobj, & amp;dma_buf_ktype, NULL,
192 "%lu", file_inode(file)->i_ino);
193 if (ret)
194 goto err_sysfs_dmabuf;
195
196 return 0;
197
198 err_sysfs_dmabuf:
199 kobject_put( & amp;sysfs_entry->kobj);
200 dmabuf->sysfs_entry = NULL;
201 return ret;
202}

More eBPF demo programs will be output on Android in the future.