eBPF:arm平台上的uprobe

发布时间:2024年01月06日

eBPF:arm平台上的uprobe

视频讲解:

eBPF arm 32 平台上的uprobe

这节视频讲下在arm 32平台上的 ebpf uprobe 的使用方法;和 x86-64 上的使用方法有好些不同点;

Linux 内核版本:4.9.88

回顾

在上节视频中,交叉编译了 zlib, libelf, libbpf-bootstrap

交叉编译前需要先执行 build_env.sh

#!/bin/sh
# 指定交叉编译工具链的绝对地址
export PATH=$PATH:/your/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

# 指定目标平台类型
export ARCH=arm

# 指定交叉编译器
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-

# 指定交叉编译好的 zlib 和 libelf 库的头文件路径
export EXTRA_CFLAGS="-I/your/elfutils-0.189/_install/include -I/your/zlib-1.3/_install/include"

# 指定交叉编译好的 zlib 和 libelf 库(.a)文件路径
export EXTRA_LDFLAGS="-L/your/elfutils-0.189/_install/lib -L/your/zlib-1.3/_install/lib"

编译

source build_env.sh  # 只需执行一次
make clean
make uprobe

移植 x86-64 平台上的uprobe代码

  1. eBPF示例:x86-64平台上的uprobe 这节视频讲解中改造过的代码拷贝过来;

    涉及到3个文件:

    • test_uprobe.c

    应用层的测试代码,定义了 uprobed_adduprobed_sub 2个函数,然后在 main 函数中循环调用;

    测试目的:ebpf uprobe 在arm 32平台上是否可以正确获取 uprobed_adduprobed_sub 2个函数的入口参数和返回值;

    • uprobe.bpf.c

      ebpf 在内核层的代码,定义 uprobe 探测点的回调函数:

      应用层 uprobed_add 函数进入和返回时的回调函数;

      应用层 uprobed_sub 函数进入和返回时的回调函数;

    • uprobe.c

      ebpf 在应用层的代码,负责把 uprobe.bpf.c 编译得到的字节码加载到内核,以及把 uprobe.bpf.c 文件中定义的回调函数 attach 到应用层测试代码中函数的 uprobe 探测点;

? 为了简单起见,不再 strip 测试代码的可执行程序,直接通过测试代码中的函数名来 attach;(如果对这部分内容不清楚的,可以再回去看看 eBPF示例:x86-64平台上的uprobe 这节视频)

  1. 直接编译 测试代码 和 ebpf

    # 编译测试代码
    arm-buildroot-linux-gnueabihf-gcc test_uprobe.c -o test_uprobe
    
    # 编译 ebpf
    make clean
    make uprobe
    

? 编译好后,直接运行,报错:

libbpf: failed to find valid kernel BTF
libbpf: Error loading vmlinux BTF: -3

? 查看BCC文档: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md

? BTF 需要 4.18 及以后的内核版本才支持;

  1. 修改代码:

    uprobe.bpf.c 中,去掉 #include "vmlinux.h" ,改为 #include <linux/bpf.h>

    再次编译ebpf,报错:

    uprobe.bpf.c:11:5: error: incomplete definition of type 'struct pt_regs'
    

    libbpf-bootstrap/vmlinux/arm/vmlinux.h 中的 struct pt_regs 定义拷贝到 uprobe.bpf.c 中;

    再次编译ebpf,并执行,报错:

    libbpf: prog 'uprobe_add': -- BEGIN PROG LOAD LOG --
    0: (79) r4 = *(u64 *)(r1 +8)
    1: (79) r3 = *(u64 *)(r1 +0)
    2: (85) call 2001000000
    invalid func 2001000000
    -- END PROG LOAD LOG --
    

    bpf_helper_defs.h 头文件中并没有定义 2001000000 的枚举值;

    阅读下 libbpf-bootstrap 开源代码说明文档:https://hub.njuu.cf/libbpf/libbpf-bootstrap

    minimal_Legacy

    This version of minimal is modified to allow running on even older kernels that 
    do not allow global variables. 
    bpf_printk uses global variables unless BPF_NO_GLOBAL_DATA is defined before 
    including bpf_helpers.h.
    

    uprobe.bpf.c 文件中,增加:

    #define BPF_NO_GLOBAL_DATA
    

    再次编译,并执行:

    可以正常运行,但是 uprobed_add 打印的结果不对;

    按照: eBPF示例:x86-64平台上的kprobe 视频中的方法,改写下int BPF_KPROBE(uprobe_add, int a, int b)

    int uprobe_add(struct pt_regs *ctx)
    {
    	int a = ctx->uregs[0];
    	int b = ctx->uregs[1];
    	bpf_printk("uprobed_add ENTRY: a = %d, b = %d\n", a, b);
    	return 0;
    }
    

    eBPF基础 视频中讲过,ebpf寄存器是64 bit,但是arm32平台寄存器是32 bit,这2个事实存在冲突,用代码来验证下;

    int uprobe_add(struct pt_regs *ctx)
    {
    	int a = ctx->uregs[0];
    	int b = ctx->uregs[1];
    	bpf_printk("uprobed_add ENTRY: a = %d, b = %d sizeof(uregs[0])=%d\n", a, b, sizeof(ctx->uregs[0]));
    	return 0;
    }
    
    /*
    打印结果
    uprobed_add ENTRY: a = 10, b = 16 sizeof(uregs[0])=8
    即内核层ebpf中, long unsigned int 是 64 bit
    */
    

    把变量 ab 用64 bit的格式打印出来:

    int uprobe_add(struct pt_regs *ctx)
    {
    	int a = ctx->uregs[0];
    	int b = ctx->uregs[1];
    
    	bpf_printk("uprobed_add ENTRY: a = 0x%llx, b = 0x%llx\n", a, b);
    	return 0;
    }
    
    /*
    打印结果:
    uprobed_add ENTRY: a = 0x700000006, b = 0x700000010
    */
    

    根据打印结果和 test_uprobe.c 代码分析可知,变量 a 的低32bit是 test_uprobe.cuprobed_add 函数的第一个参数,高32bit是第二个参数;

    强制把 struct pt_regs 中的 long unsigned int 改为 unsigned int(强制变为32 bit)

    struct pt_regs {
            unsigned int uregs[18];
    };
    

    结果正确;

    再来解决编译警告的问题:

    // 修改 libbpf-bootstrap/libbpf/src/bpf_tracing.h
    #if defined(bpf_target_arm)
    #define ___bpf_kretprobe_args0()       ctx
    #define ___bpf_kretprobe_args1(x)      ___bpf_kretprobe_args0(), (unsigned int)PT_REGS_RC(ctx)
    #define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
    #else
    #define ___bpf_kretprobe_args0()       ctx
    #define ___bpf_kretprobe_args1(x)      ___bpf_kretprobe_args0(), (void *)PT_REGS_RC(ctx)
    #define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
    #endif
    
    #if defined(bpf_target_arm)
    #define ___bpf_kprobe_args0()           ctx
    #define ___bpf_kprobe_args1(x)          ___bpf_kprobe_args0(), (unsigned int)PT_REGS_PARM1(ctx)
    #define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (unsigned int)PT_REGS_PARM2(ctx)
    #define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (unsigned int)PT_REGS_PARM3(ctx)
    #define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (unsigned int)PT_REGS_PARM4(ctx)
    #define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (unsigned int)PT_REGS_PARM5(ctx)
    #define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (unsigned int)PT_REGS_PARM6(ctx)
    #define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (unsigned int)PT_REGS_PARM7(ctx)
    #define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (unsigned int)PT_REGS_PARM8(ctx)
    #define ___bpf_kprobe_args(args...)     ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
    #else
    #define ___bpf_kprobe_args0()           ctx
    #define ___bpf_kprobe_args1(x)          ___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
    #define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (void *)PT_REGS_PARM2(ctx)
    #define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (void *)PT_REGS_PARM3(ctx)
    #define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (void *)PT_REGS_PARM4(ctx)
    #define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (void *)PT_REGS_PARM5(ctx)
    #define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (void *)PT_REGS_PARM6(ctx)
    #define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (void *)PT_REGS_PARM7(ctx)
    #define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (void *)PT_REGS_PARM8(ctx)
    #define ___bpf_kprobe_args(args...)     ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
    #endif
    

? 再重新编译 uprobe

make clean; make uprobe

ebpf kprobe 和 uprobe 在arm 32平台的处理方法一样,这里就不再详细讲了;

文章来源:https://blog.csdn.net/qq_38232169/article/details/135420034
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。