简单Linux系统环境下的内核探测

在笔者之前的文章中提到,基于内核eBPF探针的常用工具主要bpftrace、bcc,二者复杂的依赖库使得其在嵌入式Linux系统环境下常常是不可用的。截止目前,一些嵌入式SDK(例如buildroot及openwrt等)未提供这两个性能分析工具的自动化构建功能。一种可行的方案是参考Linux内核源码samples/bpf下的示例编写基于eBPF的C代码,并编译生成BTF目柡文件和可执行应用,用于嵌入式设备上的性能分析。这种方案可行但实施的效率较低。幸运的是,同属于iovisor的开源软件PLY很好地填补了这一空缺,它可以使用eBPF子系统对Linux内核进行监测,而且没有复杂的依赖库(仅依赖libc库)。其用法接近bpftrace,尽管功能较弱,但一定程度上能够满足要求。其最大的缺憾是缺少对局部变量和uprobe功能的支持。本文主要对ply的内核探测做相关的演示说明。

监测文件的打开

bpftracebcc工具都提供了一个名为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多个内置的变量和函数,如timestr等。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))

pidkpidcomm等变量由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探测一些函数的入口时,通过arg0arg1等变量可以访问到函数的入参,这些入参的数据类型为整数,据此可以实现探测的过滤。笔者编写的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_WRONLYO_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下的arg0arg1很可能会失去意义,不过可以通过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 perldocperldoc 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环境下的内核探测工具相关推荐

  1. 嵌入式LINUX环境下视频采集知识

    Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备和相应的 ...

  2. linux视频采集软件,嵌入式LINUX环境下视频采集

    Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备和相应的 ...

  3. Linux 环境下的抓包工具 - tcpdump

    Linux 环境下,通常通过 tcpdump 来进行抓包和分析.它是几乎所有 Linux 发行版本预装的数据包抓取和分析工具. 一.tcpdump 的用法 tcpdump [-aAbdDefhHIJK ...

  4. 10 款 Linux 环境下的开源替代工具

    在 Linux 操作系统下,我们经常使用 cat 命令去连接多个文件并打印到标准输出,合成几个文件为一个目标文件,追加几个文件到目标文件中. 最近我在 GitHub 上发现了一个具有相似作用的命令叫做 ...

  5. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步

    1.什么是 Rsync? Rsync 是一个开源的快速备份工具,是Linux和UNIX操作系统默认安装的组件之一,可在不同主机间镜像同步整个目录,并支持增量备份文件传输,保持链接和权限,采用优化的同步 ...

  6. linux下udp数据包接收工具,linux环境下数据包回放工具--pplayer分享

    pplayer(packet player)是我写的一款小工具,支持主流协议,专门用来测试IPS和防火墙设备,经长时间验证,简单可靠,故发布. 程序的原理很简单,首先加载pcap包中的数据包,保存在内 ...

  7. 翻译python代码的软件_Linux环境下的Python翻译工具源码

    玩蛇网Python教程源码示例,本文源码是用于Linux环境下的Python翻译工具源码详解. 学习计算机编程语言一定会涉及到英文和学习应用,但通常英语词典在Linux环境下都不如Win环境下的好用. ...

  8. linux环境下调试嵌入式设备时出现Aborted、segmentation fault、卡死的问题以及关于指针使用的一点想法

    linux环境下调试一些嵌入式设备时出现Aborted.segmentation fault.卡死的问题,这些问题可能的原因为: 1.Aborted的问题,例如: # ./logUtils0322 [ ...

  9. 嵌入式Linux安装Python环境,linux环境下安装python 3

    说明: 在linux环境下,都默认安装python 2的环境,由于python3在python2的基础上升级较大,所以安装python 3环境用于使用最新的python 3的语法. 安装过程: 1.下 ...

最新文章

  1. 553 mail from must equal authorized user解决方法
  2. mysql 日期字符串互转
  3. Windows Server 2012正式版RDS系列②
  4. MySQL常用存储引擎之Federated
  5. 置顶带滚动效果_前端面试:如何实现轮播图效果?
  6. Task On The Board CodeForces - 1367D(思维)
  7. [工具]OFFICE插件管理工具-帮助更好地管理及使用电脑安装过的OFFICE插件
  8. C语言中局部变量和全局变量 变量的存储类别
  9. Unabe to login, status: 526
  10. hdu5141 线段树
  11. (转)Aladdin PK SimCorp Dimension
  12. Java基础面试题整理-50题(附答案)
  13. 解析eas webservice
  14. 计算机中rom和ram分别指什么,RAM和ROM分别是什么意思
  15. SAP SD客户主数据
  16. 社交网络时代下的网络营销
  17. Win10系统antimalware service executable进程占用cpu过高的问题
  18. 什么是HTML,看完这篇文章就懂了
  19. java 身份证智能识别
  20. Msp430 bsl program

热门文章

  1. unittest自动化测试测试框架从0到实战详解
  2. 开源软件之七宗罪以及背后的阴谋
  3. 拒绝「技术栈」选择恐惧症
  4. Dynamics CRM2016 Web API之Create related entities in one operation
  5. 手把手教你如何做电视直播
  6. r语言各形状编号_R语言,超级英雄云词图,你们要的自定义形状来了
  7. 阿里新增37天假期,“反卷”第一战正式打响
  8. pmp通过率都97%,只要考都能过?还有啥价值?
  9. CentOS 6.5下搭建Maven私服nexus
  10. 打印孩子微信群里的作业去掉灰黑色背景