DTrace

DTrace

Posted by nomadli on August 31, 2016

book

  1. http://docs.oracle.com/cd/E19253-01/819-6959/
  2. https://docs.oracle.com/cd/E19253-01/819-6959/819-6959.pdf
  3. DTrace-Dynamic-Tracing-in-Oracle-Solaris-Mac-OS-X-and-FreeBSD

pragma

  1. pragma D 指定特定的编译指令
  2. pragma D attributes 名称稳定性/数据稳定性/依赖 provider providername (provider、module、function、name、args) 稳定性包括:Internal(内部)、Private(专用)、Obsolete(过时)、External(外部)、Unstable(不稳定)、Evolving(发展中)、Stable(稳定)、Standard(标准) 依赖包括:Unknown(未知)、CPU(特性cpu)、Platform(系列cpu)、Group(某种架构的cpu)、ISA(特定指令集)、Common(通用)。
  3. pragma D option 选项: aggrate(聚合读取的速率)、aggsize(聚合缓冲区大小)、bufresize(缓冲区调整大小策略)、bufsize(主体缓冲区大小)、cleanrate(清除速率hz)、cpu(启用跟踪的CPU)、defaultargs(允许引用未指定的宏参数)、destructive(允许破坏性操作)、dynvarsize(动态变量空间大小)、flowindent(缩进函数输入并加前缀 ->;取消缩进函数返回并加前缀 <-)、grabanon(声明匿名状态)、jstackframes(jstack() 的缺省栈帧数)、jstackstrsize(jstack() 的缺省字符串空间大小)、nspec(推理数)、quiet(仅输出显式跟踪的数据)、specsize(推理缓冲区大小)、strsize(字符串大小)、stackframes(栈帧数)、stackindent(缩进 stack() 和 ustack() 输出时要使用的空格字符数)、statusrate(状态检查的速率)、switchrate(缓冲区切换的速率)、ustackframes(用户栈帧数)、version(dtrace版本)

探针描述

  1. dtrace:::BEGIN 脚本开始时执行
  2. dtrace:::END 脚本退出时执行
  3. dtrace:::ERROR 脚本错误时执行
  4. sudo dtrace -l awk ‘{ match($2, “([a-z,A-Z]*)”);print substr($2, RSTART, RLENGTH); }’ sort -u 列出所有可用探针
  5. tick-10sec 每10秒执行 在一个cpu上触发
  6. profile-1001 每1001赫兹执行 在所有cpu上触发
  7. pid123:libSystem:printf:return pid为123的print函数
  8. objc123:NSTableView:draw?:entry pid为123obj类函数
  9. io::: start done wait-start wait-done journal-start journal-done
  10. ip:::send ip:::receive arg0-arg5
  11. 自定义静态探针 provider name{probe name(param)} name.d dtrace -h -s name.d
  12. 多个探针可以用’,’进行分隔
  13. :entry :return 函数进入和退出

断言

  1. 条件可使用 &&   / a == b && c == d /

语句

  1. sum(…) 集积函数 对key对应的值累加
  2. count() 集积函数 对key对应的函数调用次数
  3. avg(…) 集积函数 对key对应的值平均值
  4. min(…) 集积函数 对key对应的值最小
  5. max(…) 集积函数 对key对应的值最大
  6. lquantize() 集积函数 线性统计
  7. quantize() 集积函数 非线性统计
  8. trunc(@name, number)集积函数 截断
  9. printa(…) 打印集积
  10. clear(@name) 清除集积
  11. normalize(@name, .) 标准化集积
  12. trace() 打印信息
  13. exit(0) 退出Dtrace
  14. printf格式化输出
  15. tracemem(addr, len) 打印内存
  16. stack(deepth) 打印内核栈 可以不指定深度
  17. ustack(deepth, strsize) 打印用户栈,参数可不指定
  18. jstack(deepth, strsize) ustack别名, strsize默认>0
  19. stop()触发探测器的进程在退出内核时被强制停止
  20. raise(int signal)发送信号给进程
  21. copyout(buf,addr,size) 从buf拷贝size到addr内存
  22. copystr(str,addr,maxlen)从str拷贝maxlen到addr
  23. system(program,…)执行系统命令可以用格式化字符生成命令,只在处理Dtrace缓存区时才开始执行,并不在调用当时执行
  24. breakpoint() 内核断点
  25. panic()系统奔溃转储
  26. chill(nanoseconds) 相当于sleeep
  27. void* alloca(size) 分配零时空间,出{}会自动释放
  28. basename(str)复制str不包括/结尾的前缀,临时空间自动释放
  29. bcopy(str,dest,size)str在临时内存外,dest在零时内存内
  30. cleanpath(str)返回有效路径 临时内存外部
  31. void* copyin(addr, size)拷贝addr到临时缓存区
  32. string copyinstr(addr)
  33. void copyinto(saddr,size,daddr)
  34. string dirname(path)返回目录内容
  35. size_t msgdsize(mblk_t *mp) 返回数据字节数
  36. size_t msgsize(mblk_t *mp) 返回字节总数
  37. int mutex_owned(kmutex_t *mutex)返回当前内核mutex
  38. kthread_t* mutex_owner(kmutext_t *mutex)返回内核mutext的线程指针
  39. int mutex_type_adaptive(kmutex_t *mutex)判断内核互斥是否为MUTEX_ADAPTIVE类型
  40. int progenyof(pid_t pid)判断是否是指定的进程
  41. int rand(void)返回伪随机数
  42. int rw_iswriter(krwlock_t *rwlock)是否是写锁或写等待锁
  43. int rw_write_held(krwlock_t *rwlock)是否写锁定
  44. int speculation()
  45. string strjoin(str1, str2)连接两个字符串 临时缓存外部
  46. size_t strlen(str)字符长度
  47. translator structtype1 < structtype2 name > 自动结构转换 2->1
  48. xlate (structtype2)->name 运算符自动转换
  49. inline 指令定义 D 命名的常量

变量

  1. 全局(name)、线程本地(self->name)、 探针语句本地{this->name}
  2. 集积 @name 对某个key进行累计
  3. 联合数组 int x[unsigned long long, char];
  4. $1 表示Dtrace 命令的第一个参数
  5. errno int 系统调用的当前 errno 值
  6. ‘name 访问操作系统的变量 数据结构 类型
  7. int64_t arg0, …, arg9 探测器的前10个输入参数表示为原始的64位整数
  8. args[] 当前探测器键入的参数 实际类型
  9. uintptr_t caller 当前线程的程序计数器位置
  10. chipid_t chip 当前物理芯片的 CPU 芯片标识符
  11. processorid_t cpu 当前 CPU 的 CPU 标识符
  12. cpuinfo_t *curcpu 当前 CPU 的 CPU 信息
  13. lwpsinfo_t *curlwpsinfo 当前线程LWP 状态
  14. psinfo_t *curpsinfo 当前线程关联的进程的进程状态
  15. kthread_t *curthread 当前线程的操作系统内核的内部数据结构的地址
  16. string cwd 与当前线程关联的进程的当前工作目录名称。
  17. uint_t epid 当前探测器的已启用的探测器 ID
  18. int errno 线程最后一次执行的系统调用返回的错误值
  19. string execname 当前进程的名称
  20. gid_t gid 当前进程的实际组 ID
  21. uint_t id 探测器ID。系统范围内的唯一标识符
  22. uint_t ipl 触发探测器时当前 CPU 的中断优先级
  23. lgrp_id_t lgrp 当前 CPU 所属的延迟组的延迟组 ID
  24. pid_t pid 当前进程的进程 ID
  25. pid_t ppid 当前进程的父进程 ID
  26. string probefunc 探测器函数名称部分
  27. string probemod 探测器模块名称部分
  28. string probename 探测器名称部分
  29. string probeprov 探测器提供器名称部分
  30. psetid_t pset 当前 CPU 的处理器集的处理器集 ID
  31. string root 当前线程关联的进程的根目录名称
  32. uint_t stackdepth 触发探测器时当前线程的栈帧深度
  33. id_t tid 当前线程的线程 ID
  34. uid_t uid 当前进程的实际用户 ID
  35. uint64_t uregs[] 当前线程已保存的用户模式寄存器值
  36. uint64_t timestamp 纳秒相对时间
  37. uint64_t vtimestamp 纳秒当前线程已运行时间减去花费时间
  38. uint64_t walltimestamp 1970-1-1-00:00纳秒

xcode

  1. 创建d文件
  2. provider 名称 {}; 定义提供者
  3. probe name(args…) 定义探针
  4. 将d文件添加进xcode
  5. ios项目head search path add $(DERIVED_FILES_DIR)
  6. 使用的地方#import d文件名.h
  7. providename_probename、 providename_probename_enabled
  8. DTRACE_PROBES_DISABLED 关闭dtrace

linux 系统追踪

  • kprobes ftrace perf_events cBPF eBPF
  • ebpf 需要内核在3.15以上, 5.5以上才好用, 5.8引入了ring buffer
  • https://ebpf.io/projects
  • https://ebpf.io/zh-cn
  • ebpf字节码
  • c 直接开发
  • libbpf 纯C库, 生成BPF字节码, 运行时动态判断内核版本, 更新BPF字节码, 加载内核编译
  • centos 升级内核
      yum update -y
      rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
      rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-5.el7.elrepo.noarch.rpm
      rpm -qa |grep kernel
      yum remove -y kernel-headers kernel-tools kernel-tools-libs kernel-debug-devel kernel-debuginfo kernel-debuginfo-common-x86_64 旧内核名(不含arch名) clang llvm
      yum --enablerepo=elrepo-kernel install -y kernel-ml kernel-ml-devel kernel-ml-headers kernel-ml-tools kernel-ml-tools-libs
      grub2-mkconfig -o /boot/grub2/grub.cfg
      awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
      grub2-set-default 0
      grub2-editenv list
      cat /boot/config-xxx | grep KPROBES 确定内核编译是否开启内核跟踪
      cat /boot/config-xxx | grep BPF 确定内核编译是否开启BPF
      yum -y groupinstall "Development Tools"
      yum -y install zlib-devel openssl-devel python3 elfutils-libelf-devel
      #gcc 11 无法直接用gcc 4.x编译
      wget https://mirrors.aliyun.com/gnu/gcc/gcc-10.3.0/gcc-10.3.0.tar.xz
      wget https://mirrors.aliyun.com/gnu/gcc/gcc-11.1.0/gcc-11.1.0.tar.xz
      tar -xvf gcc-11.1.0.tar.xz
      cd gcc-11.1.0
      ./contrib/download_prerequisites
      mkdir build
      cd build
      ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,go --enable-plugin --enable-initfini-array --disable-libgcj --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux --disable-multilib --with-default-libstdcxx-abi=gcc4-compatible --with-abi=m64 --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release  --with-target-system-zlib --enable-objc-gc=auto  --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-nls --without-included-gettext
      unset LIBRARY_PATH CPATH C_INCLUDE_PATH PKG_CONFIG_PATH CPLUS_INCLUDE_PATH INCLUDE
      make -j16
      make install
      wget https://github.com/Kitware/CMake/releases/download/v3.20.2/cmake-3.20.2.tar.gz
      tar -xvf cmake-3.20.2.tar.gz
      cd cmake-3.20.2
      ./bootstrap --prefix=/usr
      make -j16
      make install
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/llvm-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang-tools-extra-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/compiler-rt-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/libcxx-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/libcxxabi-12.0.0.src.tar.xz
      wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/lldb-12.0.0.src.tar.xz
      tar -xvf llvm-12.0.0.src.tar.xz
      tar -xf clang-12.0.0.src.tar.xz
      mv clang-12.0.0.src clang
      tar -xf clang-tools-extra-12.0.0.src.tar.xz
      mv clang-tools-extra-12.0.0.src clang-tools-extra
      tar -xf compiler-rt-12.0.0.src.tar.xz
      mv compiler-rt-12.0.0.src compiler-rt
      tar -xf libcxx-12.0.0.src.tar.xz
      mv libcxx-12.0.0.src libcxx
      tar -xf libcxxabi-12.0.0.src.tar.xz
      mv libcxxabi-12.0.0.src libcxxabi
      tar -xf lldb-12.0.0.src.tar.xz
      mv lldb-12.0.0.src lldb
      cd llvm-12.0.0.src
      mkdir build
      cd build
      unset LIBRARY_PATH CPATH C_INCLUDE_PATH PKG_CONFIG_PATH CPLUS_INCLUDE_PATH INCLUDE
      cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=on -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt;lldb;libcxx;libcxxabi" ..
      make -j16
      make install
      git clone https://github.com/lldb-tools/lldb-mi
      cd lldb-mi
      mkdir build
      cd build
      cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_PREFIX_PATH=/usr ..
      make -j16
      make install
    
  • c 语言
    • 直接编译进内核
        yum install -y epel-release
        yum update -y
        yum groupinstall -y "Development tools"
        yum install -y elfutils-libelf-devel cmake3 git bison flex ncurses-devel
        zlib libz-dev zlib-devel libelf libelf-devel futils-libelf-devel libbpf
        install or build install llvm  >= 10
        install or build install clang >= 10
        linux 内核代码  make defconfig && make headers_install
        编写内核bpf探针 samples/bpf/xxx_kern.c
            #include <linux/bpf.h>
            #include "bpf_helpers.h"
            #define SEC(NAME) __attribute__((section(NAME), used))
            SEC("tracepoint/syscalls/sys_enter_execve")
            int bpf_prog(void *ctx){
                char msg[] = "nomadli's bpf program!\n";
                bpf_trace_printk(msg, sizeof(msg));
                return 0;
            }
            char _license[] SEC("license") = "GPL";
              
        编写用户态使用 samples/bpf/xxx_user.c
        #include <stdio.h>
        #include "bpf_load.h"
        int main(int argc, char **argv){
            if(load_bpf_file("xxx_kern.o")!=0){
                printf("The kernel didn't load BPF program\n");
                return -1;
            }
            read_trace_pipe();
            return 0;
        }
        修改Makefile 添加
        hostprogs-y += hello
        hello-objs := bpf_load.o hello_user.o
        always += hello_kern.o
        make M=samples/bpf
      
    • 手动加载如内核
        #与直接加载内核相同, 写xxx_kern.c
        #always += hello_kern.o
        #make M=samples/bpf
        llm-objdump -arch-name=bpf -S hello_kern.o 获取函数开始及长度
        dd if=/xx/hello_kern.o of=/xx/hell_kern bs=1 count=函数二进制字节长度 skip=函数开始位置(头64字节)
        #获取相关event id, 如kprodb的id
        echo 'p:sys_clone sys_clone' >> /sys/kernel/debug/tracing/kprobe_events
        cat /sys/kernel/debug/tracing/events/kprobes/sys_clone/id
        #客户端代码
        #include <unistd.h>
        #include <string.h>
        #include <sys/syscall.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <sys/stat.h>
        #include <fcntl.h>
        #include <linux/bpf.h>
        #include <linux/version.h>
        #include <linux/perf_event.h>
        #include <linux/hw_breakpoint.h>
        #include <errno.h>
        int main() {
            int bfd = open("/xx/hell_kern", O_RDONLY);
            if (bfd < 0)
            {
                printf("open eBPF program error: %s\n", strerror(errno));
                exit(-1);
            }
      
            unsigned char buf[1024] = {};
            int n = read(bfd, buf, 1024);
            for (int i = 0; i < n; ++i) {
                printf("%02x ", buf[i]);
                if (i % 8 == 0)
                    printf("\n");
            }
            close(bfd);
      
            struct bpf_insn *insn = (struct bpf_insn*)buf;
            unsigned char log_buf[4096] = {};
      
            union bpf_attr attr = {};
            attr.prog_type = BPF_PROG_TYPE_KPROBE;
            attr.insns = (unsigned long)insn;
            attr.insn_cnt = n / sizeof(struct bpf_insn);
            attr.license = (unsigned long)"GPL";
            attr.log_size = sizeof(log_buf);
            attr.log_buf = (unsigned long)log_buf;
            attr.log_level = 1;
            attr.kern_version = 264656;
      
            int pfd = syscall(SYS_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
            if (pfd < 0) {
                printf("bpf syscall error: %s\n", strerror(errno));
                printf("log_buf = %s\n", log_buf);
                exit(-1);
            }
      
            struct perf_event_attr pattr = {};
            pattr.type = PERF_TYPE_TRACEPOINT;
            pattr.sample_type = PERF_SAMPLE_RAW;
            pattr.sample_period = 1;
            pattr.wakeup_events = 1;
            pattr.config = 1234;#kprodb 的 id
            pattr.size = sizeof(pattr);
            int efd = syscall(SYS_perf_event_open, &pattr, -1, 0, -1, 0);
            if (efd < 0) {
                printf("perf_event_open error: %s\n", strerror(errno));
                exit(-1);
            }
                  
            int ret = ioctl(efd, PERF_EVENT_IOC_ENABLE, 0);
            if (ret < 0) {
                printf("PERF_EVENT_IOC_ENABLE error: %s\n", strerror(errno));
                exit(-1);
            }
      
            ret = ioctl(efd, PERF_EVENT_IOC_SET_BPF, pfd);
            if (ret < 0) {
                printf("PERF_EVENT_IOC_SET_BPF error: %s\n", strerror(errno));
                exit(-1);
            }
      
            while(1); #不退出看结果
        }
      
  • libbpf
  • linux bpf 内核锚点
    • 从/sys/kernel/debug/tracing/events/目录下开始 tp/目录/目录…
    • 可以一次跟踪父目录的所有锚点 如tp/目录
    • 最后的文件描述了锚点的文本信息 enable filter format id trigger
    • bpf_printk 打印到/sys/kernel/debug/tracing/下的pip
  • kprobes 内核跟踪
    • /sys/kernel/debug/kprobes/ blacklist enabled list
        #添加一个调用do_sys_open函数前监控,打印参数 寄存器%ax、%dx、%cx 3个参数,堆栈$stack 从4个参数开始
        echo 'p:namein do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
        #添加一个调用do_sys_open返回值 $retval 返回值
        echo 'r:nameout do_sys_open $retval' >> /sys/kernel/debug/tracing/kprobe_events
        #启用
        echo 1 > /sys/kernel/debug/tracing/events/kprobes/namein/enable
        echo 1 > /sys/kernel/debug/tracing/events/kprobes/nameout/enable
        #输出
        tail -f /sys/kernel/debug/tracing/trace
        #关闭监控
        echo 0 > /sys/kernel/debug/tracing/events/kprobes/namein/enable
        echo 0 > /sys/kernel/debug/tracing/events/kprobes/nameout/enable
        echo > /sys/kernel/debug/tracing/kprobe_events | echo -:namein >> /sys/kernel/debug/tracing/kprobe_events
        echo > /sys/kernel/debug/tracing/kprobe_events | echo -:nameout >> /sys/kernel/debug/tracing/kprobe_events
      

      ```C #include<linux/init.h> #include<linux/module.h> #include<linux/kernel.h> #include <linux/kprobes.h>

    //统计do_fork()总共执行了几次 static int total_count = 0;

    //前置方法,这里可以拿到方法入参和栈,每次执行do_fork() total_count++ static int handler_pre(struct kprobe *p, struct pt_regs *regs) { total_count++; //printk 打印的日志 可以通过dmesg 命令查看 printk(KERN_INFO “累计调用do_fork[%d]次\n”,total_count); return 0; }

    //后置方法,这里可以拿到方法返回值 static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { } //方法执行失败的回调函数 static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) { printk(KERN_INFO “fault_handler: p->addr = 0x%p, trap #%dn”,p->addr, trapnr); return 0; } //通过kprobe这个数据结构,定义要hook的内核方法名称 static struct kprobe kp = { .symbol_name = “do_fork”, }; //通过register_kprobe 方法更改内核对应方法的指令 static int kprobe_init(void){ int ret; kp.pre_handler = handler_pre; kp.post_handler = handler_post; kp.fault_handler = handler_fault;

          ret = register_kprobe(&kp);
          if (ret < 0) {
                  printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
                  return ret;
          }
          printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
          return 0;   }   //通过unregister_kprobe卸载hook   static void kprobe_exit(void){
          unregister_kprobe(&kp);
          printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);   }
    

    //构造内核模块 module_init(kprobe_init); module_exit(kprobe_exit); MODULE_LICENSE(“GPL”);

    obj-m := xxxx.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -f *.mod.c *.ko *.o

    su root insmod xxx.ko cat /var/log/messages 查看消息 lsmod | grep krpobe rm xxx.ko ```

  • uprobe 用户态追踪
    objdump -d /root/test
    #调用前 打印参数 寄存器%ax、%dx、%cx 3个参数,堆栈$stack 从4个参数开始
    echo 'p:namein /root/test:func_addr' >> /sys/kernel/debug/tracing/uprobe_events
    #返回时 $retval 返回值
    echo 'r:nameout /root/test:func_addr' >> /sys/kernel/debug/tracing/uprobe_events
    run /root/test
    #启动监控
    echo 1 > /sys/kernel/debug/tracing/events/enable
    echo 1 > /sys/kernel/debug/tracing/events/uprobes/namein/enable
    echo 1 > /sys/kernel/debug/tracing/events/uprobes/nameout/enable
    #显示信息
    tail -f /sys/kernel/debug/tracing/trace
    #停止
    echo 0 > /sys/kernel/debug/tracing/events/uprobes/namein/enable
    echo 0 > /sys/kernel/debug/tracing/events/uprobes/nameout/enable
    echo 0 > /sys/kernel/debug/tracing/events/enable
    echo -:namein >> /sys/kernel/debug/tracing/uprobe_events
    echo -:nameout >> /sys/kernel/debug/tracing/uprobe_events
    

ebpf

  • 字节码相关
    • 寄存器64位 无论cpu、内核是否是64位的
    • r0 函数返回值
    • r1-r5 函数参数, 数字或栈指针,内核代码只使用栈
    • r6-r9 内核调用后保持不变
    • r10 栈指针只读, 栈大小512字节
    • 指令64位 最多4096条指令,5.1内核100万条,op:8,dreg:4,sreg:4,off:16,imm:32
      • op code:4,source:1,class:3 source表明是寄存器还是立即数
    • 尾调用 一个bpf程序结束调用另一个bpf, 最多33个调用嵌套
    • map 主要用于bpf间传输数据,单个bpf可用64个map
    • /proc/sys/net/core/bpf_jit_enable echo 1 »
    • Netronome’s nfp driver supper jit Offloads to NIC network divce
  • libbpf BPF CO-RE(Compile Once – Run Everywhere)
    • 内核导出 BTF(BPF Type Format)类似DWARF调试信息
      • 比老的内核设置CONFIG_DEBUG_INFO_BTF=y编译内核
      • 新内核直接携带并暴露在/sys/kernel/btf/vmlinux
      • bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
    • clang __builtin_preserve_access_index 重定位信息
      • 导出字段重定位信息(field offset relocation)
    • 用户空间 libbpf加载器
      • 加载 clang -march=bpf xxx.c 编译后的 BPF ELF
      • 根据BTF clang重定位信息 裁剪信息进行 bpf字节码重定向和裁剪
  • libbpf 代码移植写法
    • 内核已经支持BPF_PROG_TYPE_TRACING
      • pid_t pid = __builtin_preserve_access_index(({ task->pid; }));
    • 内核不支持BPF_PROG_TYPE_TRACING
      • bpf_core_read(&pid, sizeof(pid), &task->pid); ==== bpf_probe_read(&pid, sizeof(pid), __builtin_preserve_access_index(&task->pid));
      • inode = BPF_CORE_READ(task, mm, exe_file, f_inode, i_ino);
      • BPF_CORE_READ_INTO(&inode, task, mm, exe_file, f_inode, i_ino);
    • 声明内核全局变量
      • extern u32 LINUX_KERNEL_VERSION __kconfig;
      • if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(4, 11, 0)){}
    • 结构体带版本
      • struct thread_struct {} 最新版系统线程结构体
      • struct thread_struct___v46 {} <= 4.6版本内核的结构体
      • 使用时thread_struct,loader时会自动选择
    • 使用全局静态变量
      • const bool xxx;
      • if (xxx) {}
      • 用户空间控制程序在加载前修改 xxx;
    • 其它宏
      • BPF_CORE_READ_STR_INTO()
      • bpf_core_read_str()
      • bpf_core_field_exists() 判断字段是否存在,
      • bpf_core_field_size() 判断字段大小
      • BPF_CORE_READ_BITFIELD() 直接内存读取
      • BPF_CORE_READ_BITFIELD_PROBED() 底层会调用 bpf_probe_read()

libbpf

#libbpf
    git clone --recurse-submodules git@github.com:libbpf/libbpf.git
    #高版本系统一般已经自带
    yum -y install elfutils-libelf-devel zlib-devel
    cd src
    BUILD_STATIC_ONLY=y OBJDIR=/a/b PKG_CONFIG_PATH=/c/d DESTDIR=/e/f make install
    #不编译只需头文件
    cp libbpf/src/libbpf_version.h libbpf/src/libbpf_coommon.h libbpf/src/libbpf_legacy.h libbpf/src/libbpf.h ./bpf/

# bpftool
    yum -y install elfutils-libelf-devel zlib-devel binutils-devel libcap-devel
    git clone --recurse-submodules git@github.com:libbpf/bpftool.git
    git submodule update --init
    cd src
    #OUTPUT/libbpf/include/bpf OUTPUT/libbpf/libbpf.a
    mkdir -p /a/b
    vi ./src/skeleton/pid_iter.bpf.c <vmlinux.h> -> "../libbpf/.github/actions/build-selftests/vmlinux.h"
    OUTPUT=/a/b/ make install

android ebpf

  • android 9.0以上
  • 需root
  • 可以使用libbpf