PLY:嵌入式Linux环境下的内核探测工具
简单Linux系统环境下的内核探测
在笔者之前的文章中提到,基于内核eBPF探针的常用工具主要bpftrace、bcc,二者复杂的依赖库使得其在嵌入式Linux系统环境下常常是不可用的。截止目前,一些嵌入式SDK(例如buildroot及openwrt等)未提供这两个性能分析工具的自动化构建功能。一种可行的方案是参考Linux内核源码samples/bpf
下的示例编写基于eBPF
的C代码,并编译生成BTF
目柡文件和可执行应用,用于嵌入式设备上的性能分析。这种方案可行但实施的效率较低。幸运的是,同属于iovisor的开源软件PLY很好地填补了这一空缺,它可以使用eBPF
子系统对Linux内核进行监测,而且没有复杂的依赖库(仅依赖libc
库)。其用法接近bpftrace
,尽管功能较弱,但一定程度上能够满足要求。其最大的缺憾是缺少对局部变量和uprobe功能的支持。本文主要对ply
的内核探测做相关的演示说明。
监测文件的打开
bpftrace
及bcc
工具都提供了一个名为opensnoop的脚本工具,用于监测系统上所有打开的文件。笔者编写了ply
版本的opensnoop.ply
,其实现基于Linux内核的tracepoint探测,脚本内容如下:
#!/usr/sbin/ply -ktracepoint:syscalls/sys_enter_open
{opentab[kpid] = data->filename;
}
tracepoint:syscalls/sys_enter_openat
{opentab[kpid] = data->filename;
}
tracepoint:syscalls/sys_exit_open /opentab[kpid] != 0/
{printf("[%d.%06d] pid: %d, kpid: %d, comm: %s, open(%s): %d\n",time / 1000000000, (time % 1000000000) / 1000000,pid, kpid, comm, str(opentab[kpid]), data->ret);delete opentab[kpid];
}
tracepoint:syscalls/sys_exit_openat /opentab[kpid] != 0/
{printf("[%d.%06d] pid: %d, kpid: %d, comm: %s, open(%s): %d\n",time / 1000000000, (time % 1000000000) / 1000000,pid, kpid, comm, str(opentab[kpid]), data->ret);delete opentab[kpid];
}
以上脚本中,使用到了ply
多个内置的变量和函数,如time
、str
等。data
变量仅针对tracepoint
有效,它类似于C语言中的结构体指针,其能指向的成员由内核确定。例如对于syscalls/sys_enter_open
这个跟踪点,data
能够指向的成员名称由内核文件/sys/kernel/tracing/events/syscalls/sys_enter_open/format
确定,可以打开该文件查看:
# cat /sys/kernel/tracing/events/syscalls/sys_enter_open/format
name: sys_enter_open
ID: 635
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 __syscall_nr; offset:8; size:4; signed:1;field:const char * filename; offset:16; size:8; signed:0;field:int flags; offset:24; size:8; signed:0;field:umode_t mode; offset:32; size:8; signed:0;print fmt: "filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))
pid
、kpid
、comm
等变量由ply
自动提供,分别对应进程的pid
、线程的pid
、及进程的名称。该脚本不建议在系统繁忙的系统中使用。在负载较低的系统环境下运行,可得到以下结果:
# ply -k trace-open.ply
[89137.000250] pid: 1, kpid: 1, comm: systemd, open(/proc/979/cgroup): 114
[89137.000251] pid: 1, kpid: 1, comm: systemd, open(/proc/912/cgroup): 114
[89138.000858] pid: 32010, kpid: 32276, comm: MemoryPoller, open(/proc/meminfo): 27
[89139.000240] pid: 14985, kpid: 33553, comm: ThreadPoolForeg, open(/etc/chromium-browser/policies/managed): -2
[89139.000240] pid: 14985, kpid: 33553, comm: ThreadPoolForeg, open(/etc/chromium-browser/policies/recommended): -2
[89140.000008] pid: 970, kpid: 970, comm: irqbalance, open(/proc/interrupts): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/stat): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/49/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/51/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/56/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/55/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/0/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/1/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/8/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/9/smp_affinity): 6
[89140.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/12/smp_affinity): 6
[89140.000941] pid: 2198, kpid: 2198, comm: gnome-shell, open(/proc/self/stat): 46
对打开文件进行统计
当系统频繁打开文件时,上面的脚本会造成系统负载增加。为避免给系统带来不必要的负荷,可以使用count()
特殊函数对打开的文件进行统计,并隔一段时间周期性地输出统计结果。这里使用到了interval
定时器,具体用法可参考官方文档。笔者编写的统计脚本open-count.ply
内容如下:
#!/usr/sbin/ply -k
kprobe:do_sys_open
{@openfreq[str(arg1)] = count();
}
interval:10s
{printf("--------------------------------------------------------\n");printf("[%d.%06d] dumping opened files in the last 10 seconds:\n",time / 1000000000, (time % 1000000000) / 1000000);print(@openfreq);clear(@openfreq);
}
上面的定时器每10秒执行一次,输出统计信息后会清空hash表openfreq
以重新计数。笔者的观测结果如下(部分):
# ply -k open-count.ply
--------------------------------------------------------
[90150.000678] dumping opened files in the last 10 seconds:@openfreq:
{ /proc/interrupts }: 10
{ /proc/irq/0/smp_affinity }: 1
{ /proc/irq/1/smp_affinity }: 1
{ /proc/irq/12/smp_affinity }: 1
{ /proc/irq/50/smp_affinity }: 1
{ /proc/irq/51/smp_affinity }: 1
{ /proc/stat }: 1
{ /proc/meminfo }: 5--------------------------------------------------------
[90160.000678] dumping opened files in the last 10 seconds:@openfreq:{ /proc/979/cgroup }: 1
{ /proc/interrupts }: 1
{ /proc/irq/0/smp_affinity }: 1
{ /proc/irq/1/smp_affinity }: 1
{ /proc/irq/12/smp_affinity }: 1
{ /proc/irq/51/smp_affinity }: 1
{ /proc/stat }: 1
{ /proc/meminfo }: 2
过滤以只读方式打开的文件
某些情况下,我们只想跟踪探测以可写方式打开的文件,忽略以只读方式打开的文件。这样可以极大地减少跟踪探测的输出结果,从而一定程度上降低ply
探测对系统负载的影响。当使用kprobe
探测一些函数的入口时,通过arg0
、arg1
等变量可以访问到函数的入参,这些入参的数据类型为整数,据此可以实现探测的过滤。笔者编写的probe-open.ply
内容如下:
#!/usr/sbin/ply -k
/* fs/open.c:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
*/
kprobe:do_sys_open
{if (arg1 != 0 && (arg2 & 0x3) != 0) {opentab[kpid] = arg1;openflags[kpid] = arg2;}
}
kretprobe:do_sys_open /opentab[kpid] != 0/
{printf("[%d.%06d] pid: %d, kpid: %d, comm: %s, open(%s): %d, trunc: %d\n",time / 1000000000, (time % 1000000000) / 1000000,pid, kpid, comm, str(opentab[kpid]), retval,(openflags[kpid] & 0x200) >> 9);delete opentab[kpid];delete openflags[kpid];
}
arg2
对应函数do_sys_open
的第三个参数flags
,当其低2位比特不为0时,表明以O_WRONLY
或O_RDWR
可写方式打开了文件,据此就实现了探测结果的过滤。同样的,kretprobe
探针加入了opentab[kpid] != 0
的限定条件,它不会输出以只读方式打开文件的结果。特殊变量retval
仅对kretprobe
有效,它表示函数的返回值。笔者探测结果如下:
# ply -k probe-open.ply
[91220.000009] pid: 970, kpid: 970, comm: irqbalance, open(/proc/irq/49/smp_affinity): 6, trunc: 1
[91242.000803] pid: 22765, kpid: 22765, comm: bash, open(/dev/null): 3, trunc: 1
打开/dev/null
文件,是笔者在另一个终端上执行echo 'Hello World' > /dev/null
触发的结果。
内核调用栈的回溯
ply
提供了stack
变量,它是多行的字符串类型,可以得到探测点的内核函数的调用栈;该功能对于调试内核非常有帮助。以笔者上一篇博客为例,Linux内核为进程加载vdso
动态库之后,实际上并没有映射vvar
只读内存段,而是仅当进程实际去访问该内存了,才会触发实际的内存映射操作。通过ply
可以得到该映射函数的调用栈回溯。脚本func-backtrace.ply
内容如下:
#!/usr/sbin/ply -k
kprobe:vvar_fault
{printf("PID: %d, TID: %d, comm: %s, accessing vdso memory:\n",pid, kpid, comm);print(stack);
}
跟踪探测结果如下:
# ply -k func-backtrace.ply
PID: 35486, TID: 35486, comm: clock_gettime, accessing vdso memory:vvar_fault+1__do_fault+62do_fault+486__handle_mm_fault+1561handle_mm_fault+218pgtable_bad+571msr_save_cpuid_features+15669_raw_write_lock_irqsave+2064046
访问内核数据
通过ply
加载的内核探针kprobe
,可以在函数后面加上一个偏移量,这样探针不会在函数入口处触发。不过并不是在函数的任意一个偏移量都可以成功加载内核探针的,eBPF
对探针所在的代码段有一定的要求。此时带有偏移量的kprobe
下的arg0
、arg1
很可能会失去意义,不过可以通过regs
变量访问探针处的寄存器。笔者编写了offset.ply
脚本(该偏量的计算仅限于内核版本:Linux ubuntu 5.13.0-39-generic #44~20.04.1-Ubuntu
),演示如何通过带偏移量的探针,确定一个脚本的解析器:
#!/usr/sbin/ply -k/*
fs/binfmt_script.c
static int load_script(struct linux_binprm *bprm)
{...file = open_exec(i_name);if (IS_ERR(file))return PTR_ERR(file);bprm->interpreter = file;return 0;
}
(gdb) disassemble load_script
Dump of assembler code for function load_script:0xffffffff813b8270 <+0>: callq 0xffffffff81077840 <__fentry__>0xffffffff813b8275 <+5>: cmpw $0x2123,0xa0(%rdi)......0xffffffff813b83f9 <+393>: mov %r12,%rdi0xffffffff813b83fc <+396>: callq 0xffffffff8132f1c0 <open_exec>
*/tracepoint:syscalls/sys_enter_execve
{newapp[kpid] = data->filename;
}tracepoint:syscalls/sys_enter_execveat
{newapp[kpid] = data->filename;
}tracepoint:syscalls/sys_exit_execve
{if (newapp[kpid] != 0) {delete newapp[kpid];}
}tracepoint:syscalls/sys_exit_execveat
{if (newapp[kpid] != 0) {delete newapp[kpid];}
}kprobe:load_script+393 /newapp[kpid]/
{if (regs->r12 != 0) {printf("PID: %d, invoker: %s, file: %s, interpreter: %s\n",pid, comm, str(newapp[kpid]), str(regs->r12));print(stack);}
}
笔者在load_script
的393字节偏移处加入内核探针,该处的寄存器r12
指向了脚本的解析器路径,通常为/bin/sh
等。ply
对内核数据的访问是有限的,不能像bpftrace
那样实现C语言层面的结构体解引用;以上脚本仅仅是将r12
寄存器转化为一个字符串并输出。笔者用ply
加载该脚本后,在另一个终端分别执行which -a perldoc
、perldoc perl
,可得到以下跟踪信息:
# ply -k offset.ply
PID: 35873, invoker: bash, file: /usr/bin/which, interpreter: /bin/shload_script+394exec_binprm+314bprm_execve+365do_execveat_common.isra.0+393__x64_sys_execve+55msr_save_cpuid_features+425_raw_write_lock_irqsave+2061404PID: 35874, invoker: bash, file: /usr/bin/perldoc, interpreter: /usr/bin/perlload_script+394exec_binprm+314bprm_execve+365do_execveat_common.isra.0+393__x64_sys_execve+55msr_save_cpuid_features+425_raw_write_lock_irqsave+2061404
可见在ubuntu
系统上,可执行文件/usr/bin/which
是一个shell
脚本,其解析器为/bin/sh
;而/usr/bin/perldoc
也是一个脚本,其解析器为/usr/bin/perl
。
PLY:嵌入式Linux环境下的内核探测工具相关推荐
- 嵌入式LINUX环境下视频采集知识
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备和相应的 ...
- linux视频采集软件,嵌入式LINUX环境下视频采集
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备和相应的 ...
- Linux 环境下的抓包工具 - tcpdump
Linux 环境下,通常通过 tcpdump 来进行抓包和分析.它是几乎所有 Linux 发行版本预装的数据包抓取和分析工具. 一.tcpdump 的用法 tcpdump [-aAbdDefhHIJK ...
- 10 款 Linux 环境下的开源替代工具
在 Linux 操作系统下,我们经常使用 cat 命令去连接多个文件并打印到标准输出,合成几个文件为一个目标文件,追加几个文件到目标文件中. 最近我在 GitHub 上发现了一个具有相似作用的命令叫做 ...
- Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
1.什么是 Rsync? Rsync 是一个开源的快速备份工具,是Linux和UNIX操作系统默认安装的组件之一,可在不同主机间镜像同步整个目录,并支持增量备份文件传输,保持链接和权限,采用优化的同步 ...
- linux下udp数据包接收工具,linux环境下数据包回放工具--pplayer分享
pplayer(packet player)是我写的一款小工具,支持主流协议,专门用来测试IPS和防火墙设备,经长时间验证,简单可靠,故发布. 程序的原理很简单,首先加载pcap包中的数据包,保存在内 ...
- 翻译python代码的软件_Linux环境下的Python翻译工具源码
玩蛇网Python教程源码示例,本文源码是用于Linux环境下的Python翻译工具源码详解. 学习计算机编程语言一定会涉及到英文和学习应用,但通常英语词典在Linux环境下都不如Win环境下的好用. ...
- linux环境下调试嵌入式设备时出现Aborted、segmentation fault、卡死的问题以及关于指针使用的一点想法
linux环境下调试一些嵌入式设备时出现Aborted.segmentation fault.卡死的问题,这些问题可能的原因为: 1.Aborted的问题,例如: # ./logUtils0322 [ ...
- 嵌入式Linux安装Python环境,linux环境下安装python 3
说明: 在linux环境下,都默认安装python 2的环境,由于python3在python2的基础上升级较大,所以安装python 3环境用于使用最新的python 3的语法. 安装过程: 1.下 ...
最新文章
- 553 mail from must equal authorized user解决方法
- mysql 日期字符串互转
- Windows Server 2012正式版RDS系列②
- MySQL常用存储引擎之Federated
- 置顶带滚动效果_前端面试:如何实现轮播图效果?
- Task On The Board CodeForces - 1367D(思维)
- [工具]OFFICE插件管理工具-帮助更好地管理及使用电脑安装过的OFFICE插件
- C语言中局部变量和全局变量 变量的存储类别
- Unabe to login, status: 526
- hdu5141 线段树
- (转)Aladdin PK SimCorp Dimension
- Java基础面试题整理-50题(附答案)
- 解析eas webservice
- 计算机中rom和ram分别指什么,RAM和ROM分别是什么意思
- SAP SD客户主数据
- 社交网络时代下的网络营销
- Win10系统antimalware service executable进程占用cpu过高的问题
- 什么是HTML,看完这篇文章就懂了
- java 身份证智能识别
- Msp430 bsl program