使用systemtap获得内核函数的局部变量

使用systemtap获得内核局部变量

         这两天在看内核的cgroup源码,就想着通过某个工具来获得一些调试信息如bt,参数返回值等,像在调试应用程序一样使用gdb来获得这些信息。所以就有了对systemtap的真正实践。
注:我测试的机器使用的内核是:2.6.32-220.23.1.tb704.el6.x86_64(这是我们公司的内核),我看的源码是http://www.kernel.org/下载的2.6.32-60。        

        这里我们不再介绍systemtap的安装,只是需要知道要使用systemtap必须安装相应内核的符号信息debuginfo。 

        Systemtap支持嵌入C代码,这是获得函数内局部变量的关键。注:运行脚本时使用加-g参数,所有嵌入的C代码必须用%{ … %}包括起来(不是%{…}%),另外头文件也需要使用这对符号包括起来(《hack debug》里面就没把头文件包含在%{%}里);嵌入的C代码不能调用阻塞式函数。        

首先我想获得一个参数其中的一个字段(该参数是一个结构体,并且传递的是指针),使用$variable可以获得参数的值,那么怎么来获得它的某一字段的值:
function root_name:long(arg:long) %{
   struct cgroup_sb_opts *opts = (struct cgroup_sb_opts *)(THIS->arg);
   THIS->__retvalue = opts->subsys_bits;
%}

        在systemtap内指针值是long类型,我们的指针指向的是struct cgroup_sb_opts,所以我们先进行一次类型转换,然后就可以用C语言的方式来获得它的字段值。注:如果该类型(struct cgroup_sb_opts)不在内核的头文件里,那么还必须手动定义该类型(可能需要递归定义所有的不在头文件里的类型)。如果返回的是string类型,则使用strlcpy (THIS->__retvalue, opts->name, MAXSTRINGLEN)来返回。提示:如果嵌入的C语言有错误或者警告,stap输出的信息的位置是它生成的临时C文件的位置,而不是我们的脚本位置,这样调试起来就很不方便,所以我们要保留这些临时文件(加-k选项),然后再去找它报的错误位置,这样就可以很容易定位到我们脚本的错误位置。         上面是一个简单的运用,因为我们直接获得的是参数的值,那么如果想获得运行中的内部局部变量的值?

         在cgroup里每个子系统有一个静态全局变量,保存该子系统一些基本操作行为,如cpu子系统该变量是cpu_cgroup_subsys,cpuset是cpuset_subsys,memory是mem_cgroup_subsys等,但是当我根据这个命令规则去找blkio子系统的该变量却没有找到。怎么去找到这个变量?在cgroup的mount过程中会根据mount的子系统调用相应的populate去创建该子系统的文件。比如我们mount –t cgroup –o cpu cpu0 /cgroup/cpu,该过程的bt如下(使用systemtap的print_backtrace()函数获得):

74447240 8388 (mkdir) call trace:
 0xffffffff81054e60 :cpu_cgroup_populate+0x0/0x30 [kernel]
 0xffffffff810c007a : cgroup_populate_dir+0x7a/0x110[kernel]
 0xffffffff810c11fc : cgroup_mkdir+0x33c/0x540[kernel]
 0xffffffff811850a7 : vfs_mkdir+0xa7/0x100[kernel]
 0xffffffff8118816e : sys_mkdirat+0xfe/0x120[kernel]
 0xffffffff811881a8 : sys_mkdir+0x18/0x20[kernel]
 0xffffffff8100b0f2 : system_call_fastpath+0x16/0x1b[kernel]
该populate函数就是我们要找的静态变量的一个成员,所以我们可以从这里入手。我们先看一下cgroup_populate_dir代码:
static int cgroup_populate_dir(struct cgroup *cgrp)
{
         int err;
         struct cgroup_subsys *ss;
 
         /* First clear out any existing files */
         cgroup_clear_directory(cgrp->dentry);
 
         err = cgroup_add_files(cgrp, NULL, files, ARRAY_SIZE(files));
         if (err < 0)
                   return err;
 
         if (cgrp == cgrp->top_cgroup) {
                   if ((err = cgroup_add_file(cgrp, NULL, &cft_release_agent)) < 0)
                            return err;
         }
 
         for_each_subsys(cgrp->root, ss) {
                   if (ss->populate && (err = ss->populate(ss, cgrp)) < 0)
                            return err;
         }
         /* This cgroup is ready now */
         for_each_subsys(cgrp->root, ss) {
                   struct cgroup_subsys_state *css = cgrp->subsys[ss->subsys_id];
                   /*
                    * Update id->css pointer and make this css visible from
                    * CSS ID functions. This pointer will be dereferened
                    * from RCU-read-side without locks.
                    */
                   if (css->id)
                            rcu_assign_pointer(css->id->css, css);
         }
 
         return 0;
}
        我们知道systemtap可以probe指定到函数内的某一行,这样就可以再通过$ss来打印populate的值,但是因为我的源码与内核并不是一个版本的,所以只能通过probe到准确的地址来指定,所以我们需要反汇编一下该函数(在学校的时候是使用objdump来反汇编vxwork-kernel和通过nm来查找对应函数符号的地址)。在linux下有个crash工具,直接运行就可以,不需要kernel文件,crash>dis cgroup_populate_dir,大致的瞟了一下汇编代码

大多数都看不懂,但是可以确定这一行就是上面的ss->populate(ss,cgrp)函数调用,所以就在写了probe函数,而且这个变量就保存在rax这个寄存器里,实在看好了,直接查看这个寄存器不就得了:

probe kernel.function(0xffffffff810c0078) {
 printf("r12= %p, rax=%p\n”, register("r12"), register("rax"));
 print_backtrace();
}
        信心满满的以为就要找到,最后打打印出来的是:r12= 0xffff880473878030,rax=0xffff88062986ecc0
再把这个地址拿到符号文件里(可以通过nm去生成,但是因为有systemtap,所以在kernel目录下会有一个/usr/src/kernels/2.6.32-220.23.1.tb704.el6.x86_64System.map文件这个也包括了所有的符号信息)去查找没有。而且显示的这个地址不是代码段的地址,应该是数据段,经过了一番错误的尝试,最后想到是不是probe的地址有问题,所以就多输出了一个addr() (即probe的地址),发现地址竟还是0xffffffff810c0000(cgroup_populate_dir函数的起始地址),而不是我们想要的0xffffffff810c0078。

         看来只能绕过probe到0xffffffff810c0078这个地址的方法(觉得应该有这种方法,但现在的知识不知道),cgroup_populate_dir这个函数只有一个参数struct cgroup *cgrp,populate这个字段的值也是可以通过它获得的,那么我们是否可以在获得该参数时直接执行这个过程:

         for_each_subsys(cgrp->root,ss) {
                   if(ss->populate && (err = ss->populate(ss, cgrp)) < 0)
                            returnerr;
         }

通过把for_each_subsys展开后,我们得到如下C嵌入函数:

function get_populate_addr:long(arg:long)%{ /* pure */ /* unprivileged */
   struct cgroup *cgrp = (struct cgroup *)(THIS->arg);
    struct cgroup_subsys *ss;
   list_for_each_entry(ss, &cgrp->root->subsys_list, sibling) {
       if (ss->populate)
           THIS->__retvalue = (long)ss->populate;
       else
           THIS->__retvalue = 0;
    }
%}

因为我们只mount一个blkio子系统,所以这个list_for_each_entry也只会执行一次,开始执行sudo stap –v –g cgroup.tap,输出:populate_addr=0xffffffff81245fd0,然后再到System.map查找,果然有:

        大功告成?最后我把这个函数名blkiocg_populate拿到代码里一搜,擦!竟然没有。什么情况?然后就到网上一搜http://lxr.linux.no/linux,在2.6.32-60上确实没有,最后在2.6.33里找到了。我下的代码也是2.6.32-60,我测试的kernel是2.6.32-220.23.1.tb704.el6.x86_64。第二天找公司的同事找到测试kernel的代码,总算确认到。也就找到了blkio_subsys这个静态全局变量。教训:代码一定要与测试程序一致!

 附:cgroup.stp
#!/usr/bin/stap                                                                                                                                                             
%{
#include 
#include 
#include 
struct cgroupfs_root {
    struct super_block *sb;
    unsigned long subsys_bits;
    int hierarchy_id;
    unsigned long actual_subsys_bits;
    struct list_head subsys_list;
    struct cgroup top_cgroup;
    int number_of_cgroups;
    struct list_head root_list;
    unsigned long flags;
    char release_agent_path[4096];
    char name[64];
};
struct cgroup_sb_opts {
    unsigned long subsys_bits;
    unsigned long flags;
    char *release_agent;
    char *name;
    bool none;
    struct cgroupfs_root *new_root;
};
%}
 
function root_name:long(arg:long) %{ /* pure */ /* unprivileged */
    struct cgroup_sb_opts *opts = (struct cgroup_sb_opts *)(THIS->arg);
    THIS->__retvalue = opts->subsys_bits;
%}
 
function get_populate_addr:long(arg:long) %{ /* pure */ /* unprivileged */
    struct cgroup *cgrp = (struct cgroup *)(THIS->arg);
    struct cgroup_subsys *ss;
    list_for_each_entry(ss, &cgrp->root->subsys_list, sibling) {
        if (ss->populate)
            THIS->__retvalue = (long)ss->populate;
        else
            THIS->__retvalue = 0;
    }
%}
function proc:string() {
    return sprintf("%d (%s)", pid(), execname())
}
 
probe begin {
    printf("starting...")
}
 
probe kernel.function("cpu_cgroup_populate") {
    printf("%s cpu_cgroup_populate call trace:\n", proc());
    print_backtrace();
}
 
probe kernel.function("cgroup_root_from_opts") {
    printf("%s cgroup_root_from_opts subsys bit= %u\n", proc(), root_name($opts));
    print_backtrace();
}
 
probe kernel.function("cgroup_populate_dir") {
    //printf("addr=%p, r12= %p, rax=%p, populate_addr=%p\n", addr(), register("r12"), register("rax"), kernel_long(register("rax")));
    printf("populate_addr=%p\n", get_populate_addr($cgrp));
    print_backtrace();
}
 
probe kernel.function(0xffffffff810c0078) {
 printf("probe addr=%p, r12= %p, rax=%p\n", addr(), register("r12"), register("rax"));
 print_backtrace();
}
 
probe kernel.function("cgroup_test_super").return {
    printf("cgroup_test_super ret:%u\n", $return);
}


参考:

/usr/share/doc/systemtap-1.6/examples

你可能感兴趣的:(linux基础,工具)