SystemTap使用技巧【一】

       SystemTap是一个强大的调试工具,确切的说应该是一门调试语言,因为它有自己的语法,也有解析、编译、运行等过程(准确的说有五个阶段),但它主要解决的问题是收集Linux内核或者用户进程的信息,主要目的是调试。我一直以为gdb、kgdb是Linux最强大的调试器,曾经爱不释手,自从发现了SystemTap之后,又有了当初喜欢gdb的那种感觉了,真的是相见恨晚啊。gdb和SystemTap不是竞争关系,而是互补关系,gdb能做的事情SystemTap做不到,比如断点/watch变量等等这些SystemTap都做不到,而SystemTap能做的事情gdb做不到或者非常麻烦才做到,比如很方便查看内核调试栈/嵌入C语言等等gdb就傻眼了。SystemTap真的非常强大,会了这个又觉得自己帅几个百分点了。来看一下几个技巧吧:

1、定位函数位置。

       比较典型的是定位内核系统调用函数在哪个文件上,以往我都是source insight或者grep找,比如要找open系统调用在内核的实现,就这样: 
root@jusse ~/linux-source-3.13.0# grep -nr 'SYSCALL_DEFINE3(open' ./ 
./fs/compat.c:1075:COMPAT_SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) 
./fs/compat.c:1461:COMPAT_SYSCALL_DEFINE3(open_by_handle_at, int, mountdirfd, 
./fs/open.c:1011:SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) 
./fs/fhandle.c:254:SYSCALL_DEFINE3(open_by_handle_at, int, mountdirfd,
       Linux比较新的内核定义系统调用都是SYSCALL_DEFINE方式,不像以前可以通过关键字sys_open来找,非常麻烦而且很慢,如果用SystemTap的话一个命令就搞定了:
root@jusse ~/linux-source-3.13.0# stap -l 'kernel.function("sys_open")' 
kernel.function("SyS_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:1011")
       我们很快知道open系统调用的实现代码就在fs/open.c的第1011行,搜索比grep快,而且很准确。
       同理,我们也可以用来定位用户进程的函数位置:
       比如nginx的文件ngx_shmtx.c里面为了兼容各个操作系统而实现了两个版本的ngx_shmtx_trylock锁操作,用#if (NGX_HAVE_ATOMIC_OPS)、#else、#endif来做条件编译,那怎么知道我们编译出来的是哪个版本呢,用SystemTap的话就很简单了,否则要去grep一下这个宏 NGX_HAVE_ATOMIC_OPS有没有定义才知道了,看看SystemTap的例子吧:
root@jusse ~/develop# stap -l 'process("/opt/nginx/sbin/nginx").function("ngx_shmtx_trylock")'    
process("/opt/nginx/sbin/nginx").function("ngx_shmtx_trylock@src/core/ngx_shmtx.c:63")
       ngx_shmtx.c第63行就是编译出来的版本。
       另外也可以定位出nginx中所有以ngx_epoll_开头的函数在哪里:
root@jusse ~/work/nginx# stap -l 'process("/opt/nginx/sbin/nginx").function("ngx_epoll_*")' 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_add_connection@src/event/modules/ngx_epoll_module.c:502") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_add_event@src/event/modules/ngx_epoll_module.c:386") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_create_conf@src/event/modules/ngx_epoll_module.c:807") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_del_connection@src/event/modules/ngx_epoll_module.c:526") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_del_event@src/event/modules/ngx_epoll_module.c:444") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_done@src/event/modules/ngx_epoll_module.c:348") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_init@src/event/modules/ngx_epoll_module.c:295") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_init_conf@src/event/modules/ngx_epoll_module.c:824") 
process("/opt/nginx-dso/sbin/nginx").function("ngx_epoll_process_events@src/event/modules/ngx_epoll_module.c:564")

2、查看实际的函数。

       如果对nginx或者内核代码有点了解的,可能知道很多地方数据结构里面用到了函数指针,这是C语言里面向对象思想的编程方式,非常有用,但对看代码的人来说就不太方便了。最近在csdn的linux bbs里看到这个帖子: http://bbs.csdn.net/topics/390972532,为了满足自己对底层实现的好奇心就深入地研究了一把。 问题是这样:我想知道内核里一个进程的进程组组长pid存在task_struct的什么地方,通过ps可以看出进程的pgid一般是自己的pid或者父进程pid:
root@jusse ~# ps -ef -o pid,ppid,pgid,comm 
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html 
PID PPID PGID COMMAND 
28400 28399 28400 bash 
5637 28400 5637 \_ cc_epoll 
5638 5637 5637 \_ cc_epoll 
5639 5637 5637 \_ cc_epoll 
5640 5637 5637 \_ cc_epoll 
5641 5637 5637 \_ cc_epoll 
5642 5637 5637 \_ cc_epoll
       这是自己写的cc_epoll进程,从上面的结果可以看出,父进程fork出5个子进程,其中父进程是进程组组长,子进程的PGID是父进程,那父进程和子进程的PGID在什么时候设置的呢,首先通过strace跟踪一下:
root@jusse ~# strace ps -ef -o pid,ppid,pgid,comm
……
stat("/proc/5640", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/5640/stat", O_RDONLY)       = 6  
read(6, "5640 (cc_epoll) S 5637 5637 2840"..., 1024) = 324
read(6, "", 700)                        = 0  
close(6)                                = 0  
……
       原来是去读取 /proc/5640/stat这个文件的内容啊。那我们就看一下它是怎么read的,在google在搜了一圈都没找到proc文件系统的实现思路,那就只能从read系统调用入手了。我们可以自己cat一下:
root@jusse ~# cat /proc/5640/stat 
5640 (cc_epoll) S 5637 5637 28400 34828 5637 4219200 63 0 0 0 0 0 0 0 20 0 1 0 746032770 8626176 30 18446744073709551615 4194304 4200196 140734779287888 140734779166376 140232232810899 0 0 0 0 18446744071581020176 0 0 17 0 0 0 0 0 0 6299160 6299888 32350208 140734779291850 140734779291861 140734779291861 140734779293677 0 
root@jusse ~/systemtap# strace cat /proc/5640/stat
……
open("/proc/5640/stat", O_RDONLY)       = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
read(3, "5640 (cc_epoll) S 5637 5637 2840"..., 32768) = 324
write(1, "5640 (cc_epoll) S 5637 5637 2840"..., 3245640 (cc_epoll) S 5637 5637 28400 34828 5637 4219200 63 0 0 0 0 0 0 0 20 0 1 0 746032770 8626176 30 18446744073709551615 4194304 4200196 140734779287888 140734779166376 140232232810899 0 0 0 0 18446744071581020176 0 0 17 0 0 0 0 0 0 6299160 6299888 32350208 140734779291850 140734779291861 140734779291861 140734779293677 0
) = 324
read(3, "", 32768)                      = 0
close(3)                                = 0
……
       用SystemTap定位read系统调用后得到的代码如下:
SystemTap使用技巧【一】_第1张图片
SystemTap使用技巧【一】_第2张图片
       从代码可以看出sys_read主要调用vfs_read,vfs_read主要调用file->f_op->read,而这个read是一个指针,要是能gdb step进去一下就知道是哪个函数了,调试nginx时我就经常这么干,可惜这是内核啊,gdb调试内核连环境都要搭半天,太麻烦了划不来。还是用SystemTap吧,SystemTap可以在vfs_read设置个probe,也就是设置一个探测点,SystemTap代码如下:
probe kernel.statement("vfs_read@/build/buildd/linux-lts-trusty-3.13.0/fs/read_write.c:392")
{
    if ($count == 32768) { //这里设置条件是为了过滤只留下需要的信息,32768是上面用strace跟踪得到read的第三个参数
        printf("open: %s, read: %s\n", symname($file->f_op->open), symname($file->f_op->read));
    }
}
       从SystemTap提供的symname函数接口可以得到指定地址的符号,也就是这小节的技巧:查看实际的函数
       运行stap后再执行 cat /proc/5640/stat就可以得到如下结果:
root@jusse ~/systemtap# stap vfs_read.stp 
open: 0x0, read: inotify_read 
open: 0x0, read: inotify_read 
open: proc_single_open, read: seq_read 
open: proc_single_open, read: seq_read 
       接下来就是分析proc_single_open和seq_read这两个函数了,通过这种方式分析下去,最后也得到进程的PGID是存在什么地方了,具体存在什么地方就略了,你也可以分析一下,从分析的过程中还能学到不少东西呢。

3、查看一个函数里面能在哪一行设置probe以及能获取哪些变量。

       Linux内核的copy_process函数比较长,但被编译器优化了不少,在一些行上设置不了probe或者获取不了局部变量。设置行数不对或者获取不到变量符号就容易得到正面这些错误:
root@jusse ~/systemtap# stap copy_process.stap 
semantic error: no line records for /build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1235 [man error::dwarf] (try :1234 or :1236) 

semantic error: while resolving probe point: identifier 'kernel' at copy_process.stap:1:7 
source: probe kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1235") 
^ 

semantic error: no match 

Pass 2: analysis failed. [man error::pass2] 
root@jusse ~/systemtap# stap copy_process.stap 
semantic error: failed to retrieve location attribute for 'p' [man error::dwarf]: identifier '$p' at copy_process.stap:3:29 
dieoffset: 0x9d4ac2 from unknown debug file for kernel 
function: copy_process at /build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1236 
source: printf("$p->pid: %d\n", $p->pid); 
^ 

Pass 2: analysis failed. [man error::pass2]
       解决行数设置不对的办法是一是按照错误提示设置,二是用stap的-L参数看能在哪些行上设probe:
root@jusse ~/systemtap# stap -L 'kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:*")' 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1134") 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1140") 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1144") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1145") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1147") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int $p:struct task_struct* 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1154") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int $p:struct task_struct* 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1162") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int $p:struct task_struct* 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1171") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int $p:struct task_struct* 
kernel.statement("copy_process@/build/buildd/linux-lts-trusty-3.13.0/kernel/fork.c:1172") $clone_flags:long unsigned int $stack_start:long unsigned int $stack_size:long unsigned int $child_tidptr:int* $pid:struct pid* $trace:int $p:struct task_struct*
       copy_process函数虽长但能获取的变量也就这么多,试图获取其他变量也会出错。我们要获取copy_process复制出来的task_struct的话只能在第1147、1154、1162、1171、1172这几行上获取$p,而这些行上不一定能执行到,从代码上看在这些行上$p是一个野指针,要在第1192行调用dup_task_struct(current)后才给$p赋值。所以在copy_process函数里设置statement探测点来获取task_struct是获取不到的。那怎么办呢,还好,copy_process函数会把复制出的task_struct返回给调用它的函数,那就可以在copy_process上设置function return探测点:
probe kernel.function("copy_process").return
{
    printf("pid: %d\n", $return->pid);
}
       要是函数没有返回你要找的变量,那就看它有没有把这个函数传给其他函数了,如果传那就在其他函数里面获取,没有的话就只能反汇编这个函数,再看这个函数把这个变量地址放在哪个寄存器上,再通过这个寄存器得到变量的地址来获取这个变量的内容。

4、获取函数参数。

       我们来看一下sys_open的代码:
SystemTap使用技巧【一】_第3张图片
       再用stap -L来看一下sys_open能设置哪些probe和能获取哪些变量:
root@jusse ~/systemtap# stap -L 'kernel.function("sys_open")' 
kernel.function("SyS_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:1011") $ret:long int
root@jusse ~/systemtap# stap -L 'kernel.statement("sys_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:*")' 
kernel.statement("SyS_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:1011") $ret:long int 
kernel.statement("SyS_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:1014") $filename:long int $flags:long int $ret:long int 
kernel.statement("SyS_open@/build/buildd/linux-lts-trusty-3.13.0/fs/open.c:1016") $filename:long int $ret:long int 
       我们要想获取用户传进来的参数filename怎么办呢,一是在sys_open函数上设置function  call探测点,二是在第1016行设置statement探测点。第二种方法是能获取filename,但获取不到最后一个参数mode。对于函数参数还有一种办法就是用SystemTap提供的*_arg函数接口,*是根据类型指定的,比较pointer_arg是获取指针类型参数,int_arg是获取整型参数,类似的还有long_arg、longlong_arg、uint_arg、ulong_arg、ulonglong_arg、s32_arg、s64_arg、u32_arg、u64_arg,例子代码如下:
root@jusse ~/systemtap# cat sys_open.stp 
probe kernel.function("sys_open").call
{
    printf("filename: %p, flags: %d, mode: %x\n", pointer_arg(1), int_arg(2), int_arg(3));
}
       这样我们就可以得到函数的指定参数了。

5、打印__user字符串。

       在内核里宏__user是给地址加上一个属性,指定这个地址是用户态地址,在内核里不能直接用,需要转换才能用。我们在SystemTap代码里面不能直接用user_string或者kernel_string,否则运行时出现下面这个错误:
ERROR: user string copy fault -14 at 00007f16323ffd90 [man error::fault] near identifier 'user_string_n' at /usr/local/share/systemtap/tapset/uconversions.stp:120:10
ERROR: kernel string copy fault at 0x00007fde54e79d90 [man error::fault] near identifier 'kernel_string' at /usr/local/share/systemtap/tapset/linux/conversions.stp:18:10
       想要打印__user限定的字符串,比如打印sys_open的第一个参数filename,要像下面这样:
root@jusse ~/systemtap# cat sys_open.stp 
probe kernel.function("sys_open").call 
{ 
    filename = user_string_quoted(pointer_arg(1)); 
    printf("filename: %s, execname: %s\n", filename, execname()); 
} 

6、打印函数调用堆栈。

       一般打印函数调用堆栈,用户进程的话直接gdb上去在文件行数或者函数设置一个断点,等待断点停下来后,用gdb命令backtrace(缩写bt)就能得到调用堆栈了,这样很有用,不管是学习新代码或者debug时都非常有用,比如在学nginx的时候,里面很多函数指针,用source insight、understand、vim+ctag都很难把代码流程读通,还是让代码运行起来后gdb上去设置断点bt一下就知道了。用户进程这种方法很管用,但在内核就不好办了,内核也一堆函数指针,很不好分析函数的调用流程,还好SystemTap给我们提供了print_backtrace,比如我们要看内核是怎么收包的,它的调用流程是怎样的,我们在netif_receive_skb函数上设置个探测点再把调用函数堆栈打印出来就知道了:
root@jusse ~/systemtap# cat netif_receive_skb.stp 
probe kernel.function("netif_receive_skb") 
{ 
    printf("--------------------------------------------------------\n"); 
    print_backtrace(); 
    printf("--------------------------------------------------------\n"); 
} 
root@jusse ~/systemtap# stap netif_receive_skb.stp
--------------------------------------------------------
 0xffffffff8164dc00 : netif_receive_skb+0x0/0x90 [kernel]
 0xffffffff8164e280 : napi_gro_receive+0xb0/0x130 [kernel]
 0xffffffff81554537 : handle_incoming_queue+0xe7/0x100 [kernel]
 0xffffffff815555d9 : xennet_poll+0x279/0x430 [kernel]
 0xffffffff8164ee09 : net_rx_action+0x139/0x250 [kernel]
 0xffffffff810702cd : __do_softirq+0xdd/0x300 [kernel]
 0xffffffff8107088e : irq_exit+0x11e/0x140 [kernel]
 0xffffffff8144e785 : xen_evtchn_do_upcall+0x35/0x50 [kernel]
 0xffffffff8176c9ed : xen_hvm_callback_vector+0x6d/0x80 [kernel]
--------------------------------------------------------

7、定义指定类型的变量。

       我在看SystemTap_Beginners_Guide文档的时候,看到nettop.stp这个例子,就试着运行一下:
root@jusse ~/source/systemtap# stap ./testsuite/systemtap.examples/network/nettop.stp 
semantic error: not accessible at this address (pc: 0xffffffff8164dc00) [man error::dwarf]: identifier '$skb' at /usr/local/share/systemtap/tapset/linux/networking.stp:64:27 
dieoffset: 0x5cb9e3e from unknown debug file for kernel 
function: netif_receive_skb at /build/buildd/linux-lts-trusty-3.13.0/net/core/dev.c:3694 
 
source: dev_name = kernel_string($skb->dev->name) 
^ 

Pass 2: analysis failed. [man error::pass2]
       出错了,说是$skb找不到,那就看看 /usr/local/share/systemtap/tapset/linux/networking.stp这个文件是怎么写的:
SystemTap使用技巧【一】_第4张图片
SystemTap使用技巧【一】_第5张图片
       原来是想获取netif_receive_skb函数的参数skb,这个就是上面第4个技巧说的获取函数参数获取不到的情况,那就改成下面这样:
SystemTap使用技巧【一】_第6张图片
       提前定义一个skb的变量,然后把后面用到$skb的地方改成skb,再运行试试:
root@jusse ~/source/systemtap# stap ./testsuite/systemtap.examples/network/nettop.stp 
semantic error: unknown type in dereference: operator '->' at /usr/local/share/systemtap/tapset/linux/networking.stp:65:30 
source: dev_name = kernel_string(skb->dev->name) 
^ 

Pass 2: analysis failed. [man error::pass2] 
       还是出错,这回是解引用一个不知道类型指针的错误,那用@cast转换一下:
SystemTap使用技巧【一】_第7张图片
root@jusse ~/source/systemtap# stap ./testsuite/systemtap.examples/network/nettop.stp 
semantic error: 'struct sk_buff' is being accessed instead of a member: operator '@cast' at /usr/local/share/systemtap/tapset/linux/networking.stp:64:11 
source: skb = @cast(pointer_arg(1), "struct sk_buff") 
^ 

Pass 2: analysis failed. [man error::pass2] 
       还是出错,这回是@cast的错误了,难道@cast不能这么用?原来@cast应该这么用:
SystemTap使用技巧【一】_第8张图片
root@jusse ~/source/systemtap# stap ./testsuite/systemtap.examples/network/nettop.stp 
PID UID DEV XMIT_PK RECV_PK XMIT_KB RECV_KB COMMAND 
0 0 eth1 7 357 0 14 swapper/0 
0 0 eth0 0 12 0 0 swapper/0 
3 0 eth1 0 2 0 0 ksoftirqd/0 
9838 0 eth1 0 2 0 0 redis-server 
8 0 eth1 0 1 0 0 rcuos/0
       这回是成功了,但每次都@cast一下,很费事啊,难道就不能提前声明定义一下吗, 寻觅了(grep -nr @cast ./)它的所有例子,看看@cast还有没有其他用法,最终还是找到一个可行的办法,再改一下:
SystemTap使用技巧【一】_第9张图片
       这回也成功了, 所以得到结论就是,@cast是可以提前定义指定类型变量的,原理就是先取变量第0个数组元素(其实就是自身)再对自身取地址就是指针变量了。

8、多级指针用法。

       之前写过一个SystemTap多级指针解引用的办法: http://blog.csdn.net/wangzuxi/article/details/40323213
function deref:long(ptr:long)
%{
    STAP_RETURN(*(void **)STAP_ARG_ptr);
%}
       但最近发现一个更简单的方法,从第7个技巧得到的灵感,看看下面二级指针用法的例子吧:
root@jusse ~/develop# cat cc_multi_pointer.c
#include 

struct test {
    int count;
};

int main(int argc, char *argv[])
{   
    struct test t = {.count = 5566};
    struct test *pt = &t;
    struct test **ppt = &pt;

    printf("t.count: %d, pt->count: %d, ppt->count: %d\n", t.count, pt->count, (*ppt)->count);

    return 0;
}

root@jusse ~/develop# gcc -Wall -g -o cc_multi_pointer ./cc_multi_pointer.c

root@jusse ~/develop# cat cc_multi_pointer.stp
probe process("./cc_multi_pointer").statement("main@./cc_multi_pointer.c:13")
{   
    printf("$t->count: %d, $pt->count: %d, $ppt->count: %d", $t->count, $pt->count, $ppt[0]->count);
}

root@jusse ~/develop# ./cc_multi_pointer
t.count: 5566, pt->count: 5566, ppt->count: 5566

root@jusse ~/develop# stap ./cc_multi_pointer.stp -c './cc_multi_pointer'
t.count: 5566, pt->count: 5566, ppt->count: 5566
$t->count: 5566, $pt->count: 5566, $ppt->count: 5566
root@jusse ~/develop# vim cc_multi_pointer.stp

root@jusse ~/develop# 
       现在三级指针你应该知道怎么搞了吧。

9、嵌入C语言代码。

       这个技巧很有用,可以获取各种信息,而且可以调用内核函数,比如我们想在进程fork出子进程时打印出进程id和进程名,也是先看看例子吧:
root@jusse ~/systemtap# cat copy_process.stp
function getprocname:string(task:long)
%{
    struct task_struct *task = (struct task_struct *)STAP_ARG_task;
    snprintf(STAP_RETVALUE, MAXSTRINGLEN, "pid: %d, comm: %s", task->pid, task->comm);
%}

function getprocid:long(task:long)
%{
    struct task_struct *task = (struct task_struct *)STAP_ARG_task;
    STAP_RETURN(task->pid);
%}

probe kernel.function("copy_process").return
{
    printf("copy_process return: %p, pid: %d, getprocname: %s, getprocid: %d\n", $return, $return->pid, getprocname($return), getprocid($return));
}
root@jusse ~/systemtap# stap -g copy_process.stp
copy_process return: 0xffff880039f61800, pid: 12212, getprocname: pid: 12212, comm: bash, getprocid: 12212
copy_process return: 0xffff880039f61800, pid: 12212, getprocname: pid: 12212, comm: bash, getprocid: 12212
copy_process return: 0xffff880039f63000, pid: 12213, getprocname: pid: 12213, comm: cc_epoll, getprocid: 12213
copy_process return: 0xffff880039f63000, pid: 12213, getprocname: pid: 12213, comm: cc_epoll, getprocid: 12213
copy_process return: 0xffff8800081a9800, pid: 12214, getprocname: pid: 12214, comm: cc_epoll, getprocid: 12214
copy_process return: 0xffff8800081a9800, pid: 12214, getprocname: pid: 12214, comm: cc_epoll, getprocid: 12214
copy_process return: 0xffff8800004d8000, pid: 12215, getprocname: pid: 12215, comm: cc_epoll, getprocid: 12215
copy_process return: 0xffff8800004d8000, pid: 12215, getprocname: pid: 12215, comm: cc_epoll, getprocid: 12215
copy_process return: 0xffff880000564800, pid: 12216, getprocname: pid: 12216, comm: cc_epoll, getprocid: 12216
copy_process return: 0xffff880000564800, pid: 12216, getprocname: pid: 12216, comm: cc_epoll, getprocid: 12216
copy_process return: 0xffff880000566000, pid: 12217, getprocname: pid: 12217, comm: cc_epoll, getprocid: 12217
copy_process return: 0xffff880000566000, pid: 12217, getprocname: pid: 12217, comm: cc_epoll, getprocid: 12217
       有三个需要注意的地方:
       1)、SystemTap脚本里面嵌入C语言代码要在每个大括号前加%前缀,是%{…… %}  而不是%{ …… }%;
       2)、获取脚本函数参数要用STAP_ARG_前缀;
       3)、一般long等返回值用STAP_RETURN,而string类型返回值要用snprintf、strncat等方式把字符串复制到STAP_RETVALUE里面。

SystemTap的技巧还有很多,以后有空再写。

附:
root@jusse ~/systemtap# uname -a
Linux jusse 3.13.0-36-generic #63~precise1-Ubuntu SMP Thu Sep 4 22:28:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

root@jusse ~/systemtap# stap -V
Systemtap translator/driver (version 2.7/0.152, commit release-2.6-65-g89538c0b970d)
Copyright (C) 2005-2014 Red Hat, Inc. and others
This is free software; see the source for copying conditions.
enabled features: TR1_UNORDERED_MAP NLS

参考:
https://sourceware.org/systemtap/tutorial/
https://sourceware.org/systemtap/tapsets/
https://sourceware.org/systemtap/man/
http://blog.csdn.net/linyt/article/category/645022
http://segmentfault.com/blog/yexiaobai/1190000000680628
http://blog.csdn.net/zhangskd/article/details/27320965


你可能感兴趣的:(调试,systemtap,systemtap)