变量

变量分为本地变量和全局变量,本地变量只能在当前的 handler 中使用,在多个 handler 中使用的变量称为全局变量,定义时加上 global 标识。

变量定义时不需要指定类型,SystemTap 会自动识别出类型。

global count_jiffies, count_ms
probe timer.jiffies(100) { count_jiffies ++ }
probe timer.ms(100) { count_ms ++ }
probe timer.ms(12345)
{
  hz=(1000*count_jiffies) / count_ms
  printf ("jiffies:ms ratio %d:%d => CONFIG_HZ=%d\n",
    count_jiffies, count_ms, hz)
  exit ()
}

hz 是本地变量,类型为 integer , SystemTap 会自动识别,在打印时,指定了 %d 格式。count_jiffies, count_ms 是全局变量,在各自的 probe 中自增统计,在 timer.ms 中汇总输出。

Target Variables

在执行 probe 时,可以访问被探测代码的上下文变量,称之为 target variable 。包括函数的入参,返回值,局部变量,设置包括全局变量等。

之前提过,可以使用 -L 选项列出目标函数的 target variable 。列出内核函数 vfs_read 的变量

 sudo stap -L 'kernel.function("vfs_read")'

输出如下:

 kernel.function("vfs_read@fs/read_write.c:339") $file:struct file* $buf:char* $count:size_t $pos:loff_t*

每个 target variable 名称前都加了 $, : 之后是类型。vfs_read 函数包含了以下变量:$file 指向了描述文件的结构,$buf 指向用户空间的内存地址,用于存储读取的数据,$count 表示读取数据的大小,$pos 表示从文件的偏移量进行读取。可以使用 -> 访问 struct 的具体的字段。

如果变量的指针指向的是基本类型,可以通过下面的函数获取到内核数据

  • kernel_char(address) 把指针地址解析为 char 类型
  • kernel_short(address) 解析为 short 类型
  • kernel_int(address) 解析为 int 类型
  • kernel_long(address) 解析为 long 类型
  • kernel_string(address) 解析为 string 类型
  • kernel_string_n(address, n) 解析为 string 类型,只把前 n 个字节返回

打印变量

有时候,我们只想打印上下文的 target variable, systemtap 也提供了一系列操作。

  • $$vars

类似于 sprintf("parm1=%x ... parmN=%x var1=%x ... varN=%x", parm1, ..., parmN, var1, ..., varN), 会打印 probe 范围内的所有变量。如果运行时的变量地址找不到的话,会打印 =?

  • $$locals

是 $$vars 的子集,只会包含本地变量

  • $$parms

是 $$vars 的子集,只会包含函数参数变量

  • $$return

只在有 return 标识的 probe 中有意义。类似于 sprintf("return=%x", $return), 如果 probe 有返回值的话,则打印,否则是空字符串。

我们来看个例子

 stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms); exit(); }'

前面提到 vfs_read 有四个参数:file, buf, count, and pos 。$$parms 根据这些参数会生成一个字符串,除了 count 参数,其余都是指针类型。下面是可能的输出

 file=0xffff8805b071fd40 buf=0x7fffe5b64b60 count=0x2004 pos=0xffff88010db17f48

只是打印地址的话,并没有太大作用。可以添加 $ 后缀,可以更详细的输出数据结构。

 sudo stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$); exit(); }'

那么就会输出

 file={.f_u={...}, .f_path={...}, .f_op=0xffffffffa0426ac0, .f_lock={...}, .f_count={...}, .f_flags=34818, .f_mode=31, .f_pos=0, .f_owner={...}, .f_cred=0xffff880353f46540, .f_ra={...}, .f_version=0, .f_security=0x0, .private_data=0x0, .f_ep_links={...}, .f_mapping=0xffff88022f18f3f8} buf="" count=8196 pos=-131925246861496

使用 $ 还不能显示嵌套的结构体。使用 $$ 可以打印嵌套的数据结构。

 sudo stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$$); exit(); }'

$$ 生成的字符串也受最长字符的限制,嵌套太多,输出并不完整。输出如下

 file={.f_u={.fu_list={.next=0xffff880117252740, .prev=0xffff880037949c00}, .fu_rcuhead={.next=0xffff880117252740, .func=0xffff880037949c00}}, .f_path={.mnt=0xffff880c142213c0, .dentry=0xffff88034e7f3d40}, .f_op=0xffffffffa01eb3c0, .f_lock={.raw_lock={.slock=0}}, .f_count={.counter=2}, .f_flags=34818, .f_mode=31, .f_pos=0, .f_owner={.lock={.raw_lock={.lock=16777216}}, .pid=0x0, .pid_type=0, .uid=0, .euid=0, .signum=0}, .f_cred=0xffff88010eba8480, .f_ra={.start=0, .size=0, .async_size=0, .ra_pages=32, .mmap_

可以使用 -D 选项把资源限制调大,后面的输出就会完整了。

 sudo stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$$); exit(); }' -DMAXSTRINGLEN=10240

 file={.f_u={.fu_list={.next=0xffff880414a74f00, .prev=0xffff88036adc7980}, .fu_rcuhead={.next=0xffff880414a74f00, .func=0xffff88036adc7980}}, .f_path={.mnt=0xffff880c142213c0, .dentry=0xffff8801fdbab800}, .f_op=0xffffffffa020d3c0, .f_lock={.raw_lock={.slock=0}}, .f_count={.counter=2}, .f_flags=34818, .f_mode=31, .f_pos=0, .f_owner={.lock={.raw_lock={.lock=16777216}}, .pid=0x0, .pid_type=0, .uid=0, .euid=0, .signum=0}, .f_cred=0xffff880414bb10c0, .f_ra={.start=0, .size=0, .async_size=0, .ra_pages=32, .mmap_miss=0, .prev_pos=-1}, .f_version=0, .f_security=0x0, .private_data=0x0, .f_ep_links={.next=0xffff88054b3b7ca8, .prev=0xffff88054b3b7ca8}, .f_mapping=0xffff88040032b9d8} buf="" count=8196 pos=-131924015866040

results matching ""

    No results matching ""