《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 探针)

探测点家族使用目标内核或模块的符号调试信息(symbolic debugging information) ,这些信息可包含在未经stripped 的可执行文件,或在一个独立的debuginfo 软件包中。通过指源或目标代码点的集合,探针可在逻辑上定位到目标执行路径。当任一处理器执行与之匹配的语句,探针处理函数将在些上下文中运行。

内核点(points in a kernel) 可由模块,源文件,行号,函数名或者它们的组合来定位。

下述是目前支持的探测点清单:

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 代表源程序标签。MPATTERNLPATTERN 两者均可包含星号(*) ,方括号“[] ”和问号(?) 等通配符。

PATTERN 是一个字符串字面值,它标识程序中的代码点。它由3 部分构成。

  1. 第一部分是函数名字,该名字与nm 工具的输出一致。此部分可使用星号和问号通配符来匹配多个函数名字。
  2. 第二部分是可选的,它以@ 字符开头,紧跟着此函数所在源文件的路径。此路径可以包含通配符模式,如mm/slab* 。大多数情况下,路径名应为从Linux 代码树顶层目录开始的相对路径,尽管某些内核要求使用绝对路径。如果相对路径不能工作,尝试使用绝对路径。
  3. 如果给定文件名,第三部仍是可选的。它以“: ”或“+ ”开头,用来标识源文件的行号。”:” 后面跟的是绝对行号,而”+” 后面跟的是函数入口的相对行号。”:*” 匹配函数的每一行,而”: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=%x var1=%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.1 kernel.function, module().function

.function 变体将探针定位到命名函数开始之处,因此function 探针可用方问上下文变量的方式来访问函数的参数。

一般语法形式:

kernel.function("func[@file]"

module("modname").function("func[@file]"

例子:

# 引用内核所有名字具有initexit 字符串的函数。

kernel.function("*init*"), kernel.function("*exit*")

 

# 引用文件kernel/sched.c 内跨越第240 行的函数。

kernel.function("*@kernel/sched.c:240")

 

# 引用模块ext3 内的所有函数

module("ext3").function("*")

4.2.2 kernel.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.cbio_init+3 这一行语句:

kernel.statement("bio_init@fs/bio.c+3")

4.3 DWARF-less probing

当目标内核或模块缺少调试信息时,你仍然可以使用kprobe 家族探针来探测它们函数的进入点和退出点。但使用此种探针时你不能获取函数参数或局部变量的值。然而当你使用这方法时,systemTap 仍然为你提供了一种访问参数的方法:

当函数因被探测而停滞在它的进入点时,可以使用编号来引用它的参数。例如,假设被探测函数声明如下:

asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t

count)

可以分别使用uint_arg(1)pointer_arg(2)ulong_arg(3) 来获得fd, bufcount 的值。此时,探针处理函数必须先调用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 探针。注意,FUNCTIONMODULE 名字中不能出现通配符,通配符引致探针不能注册。同时statement 探针只能运行在guru 模式下。

4.4 用户空间探测技术

要支持用户空间探测,只需将kernel 配置成包括utrace 扩展即可。本文撰写之时,Red HatCentOS 发行版的内核已支持utrace 了。关于utrace 更多的信息,请参阅http://people.redhat.com/roland/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")

PIDPATH 描述的进程被创建时,.begin 变体探针会被调用。如果不指定PIDPATH (如process.begin ),任何新进程的繁衍都会调用该探针。

PIDPATH 描述的线程被创建时,.thread.begin 变体探针会被调用。

PIDPATH 描述的进程结束时,.end 变体探针会被调用。

PIDPATH 描述的线程结束时,.thread.end 变体探针会被调用。

PIDPATH 描述的线程进行系统调用时,.syscall 变体探针会被调用。系统调用编号可在$syscall 上下文变量中获得。系统调用的前6 个参数可从$argN 目标变量中获取,即$arg1, $arg2 等。

PIDPATH 描述的线程从系统调用中返回时,.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 环境变量指的目录中搜索。下述是探针语法的例子:

probe process("ls").syscall {}

probe process("./a.out").syscall {}

等价于下述的探针:

probe process("/bin/ls").syscall {}

probe process("/my/directory/a.out").syscall {}

如果进程探针没有指定PIDPATH 参数,那么所有用户空间线程将被探测。然而,如果systemTap 以目标进程模式(target process mode) 运行(invoked) ,进程探针仅限于探测目标进程家族树里的那些进程。

目标进程模式(使用-c CMD-x PID 选项运行stap )蕴含所有的process.* 探针只能局限于探测给定的进程和它的子进程,但不影响kernel.* 和其它的探针类型。通常而言,CMD 字符串是运行程序的名称,而不是”/bin/sh –c”shell 程序的名字,因为utraceuprobe 探针会(从内核)接收到相当“干净”的事件流。如果CMD 中出现元字符,如重定向操作符,要求使用”/bin/sh –c CMD” 形式的名称,届时utraceuprobe 探针将从shell 中接收事件。请看下述例子:

% stap -e 'probe process.syscall, process.end {

           printf("%s %d %s/n", execname(), pid(), pp())}' /

       -c ls

下述是这个命令的一种输出:

ls 2323 process.syscall

ls 2323 process.syscall

ls 2323 process.end

如果PATH 名字为共享库, 那么所有映射该共亨库的进程均被探测。若安装了带dwarf 调试信息的版本,尝试下述语法命令:

probe process("/lib64/libc-2.8.so").function("....") { ... }

 

此命令探测所有调用进共享库里面的线程,键入”stap –c CMD””stap –x PID” 将之限制到仅探测某一命令和它的子孙进程。这里同样可使用$$var 和其它变量。可以使用-d DIRECTORY 选项告知stap 命令带调试信息文件的位置。

Process().insn process().insn.block 探针依次检查进程每个执行的指令或区块。但此探针仅在部分平台上实现,因此如果你所使用的系统没有实现该探针,那么在启动脚本时会收到错误信息。

PID PATH 描述的进程每执行一个单步指令,.insn 探针都会被调用。

PID PATH 描述的进程每执行一个区块指令,.insn.block 探针都会被调用。

若想统计进程执行的指令总数,可以使用类似下述的命令:

$ stap -e 'global steps; probe process("/bin/ls").insn {steps++}

           probe end {printf("Total instructions: %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 提供了兼容dtracemarker 和与之对应的python 脚本。你可直接使用这些基于dtrace 的内置的宏,只需将dtrace –h-G 功能打开即可。

下述是一个用户空间支持符号探测的原型例子:

# stap -e 'probe process("ls").function("*").call {

           log (probefunc()." ".$$parms)

           }' /

       -c 'ls -l'

此脚本需要命令程序带有调试信息并且内核支持utrace 才能运行。如果看见“pass 4a-time ”这样的构建失败信息,请确保你的内核支持utrace

4.5 PROCFS 探针

此类探测点允许探测procfs 伪文件系统中/proc/systemtap/MODNAME 目录下文件的创建,读和写,其中NODNAMEsytemTap 模块的名字。转换器目前支持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("User wrote: %s", $value) }

4.6 Marker 探针

Marker 探针家族关联被编译进内核或模块的静态marker 探针。这些marker 是内核里特殊的宏,与基于DWARF 的探针相比,它使用探测更快,更可靠。Marker 探针不需要利用DWARF 调试信息。

Marker 探测点名字以kernel 前缀开头,即标识用于查找marker 的模号表的源头,后缀是它自身即marker.(“MARK”)MARK 可以包含通配符,它匹配那些被编译进内核或模块的marker 宏的名字。可选地,你可以使用format(FORMAT) 来指定marker 格式字符串,以区别两个有同样名字但格式字符串不相同的marker

Marker 探针处理函数可读取可选参数$arg1$argN ,这些参数由宏(STAP_MACRO) 的调用方指定,其中NN 为宏提供的参数个数。参数个数和参数串均使用类型安全的方式进行传递。

Marker 探针中的marker 格式字符可以通过$format 变量获取,同样地,marker 的名字字符串可通过$name 变量获取。

下述是marker 探针的结构:

kernel.mark("MARK")

kernel.mark("MARK").format("FORMAT")

关于marker 探针更详细的资料,请参阅http://sourceware.org/systemtap/wiki/UsingMarkers

4.7 Syscall 探针

Syscall.* 探针别名定义了数百个探针,语法形式如下:

syscall.NAME

syscall.NAME.return

通常,syscalls(2) 用户手册中列出的系统调用都定义了2 个探针:一个是进入点,另一个是退出点。有一例外就是那些不会返回的系统调用没有定义相应的.return 探针。

每个探针别名都定义了数个变量。可查阅tapset 的源代码来找到这些变量的定义。一般来说,系统调用的每个的参数,在脚本层面上都有一个变量与之对应。例如,syscall.open 提供了file nameflagsmode 对应的变量。此外,大数多syscall 探针别名都可以使用一套标准的变量,如下所示:

  • argstr: 参数列表的字符串表示,可直接输出,除去左右两边的括号。
  • name: 系统调用的名字。
  • serstr: 对于.return 探针,为系统调用返回值的字符串表示,可直接输出。

但并不是所有的syscall.* 探针别名遵守这些约定的。如果你遇到bug ,请报告给我们。

4.8 Tracepoints

Tracepoint 探测点家族探测被编译进内核或模块的静态探测tracepoint 。正如marker 探针那样,静态探测tracepoint 是开发者在内核代码里插入的宏调用,探测起来比基于DWARF 的探针更快,更可靠。Tracepoint 探针不需要DWARF 调试信息。Tracepoint 探针比marker 探针有更强的类型信息。

Tracepoint 探针以kernel 为前缀,随后是tracepoint 自身即trace(“name”)tracepoint 名字同时包含通配符,它匹配那些由内核开发者在tracepoint 头文中定义好的名字。

基于tracepoint 探针的处理函数可以读取由宏的调用方指定的可选参数。这些参数的名字由于tracepoint 的作者来声明。例如,tracepoint 探针kernel.trace(“sched_switch”) 提供的参数有$rq, $prev$next 。若参数是复杂类型,如结构体指针,可使用DWARF $ 目标变量一样的方式来访问它的成员。Tracepoint 探针不能修改参数,但在guru 模式就可以。

Tracepoint 探针的名字可通过$$name 来获取,也可通过$$vars$$params 来获得所有参数的name=value 对组成的字符串。

4.9 定时器探针

你可以使用由标准内核jiffies 定时器所定义的间隔(interval) 异步地激活探针处理函数。Jiffy 是内核定义的时间单元,通常是1ms60ms 之间。转换器支持两种jiffies 定时器探针变种:

timer.jiffies(N)

timer.jiffies(N).randomize(M)

探针处理函数每N jiffies 运行一次。如果指定randomize 部分,处理函数每次执行相隔的时间为N 加上[-M… +M] 范围的线性随机数。N 必须在一个合理的范围(从1 到大约1,000,000 ),M 必须小于N 。这两种探针的上下文中均不能使用目标变量。探针可以在多个处理器上并发运行。

间隔可以根据实际的时间单位来指定,与jiffies 定时器类似的定时器探测点有两类变种:

timer.ms(N)

timer.ms(N).randomize(M)

其中,NM 指定的是微秒数。systemTap 提供的全部时间单位分别是:秒(ssec) ,微秒(msmsec) ,毫秒(ususec) ,纳秒(nsnsec) 和赫兹(hz)

定时器的精度依赖于目标内核。2.6.17 之前的内核,定时器的精度为jiffies ,因此间隔上卷为最接近的jiffies 间隔。2.6.17 之后,内核使用hrtimers 来实现高精度时钟,尽管如此,最终的精度还是依赖了机器构架。无论是哪种定时器,如果指定randomize 部分,都是先将随机数加到间隔后再上卷。

systemTap 提供了Profiling 定时器探针,各个CPU 上的每次系统滴答,它都执行一次。此探针没有参数,如下所示:

timer.profile

可访问被中断进程的全部上下文信息,可利用它来实现基于时间的采样profiler

下述是关于定时器探针用法的例子:

# 引用定期中断,每1000 jiffies 激活一次:

timer.jiffies(1000)

 

# 5 秒激活一次:

timer.sec(5)

 

# 引用定期中断,每一个1000 +/- 200 jiffies 激活一次:

timer.jiffies(1000).randomize(200)

4.10 return 探针

.return 变种定位到指定函数的退出时机,可使用$return 上下文变量来获取函数的返回值。Return 探同样可以通过上下文来访问函数的入口参数,但它们的值可能在函数执行的过程中被改变了。内联函数没有返回点,因此.inline 探针没有.return 修饰符。

4.11 特殊的探测点

转换器定义了beginend 探测点,用于引用SystemTap 会话的启动和结束。此两探针均没有目标变量。

4.11.1 begin

begin 探针关联到SystemTap 会话的启动事件。SystemTap 会话启动时,所有的begin 探针处理函数将被执行。所有的全局变量必须要此之前声明。

4.11.2 end

End 探针关联系SystemTap 会话的结束事件。当SystemTap 会话正常结束时,如脚本调用了exit() 函数,又或者接收到用户中断事件,所有的end 探针处理函数将被执行。如果是由于错误而导致会话结束,end 探针不会被执行。

4.11.3 error

error 探测点与end 探针点类似,不同之处只有当会话由于发生错误而结束时,error 探针才会被执行。若如此,end 探针将被忽略。在脚本结束时,可以使用error 探针做清理工作或最后的动作。

下述是一个简单的例子:

probe error { println ("Oops, errors occurred. Here's a report anyway.")

              foreach (coin in mint) { println (coin) } }

4.11.4 多个beginenderror 探针的执行顺序

可以使用可选的顺序数值来分别探制多个beginenderror 探针运行次序。如果没有指定顺序数值,顺序数值默认为0 ,则按它们出现在脚本文件的顺序来运行。顺序数值可以是正数,亦可以是负数,这对于tapset 开发者来说十分有用,可以在begin 探针内做一些初始化工作。请看下述例子:

# In a tapset file:

probe begin(-1000) { ... }

 

# In a user script:

probe begin { ... }

 

用户脚本的begin 探针的顺序数值默认为0 ,故tapset 内的begin 探针比用户脚本的begin 探针先运行。

4.11.5 never

never 探针由转换器定义,顾名思义,它永远不会被执行。它后面的语句只是用来分析模号和类型的正确性。它的一个用法就是与可选探一起连用。详情请参阅4.1.4 节。

4.12 指针类型转换

SystemTap 定义@cast() 操作符来支持类型转换功能。SystemTap 脚本使用long 数值来定义指针类型,然后使用与$target 变量一致的方法来访问类型的成员。但是当指针保存到脚本的整数变量时,转换器缺少基本的类型信息,以致不能从该指针(译者注:实际就是整数)访问它的成员。@cast() 操作符可告知转换器如何解释该指针。

在下述的句语中,将p 解释成名字为type_name 的结构体或联合体的指针,并解引用它的成员member 的值。

@cast(p, "type_name"[, "module"])->member

可选的module 参数告知转换器从何处可找到此类型的信息。可以用使分号(:) 分隔的列表来指定多个module 。如果没有指定module 参数,默认情况下,如果是dwarf 探针,它从被探测模块中查找;如果是function 探针或其它类型探针,它从内核中查找。

下述语句从内核的task_struct 结构查看它父进程的PID

@cast(pointer, "task_struct", "kernel")->parent->tgid

正常情况下,如果没有调试信息,可以在module 参数中使用尖括号(<>) 包含头文件,转换器从头文件中获取类型信息。对于内核头文件,使用kerenl 作为module 的前缀,好让SystemTap 从内核代码树的目录读取该头文件。其它头文件,则使用gcc 默认的参数目录来读头文件。请看下述的语句例子:

@cast(tv, "timeval", "<sys/time.h>")->tv_sec

@cast(task, "task_struct", "kernel<linux/sched.h>")->tgid

Guru 模式下,转换器允许脚本对指针的成员进行赋值。

类型转换的另一用处是,可在运行时决定void* 成员的类型。请看下述例子:

probe foo {

   if ($var->type == 1) {

      value = @cast($var->data, "type1")->bar

   } else {

      value = @cast($var->data, "type2")->baz

   }

   print(value)

}

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值