翼度科技»论坛 云主机 LINUX 查看内容

【eBPF-03】进阶:BCC 框架中 BPF 映射的应用 v1.0

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
eBPF 中实现内核态代码与用户态代码是可以实时通信的,这主要靠 BPF 映射 来实现。
BPF 映射 是内核空间的一段内存,以 键值对 的方式存储。内核态程序可以直接访问 BPF 映射,用户态需要通过系统调用才能访问这段地址。
BPF 映射有很多种类型,如下表所示。
类型说明BPF_HASH哈希表BPF_ARRAY数组BPF_HISTOGRAM直方图BPF_STACK_TRACE跟踪栈BPF_PERF_ARRAY硬件性能数组BPF_PERCPU_HASH单CPU哈希表BPF_PERCPU_ARRAY单CPU数组BPF_LPM_TRIE最长前缀匹配映射BPF_PROG_ARRAY尾调用程序数组......本文列举了使用 eBPF + BCC 实现的多个工具源码,索引如下表。
工具名称工具用途工具使用的 MAP涉及的具体 MAP 用法killsnoop检测进程被 kill 时的状态BPF_HASH内核态传递数据filetop检测指定时间周期内读写文件的 top 列表BPF_HASH内核态向用户态传递数据usercheck检测当前进程执行的用户BPF_HASH用户态向内核态传递数据pidpersec检测周期内通过 fork 创建的进程总数BPF_ARRAY保存全局数据vfsreadlat周期性打印 vfs 文件读取操作耗时分布情况BPF_HISTOGRAM直方图统计stacksnoop打印内核某个函数执行时的调用栈信息BPF_STACK_TRACE内核函数跟踪栈1 哈希表

哈希表与我们熟悉的 hash_map 实现和用法相似,都是由 key/value 组成,在 eBPF 程序中按需分配和释放。
我们来看几个应用了 BPF_HASH 的例子。
工具1 killsnoop

(改编自 Brendan Gregg 大神给出的源码)—— 用来检测进程被 kill 时的状态。
点击查看代码
  1. from bcc import BPF
  2. from bcc.utils import printb
  3. from time import strftime
  4. # define BPF program
  5. bpf_text = """
  6. #include <uapi/linux/ptrace.h>
  7. #include <linux/sched.h>
  8. struct val_t {
  9.         u32 pid;
  10.         int sig;
  11.         int tpid;
  12.         char comm[TASK_COMM_LEN];
  13. };
  14. struct data_t {
  15.         u32 pid;
  16.         int tpid;
  17.         int sig;
  18.         int ret;
  19.         char comm[TASK_COMM_LEN];
  20. };
  21. // 定义 BPF_HASH 名称为 infotmp,key 类型为 u32,val 类型为 struct val_t
  22. BPF_HASH(infotmp, u32, struct val_t);
  23. BPF_PERF_OUTPUT(events);
  24. int syscall__kill(struct pt_regs *ctx, int tpid, int sig) {
  25.         u64 pid_tgid = bpf_get_current_pid_tgid();
  26.         u32 pid = pid_tgid >> 32;
  27.         u32 tid = (u32)pid_tgid;
  28.         struct val_t val = {.pid = pid};
  29.         if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) {
  30.                 val.tpid = tpid;
  31.                 val.sig = sig;
  32.                 infotmp.update(&tid, &val);                // 根据 (key, val) 更新 BPF_HASH
  33.         }
  34.         return 0;
  35. };
  36. int do_ret_sys_kill(struct pt_regs *ctx) {
  37.         struct data_t data = {};
  38.         struct val_t *valp;
  39.         u64 pid_tgid = bpf_get_current_pid_tgid();
  40.         u32 pid = pid_tgid >> 32;
  41.         u32 tid = (u32)pid_tgid;
  42.         valp = infotmp.lookup(&tid);                // 根据 key 查找 BPF_HASH
  43.         if (valp == 0) {
  44.                 // missed entry
  45.                 return 0;
  46.         }
  47.         bpf_probe_read_kernel(&data.comm, sizeof(data.comm), valp->comm);
  48.         data.pid = pid;
  49.         data.tpid = valp->tpid;
  50.         data.ret = PT_REGS_RC(ctx);
  51.         data.sig = valp->sig;
  52.         events.perf_submit(ctx, &data, sizeof(data));
  53.         infotmp.delete(&tid);                // 根据 key 删除 BPF_HASH 记录
  54.         return 0;
  55. }
  56. """
  57. # initialize BPF
  58. b = BPF(text=bpf_text)
  59. kill_fnname = b.get_syscall_fnname("kill")
  60. b.attach_kprobe(event=kill_fnname, fn_name="syscall__kill")
  61. b.attach_kretprobe(event=kill_fnname, fn_name="do_ret_sys_kill")
  62. # header
  63. print("%-9s %-16s %-16s %-4s %-16s %s" % ("TIME", "PID", "COMM", "SIG",  "TPID", "RESULT"))
  64. # process event
  65. def print_event(cpu, data, size):
  66.     event = b["events"].event(data)
  67.     printb(b"%-9s %-16d %-16s %-4d %-16d %d" % (strftime("%H:%M:%S").encode('ascii'), event.pid, event.comm, event.sig, event.tpid, event.ret))
  68. # loop with callback to print_event
  69. b["events"].open_perf_buffer(print_event)
  70. while 1:
  71.     try:
  72.         b.perf_buffer_poll()
  73.     except KeyboardInterrupt:
  74.         exit()
复制代码
这个例子给出了 BPF_HASH 在内核态不同函数事件阶段之间传递消息的基本使用方式。主要有几个关键点:

  • BPF_HASH(infotmp, u32, struct val_t):定义一个 BPF 哈希表,前三个参数分别为:哈希表名称,key 的类型,val 的类型;
  • infotmp.update(&tid, &val):更新 (key, val);
  • infotmp.lookup(&tid):查询 key 对应的 val;
  • infotmp.delete(&tid):删除 (key, val);
这段程序最终的运行结果如下。

工具2 filetop

(同样改编自 Brendan Gregg 的源码)—— 用来检测指定时间周期内读写文件的 top 列表。
点击查看代码
  1. #!/usr/bin/python3
  2. from bcc import BPF
  3. from time import sleep, strftime
  4. # define BPF program
  5. bpf_text = """
  6. #include <uapi/linux/ptrace.h>
  7. #include <linux/blkdev.h>
  8. // the key for the output summary
  9. struct info_t {
  10.         unsigned long inode;
  11.         dev_t dev;
  12.         dev_t rdev;
  13.         u32 pid;
  14.         u32 name_len;
  15.         char comm[TASK_COMM_LEN];                // 进程名
  16.         // de->d_name.name may point to de->d_iname so limit len accordingly
  17.         char name[DNAME_INLINE_LEN];                // 文件名
  18.         char type;
  19. };
  20. // the value of the output summary
  21. struct val_t {
  22.         u64 reads;
  23.         u64 writes;
  24.         u64 rbytes;
  25.         u64 wbytes;
  26. };
  27. BPF_HASH(counts, struct info_t, struct val_t);                // 定义 HASH 表,key 和 val 均为一个结构体
  28. static int do_entry(struct pt_regs *ctx, struct file *file, char __user *buf, size_t count, int is_read) {
  29.         u32 tgid = bpf_get_current_pid_tgid() >> 32;
  30.         u32 pid = bpf_get_current_pid_tgid();
  31.         // skip I/O lacking a filename
  32.         struct dentry *de = file->f_path.dentry;
  33.         int mode = file->f_inode->i_mode;
  34.         struct qstr d_name = de->d_name;
  35.         if (d_name.len == 0)
  36.                 return 0;
  37.         // store counts and sizes by pid & file
  38.         struct info_t info = {
  39.                 .pid = pid,
  40.                 .inode = file->f_inode->i_ino,
  41.                 .dev = file->f_inode->i_sb->s_dev,
  42.                 .rdev = file->f_inode->i_rdev,
  43.         };
  44.         bpf_get_current_comm(&info.comm, sizeof(info.comm));
  45.         info.name_len = d_name.len;
  46.         bpf_probe_read_kernel(&info.name, sizeof(info.name), d_name.name);
  47.         // 区分操作的类型
  48.         if (S_ISREG(mode)) {
  49.                 info.type = 'R';
  50.         } else if (S_ISSOCK(mode)) {
  51.                 info.type = 'S';
  52.         } else {
  53.                 info.type = 'O';
  54.         }
  55.         struct val_t *valp, zero = {};
  56.         valp = counts.lookup_or_try_init(&info, &zero);                // 内核态尝试获取指定 key 的 val,若 val == NULL,则赋予一个默认值
  57.         if (valp) {
  58.                 if (is_read) {
  59.                         valp->reads++;
  60.                         valp->rbytes += count;
  61.                 } else {
  62.                         valp->writes++;
  63.                         valp->wbytes += count;
  64.                 }
  65.         }
  66.         return 0;
  67. }
  68. int trace_read_entry(struct pt_regs *ctx, struct file *file, char __user *buf, size_t count) {
  69.         return do_entry(ctx, file, buf, count, 1);
  70. }
  71. int trace_write_entry(struct pt_regs *ctx, struct file *file, char __user *buf, size_t count) {
  72.         return do_entry(ctx, file, buf, count, 0);
  73. }
  74. """
  75. # initialize BPF
  76. b = BPF(text=bpf_text)
  77. b.attach_kprobe(event="vfs_read", fn_name="trace_read_entry")
  78. b.attach_kprobe(event="vfs_write", fn_name="trace_write_entry")
  79. # check whether hash table batch ops is supported
  80. htab_batch_ops = True if BPF.kernel_struct_has_field(b'bpf_map_ops', b'map_lookup_and_delete_batch') == 1 else False
  81. DNAME_INLINE_LEN = 32  # linux/dcache.h
  82. interval = 1
  83. exiting = 0
  84. def sort_fn(counts):
  85.         return (counts[1].rbytes + counts[1].wbytes + counts[1].reads + counts[1].writes)
  86. while 1:
  87.         try:
  88.                 sleep(interval)
  89.         except KeyboardInterrupt:
  90.                 exit()
  91.         print('Tracing... Output every %d secs. Hit Ctrl-C to end' % interval)
  92.         print("%-7s %-16s %-6s %-6s %-7s %-7s %1s %s" % ("TID", "COMM", "READS", "WRITES", "R_Kb", "W_Kb", "T", "FILE"))
  93.         # 用户态获取 BPF_HASH
  94.         counts = b.get_table("counts")
  95.         # 这里遍历整个 BPF_HASH
  96.         for k, v in reversed(sorted(counts.items_lookup_and_delete_batch()
  97.                                 if htab_batch_ops else counts.items(),
  98.                                 key=sort_fn)):
  99.                 name = k.name.decode('utf-8', 'replace')
  100.                 if k.name_len > DNAME_INLINE_LEN:
  101.                         name = name[:-3] + "..."
  102.         # print line
  103.                 print("%-7d %-16s %-6d %-6d %-7d %-7d %1s %s" % (k.pid,
  104.                         k.comm.decode('utf-8', 'replace'), v.reads, v.writes,
  105.                         v.rbytes / 1024, v.wbytes / 1024,
  106.                         k.type.decode('utf-8', 'replace'), name))
  107.         # 用户态清空 BPF_HASH
  108.         if not htab_batch_ops:
  109.                 counts.clear()
复制代码
这个例子给出了 BPF_HASH 用于内核态向用户态传递数据的场景。主要有以下几个关键点:

  • BPF_HASH(counts, struct info_t, struct val_t):本次声明的哈希表,key 和 val 均为一个结构体,这在实操上是常见的。不过要注意 eBPF 运行栈大小限制。
  • valp = counts.lookup_or_try_init(&info, &zero):内核态的查找辅助函数,和 lookup() 用法相同,不过此函数安全性更高。若获取的 val 为空,则为其赋予一个初始值 zero。(注意,获取的 val 是一个指针,可以直接操作器结构体数据)
  • counts = b.get_table("counts"):用于用户态获取定义的 eBPF_HASH。
  • counts.items():返回所有的 (key, val),用于用户态遍历哈希表。
  • counts.clear():清空整张哈希表。
  • htab_batch_ops:这段代码定义了一个特殊的标志位,用来判断当前版本的 eBPF 是否支持 items_lookup_and_delete_batch() 辅助函数。
  • items_lookup_and_delete_batch():内核 5.6 版本才引入该函数。作用同 items() + clear(),即,获取所有的 (key, val),并清空整个哈希表。
这段代码通过 interval 变量控制检测周期(当前为 1s),并按照这个周期,检测打印进程访问文件的一个热度表,按照字节降序排列。如下图所示:

工具3 usercheck

(改编自《Learning eBPF》一书第二章给给出的部分代码)——用来检测当前进程执行的用户。
点击查看代码
  1. #!/usr/bin/python3
  2. from bcc import BPF
  3. from ctypes import *
  4. bpf_text = '''
  5. struct user_msg_t {
  6.         char message[12];
  7. };
  8. BPF_HASH(config, u32, struct user_msg_t);
  9. BPF_PERF_OUTPUT(events);
  10. struct data_t {
  11.         int pid;
  12.         int uid;
  13.         char command[16];
  14.         char message[12];
  15. };
  16. int check_user(void *ctx) {
  17.         struct data_t data = {};
  18.         struct user_msg_t *p;
  19.         char message[12] = "Hello World";
  20.         data.pid = bpf_get_current_pid_tgid() >> 32;
  21.         data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
  22.         bpf_get_current_comm(&data.command, sizeof(data.command));
  23.         p = config.lookup(&data.uid);
  24.         if (p != 0) {
  25.                 bpf_probe_read_kernel(&data.message, sizeof(data.message), p->message);
  26.         } else {
  27.                 bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
  28.         }
  29.         events.perf_submit(ctx, &data, sizeof(data));
  30.         return 0;
  31. }
  32. '''
  33. # initialize BPF
  34. b = BPF(text=bpf_text)
  35. execve_fnname = b.get_syscall_fnname("execve")
  36. b.attach_kprobe(event=execve_fnname, fn_name="check_user")
  37. # 用户态获取 HASH
  38. config = b.get_table("config")
  39. # 用户态修改 HASH
  40. config[c_int(0)] = create_string_buffer(b"Hello, Root!")
  41. config[c_int(1000)] = create_string_buffer(b"Hello, User 501!")
  42. print("%-10s %-10s %-16s %s" % ("PID", "UID", "COMM", "MSG"))
  43. def print_event(cpu, data, size):
  44.         event = b["events"].event(data)
  45.         print("%-10d %-10d %-16s %s" % (event.pid, event.uid, event.command.decode('utf-8'), event.message.decode('utf-8')))
  46. b["events"].open_perf_buffer(print_event)
  47. while 1:
  48.         try:
  49.                 b.perf_buffer_poll()
  50.         except KeyboardInterrupt:
  51.                 exit()
复制代码
这个例子给出了一个用户态主动修改 BPF_HASH 的情况。关键点:

  • config[c_int(0)] = create_string_buffer(b"Hello, Root!"):修改的方式与常规的 hash_map 类似,但是,key 和 val 的类型转换是必不可少的步骤。
python 到 C 的类型转换可以通过 ctypes 库来实现。可直接通过 pip3 install stypes 安装。
注意:
用户态可以通过这种方式向内核态传入数据,但千万要慎之又慎用这种方式去控制内核 BPF 程序的执行流程。内核态无法阻塞等待用户态处理复杂逻辑后的响应(如创建另一个进程)。[引用-1]
举例来说,当这个程序不是在最初就设定了 BPF_HASH 的值,而是通过内核传出的用户 uid动态地去打开系统文件检索用户 username,那么这个工具将无法实现预期功能了。这是因为,在当前进程执行的 execve 挂载点,用户态并没有来得及执行下一个 open 进程,因此,其通过 lookup() 获得的 username 将始终为空。
运行结果:

2 数组

工具4 pidpersec

(改编自 Brendan Gregg 给出的源码)—— 用于检测周期内通过 fork 创建的进程总数。
点击查看代码
  1. #!/usr/bin/python3
  2. from bcc import BPF
  3. from ctypes import c_int
  4. from time import sleep, strftime
  5. # load BPF program
  6. b = BPF(text="""
  7. #include <uapi/linux/ptrace.h>
  8. enum stat_types {
  9.         S_COUNT = 1,
  10.         S_MAXSTAT
  11. };
  12. BPF_ARRAY(stats, u64, S_MAXSTAT);                // 创建 ARRAY,名称为 stats,val 的类型为 u64,val 的最大数量为 S_MAXSTAT = 2
  13. static void stats_increment(int key) {
  14.         stats.atomic_increment(key);                // 索引为 key 的 val 原子自增操作
  15. }
  16. void do_count(struct pt_regs *ctx) { stats_increment(S_COUNT); }
  17. """)
  18. b.attach_kprobe(event="sched_fork", fn_name="do_count")
  19. # stat indexes
  20. S_COUNT = c_int(1)
  21. interval = 1                # 打印周期
  22. # header
  23. print("Tracing... Ctrl-C to end.")
  24. # output
  25. while (1):
  26.         try:
  27.                 sleep(interval)
  28.         except KeyboardInterrupt:
  29.                 exit()
  30.         print("%s: PIDs/sec: %d" % (strftime("%H:%M:%S"),
  31.                 b["stats"][S_COUNT].value))
  32.         b["stats"].clear()
复制代码
同为 BPF 映射类型,BPF_ARRAY 可以被看作为一类特殊的 BPF_HASH( ARRAY 的 key 从 0 开始,为非零整数),但有一下几点区别。

  • BPF_ARRAY 在初始化时会预先分配空间,并设置为零。
  • BPF_ARRAY 的大小是固定的,其元素不能被删除。
  • BPF_ARRAY 通常用于保存 val 可能会更新的信息,由于 key 默认为非负整数索引,因此,其固定索引的 val 通常代表一个意义。
  • BPF_ARRAY 和 BPF_HASH 一样,在执行更新操作时,不能保证原子性。需要进行额外的手段来保证原子操作。
实际上, HASH 和 ARRAY 在初始化时,都有一个默认的最大 size(10240)。只不过在使用 ARRAY 时,通常会指定其最大 size,以免预分配资源空间的浪费。
在此代码中,给出了一个 BPF_ARRAY 的常见用法,即,作为一个全局的计数器(跨用户态和内核态)。当然,若你问用 BPF_HASH 可不可以实现呢?答案自然是可以。数据结构并没有好坏之分,只有适合不适合之别。在此代码中:

  • BPF_ARRAY(stats, u64, S_MAXSTAT):定义一个数组,接受三个参数,分别为数组名,数组元素类型,数组大小。
  • stats.atomic_increment(key):由于修改数组元素时,不能保证原子性,因此这里需要手动调用辅助函数 atomic_increment() 为指定 key 的 val 做原子自增。
此工具运行截图。周期性打印 fork 出来的进程数量。

3 直方图

工具5 vfsreadlat

(改编自 Brendan Gregg 给出的源码)—— 用于周期性打印 vfs 文件读取操作耗时分布情况。
点击查看代码
  1. from bcc import BPF
  2. from time import sleep
  3. bpf_src = '''
  4. #include <uapi/linux/ptrace.h>
  5. BPF_HASH(start, u32);
  6. BPF_HISTOGRAM(dist);                // 创建一个直方图映射,名称为 dist
  7. int do_entry(struct pt_regs *ctx) {
  8.         u32 pid;
  9.         u64 ts;
  10.         pid = bpf_get_current_pid_tgid();
  11.         ts = bpf_ktime_get_ns();
  12.         start.update(&pid, &ts);
  13.         return 0;
  14. }
  15. int do_return(struct pt_regs *ctx) {
  16.         u32 pid;
  17.         u64 *tsp, delta;
  18.         pid = bpf_get_current_pid_tgid();
  19.         tsp = start.lookup(&pid);
  20.         if (tsp != 0) {
  21.                 delta = bpf_ktime_get_ns() - *tsp;
  22.                 dist.increment(bpf_log2l(delta / 1000));        // 修改直方图数据,key 为 bpf_log2l(delta / 1000),即 千分之差值的 2 的对数
  23.                 start.delete(&pid);
  24.         }
  25.         return 0;
  26. }
  27. '''
  28. # load BPF program
  29. b = BPF(text = bpf_src)
  30. b.attach_kprobe(event="vfs_read", fn_name="do_entry")
  31. b.attach_kretprobe(event="vfs_read", fn_name="do_return")
  32. # header
  33. print("Tracing... Hit Ctrl-C to end.")
  34. interval = 5
  35. count = -1
  36. loop = 0
  37. while (1):
  38.         if count > 0:
  39.                 loop += 1
  40.                 if loop > count:
  41.                         exit()
  42.         try:
  43.                 sleep(interval)
  44.         except KeyboardInterrupt:
  45.                 pass; exit()
  46.         print()
  47.         b["dist"].print_log2_hist("usecs")                # 打印直方图
  48.         b["dist"].clear()
复制代码
这个例子给出了一个新的 BPF_MAP 类型:直方图 BPF_HISTOGRAM。有以下几个关键:

  • BPF_HISTOGRAM(dist):创建了一个名为 dist 的直方图,默认值 BPF_HISTOGRAM(name, key_type=int, size=64)。
  • dist.increment():直方图调用 increment() 将值自增,来进行统计。
  • bpf_log2l(delta / 1000):该函数返回 log_2(delta/1000) 的值。这样做是为了压缩直方图统计范围。
  • b["dist"].print_log2_hist("usecs"):指定统计列名称为 usecs,打印直方图。
输出结果:

4 跟踪栈

工具6 stacksnoop

(改编自 Brendan Gregg 给出的源码)—— 用于打印内核某个函数执行时的调用栈信息。
点击查看代码
  1. from __future__ import print_function
  2. from bcc import BPF
  3. import argparse
  4. import time
  5. parser = argparse.ArgumentParser(
  6.         description="Trace and print kernel stack traces for a kernel function",
  7.         formatter_class=argparse.RawDescriptionHelpFormatter)
  8. parser.add_argument("function", help="kernel function name")
  9. function = parser.parse_args().function
  10. offset = False
  11. # define BPF program
  12. bpf_text = """
  13. #include <uapi/linux/ptrace.h>
  14. #include <linux/sched.h>
  15. struct data_t {
  16.         u64 stack_id;
  17.         u32 pid;
  18.         char comm[TASK_COMM_LEN];
  19. };
  20. BPF_STACK_TRACE(stack_traces, 128);                // 定义跟踪栈
  21. BPF_PERF_OUTPUT(events);
  22. void trace_stack(struct pt_regs *ctx) {
  23.         u32 pid = bpf_get_current_pid_tgid() >> 32;
  24.         struct data_t data = {};
  25.         data.stack_id = stack_traces.get_stackid(ctx, 0);                遍历通过 ctx 找到的堆栈,返回它的唯一 ID
  26.         data.pid = pid;
  27.         bpf_get_current_comm(&data.comm, sizeof(data.comm));
  28.         events.perf_submit(ctx, &data, sizeof(data));
  29. }
  30. """
  31. # initialize BPF
  32. b = BPF(text=bpf_text)
  33. b.attach_kprobe(event=function, fn_name="trace_stack")
  34. TASK_COMM_LEN = 16  # linux/sched.h
  35. matched = b.num_open_kprobes()                # 判断输入的 function 是否合法
  36. if matched == 0:
  37.         print("Function "%s" not found. Exiting." % function)
  38.         exit()
  39. stack_traces = b.get_table("stack_traces")                # 获取跟踪栈
  40. start_ts = time.time()
  41. # header
  42. print("%-18s %-12s %-6s %-3s %s" % ("TIME(s)", "COMM", "PID", "CPU", "FUNCTION"))
  43. def print_event(cpu, data, size):
  44.         event = b["events"].event(data)
  45.         ts = time.time() - start_ts
  46.         print("%-18.9f %-12.12s %-6d %-3d %s" % (ts, event.comm.decode('utf-8', 'replace'), event.pid, cpu, function))
  47.         for addr in stack_traces.walk(event.stack_id):                # 根据 stack.id 遍历堆栈
  48.                 sym = b.ksym(addr, show_offset=offset).decode('utf-8', 'replace')                # 将一个内核地址翻译成内核函数名
  49.                 print("\t%s" % sym)
  50.         print()
  51. b["events"].open_perf_buffer(print_event)
  52. while 1:
  53.         try:
  54.                 b.perf_buffer_poll()
  55.         except KeyboardInterrupt:
  56.                 exit()
复制代码
这个例子给出了 BPF_STACK_TRACE 跟踪栈的用法。关键在于:

  • BPF_STACK_TRACE(stack_traces, 128):定义一个跟踪栈,深度为 128。
  • stack_traces.get_stackid(ctx, 0):遍历通过 ctx 找到的堆栈,返回它的唯一 ID。
  • stack_traces = b.get_table("stack_traces"):用户态获取跟踪栈。
  • for addr in stack_traces.walk(event.stack_id):根据跟踪栈的唯一 id 遍历栈内元素,函数调用地址信息。拿到地址信息后,通过 b.ksym() 函数将其翻译为内核函数名。注意,b.ksym() 函数 接收一个 show_offset 参数,用于控制是否显示偏移地址。
  • matched = b.num_open_kprobes():另外,这段程序最终接收一个参数,作为跟踪的内核函数名。因此需要判断其是否合法。num_open_kprobes() 将返回能够匹配上的内核探针数量,这里被应用于检测输入的内核函数是否合法。
跟踪一个函数 do_execve,stacksnoop 运行效果如下:

总结

篇幅有限,本文先介绍这六个工具,主要涵盖了 BPF_HASH / BPF_ARRAY / BPF_HISTOGRAM / BPF_STACK_TRACE 这四种最常见的 BPF 映射 的使用方法。后面有精力的话,再补充 BPF 映射 的其他类型在 BCC 框架中的用法。
BCC 框架相关的中文材料目前不是很多,参考书也比较有限,本文涉及的源码大多改编自 Brendan Gregg 大神的开源项目,项目地址( https://github.com/iovisor/bcc )。感兴趣的朋友可以一起交流学习!

来源:https://www.cnblogs.com/lianyihong/p/17943519
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具