简单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. 纯CSS3进行hover时显示带箭头和动画的tips效果
  2. com/opensymphony/xwork2/spring/SpringObjectFactory.java:220:-1问题出现的原因及解决办法
  3. fifo 上使用 select -- 转
  4. Win10系统下Visio安装失败问题
  5. 3种常用的防盗链的方式
  6. 中国计算机学会CCF推荐国际学术会议和期刊目录-计算机网络
  7. SAP ABAP Netweaver里的胖接口(fat interface)
  8. php7.1 aes 加密解密,PHP7.1中AES加密解密方法 mcrypt_module_open()替换方案
  9. 浅析 record 使用场景
  10. 一个知乎重度用户眼中的知乎
  11. 黄永成think php rest,Rest控制器
  12. 仿uc设置默认浏览器
  13. **Unity环境光遮蔽(Ambient Occlusion)Shader实现逻辑**
  14. 缺陷管理工具--mantis使用过程
  15. Java 运算符 输入 分支语句
  16. 抖音账号如何打造,抖音直播带货怎么做:国仁楠哥
  17. SSM框架-MyBatis(一)
  18. struts2技术内幕读书笔记1
  19. 【工具】Chrome浏览器书签误删恢复
  20. 【webrtc】老版本的OnReceivedPayloadData 及VCMPacket

热门文章

  1. jquery获取以固定字符串开头的id的div
  2. 地方网站如何留住用户?
  3. 云计算的五大技术点+背景介绍
  4. ios编程360相机滤镜
  5. 大数据/数仓面试灵魂30问
  6. Android学习之工具类二:播放声音工具类
  7. docker(ubuntu)中安装cron运行crontab定时任务
  8. (转)WIFI基本知识整理
  9. 【2023】某python语言程序设计跟学第四周内容
  10. python计算均方误差_Python:如何计算分布的均方误差?