SystemTap(stap)架构和原理介绍,以及脚本编写举例

news2024/9/20 10:53:14

1 SystemTap简介

SystemTap是一个诊断Linux系统性能或功能问题的开源工具。它允许开发人员和系统管理员深入研究内核甚至用户空间应用程序的行为,以便发现错误状态、性能问题,或者仅仅为了解系统是如何工作的。它使得对运行时的Linux系统进行诊断调式变得更容易、更简单。通过使用SystemTap,可以很容易地总结和可视化与系统工作有关的信息,使您能够轻松地获得处理简单和复杂问题所需的信息

为了诊断系统问题或性能,开发者或调试人员只需要写一些脚本,而且SystemTap本身也提供了很多脚本,称为"tapset"方便开发。通过SystemTap提供的命令行接口就可以对正在运行的内核进行诊断调试,以前需要的修改或插入调试代码、重新编译内核、安装内核和重启动等这些琐碎的工作完全消除

当前该项目的主要开发人员为来自Red Hat, IBM, Intel和Hitachi的工程师。其中Redhat主要负责脚本转换/翻译器和运行时库,IBM负责kprobe和relayfs,Intel负责转换器安全检查以及performance monitor tapset。

2 SystemTap架构

2.1 Kprobes简介

SystemTap用于检查运行的内核的两种方法是 Kprobe和返回探针(return probes)。
Kprobes从 2.6.9 版本开始就添加到主流的 Linux 内核中,并且为探测内核提供一般性服务,最重要的两种服务是 Kprobe 和 Kretprobe。

Kprobe是一个通用钩子,可以插入内核代码中的几乎任何地方。为了允许它探测一条指令,该指令的第一个字节被替换为所使用架构的断点指令。当命中这个断点时,Kprobe接管并执行对应的探针处理程序代码,执行完成之后,接着执行原始的指令(从断点开始)。

Kretprobe(返回探针)有所不同,它操作调用函数的返回结果。因为一个函数可能有多个返回点,它实际使用一种称为 trampoline(蹦床) 的简单技术。它向函数条目添加一小段代码,而不是检查函数中的每个返回点。这段代码使用 trampoline 地址替换堆栈上的返回地址。当函数退出时,它返回到trampoline地址,而不是最初设置它返回的位置,并调用探测的处理程序代码。然后从 Kretprobe 返回到实际的调用方。Kretprobe的工作原理如图2-1所示。
在这里插入图片描述

图2-1 kretprobe工作原理

2.2 SystemTap流程

SystemTap的基本流程主要涉及到 3 个交互实用程序stap、staprun和stapio,以及如下5个阶段:
1. 解析阶段:将stap脚本解析成语法解析树,会执行预处理,以及语义和语法正确性检查。
2. 细化阶段:解析脚本中的符合和引用,导入脚本中引用的脚本库tapsets(目录一般为/usr/share/SystemTap/tapset/)和内核调试信息。
3. 转化阶段:结合细化阶段的输出,将解析树转换成 C 源代码。
4. 构建阶段:将C代码编译成可以在内核中动态加载和执行的内核模块。
5. 执行阶段:staprun 和 stapio负责将模块安装到内核中并将输出发送到 stdout。如果在shell中按组合键 Ctrl-C或脚本退出,将执行清除进程,这将导致卸载模块并退出所有相关的实用程序。SystemTa在加载这个模块后,会开启脚本中的探测点。这个功能由systemtap-runtime包中的staprun提供;当某个事件发生时,对应的处理句柄(handler)就会执行;当SystemTap会话终止时,探测就会停止,对应的内核模块也会卸载。
SystemTap的流程如图2-2和图2-3所示。

图2-2 SystemTap流程图

在这里插入图片描述

图2-3 SystemTap逻辑流程图

SystemTap 的一个有趣特性是缓存脚本转换的能力。如果安装后的脚本没有更改,您可以使用现有的模块,而不是重新构建模块。图2-4显示了user-space和kernel-space 元素以及基于 stap的转换流程。
在这里插入图片描述

图2-4 SystemTap用户内核空间

2.3 SystemTap原理

SystemTap的核心思想是定义一个事件(event),以及给出处理该事件的句柄(Handler)。当一个特定的事件发生时,内核运行该处理句柄,就像快速调用一个子函数一样,处理完之后恢复到内核原始状态。这里有两个概念:

  • 事件(Event):SystemTap定义了很多种事件,例如进入或退出某个内核函数、定时器时间到、整个SystemTap会话启动或退出等等。

  • 句柄(Handler):就是一些脚本语句,描述了当事件发生时要完成的工作,通常是从事件的上下文提取数据,将它们存入内部变量中,或者打印出来。

SystemTap工作原理是通过将脚本语句翻译成C语句,编译成内核模块。模块加载之后,将所有探测的事件以钩子的方式挂到内核上,当任何处理器上的某个事件发生时,相应钩子上句柄就会被执行。最后,当SystemTap会话结束之后,钩子从内核上取下,移除模块。整个过程用一个命令 stap 就可以完成。

SystemTap使用了类似于awk和C语言的脚本语言(类似于Dtrace的D语言)。它只使用了三种数据类型:整数(integers)、字符串(strings)和关联数组(associative Arrays)。它有完整的控制结构,包括块(blocks)、条件(conditionals)、循环(loops)和函数(functions)。语句分割符;是可选的,变量不需要声明类型,它们是根据上下文自动推测和检查的,它使用了kprobe提供的接口来实现探测,对于每一个探测,需要定义探测点以及相应的处理函数,探测点就是指kprobe中被探测的函数或指令地址(也被称为内核事件),但在SystemTap中,用户可以指定原文件,原代码的某一行,或者一个异步事件,如周期性的定时器,探测点使用了层次化的命名方式,探测点处理函数能够立刻输出数据,与printk很类似,它也能查看内核数据。脚本被一个翻译器转换成C代码并编译成一个内核模块。探测点根据内核的DWARF调试信息映射到内核的虚地址(因此SystemTap要求用户必须准备好可用的内核调试信息),所有的脚本内容在转换时进行严格的检查,并且在运行时也要检查(如无限循环、内存使用、递归和无效指针等),因此有好的安全性,不会影响正在运行的系统(这对生产系统是非常重要的)。 SystemTap包含了一个黑名单,其中列出的函数不能被SystemTap探测,因为它们会导致无限探测循环、锁重入等问题。

图2-5直观地给出了SystemTap的工作原理:
在这里插入图片描述

图2-5 SystemTap工作原理

SystemTap脚本文件是.stp后缀的文件,使用的脚本语言是前面讲到的SystemTap自己定义的脚本语言,一个SystemTap脚本描述了将要探测的探测点以及定义了相关联的处理函数,每一个探测点对应于一个内核函数或事件或函数内部的某一位置。被关联的处理函数将在内核执行到对应的探测点时被执行。

tapsets是一个脚本库,包含了许多tapset,每一个tapset一般为某一内核子系统或特定的功能块预定义了一套探测点、辅助函数或全局变量供用户脚本或其它的tapset引用,它定义的一些数据能够被每一个探测点处理函数或脚本使用,这些数据通常通过使用处理函数语句块(HSB Handler Statement Block)来出口,HSB语句块中的变量就是被出口的数据。tapset一般由该内核子系统的开发者或对子系统非常了解的开发者编写,既使用了脚本语言,也使用了C语言,并且它已经被测试和验证,可以安全使用。tapsets属于SystemTap发行包的一部分。

SystemTap实现了一个脚本转换器/翻译器,当用户执行一个SystemTap脚本时,SystemTap将首先对它进行分析和一些安全检查,如果它引用了SystemTap预定义的脚本库提供的函数,SystemTap也将读取脚本库得到相应的代码,对于一些内核变量或符号的引用,它必须根据内核调试信息来解析到相应的地址。然后,它被转换成C代码,在这个转换中,SystemTap将根据需要增加必要的锁和安全检查代码。探测点之间共享的变量将被转换成恰当的静态声明并有锁保护,每组本地变量被转换到一个合成的调用帧结构中以避免消耗内核的栈空间。关联到探测点的处理函数被封装成一个接口函数,那调用恰当的kprobe接口函数来注册该探测点。

产生的C代码包含了一些对运行时tapset的引用,运行时tapset库提供了许多SystemTap接口函数,如通用的查询表、受限内存管理、启动、关闭、I/O操作以及其它一些函数。生成的C代码编译链接之后生成一个可加载的内核模块。为了快速得到运行结果,SystemTap使用了relayfs,当加载生成的内核模块后,该模块的初始化函数初始化自身,然后调用kprobe接口函数注册脚本中定义的探测点。当内核运行到注册的探测点时,相应的处理函数被调用,用户在处理函数中的输出语句将调用relayfs接口函数输出结果数据,用户在处理函数也可以调用一些内核的性能测量函数。当用户主动停止或脚本设定的条件满足时,模块将调用退出函数卸载已经注册的探测点并做一些清理处理就卸载模块自身。

SystemTap在运行时启动了一个进程,它专门负责通过relayfs读去模块的输出数据并即时地输出给用户。

3 SystemTap脚本编写

3.1 探针

SystemTap脚本由探针和在触发探针时需要执行的代码块组成。探针有许多预定义模式,表3-1列出了其中的一部分。这个表列举了几种探针类型,包括调用内核函数和从内核函数返回。

探针类型说明
begin在脚本开始时触发
end在脚本结束时触发
kernel.function(“sys_sync”)调用 sys_sync 时触发
kernel.function(“sys_sync”).call同上
kernel.function(“sys_sync”).return返回 sys_sync 时触发
kernel.syscall.*进行任何系统调用时触发
kernel.function(“*@kernel/fork.c:934”)到达 fork.c 的第 934 行时触发
module(“ext3”).function(“ext3_file_write”)调用 ext3 write 函数时触发
timer.jiffies(1000)每隔 1000 个内核 jiffy 触发一次
timer.ms(200).randomize(50)每隔 200 毫秒触发一次,带有线性分布的随机附加时间(-50 到 +50
表3-1 探针

下面这个简单的探针例子,它在调用内核系统调用 sys_sync 时触发。当该探针触发时,您希望计算调用的次数,并发送这个计数以及表示调用进程名字和ID(PID)的信息。

首先,声明一个任何探针都可以使用的全局值(全局名称空间对所有探针都是通用的),然后将它初始化为0。其次,定义您的探针,它是一个探测内核函数 sys_sync的条目。与探针相关联的脚本将递增 count变量,然后发出一条消息,该消息定义调用的次数和当前调用的进程名字和PID。

global count=0
probe kernel.function("sys_sync") {
  count++
  printf("sys_sync called %d times, currently by execname:%s pid:%d\n",
         count, execname(), pid());
}

运行结果如下:
在这里插入图片描述

3.2 元素

SystemTap 允许定义多种类型的变量,但类型是从上下文推断得出的,因此不需要使用类型声明。在 SystemTap 中,您可以找到数字(64 位签名的整数)、整数(64 位)、字符串和字面量(字符串或整数)。您还可以使用关联数组和统计数据。

SystemTap 提供 C 语言中常用的所有必要操作符,并且用法也是一样的。您还可以找到算术操作符、二进制操作符、赋值操作符和指针废弃。您还看到从 C 语言带来的简化,其中包括字符串连接、关联数组元素和合并操作符。

3.3 语句

在探针内部,SystemTap 提供一组类似于C一样易于使用的语句。注意,尽管该语句允许您开发复杂的脚本,但每个探针只能执行 1000 条语句(这个数量是可配置的)。表3-2列出了一小部分语句作为例子。注意,在这里的许多元素和 C 中的一样,尽管有一些附加的东西是特定于SystemTap的。

语句说明
if (exp) {} else {}标准的 if-then-else 语句
for (exp1 ; exp2 ; exp3 ) {}一个 for 循环
while (exp) {}标准的 while 循环
do {} while (exp)一个 do-while 循环
break退出迭代
continue继续迭代
next从探针返回
表3-2 语句

3.4 函数

SystemTap 提供许多内部函数,这些函数提供关于当前上下文的额外信息。例如,您可以使用 caller() 识别当前的调用函数,使用 cpu() 识别当前的处理器号码,以及使用 pid() 返回 PID,对调用堆栈访问等。表3-2列出了一小部分函数

函数名说明
execname()获取当前进程的名称,即可执行文件的名称
pid()获取当前进程的PID
tid()获取当前线程的ID
cpu()获取当前CPU的ID
pp()描述当前被处理的探针点的字符串。例如probe process.syscall,process.end { /* scripts */},在块中调用pp()会返回"process.syscall"和"process.end"。
probefunc()获取当前probe的函数名称。例如probe sys_read函数,在probe块中调用该函数就会返回sys_read。注意这个函数的返回值是从pp()返回的字符串中解析得到的。
ppfunc()获取当前probe的函数名称。在probe指定文件中的函数中时非常有用,可以知道当前的probe位于哪个函数。
gettimeofday_s()自从Epoch开始的秒数,相同的还有ms、us和ns
tz_ctime(sec)根据输入的秒数,转换为date格式的字符串。tz_ctime(gettimeofday_s())
get_cycles()获取处理器周期数
print_backtrace()打印内核调用栈信息
print_ubacktrace()打印用户态调用栈信息
thread_indent()输出当前线程的信息,格式为"相对时间 程序名称(线程id):(空格)",如果当probe的函数执行的次数约到,空格的数量也就越多。这个函数还有一个参数,用来控制空格的数量。如果参数值越大,则空格的数量越多。相对时间是当前的时间(以微秒为单位)减去指定线程第一次执行thread_indent时的时间。
表3-3 函数

下面给出调用这些函数一个脚本示例

probe begin,end {
    sec = gettimeofday_s()
    printf("pp() = %s, probefunc() = %s, ppfunc() = %s\n",
            pp(), probefunc(), ppfunc())
    printf("execname = %s, pid = %d, tid = %d, cpu = %d\n",
            execname(), pid(), tid(), cpu())
    printf("gettimeofday_s() = %ld.\n", sec)
    printf("tz_ctime(sec) = %s.\n", tz_ctime(sec))
    printf("get_cycles() = %d.\n", get_cycles())

}

probe kernel.function("sys_sync") {
    printf("\n====sys_sync entry====\n")
    printf("pp() = %s, probefunc() = %s, ppfunc() = %s\n",
            pp(), probefunc(), ppfunc())
    printf("execname = %s, pid = %d, tid = %d, cpu = %d\n",
            execname(), pid(), tid(), cpu())
    print_backtrace()
    thread_indent(1)
}

probe kernel.function("sys_sync").return {
    printf("\n====sys_sync return====\n")
    printf("pp() = %s, probefunc() = %s, ppfunc() = %s\n",
            pp(), probefunc(), ppfunc())
    printf("execname = %s, pid = %d, tid = %d, cpu = %d\n",
            execname(), pid(), tid(), cpu())
}

运行结果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2077106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

递归算法及应用

一.简介 1.介绍 递归(Recursion)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法,其核心思想是分治策略。 在日常开发中,我们使用循环语句远远大于递归,但这不能说明递归就没有用武之地&am…

服务器(百度云)部署项目(jar包)

java项目打包成jar包:clean------compile------install jar包上传到服务器上 和jar包相同的文件里,创建Dockerfile文件。 Dockerfile文件的内容informationerasure是jar包名,这里可根据自己定义的名字进行更换。 Dockerfile文件内容&#x…

chrome打印dom节点不显示节点信息

正常直接console dom节点 代码改成 var parser new DOMParser(); var docDom parser.parseFromString(testHtml, text/html); console.log(docDom) let htmlHeader ref< HTMLElement | null>(null) let htmlBoby ref< HTMLElement | null>(null) htmlHeader.v…

Datawhale AI 夏令营 第五期 CV Task1

活动简介 活动链接&#xff1a;Datawhale AI 夏令营&#xff08;第五期&#xff09; 以及CV里面的本次任务说明&#xff1a;Task 1 从零上手CV竞赛 链接里的教程非常详细&#xff0c;很适合小白上手&#xff0c;从报名赛事到使用服务器平台再到跑模型&#xff0c;手把手教&…

【Go语言基础】调度器模型GPM与垃圾回收器GC

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了Go语言学习的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于Go语言趣学指南进行的&#xff0c;每个知识点的修正和深入主要参…

ubuntu系统在线安装下载firefox-esr流览器

1、在线firefox流览器 Firefox ESR(Extended Support Release)是火狐浏览器的长期支持版本&#xff0c;针对同一个主版本提供一年左右的安全性与稳定性支持。如果您因为火狐浏览器改版而导致有原本能用的功能变得不能使用的话(例如Firefox 64.0把RSS订阅的功能拿掉了)&#xf…

IO进程day02(文件IO)

目录 【1】什么是文件IO 1》概念 2》特点 3》操作 【2】函数接口 1》打开文件open() 思考&#xff1a;文件IO和标准IO的打开方式的对应关系 2》关闭文件 close() 3》读写文件 read write 1> 读文件 read() 2> 写文件 write() 练习&#xff1a;文件IO实现cp…

使用AWS的EC2服务如何降低成本

在现代企业中&#xff0c;云计算已经成为推动业务创新和发展的重要工具。亚马逊云服务&#xff08;AWS&#xff09;的弹性计算云&#xff08;EC2&#xff09;提供了灵活的计算能力&#xff0c;企业可以根据需求快速部署和管理应用。然而&#xff0c;如何在使用EC2服务的过程中有…

无人机之多旋翼无人机的用途

一、航拍与摄影 高清摄像&#xff1a;多旋翼无人机搭载高清摄像头&#xff0c;可以进行高空拍摄&#xff0c;获取清晰的图像和视频资料&#xff0c;广泛应用于影视制作和新闻报道。 实时传输&#xff1a;无人机可通过图像传输设备&#xff0c;实现实时画面回传&#xff0c;为…

linux常见基础命令

Linux基础命令 (下面这些命令都是最常见的命令.更复杂的会在之后的C语言学习陆续深入) 1、 pwd 功能&#xff1a; print work directory的缩写&#xff0c;显示当前目录的绝对路径 2、 cd 功能&#xff1a; change directory的缩写&#xff0c;切换目录 绝对路径&#xff1a;以…

知识改变命运 数据结构【二叉树OJ题】

1. 检查两颗树是否相同OJ链接 class Solution {public boolean isSameTree(TreeNode p, TreeNode q) {if(pnull&&q!null||p!null&&qnull) {return false;}if (pnull&&qnull) {return true;}if (p.val!q.val) {return false;}boolean leftisSameTree(p.…

数据库内容保密检查系统:及时发现“潜在”安全威胁

日前&#xff0c;国内专注于保密与非密领域的分级保护、等级保护、业务连续性安全和大数据安全产品解决方案与相关技术研究开发的领军企业——国联易安自主研发的国联数据库内容保密检查系统V1.0通过国保局涉密检测&#xff0c;获得涉密信息系统产品检测证书。其主要具备以下主…

nginx访问控制,用户认证,https

nginx访问控制 用于location段Allow&#xff1a;设定允许哪台或哪些主机访问&#xff0c;多个参数间用空格隔开Deny&#xff1a;设定禁止那台或哪些主机访问&#xff0c;多个参数间用空格隔开比如&#xff1a;allow 192.168.100.20 192.168.100.30;deny all;拒绝某台主机访问…

JavaEE 第18节 TCPUDP优缺点(对比)

目录 前言1、UDP(User Datagram Protocol)优点缺点 2、TCP(Transmission Control Protocol)优点缺点 3、总结 前言 基于TCP/IP协议栈&#xff0c;TCP和UDP属于传输层协议。在Java生态中也分别提供了相关套接字&#xff08;Socket&#xff09;1的API&#xff0c;方便网络编程。…

基于OpenCV+MFC的KCF测速软件

基于OpenCVMFC的KCF测速软件 引言原理介绍使用介绍&#xff08;1&#xff09;主界面&#xff08;2&#xff09;打开视频&#xff08;3&#xff09;点击KCF测速&#xff08;4&#xff09;框选待检测目标&#xff08;5&#xff09;测速结果 资源链接&#xff08;包含源码&#xf…

How do I do function calling in Azure Openai using the javascript sdk

题意&#xff1a;如何使用 JavaScript SDK 在 Azure OpenAI 中进行函数调用&#xff1f; 问题背景&#xff1a; I want to be able to call functions based on the user input, I could do this with the openai library but cant find a way to do so in the azure openai l…

uniapp中$off没写正确引发的问题~

你们好&#xff0c;我是金金金。 场景 我正在使用uniapp开发微信小程序&#xff0c;涉及到几个页面之间的事件通信&#xff0c;采用的是uniapp里面的$on和&off来达到页面之间的互相通信功能如下&#xff1a;有一个选择城市的页面&#xff0c;当选择了某个城市&#xff0c;另…

树数据结构(Tree Data Structures)的全面指南:深度解析、算法实战与应用案例

树数据结构&#xff08;Tree Data Structures&#xff09;的全面指南&#xff1a;深度解析、算法实战与应用案例 引言 树数据结构&#xff08;Tree Data Structures&#xff09;作为计算机科学中的基石之一&#xff0c;以其独特的层次结构和分支特性&#xff0c;在众多领域发…

Hive SQL

一、基本数据类型 tinyint 1byte 有符号整数 smallint 2byte 有符号整数 int 4byte 有符号整数 bigint 8byte 有符号整数 boolean 布尔类型&#xff0c;true或者false float 单精度浮点数 double 双精度浮点数 decim…

系统数据迁移脱敏方案(word)

数据脱敏工作不仅要确保敏感信息被去除&#xff0c;还需要尽可能的平衡脱敏所花费的代价、使用方的业务需求等多个因素。因此&#xff0c;为了确保数据脱敏的过程、代价可控&#xff0c;得到的结果正确且满足业务需要&#xff0c;在实施数据脱敏时&#xff0c;应从技术和管理两…