Systemtap parse preprocessing stage - Conditional compilation

6 minute read

背景

One of the steps of parsing is a simple preprocessing stage.   
The preprocessor supports conditionals with a general form similar to the ternary operator (Section [*]).  
%( CONDITION %? TRUE-TOKENS %)  
%( CONDITION %? TRUE-TOKENS %: FALSE-TOKENS %)  
  
The CONDITION is a limited expression whose format is determined by its first keyword.   
The following is the general syntax.  
%( <condition> %? <code> [ %: <code> ] %)  
stap 预处理阶段支持条件编译, 类似C里面的#ifdef 这种的, stap的条件编译语法与三目操作符类似, 语法如下 :   
%( <condition> %? <code> [ %: <code> ] %)  
中括号部分表示可选.  
例如 :   
[root@db-172-16-3-39 ~]# stap -e '%( CONFIG_UTRACE == "y" %? probe begin {printf("true\n"); exit();} %: probe begin {printf("false\n"); exit();} %)'  
true  
  
[root@db-172-16-3-39 ~]# stap -e '%( CONFIG_UTRACE == "n" %? probe begin {printf("true\n"); exit();} %: probe begin {printf("false\n"); exit();} %)'  
false  
  
[root@db-172-16-3-39 ~]# stap -e '%( 1==1 %? probe begin {printf("true\n"); exit();} %: probe begin {printf("false\n"); exit();} %)'  
true  
  
[root@db-172-16-3-39 ~]# stap -e '%( 1!=1 %? probe begin {printf("true\n"); exit();} %: probe begin {printf("false\n"); exit();} %)'  
false  
因为是预编译, 以下的条件为false, 但是缺少%:部分, 所以这个条件编译的结果为空, 报错如下.  
[root@db-172-16-3-39 ~]# stap -e '%( 1!=1 %? probe begin {printf("true\n"); exit();} %)'  
Input file '<input>' is empty or missing.  
Pass 1: parse failed.  Try again with another '--vp 1' option.  
  
条件1, 判断变量是否定义.  
Conditions based on available target variables  
  
The predicate @defined() is available for testing whether a particular $variable/expression is resolvable at translation time. The following is an example of its use:  
  probe foo { if (@defined($bar)) log ("$bar is available here") }  
变量是否定义的判断不能放在条件预编译中处理. 报错parse error: expected 'arch' or 'kernel_v' or 'kernel_vr' or 'CONFIG_...'  
条件预编译的条件TOKEN只能是arch, kernel_v, kernel_vr或者CONFIG_..., 所以@define不能算条件预编译.  
如下 :   
[root@db-172-16-3-39 tapset]# stap --vp 1 -e '%( @defined(abc) %? probe begin {printf("true\n"); exit();} %)'  
parse error: expected 'arch' or 'kernel_v' or 'kernel_vr' or 'CONFIG_...'  
             or comparison between strings or integers  
        at: identifier '@defined' at <input>:1:4  
     source: %( @defined(abc) %? probe begin {printf("true\n"); exit();} %)  
                ^  
parse error: incomplete conditional - missing '%('  
        at: operator '%?' at <input>:1:18  
     source: %( @defined(abc) %? probe begin {printf("true\n"); exit();} %)  
                              ^  
parse error: incomplete conditional - missing '%('  
        at: operator '%)' at <input>:1:61  
     source: %( @defined(abc) %? probe begin {printf("true\n"); exit();} %)  
                                                                         ^  
3 parse errors.  
Pass 1: parsed user script and 85 library script(s) using 146796virt/23708res/3016shr/21392data kb, in 160usr/10sys/173real ms.  
Pass 1: parse failed.  Try again with another '--vp 1' option.  
但是它可以用在probe语法中.  
例如  
probe begin if (condition) {...}  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin if (1==1) {printf("trigged\n"); exit()}'  
trigged  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin if (1!=1) {printf("trigged\n"); exit()}'  
[root@db-172-16-3-39 tapset]#   

详见

http://blog.163.com/digoal@126/blog/static/1638770402013811957335/


判断变量是否存在, 一般用于handler内部, 放在handler外面也会报错.  
[root@db-172-16-3-39 tapset]# stap --vp 01 -e 'probe kernel.function("icmp_echo") if (@defined($var1)) { printf("trigged\n"); exit(); }'  
semantic error: unexpected @defined: identifier '@defined' at <input>:1:40  
        source: probe kernel.function("icmp_echo") if (@defined($var1)) { printf("trigged\n"); exit(); }  
                                                       ^  
  
Pass 2: analyzed script: 1 probe(s), 1 function(s), 0 embed(s), 0 global(s) using 222896virt/81852res/45260shr/37356data kb, in 300usr/70sys/379real ms.  
Pass 2: analysis failed.  Try again with another '--vp 01' option.  
通常的用法如下 :   
[root@db-172-16-3-39 tapset]# stap -e 'probe kernel.function("icmp_echo") { if (@defined($skb)) printf("trigged\n"); exit(); }'  
trigged  
[root@db-172-16-3-39 tapset]# stap -e 'probe kernel.function("icmp_echo") { if (@defined($var1)) printf("trigged\n"); exit(); }'  
  
条件2, 内核版本的比较.  
Conditions based on kernel version: kernel_v, kernel_vr  
  
If the first part of a conditional expression is the identifier kernel_v or kernel_vr,   
the second part must be one of six standard numeric comparison operators ``<'', ``<='', ``=='', ``!='', ``>'', or ``>='',   
and the third part must be a string literal that contains an RPM-style version-release value.   
The condition returns true if the version of the target kernel (as optionally overridden by the -r option) matches the given version string.   
The comparison is performed by the glibc function strverscmp.  
kernel_v refers to the kernel version number only, such as ``2.6.13".  
kernel_vr refers to the kernel version number including the release code suffix, such as ``2.6.13-1.322FC3smp''.  
在条件预编译中使用kernel_v或者kernel_vr可以比较内核版本.  
例如 :   
[root@db-172-16-3-39 tapset]# uname -r  
2.6.18-348.12.1.el5  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( kernel_v == "2.6.181" %? printf("true\n"); %: printf("false\n"); %) exit();}'   
false  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( kernel_v == "2.6.18" %? printf("true\n"); %: printf("false\n"); %) exit();}'  
true  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( kernel_vr == "2.6.18-348.12.1.el5" %? printf("true\n"); %: printf("false\n"); %) exit();}'  
true  
还可以判断版本号的大小, 大于等于, 不等于 等.  
底层使用glibc函数strverscmp来实现.  
  
条件3, 硬件架构判断.  
Conditions based on architecture: arch  
  
If the first part of the conditional expression is the identifier arch which refers to the processor architecture, then the second part is a string comparison operator ''=='' or ''!='', and the third part is a string literal for matching it. This comparison is a simple string equality or inequality. The currently supported architecture strings are i386, i686, x86_64, ia64, s390, and powerpc.  
硬件架构判断只能使用等于或不等于. 例如 :   
[root@db-172-16-3-39 tapset]# uname -m  
x86_64  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( arch == "x86" %? printf("true\n"); %: printf("false\n"); %) exit();}'  
false  
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( arch == "x86_64" %? printf("true\n"); %: printf("false\n"); %) exit();}'  
true  
  
条件4, 执行stap脚本的用户权限判断.  
Conditions based on privilege level: systemtap_privilege  
  
If the first part of the conditional expression is the identifier systemtap_privilege which refers to the privilege level the systemtap script is being compiled with, then the second part is a string comparison operator ''=='' or ''!='', and the third part is a string literal for matching it.   
This comparison is a simple string equality or inequality.   
The possible privilege strings to consider are "stapusr" for unprivileged scripts, and "stapsys" or "stapdev" for privileged scripts.   
(In general, to test for a privileged script it is best to use != "stapusr".)  
  
This condition can be used to write scripts that can be run in both privileged and unprivileged modes, with additional functionality made available in the privileged case.  
在1.8的stap版本中报错, 如下 :   
[root@db-172-16-3-39 tapset]# stap -e 'probe begin {%( systemtap_privilege == "stapusr" %? printf("true\n"); %: printf("false\n"); %) exit();}'  
parse error: expected 'arch' or 'kernel_v' or 'kernel_vr' or 'CONFIG_...'  
             or comparison between strings or integers  
        at: identifier 'systemtap_privilege' at <input>:1:17  
     source: probe begin {%( systemtap_privilege == "stapusr" %? printf("true\n"); %: printf("false\n"); %) exit();}  
                             ^  
parse error: incomplete conditional - missing '%('  
        at: operator '%?' at <input>:1:50  
     source: probe begin {%( systemtap_privilege == "stapusr" %? printf("true\n"); %: printf("false\n"); %) exit();}  
                                                              ^  
parse error: incomplete conditional - missing '%('  
        at: operator '%:' at <input>:1:71  
     source: probe begin {%( systemtap_privilege == "stapusr" %? printf("true\n"); %: printf("false\n"); %) exit();}  
                                                                                   ^  
parse error: incomplete conditional - missing '%('  
        at: operator '%)' at <input>:1:93  
     source: probe begin {%( systemtap_privilege == "stapusr" %? printf("true\n"); %: printf("false\n"); %) exit();}  
                                                                                                         ^  
4 parse errors.  
Pass 1: parse failed.  Try again with another '--vp 1' option.  
  
[root@db-172-16-3-39 tapset]# stap -V  
Systemtap translator/driver (version 1.8/0.152 non-git sources)  
Copyright (C) 2005-2012 Red Hat, Inc. and others  
This is free software; see the source for copying conditions.  
enabled features: AVAHI LIBRPM LIBSQLITE3 NSS BOOST_SHARED_PTR TR1_UNORDERED_MAP NLS  
  
最后, 以下是书上的例子,   
5. True and False Tokens  
TRUE-TOKENS and FALSE-TOKENS are zero or more general parser tokens, possibly including nested preprocessor conditionals, that are pasted into the input stream if the condition is true or false.   
  
For example, the following code induces a parse error unless the target kernel version is newer than 2.6.5.  
%( kernel_v <= "2.6.5" %? **ERROR** %)      # invalid token sequence  
  
The following code adapts to hypothetical kernel version drift.  
实现对不同的版本使用不同的探针函数名:  
probe kernel.function (  
    %( kernel_v <= "2.6.12" %? "__mm_do_fault" %:  
        %( kernel_vr == "2.6.13-1.8273FC3smp" %? "do_page_fault" %: UNSUPPORTED %)  
    %)) { /* ... */ }  
  
%( arch == "ia64" %?  
    probe syscall.vliw = kernel.function("vliw_widget") {}  
%)  
  
The following code adapts to the presence of a kernel CONFIG option.  
%( CONFIG_UTRACE == "y" %?  
    probe process.syscall {}  
%)  

参考

1. https://sourceware.org/systemtap/langref/Language_elements.html

2. man strverscmp

Flag Counter

digoal’s 大量PostgreSQL文章入口