Lesson 14. strlen_count.py
这一课中我们的程序要跟踪的是strlen()这个用户态库函数,并统计这个函数中不同参数出现的频次。用例输出如下:
代码如下:
#!/usr/bin/python
#
# strlen_count Trace strlen() and print a frequency count of strings.
# For Linux, uses BCC, eBPF. Embedded C.
#
# Written as a basic example of BCC and uprobes.
#
# Also see strlensnoop.
#
# Copyright 2016 Netflix, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from __future__ import print_function
from bcc import BPF
from bcc.utils import printb
from time import sleep
# load BPF program
b = BPF(text="""
#include <uapi/linux/ptrace.h>
struct key_t {
char c[80];
};
BPF_HASH(counts, struct key_t);
int count(struct pt_regs *ctx) {
if (!PT_REGS_PARM1(ctx))
return 0;
struct key_t key = {};
u64 zero = 0, *val;
bpf_probe_read_user(&key.c, sizeof(key.c), (void *)PT_REGS_PARM1(ctx));
// could also use `counts.increment(key)`
val = counts.lookup_or_try_init(&key, &zero);
if (val) {
(*val)++;
}
return 0;
};
""")
b.attach_uprobe(name="c", sym="strlen", fn_name="count")
# header
print("Tracing strlen()... Hit Ctrl-C to end.")
# sleep until Ctrl-C
try:
sleep(99999999)
except KeyboardInterrupt:
pass
# print output
print("%10s %s" % ("COUNT", "STRING"))
counts = b.get_table("counts")
for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
printb(b"%10d \"%s\"" % (v.value, k.c))
本节需要学习:
- PT_REGS_PARM1(ctx): 这个宏用于获取被跟踪函数strlen()的第一个参数,也就是要处理的字符串。
- b.attach_uprobe(name="c", sym="strlen", fn_name="count"):Attach "c"库(if this is the main program, use its pathname),跟踪其用户态函数strlen(),并在strlen()函数执行时调用我们的挂接的uprobe函数count()。
Lesson 15. nodejs_http_server.py
这一次我们将跟踪用户态静态定义tracing (USDT) 探针,这就是用户态版本的tracepoint。
代码如下:
#!/usr/bin/python
#
# nodejs_http_server Basic example of node.js USDT tracing.
# For Linux, uses BCC, BPF. Embedded C.
#
# USAGE: nodejs_http_server PID
#
# Copyright 2016 Netflix, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from __future__ import print_function
from bcc import BPF, USDT
from bcc.utils import printb
import sys
if len(sys.argv) < 2:
print("USAGE: nodejs_http_server PID")
exit()
pid = sys.argv[1]
debug = 0
# load BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
int do_trace(struct pt_regs *ctx) {
uint64_t addr;
char path[128]={0};
bpf_usdt_readarg(6, ctx, &addr);
bpf_probe_read_user(&path, sizeof(path), (void *)addr);
bpf_trace_printk("path:%s\\n", path);
return 0;
};
"""
# enable USDT probe from given PID
u = USDT(pid=int(pid))
u.enable_probe(probe="http__server__request", fn_name="do_trace")
if debug:
print(u.get_text())
print(bpf_text)
# initialize BPF
b = BPF(text=bpf_text, usdt_contexts=[u])
# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "ARGS"))
# format output
while 1:
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
print("value error")
continue
except KeyboardInterrupt:
exit()
printb(b"%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
本节需要学习:
- bpf_usdt_readarg(6, ctx, &addr): 从USDT probe中读取第6个参数地址到addr中。
- bpf_probe_read_user(&path, sizeof(path), (void *)addr):现在将addr 指向path。
- u = USDT(pid=int(pid)): 使用指定的PID初始化USDT tracing。
- u.enable_probe(probe="http__server__request", fn_name="do_trace"): 将我们的BPF C函数do_trace() 添加到USDT探测点Node.js的http__server__request 处。
- b = BPF(text=bpf_text, usdt_contexts=[u]): 需要将我们的USDT对象"u"传入到BPF对象的创建函数中。
Lesson 16. task_switch.c
这是前期课程中已经包含的一部分内容, 这里主要是回顾和巩固一下我们前面已经学过的内容。
这是一个比Hello World稍复杂的跟踪实例。这个程序会在每次任务切换时被调用,它会把新/老任务的pids记录到BPF map中。
下面这段C程序引入了一个新概念: 参数 prev。这个参数由BCC前端进行特殊处理,因此对该变量的访问是从kprobe基础结构传递的已保存上下文中获取。从位置1开始的参数的原型应该与正在被探测的内核函数的原型匹配。这样一来程序就对函数参数的访问权无缝对接。
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct key_t {
u32 prev_pid;
u32 curr_pid;
};
BPF_HASH(stats, struct key_t, u64, 1024);
int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
struct key_t key = {};
u64 zero = 0, *val;
key.curr_pid = bpf_get_current_pid_tgid();
key.prev_pid = prev->pid;
// could also use `stats.increment(key);`
val = stats.lookup_or_try_init(&key, &zero);
if (val) {
(*val)++;
}
return 0;
}
用户态组件加载上面文件的内容并attache到finish_task_switch()内核函数的kprobe探测钩子中。 通过[]这个操作将可以访问到程序中BPF对象中BPF_HASH元素,这样就可以直接访问到内核中的变量。使用这个对象就像python中的其他对象一样:read, update, and deletes 等等内置的函数都是标准配置。
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct key_t {
u32 prev_pid;
u32 curr_pid;
};
BPF_HASH(stats, struct key_t, u64, 1024);
int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
struct key_t key = {};
u64 zero = 0, *val;
key.curr_pid = bpf_get_current_pid_tgid();
key.prev_pid = prev->pid;
// could also use `stats.increment(key);`
val = stats.lookup_or_try_init(&key, &zero);
if (val) {
(*val)++;
}
return 0;
}
Lesson 17. Further Study
更多例子可以参见Sasha Goldshtein's linux-tracing-workshop。
总结
- BCC的代码分为python和C两部分,C代码运行在内核态,用于打点采集系统调用的信息,然后将结果返回给python代码
- BCC的核心为C代码,需要了解系统调用接口,作为信息采集点;以及BCC本身提供的C代码,提供了获取当前时间,进程PID,map数据结构,输出等功能。
- 推荐
TRACEPOINT_PROBE
进行打点检测,perf list
可以列出系统支持的tracepoint,该tracepoint基本包含了系统的大部分系统调用,如clone
,sync
等(要求内核版本>=4.7),但网络相关的还是需要使用kprobes对内核函数进行打点,如icmp_rcv
。