bpftrace
一、工具安装
sudo apt install bpftrace
二、bpftrace语句结构
bpftrace -e
# 监控事件(探针)
'tracepoint:syscalls:sys_enter_accept
# 过滤条件
/ comm == "redis-server" /
# 事件触发后对应的操作
{ printf("accept\n"); }'
bpftrace moniter.bt
三、常见探针类型
- kprobe(动态探针): 内核函数入口触发
- kretprobe(动态探针):内核函数出口触发
- uprobe(动态探针): 用户空间函数入口触发
- uretprobe(动态探针): 用户空间函数出口触发
- tracepoint(静态探针): 特定事件触发,系统调用
四、bpftrace内置变量
- uid:用户id
- tid:线程id
- pid:进程id
- cpu:cpu id
- cgroup:cgroup id
- probe:当前trace点
- comm:进程名称
- nsecs:纳秒级别时间戳
- kstack:内核线程描述
- curtask:当前集成的task_struct 地址
- args:获取当前kprobe或者tracepoint的参数数列表
- arg0:获取kprobe的第一个变量,tracepoint不可用
- arg1:获取kprobe的第二个变量,tracepoint不可用
- arg2:获取kprobe的第三个变量,tracepoint不可用
- retval:kretprobe中获取函数返回值
- args->ret: kretprobe中获取函数返回值
五、自定义变量
以符号开头定义变量如符号开头定义变量 如符号开头定义变量如$pic = 10;
map变量 在内核和用户空间之间存储和检索数据,定义方式以@符号开始
如:@hashmap[$key] = $value
Bpftrace 默认在结束时会打印从内核接收到的 map 变量
六、内置函数
- exit():推出bpftrace程序
- str(char*):装换一个指针到string类型
- system(format[])
- printf(char *fmt, …) - 格式化打印
- time(char *fmt) - 格式化打印时间
- join(char *arr[] [, char *delim]) - 打印字符串数组
- str(char *s [, int length]) - 返回指向s的字符串指针
- ksym(void *p) - 解析内核地址
- usym(void *p)- 解析用户空间地址
- kaddr(char *name) - 解析内核符号 //kernel addresss
- uaddr(char *name) - 解析用户空间符号 //user address
- reg(char *name) - 返回存储在指定寄存器上的值
- system(char *fmt) - 执行系统命令
- exit() - 退出bpftrace
- cgroupid(char *path) - 解析cgroupID
- kstack([StackMode mode, ][int level]) - 内核栈回溯
- ustack([StackMode mode, ][int level]) - 用户栈回溯
- ntop([int af, ]int|char[4|16] addr) - 将ip地址转换为文本
- cat(char *filename) - 打印文件内容
- signal(char[] signal | u32 signal) - 给当前进程发送信号
- strncmp(char *s1, char *s2, int length) - 比较两个字符串的前n个字节
- override(u64 rc) - 重写返回值
- buf(void *d [, int length]) - 返回d指向的16进制内容
- sizeof(…) - 返回一个类型或语句的尺寸Return size of a type or expression
- print(…) - 使用默认格式打印一个非map的值
- strftime(char *format, int nsecs) - 返回格式化的时间戳
- path(struct path *path) - 返回完整路径
- uptr(void *p) - 注释为用户空间指针
- kptr(void *p) - 注释为内核空间指针
- macaddr(char[6] addr) - 转换mac地址
七、bpftrace脚本示例
1、内核中调用accept函数的socket信息
#include <net/sock.h>
#include <linux/socket.h>
// bpftrace脚本启动开始
BEGIN
{
printf("%8s %6s %16s", "TIME", "PID", "COMM");
printf("%19s:%5s %16s:%5s\n", "SADDR", "SPORT", "DADDR", "DPORT");
}
kretprobe:inet_csk_accept
{
// 获取kretprobe中获取函数返回值
$sk = (struct sock*)retval;
$inet_family = $sk->__sk_common.skc_family;
if ($inet_family == AF_INET) {
$daddr = ntop($sk->__sk_common.skc_daddr);
$saddr = ntop($sk->__sk_common.skc_rcv_saddr);
}
$dport = $sk->__sk_common.skc_dport;
$lport = $sk->__sk_common.skc_num;
$dport = bswap($dport);
time("%H:%M:%S ");
printf("%6d %16s", pid, comm);
printf("%19s:%5d %16s:%5d\n", $saddr, $lport, $daddr, $dport);
}
// bpftrace脚本结束
END
{
printf("END\n");
}
2、mysql每条sql语句实行时间(mysql执行sql语句时会调用dispatch函数)
BEGIN
{
printf("Tracing MySQL Server\n");
printf("%10s %6s %s\n", "TIME(ns)", "PID", "SQL");
}
uprobe:/usr/sbin/mysqld:*dispatch_command*
{
// 获取开始时间
@start[tid] = nsecs;
}
uprobe:/usr/sbin/mysqld:*dispatch_command*
/@start[tid]/
{
$dur = nsecs - @start[tid];
//str(*arg1) 获取执行命令,即第二个参数
printf("%10u %6d %s\n", $dur, pid, str(*arg1));
}
3、监控内存申请和释放
BEGIN
{
printf("%10s %16s %s\n", "TYPE", "NAME", "PID");
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc
/ comm == "mytask" /
{
printf("%10s %16s %d\n", "malloc", comm, pid);
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free
/ comm == "mytask"/
{
printf("%10s %16s %d\n", "free", comm, pid);
}
4、监控tcp声明周期
#include <net/sock.h>
#include <linux/socket.h>
BEGIN
{
printf("%8s %6s %16s", "TIME", "PID", "COMM");
printf("%19s:%5s %16s:%5s %s\n", "SADDR", "SPORT", "DADDR", "DPORT", "DURms");
}
kprobe:tcp_set_state
{
$sk = (struct sock*)arg0;
$newstate = arg1;
if ($newstate <= TCP_SYN_RECV) { // begin
@birth[$sk] = nsecs;
}
if ($newstate == TCP_CLOSE ) { // end
$dur = (nsecs - @birth[$sk]) / 1e6;
$inet_family = $sk->__sk_common.skc_family;
if ($inet_family == AF_INET) {
$daddr = ntop($sk->__sk_common.skc_daddr);
$saddr = ntop($sk->__sk_common.skc_rcv_saddr);
}
$dport = $sk->__sk_common.skc_dport;
$lport = $sk->__sk_common.skc_num;
$dport = bswap($dport);
time("%H:%M:%S ");
printf("%6d %16s", pid, comm);
printf("%19s:%5d %16s:%5d %d\n", $saddr, $lport, $daddr, $dport, $dur);
}
}
5、内核插入探针
#include <linux/module.h>
#include <linux/sched/signal.h>
pid_t pid = 0;
// 使用module_param宏声明pid为模块的参数,用户可以在加载模块时通过命令行参数设置它;S_IRUSR表示参数只可读
module_param(pid, int, S_IRUSR);
// insmod kernel_module.ko
int kernel_module_init(void) {
#if 0
struct pid *spid;
struct task_struct *task; //
spid = find_get_pid(pid);
task = get_pid_task(spid, PIDTYPE_PID);
printk("kernel_module_init: %d, %s\n", pid, task->comm);
#else
struct task_struct *task;
// 遍历所有的进程
for_each_process(task) {
printk("kernel_module_init: %d, %s\n", task->pid, task->comm);
}
#endif
return 0;
}
// rmmod kernel_module
void kernel_module_exit(void) {
printk("kernel_module_exit\n");
}
// 两个宏,分别来注册模块的初始化函数和推出函数 使用insmod 命令时,调用kernel_module_init函数
module_init(kernel_module_init);
// 使用rmmod命令你时,调用kernel_module_exit函数
module_exit(kernel_module_exit);
// 声明开源
MODULE_LICENSE("GPL");
obj-m := kernel_module.o
KERNELBUILD := /lib/modules/$(shell uname -r)/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm *.ko *.o *.mod.c *.mod modules.order Module.symvers
使用 insmod命令可以将生成的.ko文件植入到Linux内核中
sudo insmod kernel_module.ko # 内核加载kernel_module模块
sudo insmod kernel_module.ko pid=7793 # 内核加载kernel_module模块,并指定参数pid=7793
sudo rmmod kernel_module.ko # 删除内核kernel_module模块
xxx@xxx:~$ sudo dmesg
[3422967.890650] kernel_module_exit
xxx@xxx:~$ sudo dmesg
[3422967.890650] kernel_module_exit
[3422973.892238] kernel_module_init: 1, systemd
[3422973.892257] kernel_module_init: 2, kthreadd
[3422973.892272] kernel_module_init: 3, rcu_gp
[3422973.892287] kernel_module_init: 4, rcu_par_gp
[3422973.892302] kernel_module_init: 5, slub_flushwq
[3422973.892317] kernel_module_init: 6, netns
......
[3422973.901814] kernel_module_init: 75224, sshd
[3422973.901830] kernel_module_init: 75226, bash
[3422973.901847] kernel_module_init: 75266, kworker/u256:0
[3422973.901864] kernel_module_init: 75267, sudo
[3422973.901881] kernel_module_init: 75268, sudo
[3422973.901898] kernel_module_init: 75269, insmod