《System语言详解》4 探测点.docx
- 文档编号:26610852
- 上传时间:2023-06-20
- 格式:DOCX
- 页数:18
- 大小:25.03KB
《System语言详解》4 探测点.docx
《《System语言详解》4 探测点.docx》由会员分享,可在线阅读,更多相关《《System语言详解》4 探测点.docx(18页珍藏版)》请在冰豆网上搜索。
《System语言详解》4探测点
《System语言详解》——4.探测点
英文原文:
http:
//sourceware.org/systemtap/langref/Probe_points.html
译者:
林永听
注:
本系列文章为作者连载作品,请勿转载,否则视为侵权。
4探测点
4.1探测点的一般语法形式
探测点采用点分格式的语法,事件命名空间划分成多个部分,类似于域名系统。
每部分可以由字符串或数字等字面值来参数化,与函数调用的语法格式十分相似。
下面是符合语法规则的探测点:
kernel.function("foo")
kernel.function("foo").return
module{"ext3"}.function("ext3_*")
kernel.function("no_such_function")?
syscall.*
end
timer.ms(5000)
在某种程度上,探测点可分成同步和异步两大类。
当CPU执行到探针指定的指令时,产生一个同步事件,探针可利用引用点(指令地址)来获取更多的上下文数据。
另一探测点家族与异步事件相关,如定时器。
与同步探测点不同的是,它没有固定的引用点。
每个探测点可以指定匹配多个位置,例如使用通配符或多个探针别名,多个位置均被探测。
在探针声明中,使用逗句作为分隔符来指定多个探测位置。
4.1.1前缀
探测点的前缀阐明了探测目标,如kernel,module和timer,等等。
4.1.2后缀
后缀进一步细化了探测点,例如.return探测函数的退出点。
缺少后缀时意味着探测函数的进入点。
4.1.3文件名和函数名的通配符
探测点各部分若包含星号字符(*),则扩展为与之匹配的探测点。
请看下述例子:
kernel.syscall.*
kernel.function("sys_*)
4.1.4可选探测点
如果探测点后面跟随一个问号(?
)字符,表明这是可选探测点,即使它扩展失败,也不会导致错误。
从顶层开始到底层,各层的别名和通配符扩展同样遵循此法则。
可选探测点的语法形式如下:
kernel.function("no_such_function")?
4.2内置探测点分类(DWARF探针)
探测点家族使用目标内核或模块的符号调试信息(symbolicdebugginginformation),这些信息可包含在未经stripped的可执行文件,或在一个独立的debuginfo软件包中。
通过指源或目标代码点的集合,探针可在逻辑上定位到目标执行路径。
当任一处理器执行与之匹配的语句,探针处理函数将在些上下文中运行。
内核点(pointsinakernel)可由模块,源文件,行号,函数名或者它们的组合来定位。
下述是目前支持的探测点清单:
kernel.function(PATTERN)
kernel.function(PATTERN).call
kernel.function(PATTERN).return
kernel.function(PATTERN).return.maxactive(VALUE)
kernel.function(PATTERN).inline
kernel.function(PATTERN).label(LPATTERN)
module(MPATTERN).function(PATTERN)
module(MPATTERN).function(PATTERN).call
module(MPATTERN).function(PATTERN).return.maxactive(VALUE)
module(MPATTERN).function(PATTERN).inline
kernel.statement(PATTERN)
kernel.statement(ADDRESS).absolute
module(MPATTERN).statement(PATTERN)
.function变体使探针定位在命名函数的开始之处,因此探针可用上下文变量的方式来获取函数参数。
.return变体让探针定位到命名函数返回的那一时刻,因此,探针可能过上下文变量$return来获取函数的返回值。
探针仍然可以获得函数的参数,但此时它们的值可能在函数执行过程中发生了变化。
可以使用.maxactive进一步修饰return探针,它指定该函数有多少个实例可以同时被探测。
大多数情况下,不需要指定.maxactive,默认情况已足够使用了。
然而,如果被忽略的探针过多,可以尝试将.maxactive调高,再看看被忽略的探针是否减少。
.inline修饰符使.function变体过滤出那些仅为内联函数的实例,而.call修饰符刚好选择相反的子集。
内联函数没有唯一的返回点,因此.inline探针不支持.return后缀。
.statement变体使探针探测到确切的代码行,函数内的局部变量对探针来说是可见的。
在上述探针描述中,MPATTERN是一个字符串字面值,它标识加载的内核模块;LPATTERN代表源程序标签。
MPATTERN和LPATTERN两者均可包含星号(*),方括号“[]”和问号(?
)等通配符。
PATTERN是一个字符串字面值,它标识程序中的代码点。
它由3部分构成。
第一部分是函数名字,该名字与nm工具的输出一致。
此部分可使用星号和问号通配符来匹配多个函数名字。
第二部分是可选的,它以@字符开头,紧跟着此函数所在源文件的路径。
此路径可以包含通配符模式,如mm/slab*。
大多数情况下,路径名应为从Linux代码树顶层目录开始的相对路径,尽管某些内核要求使用绝对路径。
如果相对路径不能工作,尝试使用绝对路径。
如果给定文件名,第三部仍是可选的。
它以“:
”或“+”开头,用来标识源文件的行号。
”:
”后面跟的是绝对行号,而”+”后面跟的是函数入口的相对行号。
”:
*”匹配函数的每一行,而”:
x-y”可以从x行匹配到y行。
另外,PATTERN指定为数字常量时,它表示模块的相对地址或内核的绝对地址。
部分在编译单元内可见的源代码级别变量,诸如函数参数,局部或全局变量,在探针处理函数内同样是可见的。
在脚本里使用美元符号($)加上它们的名字就可以引用这些变量。
此外,特殊的语法可防止无节制地遍历结构体,指针和数组。
$var引用可见(in-scope)变量var。
如果它的类型是整数类型(译者注:
即char,short,int,long这些类型),脚本会把它转换为64位的整数。
如果指针的类型是字符串(char*),脚本会使用kernel_string()和user_string()函数将它拷贝到SystemTap的字符串变量。
$var->field遍历结构体的field成员。
可重复使用->操作符沿着子指针链访问各级成员。
$var[N]访问数组的元素,下标由N指定。
下标只能是字面值整数。
$$vars扩展为字符串并等价于sprintf("parm1=%x...parmN=%xvar1=%x...varN=%x",$parm1,...,$parmN,$var1,...,$varN)
$$locals扩展为字符串并等价于sprintf("var1=%x...varN=%x",$var1,...,$varN)。
$$parms扩展为字符串并等价于sprintf("parm1=%x...parmN=%x",$parm1,...,$parmN)。
4.2.1kernel.function,module().function
.function变体将探针定位到命名函数开始之处,因此function探针可用方问上下文变量的方式来访问函数的参数。
一般语法形式:
kernel.function("func[@file]"
module("modname").function("func[@file]"
例子:
#引用内核所有名字具有init或exit字符串的函数。
kernel.function("*init*"),kernel.function("*exit*")
#引用文件kernel/sched.c内跨越第240行的函数。
kernel.function("*@kernel/sched.c:
240")
#引用模块ext3内的所有函数
module("ext3").function("*")
4.2.2kernel.statement,module().statement
.statement变体允许探针定位到确切的代码行,此代码行可见的变量均可被脚本访问。
一般语法形式如下:
kernel.statement("func@file:
linenumber")
module("modname").statement("func@file:
linenumber")
例子:
#引用文件kernel/sched.c内第2917行这一语句:
kernel.statement("*@kernel/sched.c:
2917")
#引用文件fs/bio.c内bio_init+3这一行语句:
kernel.statement("bio_init@fs/bio.c+3")
4.3DWARF-lessprobing
当目标内核或模块缺少调试信息时,你仍然可以使用kprobe家族探针来探测它们函数的进入点和退出点。
但使用此种探针时你不能获取函数参数或局部变量的值。
然而当你使用这方法时,systemTap仍然为你提供了一种访问参数的方法:
当函数因被探测而停滞在它的进入点时,可以使用编号来引用它的参数。
例如,假设被探测函数声明如下:
asmlinkagessize_tsys_read(unsignedintfd,char__user*buf,size_t
count)
可以分别使用uint_arg
(1),pointer_arg
(2)和ulong_arg(3)来获得fd,buf和count的值。
此时,探针处理函数必须先调用asmlinkage(),因为在某些架构里,asklinkage属性影响函数参数的传递方式。
译者注:
例子中的sys_read函数在定义时使用了asmlinkage属性,在不同的CPU架构上有不同的参数传递方式,例如使用寄存器和堆栈一起传递参数。
在我们熟悉的x86CPU上,asklinkage修饰符的要义是通过堆栈来传递函数参数。
因此在systemTap脚本里,需要调用asklinkage()函数来根据CPU架构来初始化一系列数据,好让后面的type_arg(N)调用知道在寄存器还是堆栈里获得参数的值。
当函数因被探测而停滞在它的退出点时,此种非DWARF探针不支持$return目标变量。
但可以通过调用returnval()来获得寄存器的值,函数的返回值通常是保持在这一寄存器里的,也可调用returnstr()来获得返回值的字符串形式。
在处理函数代码里面,可调用register("regname")来获得它被调用时特定CPU寄存器的值。
u_register("regname")类似于register("regname"),不同的是它将寄存器的值解释成无符号整数。
SystemTap支持下述的kprobe结构:
kprobe.function(FUNCTION)
kprobe.function(FUNCTION).return
kprobe.module(NAME).function(FUNCTION)
kprobe.module(NAME).function(FUNCTION).return
kprobe.statement(ADDRESS).absolute
.function探针探测内核函数,而.module探针探测特定模块的函数。
如果知道内核或模块函数的绝对地址,可使用.statement探针。
注意,FUNCTION和MODULE名字中不能出现通配符,通配符引致探针不能注册。
同时statement探针只能运行在guru模式下。
4.4用户空间探测技术
要支持用户空间探测,只需将kernel配置成包括utrace扩展即可。
本文撰写之时,RedHat和CentOS发行版的内核已支持utrace了。
关于utrace更多的信息,请参阅。
用户空间探测有几种形式。
无调试符号的探测点,如process(PID).statement(ADDRESS).absolute类似于kernel.statement(ADDRESS).absolute,它们都使用原始的(raw)、未经验证的虚拟地址,并且不能使用$variable目标变量。
目标PID参数必须是正在运行的进程,ADRESS必须是一个有效的指令地址。
进程里的所有线程均被探测。
此探针只能运行guru模式下。
你可探测无调试符号的用户-内核接口事件,这些事件由utrace进行处理。
可以通过下述的方式来探测:
process(PID).begin
process("PATH").begin
process.begin
process(PID).thread.begin
process("PATH").thread.begin
process.thread.begin
process(PID).end
process("PATH").end
process.end
process(PID).thread.end
process("PATH").thread.end
process.thread.end
process(PID).syscall
process("PATH").syscall
process.syscall
process(PID).syscall.return
process("PATH").syscall.return
process.syscall.return
process(PID).insn
process("PATH").insn.block
process(PID).insn.block
process("PATH").insn
process("PATH").mark("LABEL")
process("PATH").function("NAME")
process("PATH").statement("*@FILE.c:
123")
process("PATH").function("*").return
process("PATH").function("myfun").label("foo")
当PID或PATH描述的进程被创建时,.begin变体探针会被调用。
如果不指定PID或PATH(如process.begin),任何新进程的繁衍都会调用该探针。
当PID或PATH描述的线程被创建时,.thread.begin变体探针会被调用。
当PID或PATH描述的进程结束时,.end变体探针会被调用。
当PID或PATH描述的线程结束时,.thread.end变体探针会被调用。
当PID或PATH描述的线程进行系统调用时,.syscall变体探针会被调用。
系统调用编号可在$syscall上下文变量中获得。
系统调用的前6个参数可从$argN目标变量中获取,即$arg1,$arg2等。
当PID或PATH描述的线程从系统调用中返回时,.syscall.return变体探针被调用。
系统调用编号同样可以$syscall上下文变量中获得,而系统调用的返回值可在$return上下文变量中获得。
.mark变体探针由应用程序定义的静态探针来调用,更多信息请参阅4.4.1节。
除此之外,用户空间程序和共享库支持带完整调试符号的源代码级别的探针。
它们十分类似于上述基于DWARF带调试符号的内核或模块探针,并且访问上下文$变量的方式也很相似。
process("PATH").function("NAME")
process("PATH").statement("*@FILE.c:
123")
process("PATH").function("*").return
process("PATH").function("myfun").label("foo")
对于所有进程探针,PATH名字引用可执行文件,执行文件的搜索方式和shell的完全一致:
要么明确指定该可以执行文件的路径,要么指定从当前工作目录开始的相对路径,好PATH参数以./字符串开始。
否则从$PATH环境变量指的目录中搜索。
下述是探针语法的例子:
probeprocess("ls").syscall{}
probeprocess("./a.out").syscall{}
等价于下述的探针:
probeprocess("/bin/ls").syscall{}
probeprocess("/my/directory/a.out").syscall{}
如果进程探针没有指定PID或PATH参数,那么所有用户空间线程将被探测。
然而,如果systemTap以目标进程模式(targetprocessmode)运行(invoked),进程探针仅限于探测目标进程家族树里的那些进程。
目标进程模式(使用-cCMD或-xPID选项运行stap)蕴含所有的process.*探针只能局限于探测给定的进程和它的子进程,但不影响kernel.*和其它的探针类型。
通常而言,CMD字符串是运行程序的名称,而不是”/bin/sh–c”子shell程序的名字,因为utrace和uprobe探针会(从内核)接收到相当“干净”的事件流。
如果CMD中出现元字符,如重定向操作符,要求使用”/bin/sh–cCMD”形式的名称,届时utrace和uprobe探针将从shell中接收事件。
请看下述例子:
%stap-e'probeprocess.syscall,process.end{
printf("%s%d%s/n",execname(),pid(),pp())}'/
-cls
下述是这个命令的一种输出:
ls2323process.syscall
ls2323process.syscall
ls2323process.end
如果PATH名字为共享库,那么所有映射该共亨库的进程均被探测。
若安装了带dwarf调试信息的版本,尝试下述语法命令:
probeprocess("/lib64/libc-2.8.so").function("...."){...}
此命令探测所有调用进共享库里面的线程,键入”stap–cCMD”或”stap–xPID”将之限制到仅探测某一命令和它的子孙进程。
这里同样可使用$$var和其它变量。
可以使用-dDIRECTORY选项告知stap命令带调试信息文件的位置。
Process().insn和process().insn.block探针依次检查进程每个执行的指令或区块。
但此探针仅在部分平台上实现,因此如果你所使用的系统没有实现该探针,那么在启动脚本时会收到错误信息。
PID或PATH描述的进程每执行一个单步指令,.insn探针都会被调用。
PID或PATH描述的进程每执行一个区块指令,.insn.block探针都会被调用。
若想统计进程执行的指令总数,可以使用类似下述的命令:
$stap-e'globalsteps;probeprocess("/bin/ls").insn{steps++}
probeend{printf("Totalinstructions:
%d/n",steps);}'/
-c/bin/ls
但使用此特性会使进程执行速度放慢很多。
4.4.1静态用户空间探测技术
你可以探测程序的静态符号测量仪(instrumentation),只需将此测量信编译进编程或共享库,使用下述语法即可:
process("PATH").mark("LABEL")
.mark变体由静态探针调用,而该静态探针是由应用程序使用STAP_PROBE1(handle,LABEL,arg1)预先定义的。
STAP_PROBE1定义在sdt.h文件中,参数如下:
参数
描述
handle
应用程序句柄(handle)
LABEL
对应.mark探针的参数
arg1
参数(译者注:
传递给探针的参数)
STAP_PROBE1为探针提供1个参数,STAP_PROBE2可提供2个,依次类推。
探针可通过上下文变量$arg1,$arg2等来获取参数。
此外,可以利用dtrace脚本定制新的STAP_PROBE宏。
Sdt.h文件使用DTRACE_PROBE提供了兼容dtrace的marker和与之对应的python脚本。
你可直接使用这些基于dtrace的内置的宏,只需将dtrace–h或-G功能打开即可。
下述是一个用户空间支持符号探测的原型例子:
#stap-e'probeprocess("ls").function("*").call{
log(probefunc()."".$$parms)
}'/
-c'ls-l'
此脚本需要命令程序带有调试信息并且内核支持utrace才能运行。
如果看见“pass4a-time”这样的构建失败信息,请确保你的内核支持utrace。
4.5PROCFS探针
此类探测点允许探测procfs伪文件系统中/proc/systemtap/MODNAME目录下文件的创建,读和写,其中NODNAME为sytemTap模块的名字。
转换器目前支持4种探测点变种:
procfs("PATH").read
procfs("PATH").write
procfs.read
procfs.write
PATH是被探测的文件,它是以/proc/systemtap/MODNAME为起始目录的相对路径。
如果没有指定PATH参数(上述清单中的最后两个变种),PATH的值默认为”command”。
当用户程序读取/proc/systemtap/MODNAME/PATH文件时,相应的procfs读探针将被激活(triggered)。
从文件中已读取的数据串被分配到$value变量,如下所示:
procfs("PATH").read{$value="100/n"}
当用户程序写数据到/proc/systemtap/MONNAME/PATH文件时,相应的procfs写探针将被激法。
即将要写到文件的数据被分配到$value变量,如下所示:
procfs("PATH").write{printf("Userwrote:
%s",$value)}
4.6Marker探针
Marker探针家族关联被编译进内核或模块的静态marker探针。
这些marker是内核里特殊的宏,与基于DWARF的探针相比,它使用探测更快,更可靠。
Marker探针不需要利用DWARF调试信息。
Marker探测点名字以kernel前缀开头,即标识用于查找marker的模号表的源头,后缀是它自身即marker
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- System语言详解 System语言详解4 探测点 System 语言 详解 探测