《System语言详解》——3. SystemTap脚本的各大组件

3 SystemTap 脚本的各大组件

SystemTap 脚本语言里的主要结构是定义探针(probe) 。探针将抽象事件与语句块或探针处理函数联系到一起,事件发生时,将执行这些语句块或探针处理函数。

下述例子展示了如何利用两个探针来跟踪函数的进入和退出。

probe kernel.function("sys_mkdir").call { log ("enter") }

probe kernel.function("sys_mkdir").return { log ("exit") }

可以使用列表选项(-l) 来查看内核所有可探测函数(probe-able functions) ,例如:

# stap -l 'kernel.function("*")' | sort

3.1 探针定义

探针定义的一般语法形式如下:

probe PROBEPOINT [, PROBEPOINT] { [STMT ...] }

探针里具体定义的事件称为探测点 (probe points) 。转换器(译者注:转换器是systemTap 工具的一部分,它负责将systemTap脚本转换成C 代码的工作)已预先定义了大量的探测点,同时tapset 脚本可能使用探针别名来定义另一些探测点。Stapprobes(5) 用户手册列出了所有可供使用的探测点。

探针处理函数总是根据事件的上下文来解释执行。那些与内核代码关联的事件,它的上下文包括内核源代码那里定义的变量。SystemTap 脚本使用一种以$ 字符开头的变量,称为目标变量。只有内核代码编译时没有打开优化选项,内核镜像才会保存该目标变量引用的内存地址,这样目标变量才能被访问。这与调试器调试被优化的代码一样,同受此限制。

3.2 探针别名

探针别名的一般语法形式如下:

probe <alias> = <probepoint> { <prologue_stmts> }

probe <alias> += <probepoint> { <epilogue_stmts> }

 

我们可以使用探针别名的方法来定义新的探测点。在形式上,探测点别名和探针定义很相似,它只在现有探测点之上定义另一新的探测点名字作为它的别名,并没有激活探针。新的探针别名可以定义在一个或多个现有探针别名之上,并且多个探针别名可以定义在同一探针(别名)之上。请看下面例子:

probe socket.sendmsg = kernel.function ("sock_sendmsg") { ... }

probe socket.do_write = kernel.function ("do_sock_write") { ... }

probe socket.send = socket.sendmsg, socket.do_write { ... }

 

SystemTap 脚本的探针别名分为两种,序式(prologue style) 别名和跋式(epilogue style) 别名,定义时分别由等号(=) 和“+=”符号表示。

由新探测点别名定义的探针,它利用探针别名定义时预先定义的处理函数来创建真正的探针。

这种预先定义的方式有多种好处。它允许在将控制权交到由用户定义的探针处理函数前,根据别名定义时语句块来预先处理探针的上下文。它的用处多种多样,演示如下:

# 忽略当前探针,直到满足给定的条件:

if ($flag1 != $flag2) next

 

# 为使用该别名的探针提供有用的值

name = "foo"

 

# 将目标变量的值提取到一般的局部变量

var = $var

3.2 序式别名(= )

序式别名定义时后面紧跟着的语句块将隐式地添加到使用该别名的探针,作为探针处理函数的序幕。请看下述例子:

# 定义新的探测点syscall.read ,它扩展为

# kernel.function("sys_read") 探测点,而给定的语句将作为探针处函

# 数的序幕。

probe syscall.read = kernel.function("sys_read") {

    fildes = $fd

}

3.2.2 跋式别名(+= )

跋式别名定义时后面紧跟着的语句块将隐式地添加到任何引用该别名的探针,并作为此探针处函数的 跋。通常它的语句块内不会定义新的变量(因为后面没有代码使用它),但可以针对跋式别名语句块内设置的变量或探针内用户定义的变量采取相应的动作。请看下面例子:

# 定义一个新的探测点,给定的语句作为 跋

probe syscall.read += kernel.function("sys_read") {

    if (traceme) println ("tracing me")

}

3.2.3 探针别名用法

探针别名用法与其它内置的探针类型用法一致,只需将之命名(naming) 即可:

probe syscall.read {

    printf("reading fd=%d/n", fildes)

}

3.2.4 空置的别名变量(Unused alias variables)

空置别名变量是定义在探针别名内的变量,通常是由var = $var 这类赋值语句来定义的,但事实上它没有被该别名实例化的探针上使用。这些变量被白白丢弃了。

3.3 变量

变量和函数标识符是由字母或数字组成的字符串,同时包括下划线(_) 和美元符号($) ,但不能以纯数字字母开始。默认情况下,变量只是探针或函数语句块内的局部变量,因此它的使用范围和生命周期仅局限于具体的探针和函数调用。关联数组内可以储存字符串和整数,同时字符串或整数元组可作为关联数组的键值。数组必须定义为全局变量,不允许定义局部数组变量。

换转器对各个标识符都进行类型推导,包括数组下标,函数实参。与标识符类型不相容的使用均导致错误。

变量一旦定义为全局变量,所有的探针和SystemTap 会话里其它实例共享该全局变量。所有全局变量都在同一命名空间下,无论全局变量定义在那个文件。由于实际并发的限制,如多个探针处理函数,探针在运会时对使用的每个全局变量会自动加上读或写锁。全局变量声明可以在代码块内,亦可在脚本文件最外层结构的任何地方。那些仅定义,但没有读取的全局变量,会话结束时会自动输出它的值。下述的声明表明var1 和var2 是全局变量。转换器会为它们进行类型推导,如果变量用作数组,换转器会进一步推导它键值的类型。

global var1[=<value>], var2[=<value>]

3.4 辅助函数

辅助函数的一般语法形式是:

function <name>[:<type>] ( <arg1>[:<type>], ... ) { <stmts> }

 

SystemTap 脚本可定义子例程来分解公共的工作。函数可以有任意多个标量参数,但必须返回单标量值。这里标量是指整数或字符串。更多关于标量的信息,请参阅3.3 节和5.3 节。下述是一个函数声明的例子:

function thisfn (arg1, arg2) {

    return arg1 + arg2

}

 

注意,函数不需要声明它的类型(译者注:函数类型即函数的返回类型),它是由转换器推导出来的。如果需要,函数定义时可以显式声明它的返回值类型,或参数类型,或者两者。这对emdedded-C 函数非常有用。在下述例子中,类型推导引擎只需推断arg2 参数的类型,为string 。

function thatfn:string(arg1:long, arg2) {

    return sprintf("%d%s", arg1, arg2)

}

 

数函可以调用其它函数,可递归调用自己,但有一限制就是不能超过最大的内嵌调用层次,详情请参阅1.6 节。

3.5 Embedded C

SystemTap 支持guru 模式, 脚本安全特性,诸如代码或数据内存引用保护,统统失效。可在stap 命令上使用 ''-g'' 选项来打开Guru 模式。在guru 模式下,转换器接受脚本文件由``%{'' 和``%}'' 标记对包含的embedded 代码。Embedded 代码不经分析 逐字翻译并生成 C 代码。在脚本的最外层, guru 模式下可以使用 #include 指令,或任何方便其它 embedded 代码使用的辅助定义。

3.6 Embedded C 函数

Embedded C 函数的一般语法形式:

function <name>:<type> ( <arg1>:<type>, ... ) %{ <C_stmts> %}

 

函数体允许是embedded 代码。若如此,脚本语言主体全部由%{ 和%} 标记包含的C 代码片段所取代。分析器(parser) 不会让这些C 代码胡作非为,它只能做析器视为合理和安全的事情。

在并发,资料消耗和运行时限制方面,SystemTap 语言代码仍然存在大量非文档化但复杂的安全约束。但这些约束不能应用于embedded C 代码,编写这类代码要小心,因为它会原封不动地翻译成C 代码。解引指针时必须特别小心,使用kread() 宏来解引指针仍然会造成潜在非法或危险的可能。如果不能确定,宁可谨慎并使用kread() 。Embedded C 生成的代码所使用的安全机制有多种,其中之一是使用kread() 宏。它保护指针访问,以避免系统 崩溃 ( crash) 。

例如,在embedded C 中访问指针链name = skb->dev->name ,所用代码如下:

struct net_device *dev;

char *name;

dev = kread(&(skb->dev));

name = kread(&(dev->name));

 

函数使用THIS 宏来访问输入和输出值的内存位置。如下例所示:

function add_one (val) %{

    THIS->__retvalue = THIS->val + 1;

%}

function add_one_str (val) %{

    strlcpy (THIS->__retvalue, THIS->val, MAXSTRINGLEN);

    strlcat (THIS->__retvalue, "one", MAXSTRINGLEN);

%}

 

为确保这种方法能工作,转换器必须能够从函数的调用来推导它的参数和返回值类型。你可以查核一般脚本语言函数生成的 C 代码,来编写兼容的 embedded-C 代码。注意,所有 SystemTap 函数和探针均运行在中断禁用情况下,因此你不能在 embedded C 代码里调用会睡眠的函数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值