Systemtap Language elements - 1

9 minute read

背景

1. 标识  
变量名, 函数名的命名规则 : 由数字, 字母, 下划线, $ 符号组成. 不能以数字开头.  
$符号开头的变量名用来表示被跟踪的源码中的变量. 其他开头的变量名用来表示stap脚本中的变量名或函数名.  
  
2. 数据类型  
The SystemTap language includes a small number of data types, but no type declarations.   
A variable's type is inferred from its use.   
To support this inference, the translator enforces consistent typing of function arguments and return values, array indices and values.   
There are no implicit type conversions between strings and numbers.   
Inconsistent type-related use of an identifier signals an error.  
定义变量时无需声明类型, systemtap包含数据类型long,string,array,statistic  
5.2.1 Literals  
Literals are either strings or integers. Literal integers can be expressed as decimal, octal, or hexadecimal, using C notation. Type suffixes (e.g., L or U) are not used.  
  
5.2.2 Integers  
Integers are decimal, hexadecimal, or octal, and use the same notation as in C. Integers are 64-bit signed quantities, although the parser also accepts (and wraps around) values above positive $2^{63}$ but below $2^{64}$.  
  
5.2.3 Strings  
Strings are enclosed in quotation marks (``string''), and pass through standard C escape codes with backslashes. A string literal may be split into several pieces, which are glued together, as follows.  
str1 = "foo" "bar"  
  /* --> becomes "foobar" */  
  
str2 = "a good way to do a multi-line\n"  
       "string literal"  
  /* --> becomes "a good way to do a multi-line\nstring literal" */  
  
str3 = "also a good way to " @1 " splice command line args"  
  /* --> becomes "also a good way to foo splice command line args",  
     assuming @1 is given as foo on the command line */  
Observe that script arguments can also be glued into a string literal.  
  
Strings are limited in length to MAXSTRINGLEN. For more information about this and other limits,  
详见  
http://blog.163.com/digoal@126/blog/static/16387704020138392759478/  
  
3. 分号;  
空语句, 一般用于隔开多个表达式, 多个probe之间不需要使用分号隔开  
  
4. 注释  
# ... shell style, to the end of line  
// ... C++ style, to the end of line  
/* ... C style ... */  
支持以上三种注释  
  
5. 空格  
As in C, spaces, tabs, returns, newlines, and comments are treated as whitespace. Whitespace is ignored by the parser.  
空格不被stap解释, 忽略.  
  
6. 表达式  
5.6.1 Binary numeric operators  
* / % + - >> << & ^ | && ||  
* 乘法  
/ 除法  
% 取余数  
+ 加法  
- 减法  
>> 比特右移: 8>>1 = 4  
<< 比特左移: 4<<1 = 8  
& 比特与: 5&1 = 101 & 001 = 001 = 1  
^ 比特异或: 5^1 = 101 ^ 001 = 100 = 4  
| 比特或: 5|1 = 101 | 001 = 101 = 5  
&& 逻辑与: 5&&0 = 0 (结果为1真0假)  
|| 逻辑或: 5||0 = 1 (结果为1真0假)  
  
5.6.2 Binary string operators  
. (string concatenation)  
字符串连接操作符 : "hello" . "world" = helloworld  
  
5.6.3 Numeric assignment operators  
= *= /= %= += -= >>= <<= &= ^= |=  
数字赋值操作符 :   
=   
*=   
/=   
%=   
+=   
-=   
>>=   
<<=   
&=   
^=   
|=  
  
5.6.4 String assignment operators  
= .=  
字符串赋值操作符  
=  
.=  
  
5.6.5 Unary numeric operators  
+ - ! ~ ++ --  
一元数字操作符  
+ 正  
- 负  
! 逻辑非: !5=0, !1=0, !0=1  
~ 比特反转: ~1=0xfffffffffffffffe, ~2=0xfffffffffffffffd  
++ : 自增  
-- : 自减  
  
5.6.6 Binary numeric, string comparison, or regular expression matching operators  
比较操作符,   
<   
>   
<=   
>=   
==   
!=   
字符串规则表达式匹配操作符  
=~   
!~  
The regular expression matching (=~ and !~) is currently an experimental feature. The second operand must be a string literal containing a syntactically valid regular expression. The regular expression syntax supports most of the features of POSIX Extended Regular Expressions, aside from subexpression reuse (\1) and named character classes ([:digit:], [:alpha:], ...). The ability to capture and extract the contents of the matched string and subexpressions has not yet been implemented.  
  
规则表达式匹配是一个实验特性, 在1.8的版本中未能测试出来可以正常使用. 使用2.4的版本正常.  
2.4的安装如下 :   
http://blog.163.com/digoal@126/blog/static/163877040201391391613269/  
测试 :   
[root@db-172-16-3-150 ~]# /opt/systemtap/bin/stap -e 'probe begin {if (@1 =~ "^abc") printf("match ^abc\n"); exit()}' "abcdef"  
match ^abc  
[root@db-172-16-3-150 ~]# /opt/systemtap/bin/stap -e 'probe begin {if (@1 =~ "^abc") printf("match ^abc\n"); exit()}' "Habcdef"  
  
5.6.7 Ternary operator  
cond ? exp1 : exp2  
三目操作符, cond真则执行表达式1, 假则执行表达式2  
[root@db-172-16-3-39 memory]# stap -e 'probe begin {1==1 ? printf("true\n") : printf("false\n"); exit();}'  
true  
[root@db-172-16-3-39 memory]# stap -e 'probe begin {1==2 ? printf("true\n") : printf("false\n"); exit();}'  
false  
  
5.6.8 Grouping operator  
( exp )  
分组操作符: 括号.  
  
5.6.9 Function call  
函数调用语法  
General syntax:  
fn ([ arg1, arg2, ... ])  
  
5.6.10 $ptr->member  
ptr is a kernel pointer available in a probed context.  
结构指针, 取结构内数据的操作符. 用于探针对应的函数的上下文变量  
->  
5.6.11 Pointer typecasting  
Typecasting is supported using the @cast() operator. A script can define a pointer type for a long value, then access type members using the same syntax as with $target variables. After a pointer is saved into a script integer variable, the translator loses the necessary type information to access members from that pointer. The @cast() operator tells the translator how to read a pointer.  
  
因为systemtap脚本中支持的变量类型有限, 对于结构数据的指针, 在systemtap中可以存储为整型变量(地址). 这种数据转存将导致类型丢失, 所以为了从这个整型变量中的得到它存储的指针对应的结构值, 就需要进行类型转换.  
类型转换的语法@cast(p, "type_name"[, "module"]), 转换后可以进行与$target一样的操作, 如suffix $$, ->等.  
  
The following statement interprets p as a pointer to a struct or union named type_name and dereferences the member value:  
  
@cast(p, "type_name"[, "module"])->member  
The optional module parameter tells the translator where to look for information about that type. You can specify multiple modules as a list with colon (:) separators. If you do not specify the module parameter, the translator defaults to either the probe module for dwarf probes or to kernel for functions and all other probe types.  
  
The following statement retrieves the parent PID from a kernel task_struct:  
  
@cast(pointer, "task_struct", "kernel")->parent->tgid  
The translator can create its own module with type information from a header surrounded by angle brackets (< >) if normal debugging information is not available. For kernel headers, prefix it with kernel to use the appropriate build system. All other headers are built with default GCC parameters into a user module. The following statements are examples.  
  
@cast(tv, "timeval", "<sys/time.h>")->tv_sec  
@cast(task, "task_struct", "kernel<linux/sched.h>")->tgid  
In guru mode, the translator allows scripts to assign new values to members of typecasted pointers.  
  
Typecasting is also useful in the case of void* members whose type might be determinable at run time.  
  
probe foo {  
   if ($var->type == 1) {  
      value = @cast($var->data, "type1")->bar  
   } else {  
      value = @cast($var->data, "type2")->baz  
   }  
   print(value)  
}  
举例 :   
我们选取一个DWARF-based 探针  
kernel.function("icmp_echo@net/ipv4/icmp.c:843")  
输出这个探针中有哪些上下文变量.  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {printf("%s\n", $$vars); exit();}'  
skb=0xffff81021b4e7280  
这个探针中只有一个变量skb. 使用suffix $$输出它的结构数据.  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {printf("%s\n", $$vars$$); exit();}'  
skb={.next=0x0, .prev=0x0, .sk=0x0, .tstamp={.off_sec=1381205475, .off_usec=140535}, .dev=0xffffffff80352b80, .input_dev=0xffffffff80352b80, .h={.th=0xffff8101eda8a424, .uh=0xffff8101eda8a424, .icmph=0xffff8101eda8a424, .igmph=0xffff8101eda8a424, .ipiph=0xffff8101eda8a424, .ipv6h=0xffff8101eda8a424, .raw="}, .nh={.iph=0xffff8101eda8a410, .ipv6h=0xffff8101eda8a410, .arph=0xffff8101eda8a410, .raw="E"}, .mac={.raw=""}, .dst=0xffff81011d1dfe40, .sp=0x0, .cb="", .len=56, .data_len=0, .mac_len=14, .csum=330099  
下面要把skb这个变量赋予给stap中的脚本变量, 前面已经说了, 这样做的话, stap中的脚本变量会丢失类型. 因为脚本中存储的是整型.  
可以使用%p输出这个地址.  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%p\n", var); exit();}'  
0xffff81011a4a5e80  
如果不做类型转换, var不能使用$skb的其他用法, 例如$skb$$  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%s\n", var$$); exit();}'  
WARNING: never-assigned local variable 'var$$' (alternatives: var): identifier 'var$$' at <input>:1:62  
 source: probe kernel.function("icmp_echo") {var=$skb; printf("%s\n", var$$); exit();}  
                                                                      ^  
WARNING: Eliding assignment to var at operator '=' at <input>:1:40  
WARNING: Eliding side-effect-free expression : identifier 'var' at :1:37  
 source: probe kernel.function("icmp_echo") {var=$skb; printf("%s\n", var$$); exit();}  
                                             ^  
  
下面要对var进行转换, 首先要知道skb这个变量的类型, 我们找到源码   
/usr/src/debug/kernel-2.6.18/linux-2.6.18-348.12.1.el5.x86_64/net/ipv4/icmp.c  
static void icmp_echo(struct sk_buff *skb)  
所以skb对应的是结构sk_buff 的指针.  
类型转换@cast(var, "sk_buff")  
如果要加上头文件位置的话, 不要使用全路径/usr/src/debug/kernel-2.6.18/linux-2.6.18-348.12.1.el5.x86_64/net/ipv4/icmp.c  
请使用如下 :   
@cast(var,"sk_buff","kernel<linux/skbuff.h>")  
例子 :   
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%s\n", @cast(var,"sk_buff","kernel<linux/skbuff.h>")$$); exit();}'  
{.next=0x0, .prev=0x0, .sk=0x0, .tstamp={.off_sec=1381207237, .off_usec=54643}, .dev=0xffffffff80352b80, .input_dev=0xffffffff80352b80, .h={.th=0xffff810119b3c024, .uh=0xffff810119b3c024, .icmph=0xffff810119b3c024, .igmph=0xffff810119b3c024, .ipiph=0xffff810119b3c024, .ipv6h=0xffff810119b3c024, .raw="}, .nh={.iph=0xffff810119b3c010, .ipv6h=0xffff810119b3c010, .arph=0xffff810119b3c010, .raw="E"}, .mac={.raw=""}, .dst=0xffff8101eedb2e00, .sp=0x0, .cb="", .len=56, .data_len=0, .mac_len=14, .csum=119797980,   
  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%s\n", @cast(var,"sk_buff")$$); exit();}'  
{.next=0x0, .prev=0x0, .sk=0x0, .tstamp={.off_sec=1381207264, .off_usec=581377}, .dev=0xffffffff80352b80, .input_dev=0xffffffff80352b80, .h={.th=0xffff8101ef486824, .uh=0xffff8101ef486824, .icmph=0xffff8101ef486824, .igmph=0xffff8101ef486824, .ipiph=0xffff8101ef486824, .ipv6h=0xffff8101ef486824, .raw="}, .nh={.iph=0xffff8101ef486810, .ipv6h=0xffff8101ef486810, .arph=0xffff8101ef486810, .raw="E"}, .mac={.raw=""}, .dst=0xffff8101eedb2e00, .sp=0x0, .cb="", .len=56, .data_len=0, .mac_len=14, .csum=4249748145  
  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%p\n", @cast(var,"sk_buff")->next); exit();}'  
0x0  
  
[root@db-172-16-3-39 memory]# stap -e 'probe kernel.function("icmp_echo") {var=$skb; printf("%d\n", @cast(var,"sk_buff")->tstamp->off_sec); exit();}'  
1381207332  
  
5.6.12 <value> in <array_name>  
This expression evaluates to true if the array contains an element with the specified index.  
判断value是否是数组的下标, 返回0假 或 1真;  
举例 :   
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a"]=1; arr1["b"]=2; printf("%d\n", @1 in arr1); exit();}' "a"   
1  
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a"]=1; arr1["b"]=2; printf("%d\n", @1 in arr1); exit();}' "b"   
1  
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a"]=1; arr1["b"]=2; printf("%d\n", @1 in arr1); exit();}' "c"   
0  
  
5.6.13 [ <value>, ... ] in <array_name>  
The number of index values must match the number of indexes previously specified.  
如果数组的下标是多元下标, 那么需要使用这种语法来匹配 :   
[ <value>, ... ] in <array_name>  
下标个数必须相同. 否则报错  
举例 :   
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a","c"]=2; printf("%d\n", ["b", "a", "c"] in arr1); exit();}'  
1  
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a","c"]=2; printf("%d\n", ["a", "c", "b"] in arr1); exit();}'  
0  
如果下标个数不一致, 将报错  
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a","c"]=2; printf("%d\n", ["a", "c"] in arr1); exit();}'  
semantic error: inconsistent arity (3 vs 2): identifier 'arr1' at <input>:1:98  
        source: global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a","c"]=2; printf("%d\n", ["a", "c"] in arr1); exit();}  
                                                                                                                 ^  
  
semantic error: arity 3 first inferred here: identifier 'arr1' at :1:27  
        source: global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a","c"]=2; printf("%d\n", ["a", "c"] in arr1); exit();}  
                                          ^  
  
Pass 2: analysis failed.  Try again with another '--vp 01' option.  
同样, 在定义数组时, 也必须与第一次定义一致, 所以以下操作也是错误的 :   
[root@db-172-16-3-39 memory]# stap -e 'global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a"]=2; exit();}'  
semantic error: inconsistent arity (3 vs 2): identifier 'arr1' at <input>:1:48  
        source: global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a"]=2; exit();}  
                                                               ^  
  
semantic error: arity 3 first inferred here: identifier 'arr1' at :1:27  
        source: global arr1; probe begin {arr1["a","b","c"]=1; arr1["b","a"]=2; exit();}  
                                          ^  
  
Pass 2: analysis failed.  Try again with another '--vp 01' option.  
  
6. stap参数传入  
Literals passed in from the stap command line  
  
Literals are either strings enclosed in double quotes ('' '') or integers.  
Script arguments at the end of a command line are expanded as literals.   
You can use these in all contexts where literals are accepted.   
A reference to a nonexistent argument number is an error.  
  
5.7.1 $1 ... $<NN> for literal pasting 传入整型用$  
Use $1 ... $<NN> for pasting the entire argument string into the input stream, which will be further lexically tokenized.  
  
5.7.2 @1 ... @<NN> for strings 传入字符串用@  
Use @1 ... @<NN> for casting an entire argument as a string literal.  
  
5.7.3 Examples  
For example, if the following script named example.stp  
probe begin { printf("%d, %s\n", $1, @2) }  
is invoked as follows  
# stap example.stp '5+5' mystring  
then 5+5 is substituted for $1 and "mystring" for @2. The output will be  
10, mystring  
  
[参考]  
1. https://sourceware.org/systemtap/langref/Language_elements.html  
2. http://blog.163.com/digoal@126/blog/static/16387704020138392759478/  
3. http://en.wikipedia.org/wiki/Unary_operation  
4. http://en.wikipedia.org/wiki/Signed_number_representations  
5. http://blog.163.com/digoal@126/blog/static/163877040201391391613269/  
6.   
/usr/src/linux/include/linux/skbuff.h  
struct sk_buff {  
        /* These two members must be first. */  
        struct sk_buff          *next;  
        struct sk_buff          *prev;  
  
        struct sock             *sk;  
        struct skb_timeval      tstamp;  
        struct net_device       *dev;  
        struct net_device       *input_dev;  
  
        union {  
                struct tcphdr   *th;  
                struct udphdr   *uh;  
                struct icmphdr  *icmph;  
                struct igmphdr  *igmph;  
                struct iphdr    *ipiph;  
                struct ipv6hdr  *ipv6h;  
                unsigned char   *raw;  
        } h;  
  
        union {  
                struct iphdr    *iph;  
                struct ipv6hdr  *ipv6h;  
                struct arphdr   *arph;  
                unsigned char   *raw;  
        } nh;  
  
        union {  
                unsigned char   *raw;  
        } mac;  
  
        struct  dst_entry       *dst;  
        struct  sec_path        *sp;  
... 略  
6.   
/usr/src/debug/kernel-2.6.18/linux-2.6.18-348.12.1.el5.x86_64/net/ipv4/icmp.c  
static void icmp_echo(struct sk_buff *skb)  
{  
        if (!sysctl_icmp_echo_ignore_all) {  
                struct icmp_bxm icmp_param;  
  
                icmp_param.data.icmph      = *skb->h.icmph;  
                icmp_param.data.icmph.type = ICMP_ECHOREPLY;  
                icmp_param.skb             = skb;  
                icmp_param.offset          = 0;  
                icmp_param.data_len        = skb->len;  
                icmp_param.head_len        = sizeof(struct icmphdr);  
                icmp_reply(&icmp_param, skb);  
        }  
}  

Flag Counter

digoal’s 大量PostgreSQL文章入口