• 深入ftrace uprobe原理和功能介绍


    上一章我们学习了,kprobe 可以实现动态内核的注入,基于中断的方法在任意指令中插入追踪代码,并且通过 pre_handler/post_handler去接收回调。另一个 kprobe 的同族是 kretprobe,只不过是针对函数级别的内核监控,根据用户注册时提供的 entry_handlerret_handler 来分别在函数进入时和返回前进行回调。

    本章的我们来学习uprobe ,顾名思义,相对于内核函数/地址的监控,主要用于用户态函数/地址的监控。听起来是不是有点神奇,内核怎么监控用户态函数的调用呢?本章的内容包括:

    • 如何使用uprobe
    • 内核是如何通过uprobe监控用户态的调用,其原理是如何的

    1 如何使用uprobe

    站在用户视角,我们先看个简单的例子,假设有这么个一个用户程序:

    // test.c
    #include 
    void foo() {
        printf("hello, uprobe!\n");
    }
    int main() {
        foo();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    编译好之后,查看某个符号的地址,然后告诉内核我要监控这个地址的调用:

    #gcc test.c -o test
    #readelf -s test | grep foo
        56: 000000000000115a    19 FUNC    GLOBAL DEFAULT   14 foo
    #echo 1 > /sys/kernel/debug/tracing/events/uprobes/p_test_0x115a/enable
    #echo 1 > /sys/kernel/debug/tracing/events/uprobes/p_test_0x115a/enable
    #echo 1 > /sys/kernel/debug/tracing/tracing_on
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后运行用户程序并检查内核的监控返回:

    $ ./test && ./test
    hello, uprobe!
    hello, uprobe!
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    对于内核如何使用uprobe,请参考内核文档uprobetracer.html,其使用基本跟kprobe类似

    在这里插入图片描述

    2 实现原理

    上面的接口是基于 tracefs,即读写文件的方式去与内核交互实现 uprobe 监控。

    在这里插入图片描述

    其中写入 uprobe_events 时会经过一系列内核调用,最终会调用到create_or_delete_trace_uprobe

    在这里插入图片描述

    对于__trace_uprobe_create跟使用kprobe类似,也是大家使用的三板斧

    • **alloc_trace_uprobe:**分配 uprobe 结构体

    在这里插入图片描述

    • **register_trace_uprobe:**注册 uprobe:

    • regiseter_uprobe_event: 将 probe 添加到全局列表中,并创建对应的 uprobe debugfs 目录,即上文示例中的 p_test_0x115a

    对于uprobe其实整个流程跟kprobe基本类似,我们重点关注于uprobe_register的函数做了些什么

    在这里插入图片描述

    当已经注册了 uprobe 的 ELF 程序被执行时,可执行文件会被 mmap 映射到进程的地址空间,同时内核会将该进程虚拟地址空间中对应的 uprobe 地址替换成断点指令。

    在这里插入图片描述

    在这里插入图片描述

    与 kprobe 类似,我们可以在触发 uprobe 时候根据对应寄存器去提取当前执行的上下文信息,比如函数的调用参数等。同时 uprobe 也有类似的同族: uretprobe

    通过设置uprobe,我们来看看通过gdb跟踪后,反汇编对应的文件,对于x86平台是不是修改成int3指令,确实是修改成int3指令
    在这里插入图片描述

    指定位置上的指令,头部修改为软件中断指令(同时原指令存档他处):

    1. 当执行到该位置时,触发软件中断,陷入内核
    2. 在内核,执行以 注入的 Handler
    3. 单步执行原指令
    4. 修正寄存器和栈,回到原有指令流

    在这里插入图片描述

    3 uprobe内核模块验证

    我们以ubuntu为试验环境,使用uprobe一般都是编写内核驱动,在模块中定义uprobe_consumer ,然后调用uprobe的API(uprobe_register)来进行注册uprobe

    #include 
    #include 
    #include 
    #include 
    #include 
    
    MODULE_AUTHOR("john doe");
    MODULE_LICENSE("GPL v2");
    
    static char *filename;
    module_param(filename, charp, S_IRUGO);
    
    static long offset;
    module_param(offset, long, S_IRUGO);
    
    static int handler_pre(struct uprobe_consumer *self, struct pt_regs *regs){
            pr_info("handler: arg0 = %d arg1 =%d \n", (int)regs->di, (int)regs->si);
            return 0;
    }
    
    static int handler_ret(struct uprobe_consumer *self,
                                    unsigned long func,
                                    struct pt_regs *regs){
            pr_info("ret_handler ret = %d \n", (int)regs->ax);
            return 0;
    }
    
    static struct uprobe_consumer uc = {
            .handler = handler_pre,
            .ret_handler = handler_ret,
    };
    
    
    static struct inode *inode;
    
    static int __init uprobe_init(void) {
            struct path path;
            int ret;
    
            ret = kern_path(filename, LOOKUP_FOLLOW, &path);
            if (ret < 0) {
                    pr_err("kern_path failed, returned %d\n", ret);
                    return ret;
            }
    
            inode = igrab(path.dentry->d_inode);
            path_put(&path);
    
            ret = uprobe_register(inode, offset, &uc);
            if (ret < 0) {
                    pr_err("register_uprobe failed, returned %d\n", ret);
                    return ret;
            }
    
            return 0;
    }
    
    static void __exit uprobe_exit(void) {
            uprobe_unregister(inode, offset, &uc);
    }
    
    module_init(uprobe_init);
    module_exit(uprobe_exit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    此外,没有像 Kprobes 那样提供 uretprobe_register,如果 ret_handler 设置为值,则设置uretprobe,生成Makefile

    obj-m := hello-uprobe-world.o
    KDIR    := /lib/modules/$(shell uname -r)/build
    VERBOSE = 0
    
    all:
            $(MAKE) -C $(KDIR) M=$(PWD) KBUILD_VERBOSE=$(VERBOSE) CONFIG_DEBUG_INFO=y modules
    clean:
            rm -f *.o *.ko *.mod.c Module.symvers modules.order
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编译生成ko文件:

    在这里插入图片描述

    准备要跟踪的程序:

    #include 
    
    int add(int a, int b) {
            return a + b;
    }
    
    int main(void) {
            add(1, 2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    安装ko文件,并执行该文件,我们跟踪add函数,其offset可以通过获取

    在这里插入图片描述

    在这里插入图片描述

    4 uprobe_event验证

    uprobe_events 是一种无需创建内核模块即可使用 Uprobe 的机制。 您可以在与 Ftrace 相同的界面中动态创建探测

    root@rlk:/sys/kernel/tracing# echo 'p:sample_uprobe /root/Make/main:0x114a %di %si' >  /sys/kernel/debug/tracing/uprobe_events
    root@rlk:/sys/kernel/tracing# echo 'r:sample_uretprobe /root/Make/main:0x114a %ax' >>  /sys/kernel/debug/tracing/uprobe_events
    root@rlk:/sys/kernel/tracing# echo 1 > /sys/kernel/tracing/events/uprobes/sample_uprobe/enable
    root@rlk:/sys/kernel/tracing# echo 1 > /sys/kernel/tracing/events/uprobes/sample_uretprobe/enable
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查看结果:

    在这里插入图片描述

    5 perf_event_open

    perf_event_open系统调用将 BPF 程序附加到 uprobe 事件。 直接编写 BPF 程序可能比较痛苦,因此需要通过 bpftrace 来使用它。

    bpftrace -e ‘uretprobe:/root/work/uprobe/main:add {printf(“%d\n”, retval); exit(); }’

    在这里插入图片描述

    在这里插入图片描述

    检查perf_event_open的使用方式

    在这里插入图片描述

    6 参考文档

    Linux 内核监控在 Android 攻防中的应用

    Uprobes概述和使用 - SIer,但我想做技术博客 (kimullaa.com)

    Uprobe-tracer: Uprobe-based Event Tracing

  • 相关阅读:
    【单元测试】--高级主题
    X11 Xlib截屏问题及深入分析四 —— XOpenDisplay函数源码分析(1)
    Beacon帧
    为什么不建议你用 MongoDB 这类产品替代时序数据库?
    Qt的QChartView实现缩放和放大功能
    【20221114】【每日一题】子集
    ssm养老院信息管理系统 毕业设计源码181550
    HTML期末大作业(HTML+CSS+JavaScript响应式游戏资讯网站bootstrap网页)
    iPhone手机分辨率整理
    彻底解决JDK安装包点击后无反应
  • 原文地址:https://blog.csdn.net/u012489236/article/details/127954817