bcc Reference Guide 中文翻译

bcc Reference Guide

BPF C

这一部分是 bcc 程序的 c 部分

Events & Arguments

1、kprobes

语法格式:kprobe__*kernel_function_name*

kprobe_是一个用来建立一个kprobe的前缀,kprobe就是内核函数调用的动态追踪。后半部分是要追踪的内核函数的名称。这种方式是第一种使用kprobe的方式,还有另外一种方式来使用kprobe,就是使用BPF.attach_kprobe()把bcc程序和内核函数相关联,这个后面会介绍。

例如说:

int  kprobe__tcp_v4_connect(struct pt_regs * ctx,struct sock * sk)
    [...]
}

在tcp_v4_connect函数上使用kprobe,参数如下:

  • struct pt_regs *ctx:
  • struct sock *sk:这是tcp_v4_connect 内核函数的第一个参数。

tcp_v4_connect在内核中的定义如下:

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len);

第一个参数总是 struct pt_regs *,其余的参数就是这个内核函数的参数(如果不使用的话,就不用写了)。

注:该用例出自:bcc/examples/tracing/tcpv4connect.py

2、kretprobes

语法格式:kretprobe__*kernel_function_name*

这里需要关注一下kprobe、kretprobes,可以参考一下文档:

http://adam8157.info/blog/2013/06/debugging-kernel-with-kprobes/

https://www.kernel.org/doc/Documentation/kprobes.txt

kretprobe__是一个用来创建kretprobe的前缀,kretprobe就是对内核函数return的动态追踪。后面的是内核函数名称,和前面的一样,也可以声明一个普通的c函数,然后使用BPF.attach_kretprobe()和内核函数相关联。

返回的参数可以在c程序中使用PT_REGS_RC()来获得,给出一个例子:

int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{
    int ret = PT_REGS_RC(ctx);
    [...]
}

这个就对tcp_v4_connect的返回值使用了kretprobe,并且把该函数的返回值保存到了ret中。

3、Tracepoint

语法格式:TRACEPOINT_PROBE(*category*, *event*)

这是一个宏,用于使用instruments由category:event定义的tracepoint

参数可以通过args结构获得,也就是tracepoint的参数。

一种查看这些格式的方法是查看:/sys/kernel/debug/tracing/events/category/event/format.

注意这里在系统中进入/sys/kernel/debug/tracing/events/,ls一下看到的就是category,再进去一个文件夹就是event,进入对应的event文件夹,再cat format,就可以看到tracepoint的参数了

在需要上下文作为参数的每个函数中,可以使用args结构代替ctx。这主要包括perf_submit()。

例子:

TRACEPOINT_PROBE(random, urandom_read) {
    // args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
    bpf_trace_printk("%d\\n", args->got_bits);
    return 0;
}

这个例子是对random:urandom_read跟踪点进行检测,并输出tracepoint的参数got_bits。

注:在Linux中打开:/sys/kernel/debug/tracing/events/random/urandom_read,之后cat format:

name: urandom_read
ID: 1123
format:
	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
	field:int common_pid;	offset:4;	size:4;	signed:1;

	field:int got_bits;	offset:8;	size:4;	signed:1;
	field:int pool_left;	offset:12;	size:4;	signed:1;
	field:int input_left;	offset:16;	size:4;	signed:1;

print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left %d", REC->got_bits, REC->pool_left, REC->input_left

信息是很全的,最后连打印的格式都写好了,但是还需要看一下tracepoint的用法。

4、uprobes

使用uprobe的方式只有先声明一个c函数,然后通过BPF.at tach_uprobe()(稍后讨论)将其关联为Python中的uprobe探针。

参数可以使用 PT_REGS_PARMn宏进行检查。

一个例子:

int count(struct pt_regs *ctx) {
    char buf[64];
    bpf_probe_read(&buf, sizeof(buf), (void *)PT_REGS_PARM1(ctx));
    bpf_trace_printk("%s %d", buf, PT_REGS_PARM2(ctx));
    return(0);
}

把第一个参数读取成字符串,把第二个参数打印成整数。

5、uretprobes

使用uretprobe的方式只有先声明一个c函数,然后通过BPF.at tach_uretprobe()(稍后讨论)将其关联为Python中的uretprobe探针。

给定函数声明为:*function_name*(struct pt_regs *ctx),返回值可就是:PT_REGS_RC(ctx)

一个例子:

BPF_HISTOGRAM(dist);
int count(struct pt_regs *ctx) {
    dist.increment(PT_REGS_RC(ctx));
    return 0;
}

这将增加由返回值索引为bucket的 dist 直方图。

6、USDT probes

这些是用户静态定义的跟踪(USDT)探针,可以将其放置在某些应用程序或库中,以提供等效于用户级别的跟踪点。为USDT支持方法提供的主要BPF方法是Enable_probe()。使用USDT探针首先要声明一个c函数,然后通过USDT.enable_probe()将其关联为Python中的USDT探针。

参数可以这样获取:bpf_usdt_readarg(index, ctx, &addr)

一个例子:

int do_trace(struct pt_regs *ctx) {
    uint64_t addr;
    char path[128];
    bpf_usdt_readarg(6, ctx, &addr);
    bpf_probe_read(&path, sizeof(path), (void *)addr);
    bpf_trace_printk("path:%s\\n", path);
    return 0;
};

这个在读取第六个USDT参数,之后将它转换成字符串存储在path中

通过BPF::initC API中的第三个参数初始化USDT时,如果任何USDT失败init,则整个操作BPF::init都会失败。如果某些USDT init失败,但无关紧要的话,就可以在使用BPF::init_usdt之前使用BPF::init

7、Raw Tracepoints

语法格式:RAW_TRACEPOINT_PROBE(*event*)

这是一个宏,用于使用event定义的raw tracepoint

参数是一个指向bpf_raw_tracepoint_args结构的指针,这个参数在bcc/src/cc/compat/linux/virtual_bpf.h中定义,结构字段args包含raw tracepoint的所有参数,可以在Linux树Include/trace/events目录中找到这些参数。(linux源码)

一个例子:

RAW_TRACEPOINT_PROBE(sched_switch)
{
    // TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next)
    struct task_struct *prev = (struct task_struct *)ctx->args[1];
    struct task_struct *next= (struct task_struct *)ctx->args[2];
    s32 prev_tgid, next_tgid;

    bpf_probe_read(&prev_tgid, sizeof(prev->tgid), &prev->tgid);
    bpf_probe_read(&next_tgid, sizeof(next->tgid), &next->tgid);
    bpf_trace_printk("%d -> %d\\n", prev_tgid, next_tgid);
}

这个例子使用sched:sched_switch tracepoint,并且打印prev和next进程的tgid号。

注:在Linux树Include/trace/events/sched.h中可以找到关于该tracepoint的定义:

TRACE_EVENT(sched_switch,

	TP_PROTO(bool preempt,
		 struct task_struct *prev,
		 struct task_struct *next),

	TP_ARGS(preempt, prev, next),

	TP_STRUCT__entry(
		__array(	char,	prev_comm,	TASK_COMM_LEN	)
		__field(	pid_t,	prev_pid			)
		__field(	int,	prev_prio			)
		__field(	long,	prev_state			)
		__array(	char,	next_comm,	TASK_COMM_LEN	)
		__field(	pid_t,	next_pid			)
		__field(	int,	next_prio			)
	),
            
···
8、system call tracepoint

语法格式:syscall__SYSCALLNAME

syscall__是一个特殊的前缀,它为前缀后的系统调用名称创建一个kprobe。也可以通过声明一个普通的C函数,然后使用Python的 BPF.get_syscall_name(SYSCALLNAME)BPF.attach_kprobe()将其关联来使用它。

参数在函数定义中声明:syscall__SYSCALLNAME(struct pt_regs *ctx, [, argument1 ...])

一个例子:

int syscall__execve(struct pt_regs *ctx,
    const char __user *filename,
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{
    [...]
}

这个例子使用execve系统调用

第一个参数永远是struct pt_regs *,剩余的参数就是函数的参数,(不一定非要声明,如果不用的话就不用声明了)

对应的python代码:

b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_name("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
9、kfuncs

语法格式:KFUNC_PROBE(*function*, typeof(arg1) arg1, typeof(arg2) arge ...)

这是一个宏,它在执行函数之前通过trampoline来instruments内核函数。它由function和定义为argn的函数参数定义。

一个例子:

KFUNC_PROBE(do_sys_open, int dfd, const char *filename, int flags, int mode)
{
    ...

这里注入/装备do_sys_open函数,并使它的参数作为标准参数值可访问。

10、kretfuncs

语法格式:KRETFUNC_PROBE(*function*, typeof(arg1) arg1, typeof(arg2) arge ..., int ret)

这是一个宏,这个宏通过trampoline在函数执行执行之后注入/装备内核函数,它由function定义,函数的参数定义为srgn。

最后一个参数是注入/装备函数的返回值。

一个例子:

KFUNC_PROBE(do_sys_open, int dfd, const char *filename, int flags, int mode, int ret)
{
    ...

这里注入do_sys_open函数,使其参数和返回值作为标准参数值可访问。

Data

1、bpf_probe_read()

语法格式:int bpf_probe_read(void *dst, int size, const void *src)

返回值:成功的话返回0

这将内存位置复制到BPF堆栈,以便BPF以后可以对其进行操作。为了安全起见,所有内存读取必须通过bpf_probe_read()。这在某些情况下是自动发生的,例如去引用内核变量,因为bcc将重写bpf程序,以包括必要的bpf_probe_reads()。

2、bpf_probe_read_str()

语法格式:int bpf_probe_read_str(void *dst, int size, const void *src)

返回值:> 0 成功,包括全程跟踪的成功,< 0 错误

把一个NULL终止的字符串从内存位置复制到BPF堆栈,以便BPF以后可以对其进行操作。如果字符串长度小于size,则目标不会填充进一步的空字节。如果字符串长度大于大小,则只复制大小-1字节,并将最后一个字节设置为NULL。

3、bpf_ktime_get_ns()

语法格式:u64 bpf_ktime_get_ns(void)

返回值:当前时间(纳秒)

4、bpf_get_current_pid_tgid()

语法格式:u64 bpf_get_current_pid_tgid(void)

返回值:current->tgid << 32 | current->pid

返回较低32位中的进程ID(内核角度的PID,在用户空间中通常表示为线程ID)和上32位中的线程组ID(用户空间通常认为是PID)。通过直接将此设置为u32,我们丢弃了上面的32位。

5、bpf_get_current_uid_gid()

语法格式:u64 bpf_get_current_uid_gid(void)

返回值:current_gid << 32 | current_uid

返回用户ID和组IDs

6、bpf_get_current_comm()

语法格式:bpf_get_current_comm(char *buf, int size_of_buf)

返回值:成功返回0

用当前进程名称填充第一个参数地址。它应该是指向至少大小为TASK_COMM_LEN的字符数组的指针,该数组在linux/sched.h中定义。

一个例子:

#include <linux/sched.h>

int do_trace(struct pt_regs *ctx) {
    char comm[TASK_COMM_LEN];
    bpf_get_current_comm(&comm, sizeof(comm));
[...]
7、bpf_get_current_task()

语法格式:bpf_get_current_task()

返回值:返回一个指向当前进程的task_struct结构

返回指向当前任务的task_struct对象的指针。这个helper可以用来计算进程的CPU时间、识别内核线程、获取当前CPU的运行队列或检索许多其他信息。

使用Linux4.13,由于字段随机化的问题,可能需要两个#定义指令,然后才包括:

#define randomized_struct_fields_start  struct {
#define randomized_struct_fields_end    };
#include <linux/sched.h>

int do_trace(void *ctx) {
    struct task_struct *t = (struct task_struct *)bpf_get_current_task();
[...]
8、bpf_log2l()

语法格式:unsigned int bpf_log2l(unsigned long v)

返回值:返回所提供值的log-2。这经常被用来创建直方图的索引,构造幂-2直方图.

9、bpf_get_prandom_u32()

语法格式:u32 bpf_get_prandom_u32()

返回值:返回伪随机u32。

Output

1、bpf_trace_printk()

语法格式:int bpf_trace_printk(const char *fmt, ...)

返回值:成功返回0

一个用于printf()到公共trace_pipe(/sys/kernel/debug/tracing/trace_pipe)的简单内核设施。对于一些快速示例来说,这是可以的,但有局限性:最多三个参数,只能有一个字符串,以及trace_pipe是全局共享的,因此并发程序将有冲突的输出。更好的接口是通过BPF_PER F_OUTP UT()。请注意,调用此助手比原始内核版本简单,后者以fmt_size作为第二个参数。

2、BPF_PERF_OUTPUT

语法格式:BPF_PERF_OUTPUT(name)

创建BPF table,用于通过PE RF环缓冲区将自定义事件数据推送到用户空间。这是将每个事件数据推送到用户空间的首选方法。

一个例子:

struct data_t {
    u32 pid;
    u64 ts;
    char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);

int hello(struct pt_regs *ctx) {
    struct data_t data = {};

    data.pid = bpf_get_current_pid_tgid();
    data.ts = bpf_ktime_get_ns();
    bpf_get_current_comm(&data.comm, sizeof(data.comm));

    events.perf_submit(ctx, &data, sizeof(data));

    return 0;
}

返回的table是events,数据是通过events.perf_submit()方法传入进去的。下面一个就进行解释。

3、perf_submit()

语法格式:int perf_submit((void *)ctx, (void *)data, u32 data_size)

返回值:成功返回0

这是一个BPF_PERF_OUTPUT的方法,用于提交特定数据到用户空间。

ctx参数是由kprobes和kretprobes提供的,对于SCHED_CLS 或者 SOCKET_FILTER 程序, 必须使用 struct __sk_buff *skb 替代。

MAPS

maps是BPF数据的存储方式,是更高级对象类型的基础,包括table表、hashes哈希和histograms直方图。

1、BPF_TABLE

语法格式:BPF_TABLE(_table_type, _key_type, _leaf_type, _name, _max_entries)

创建一个map,名称是_name,大多数时候会通过更高级别的宏(例如BPF_HASH,BPF_HIST等)使用。

它的方法有:map.lookup(), map.lookup_or_try_init(), map.delete(), map.update(), map.insert(), map.increment().

一些例子:
search /examples,
search /tools

Pinned Maps

绑定到BPF 文件系统的map可以通过这样的扩展语法访问:BPF_TABLE_PINNED(_table_type, _key_type, _leaf_type, _name, _max_entries, “/sys/fs/bpf/xyz”)

类型信息不强制要求,实际的地图类型取决于map被绑定在位置。

一个例子:

BPF_TABLE_PINNED("hash", u64, u64, ids, 1024, "/sys/fs/bpf/ids");
2、BPF_HASH

语法格式:BPF_HASH(name [, key_type [, leaf_type [, size]]])

创建一个名为 name的散列映射(关联数组),具有可选参数。

默认是:BPF_HASH(name, key_type=u64, leaf_type=u64, size=10240)

一个例子:

BPF_HASH(start, struct request *);

创建了一个叫start的哈希,key值是struct request *,value是默认的u64类型,这个哈希在disksnoop.py中使用,用于保存每个IO访问的时间戳,key是一个指向struct request的指针,value是时间戳。

它的方法有:map.lookup(), map.lookup_or_try_init(), map.delete(), map.update(), map.insert(), map.increment().(和BPF_TABLE一样)

一些例子:
search /examples,
search /tools

3、BPF_ARRAY

语法格式:BPF_ARRAY(name [, leaf_type [, size]])

建立一个int-索引的array,便于快速查找和更新,建立的array名为name,还有一些其余的参数。

默认是:BPF_ARRAY(name, leaf_type=u64, size=10240)

一个例子:

BPF_ARRAY(counts, u64, 32);

建立了一个名为counts的array,共有32个空间,存储的值的类型是u64。这个数组用于funccount.py中,用于存储每个函数调用的次数

它的方法:map.lookup(), map.update(), map.increment().

文档注:所有数组元素都是用零值预先分配的,不能删除。

一些例子:
search /examples,
search /tools

4、BPF_HISTOGRAM

语法格式: BPF_HISTOGRAM(name [, key_type [, size ]])

创建一个直方图的map类型,名称是name,和一些其他的参数。

默认是:BPF_HISTOGRAM(name, key_type=int, size=64)

一个例子:

BPF_HISTOGRAM(dist);

创建一个直方图名称为dist,默认有64个空间,默认是通过int类型的key值来进行索引。

它的方法:map.increment().

一些例子:
search /examples,
search /tools

5、BPF_STACK_TRACE

语法格式:BPF_STACK_TRACE(name, max_entries)

创建名为name的堆栈跟踪映射,并提供max_entries个计数。这些映射用于存储堆栈跟踪。

一个例子:

BPF_STACK_TRACE(stack_traces, 1024);

创建了一个堆栈跟踪映射名为stack_traces,堆栈跟踪条目的最大数量为1024个。

它的方法:map.get_stackid().

一些例子:
search /examples,
search /tools

6、BPF_PERF_ARRAY

语法格式:BPF_PERF_ARRAY(name, max_entries)

创建一个perf数组名为name,提供max_entries入口计数,这个数量必须和系统的cpu数量相同,这个数组用于获取硬件性能计数器。

一个例子:

text="""
BPF_PERF_ARRAY(cpu_cycles, NUM_CPUS);
"""
b = bcc.BPF(text=text, cflags=["-DNUM_CPUS=%d" % multiprocessing.cpu_count()])
b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLES)

创建了一个perf数组名为cpu_cycle,条目数等于cpu的个数。配置后的数组以后调用map.perf_read() ,会返回一个硬件计算的计数器,该计数器的周期数从过去的某个点经过。每个table一次只能配置一种类型的硬件计数器。

它的方法:map.perf_read().

一些例子:
search /tests

7、BPF_PERCPU_ARRAY

语法格式:BPF_PERCPU_ARRAY(name [, leaf_type [, size]])

创建一个int-索引的NUM_CPU数组,这些数组是为最快查找和更新而优化的,名为name,还有一些其他参数,每个CPU都会有一份独立的这个数组的拷贝,并且这些拷贝不以任何方式同步。

文档注:由于内核(在linux/mm/percpu.c)中定义的限制,leaf_type的大小不能超过32KB。
换言之,BPF_PER_CPU_ARRAY 元素的大小不能大于32KB。

默认是:BPF_PERCPU_ARRAY(name, leaf_type=u64, size=10240)

一个例子:

BPF_PERCPU_ARRAY(counts, u64, 32);

创建一个名为counts,有32个空间,存储64位int类型数据的NUM_CPU数组。

它的方法:map.lookup(), map.update(), map.increment().

Note that all array elements are pre-allocated with zero values and can not be deleted.

文档注:所有数组元素都是用零值预先分配的,不能删除。

一些例子:

search /examples,
search /tools

8、BPF_LPM_TRIE

语法格式:BPF_LPM_TRIE(name [, key_type [, leaf_type [, size]]])

创建一个最长的前缀匹配trie映射,名为name,具有可选参数。

默认是:BPF_LPM_TRIE(name, key_type=u64, leaf_type=u64, size=10240)

一个例子:

BPF_LPM_TRIE(trie, struct key_v6);

创建一个名为trie,key值是struct key_v6,value默认是u64的LPM trie map

它的方法:map.lookup(), map.lookup_or_try_init(), map.delete(), map.update(), map.insert(), map.increment().

一些例子:
search /examples,
search /tools

9、BPF_PROG_ARRAY

语法格式:BPF_PROG_ARRAY(name, size)

创建一个名为name的带有size项的程序数组prog_array,数组的每个条目要么是bpf程序的文件描述符,要么是NULL,数组充当跳转表,以便bpf程序可以“tail-call”其他bpf程序(尾调用)。

它的方法:map.call().

一些例子:
search /examples,
search /tests,
assign fd

10、BPF_DEVMAP

语法格式:BPF_DEVMAP(name, size)

创建一个名为name的设备映射,其中包含size项。map的每个条目都是一个ifindex到一个网络接口。这个map仅在XDP中使用。

一个例子:

BPF_DEVMAP(devmap, 10);

它的方法:map.redirect_map().

一些例子:
search /examples

11、BPF_CPUMAP

bcc python

INIALIZATION

构造器Constructors

1、BPF

语法格式: BPF({text=BPF_program | src_file=filename} [, usdt_contexts=[USDT_object, ...]] [, cflags=[arg1, ...]] [, debug=int])

创建一个BPF对象,这是定义一个BPF程序的主要对象,之后利用它进行输出交互

textsrc_file二者之一必须提供,不是两个都必须提供。

cflags制定要传给编译器的其他参数,例如: -DMACRO_NAME=value-I/include/path。参数作为数组传递,每个元素都是一个附加参数。请注意,字符串不会在空格上拆分,因此每个参数必须是数组的不同元素,例如["-include", "header.h"]

debug标志控制调试输出,并且可以一起进行:

  • DEBUG_LLVM_IR = 0x1 编译 LLVM IR

  • DEBUG_B PF=0x2加载的BPF字节码和分支上的寄存器状态

  • DEBUG_PREP ROCESS OR=0x4预处理器结果

  • DEBUG_SOURCE = 0x8 嵌入源程序的ASM指令

  • DEBUG_BPF_REGISTER_STATE = 0x10 除了DEBUG_B PF之外,所有指令上的注册状态

一个例子:

# 在一行中定义整个BPF程序:
BPF(text='int do_trace(void *ctx) { bpf_trace_printk("hit!\\n"); return 0; }');

# 将程序定义为一个变量:
prog = """
int hello(void *ctx) {
    bpf_trace_printk("Hello, World!\\n");
    return 0;
}
"""
b = BPF(text=prog)

# 源文件:
b = BPF(src_file = "vfsreadlat.c")

# 包括一个USDT对象:
u = USDT(pid=int(pid))
[...]
b = BPF(text=bpf_text, usdt_contexts=[u])

# 添加包括路径:
u = BPF(text=prog, cflags=["-I/path/to/include"])

一些例子:
search /examples,
search /tools

2、USDT

语法格式:USDT({pid=pid | path=path})

创建一个对象使用User Statically-Defined Tracing(USDT)探针。其主要方法是enable_probe()

参数:

  • pid:attach到这个PID进程上
  • path:从这个二进制路径探测来使用USDT探针

一个例子:

# include a USDT object:
u = USDT(pid=int(pid))
[...]
b = BPF(text=bpf_text, usdt_contexts=[u])

一些例子:
search /examples,
search /tools

EVENTS

1、attach_kprobe()

语法格式:BPF.attach_kprobe(event="event", fn_name="name")

使用内核动态跟踪来检测内核函数Event(),并附加我们的C语言函数Name(),以便在调用内核函数时调用。

一个例子:

b.attach_kprobe(event="sys_clone", fn_name="do_trace")

这里会注入到内核的sys_clone()函数,之后每次执行时都会调用我们定义的do_trace()函数

可以不止一次调用attach_kprobe(),并将BPF函数附加到多个内核函数。

请参阅前面的kprobe部分,了解如何从BPF中检测参数。

一些例子:
search /examples,
search /tools

2、attach_kretprobe()

语法格式:BPF.attach_kretprobe(event="event", fn_name="name" [, maxactive=int])

使用函数返回的内核动态追踪注入到内核event()函数的返回处,之后吧我们的C语言定义的函数name()附加到这里,当内核函数返回时就会触发执行。

一个例子:

b.attach_kretprobe(event="vfs_read", fn_name="do_return")

这里会注入到内核vfs_read函数,之后该函数每次执行的时候都会执行我们定义的BPF函数do_return()。

可以不止一次调用attach_kretprobe(),并将BPF函数附加到多个内核函数返回处。

当一个kretprobe安装在内核函数上时,它可以捕捉到多少个并行调用是有限制的。可以用maxactive更改该限制。有关其默认值,请参阅kprobe文档。

请参阅前面的kretprobe部分,了解如何从BPF中检测返回值。

一些例子:
search /examples,
search /tools

3、attach_tracepoint()

语法格式:BPF.attach_tracepoint(tp="tracepoint", fn_name="name")

注入tracepoint描述的内核跟踪点,并在击中时运行BPF函数name()

这是使用追踪点的一种明确的方法。前面的tracepoint部分所涵盖的TRACEPINT_PROBE语法是一种替代方法,其优点是自动声明包含跟踪点参数的args结构。使用attach_tracepoint(),需要在BPF程序中声明跟踪点参数。

一个例子:

# 定义BPF程序
bpf_text = """
#include <uapi/linux/ptrace.h>

struct urandom_read_args {
    // from /sys/kernel/debug/tracing/events/random/urandom_read/format
    u64 __unused__;
    u32 got_bits;
    u32 pool_left;
    u32 input_left;
};

int printarg(struct urandom_read_args *args) {
    bpf_trace_printk("%d\\n", args->got_bits);
    return 0;
};
"""

# load BPF program
b = BPF(text=bpf_text)
b.attach_tracepoint("random:urandom_read", "printarg")

注意,printarg()的第一个参数现在是我们定义的结构。

现场实例:
code,
search /examples,
search /tools

4、attach_uprobe()

语法格式:BPF.attach_uprobe(name="location", sym="symbol", fn_name="name" [, sym_off=int]), BPF.attach_uprobe(name="location", sym_re="regex", fn_name="name"), BPF.attach_uprobe(name="location", addr=int, fn_name="name")

使用用户级动态跟踪,从库或以Location命名的二进制文件中检测用户级函数symbol(),并在调用用户级函数时附加我们定义的C函数name()。如果给出了sym_off,则将函数附加到符号内的偏移量上。

实际地址addr可以代替sym,在这种情况下,sym必须设置为其默认值。如果文件是non-PIE可执行文件,则addr必须是虚拟地址,否则它必须是相对于文件加载地址的偏移量。

可在“sym_re”中提供正则表达式,而不是符号名称。然后,uprobe将附加到与所提供的正则表达式匹配的符号上。

库可以在没有lib前缀的名称参数中给出,也可以使用完整路径(/usr/lib/.)。只能在完整路径(/bin/sh)下给出二进制文件。

一个例子:

b.attach_uprobe(name="c", sym="strlen", fn_name="count")

这将使用来自libc的strlen()函数,并在调用时调用我们的BPF函数count()。注意“lib”在“libc”中不是必须要声明的。

其他例子:

b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry")
b.attach_uprobe(name="/usr/bin/python", sym="main", fn_name="do_main")

可以不止一次调用attach_uprobe(),并将BPF函数附加到多个用户级函数。

有关如何从BPF中检测参数的,请参阅前面的uprobes部分。

现场实例:
search /examples,
search /tools

5、attach_uretprobe()

语法格式:BPF.attach_uretprobe(name="location", sym="symbol", fn_name="name")

使用对函数返回的用户级动态跟踪,从库或以location命名的二进制文件中返回用户级函数symbol(),并附加我们的C定义函数name(),以便在用户级函数返回时调用。

一个例子:

b.attach_uretprobe(name="c", sym="strlen", fn_name="count")

这将使用来自libc的strlen()函数,并在返回时调用我们的BPF函数count()

其他例子:

b.attach_uretprobe(name="c", sym="getaddrinfo", fn_name="do_return")
b.attach_uretprobe(name="/usr/bin/python", sym="main", fn_name="do_main")

可以不止一次调用attach_uretprobe(),并将BPF函数附加到多个用户级函数。

请参阅前面的uretprobes部分,了解如何从BPF中检测返回值。

现场实例:
search /examples,
search /tools

6、USDT.enable_probe()

语法格式:USDT.enable_probe(probe=probe, fn_name=name)

把一个BPF的c程序name附加到USDT probe probe

一个例子:

# enable USDT probe from given PID
u = USDT(pid=int(pid))
u.enable_probe(probe="http__server__request", fn_name="do_trace")

从给定的PID中启用USDT探针

要检查二进制文件是否有USDT探针,以及它们是什么,可以运行readelf -n binary并检查stap调试部分。

现场实例:
search /examples,
search /tools

7、attach_raw_tracepoint()

语法格式:BPF.attach_raw_tracepoint(tp="tracepoint", fn_name="name")

使用Trace PointEventsonly,nocategory)描述的内核raw tracepoint,并在命中时运行BPF函数Name()

这是使用追踪点的一种明确的方法。前面的原始跟踪点部分所涵盖的RAW_TRACEPOINT_PROBE语法是一种替代方法。

一个例子:

b.attach_raw_tracepoint("sched_swtich", "do_trace")

现场实例:
search /tools

DEBUG OUTPUT

1、trace_print()

语法格式:BPF.trace_print(fmt="fields")

此方法不断读取全局共享/sys/kernel/debug/tracing/trace_pipe文件并打印其内容。这个文件可以通过BPF和bpf_trace_printk()函数写入,但是该方法有局限性,包括缺乏并发跟踪支持。前面所述的BPF_PERF_OUTPUT机制优先。

参数:

  • fmt:可选参数,并且可以包含字段格式化字符串。默认为“none”。

一个例子:

# print trace_pipe output as-is:
b.trace_print()

# print PID and message:
b.trace_print(fmt="{1} {5}")

现场实例:
search /examples,
search /tools

2、trace_fields()

语法格式:BPF.trace_fields(nonblocking=False)

此方法从全局共享/sys/kernel/debug/tracing/trace_pipe文件中读取一行,并将其作为字段返回。这个文件可以通过BPF和bpf_trace_printk()函数写入,但是该方法有局限性,包括缺乏并发跟踪支持。前面所述的BPF_PER F_OUTP UT机制优先。

参数:

  • nonblocking:可选的,默认为“False”。当设置为“True”时,程序不会阻塞等待输入。

一个例子:

while 1:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
    except ValueError:
        continue
    [...]

现场实例:
search /examples,
search /tools

OUTPUT

BPF程序的正常输出是:

  • per-event:使用PERF_EVENT_OUTPUT,open_perf_buffer(),和perf_buffer_poll()
  • map 总结:使用items(),或者print_log2_hist(),这个在Maps章节
1、perf_buffer_poll()

语法格式:BPF.perf_buffer_poll(timeout=T)

此轮询来自所有的环缓冲区,调用open_perf_Buffer时为每个条目提供的回调函数。

超时参数是可选的,并以毫秒为单位测量。在没有定义的情况下,将无限期地继续进行。

一个例子:

# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit();

现场实例:
code,
search /examples,
search /tools

MAPS

maps是BPF数据存储,在bcc中使用来实现一个表,然后在表的上层实现更高层次的对象,包括哈希和直方图。

1、get_table()

语法格式:BPF.get_table(name)

返回表对象。这不再使用,因为现在可以从BPF中将表读为项。

例子:

counts = b.get_table("counts")

counts = b["counts"]

二者是同等的效果。

2、open_perf_buffer()

语法格式:table.open_perf_buffers(callback, page_cnt=N, lost_cb=None)

这在BPF中定义为BPF_PERF_OUTPUT()的table操作,并将回调Python函数callback关联起来,以便在Perf环缓冲区中可用数据时调用。这是推荐的机制的一部分,用于将每个事件的数据从内核传输到用户空间。perf环缓冲区的大小可以通过page_cnt参数来指定,该参数必须是两个页数的幂,默认为8。如果回调处理数据的速度不够快,一些提交的数据可能会丢失。lost_cb将被调用来记录/监视丢失的计数。如果loss_cb是默认的none值,它只会打印一行消息到stderr

一个例子:

# process event
def print_event(cpu, data, size):
    event = ct.cast(data, ct.POINTER(Data)).contents
    [...]

# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

请注意,传输的数据结构将需要在BPF程序中在C中声明。例如:

// define output data structure in C
struct data_t {
    u32 pid;
    u64 ts;
    char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
[...]

在Python中,您可以让BCC自动从C声明生成数据结构(推荐):

def print_event(cpu, data, size):
    event = b["events"].event(data)
[...]

或手动定义:

# define output data structure in Python
TASK_COMM_LEN = 16    # linux/sched.h
class Data(ct.Structure):
    _fields_ = [("pid", ct.c_ulonglong),
                ("ts", ct.c_ulonglong),
                ("comm", ct.c_char * TASK_COMM_LEN)]

def print_event(cpu, data, size):
    event = ct.cast(data, ct.POINTER(Data)).contents
[...]

现场实例:
code,
search /examples,
search /tools

3、items()

语法格式:table.items()

返回table中key的数组。这可以与BPF_HASH map一起使用,以便在键上获取和迭代。

一个例子:

# 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):
    print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))

此示例还使用sorted()方法按值排序。

现场实例:
search /examples,
search /tools

4、values()

语法格式:table.values()

返回table中values的数组。

5、clear()

语法格式:table.clear()

清空table:删除所有目录

一个例子:

# print map summary every second:
while True:
    time.sleep(1)
    print("%-8s\n" % time.strftime("%H:%M:%S"), end="")
    dist.print_log2_hist(sym + " return:")
    dist.clear()

现场实例:
search /examples,
search /tools

6、print_log2_hist()

语法格式:table.print_log2_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)

在ASCII中打印一个表作为log2直方图。该表必须存储为log2,可以使用BPF函数“`bpf_log2l()”完成。

参数:

  • val_type:可选的,列标题
  • section_header:如果直方图有一个次要键,则会打印多个table,section_header可以用作每个table的标题描述
  • section_print_fn:如果section_print_fn不是None,则将传递桶值。

一个例子:

b = BPF(text="""
BPF_HISTOGRAM(dist);

int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)
{
	dist.increment(bpf_log2l(req->__data_len / 1024));
	return 0;
}
""")
[...]

b["dist"].print_log2_hist("kbytes")

输出:

     kbytes          : count     distribution
       0 -> 1        : 3        |                                      |
       2 -> 3        : 0        |                                      |
       4 -> 7        : 211      |**********                            |
       8 -> 15       : 0        |                                      |
      16 -> 31       : 0        |                                      |
      32 -> 63       : 0        |                                      |
      64 -> 127      : 1        |                                      |
     128 -> 255      : 800      |**************************************|

该输出显示多模态分布,最大模式为128->255字节,计数为800。

这是一个很有效的途径来总结数据,总结的过程在内核中完成,最后只有count列传输到用户空间。

现场实例:
search /examples,
search /tools

6、print_linear_hist()

暂时略

HELPERS

bcc提供的一些辅助方法。请注意,由于我们在Python中,所以我们可以导入任何Python库及其方法,例如,库:argparse、collections、ctype、datetime、Re、socket、struct、subprocess、sys和time。

1、ksym()

语法格式:BPF.ksym(addr)

将内核内存地址转换为内核函数名,该函数名将被返回。

一个例子:

print("kernel function: " + b.ksym(addr))

现场实例:
search /examples,
search /tools

2、ksymname()

语法格式:BPF.ksymname(name)

将内核名称转换为地址。这是ksym的反面,当函数名未知时返回-1…

一个例子:

print("kernel address: %x" % b.ksymname("vfs_read"))

现场实例:
search /examples,
search /tools

3、sym()

语法格式:BPF.sym(addr, pid, show_module=False, show_offset=False)

将内存地址转换为一个PID的函数名,该函数名将被返回。小于零的PID将访问内核符号缓存。show_moduleshow_offset参数控制是否应显示符号所在的模块,以及是否应显示符号开头的指令偏移。这些额外的参数默认为false

一个例子:

print("function: " + b.sym(addr, pid))

现场实例:
search /examples,
search /tools

4、num_open_kprobes()

语法格式:BPF.num_open_kprobes()

返回打开的k[ret]探针的数量。对于在附加和分离探针时使用event_re的场景可能很有用。不包括perf_events reader。

一个例子:

b.attach_kprobe(event_re=pattern, fn_name="trace_count")
matched = b.num_open_kprobes()
if matched == 0:
    print("0 functions matched by \"%s\". Exiting." % args.pattern)
    exit()

现场实例:
search /examples,
search /tools

5、get_syscall_fnname()

语法格式: BPF.get_syscall_fnname(name : str)

返回syscall对应的内核函数名,此助手函数将尝试不同的前缀,并使用正确的前缀与syscall名称连接。请注意,不同版本的linux内核的返回值可能会有所不同,有时会造成麻烦…(见#2590

一个例子:

print("The function name of %s in kernel is %s" % ("clone", b.get_syscall_fnname("clone")))
# sys_clone or __x64_sys_clone or ...

现场实例:
search /examples,
search /tools

BPF ERRORS

请参阅Documentation/networking/filter.txt下内核源中的Understanding eBPF verifier messages部分。

1、Invalid mem access

这可能是因为试图直接读取内存,而不是在BPF堆栈上对内存进行操作。所有的内存读取必须通过bpf_probe_read()来复制内存到bpf堆栈中,在一些简单的去引用的情况下,bcc重写程序可以自动地将内存复制到bpf堆栈中。bpf_probe_read()做所有必要的检查。

例如:

bpf: Permission denied
0: (bf) r6 = r1
1: (79) r7 = *(u64 *)(r6 +80)
2: (85) call 14
3: (bf) r8 = r0
[...]
23: (69) r1 = *(u16 *)(r7 +16)
R7 invalid mem access 'inv'

Traceback (most recent call last):
  File "./tcpaccept", line 179, in <module>
    b = BPF(text=bpf_text)
  File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 172, in __init__
    self._trace_autoload()
  File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 612, in _trace_autoload
    fn = self.load_func(func_name, BPF.KPROBE)
  File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 212, in load_func
    raise Exception("Failed to load BPF program %s" % func_name)
Exception: Failed to load BPF program kretprobe__inet_csk_accept

2、Cannot call GPL only function from proprietary program

当从非GPL BPF程序调用GPL唯一助手时,会发生此错误。若要修复此错误,请不要使用专有BPF程序中的GPL专用助手,或在GPL兼容许可证下重新保存BPF程序。检查哪些(BPF helpers)是GPL唯一的,以及哪些许可证被认为是GPL兼容的。

例如,从专有程序调用bpf_get_stackid(),这是一个GPL专用BPF助手(# define BPF_LICENSE 专有):

bpf: Failed to load program: Invalid argument
[...]
8: (85) call bpf_get_stackid#27
cannot call GPL only function from proprietary program

ENVIRONMENT VARIABLES

1、KERNEL SOURCE DIRECTORY

eBPF程序编译需要内核源程序或带有标头的内核头程序已汇编。如果您的内核源位于BCC的非标准位置然后找不到,就要给BCC提供绝对路径的位置,通过BCC_KERNEL_SOURCE设置为它。

2、KERNEL VERSION OVERRIDING

默认情况下,BCC将LINUX_VERSION_CODE存储在生成的eBPF对象中,然后在加载eBPF程序时将其传递到内核。有时这是非常不方便的,特别是当内核稍微更新时,例如LTS内核发布。轻微的不匹配将导致很多问题。通过将BCC_LINUX_VERSION_CODE设置为正在运行的内核版本,可以绕过验证内核版本的检查。这对于使用kprobe的程序来说是必需的。这需要以格式编码:(VERSION*65536)+(PATCHLEVEL*256)+ SUBLEVEL。例如,如果运行的内核是4.9.10',则可以设置export BCC_LINUX_VERSION_CODE=264458以成功覆盖内核版本检查。

本文来自同门分享
参考链接:https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值