BCC Python开发教程&常用BCC工具(四)

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))

本节需要学习:

  1. PT_REGS_PARM1(ctx): 这个宏用于获取被跟踪函数strlen()的第一个参数,也就是要处理的字符串。
  2. 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))

本节需要学习:

  1. bpf_usdt_readarg(6, ctx, &addr): 从USDT probe中读取第6个参数地址到addr中。
  2. bpf_probe_read_user(&path, sizeof(path), (void *)addr):现在将addr 指向path。
  3. u = USDT(pid=int(pid)): 使用指定的PID初始化USDT tracing。
  4. u.enable_probe(probe="http__server__request", fn_name="do_trace"): 将我们的BPF C函数do_trace() 添加到USDT探测点Node.js的http__server__request 处。
  5. 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基本包含了系统的大部分系统调用,如clonesync等(要求内核版本>=4.7),但网络相关的还是需要使用kprobes对内核函数进行打点,如icmp_rcv
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值