使用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 <linux/cgroup.h>
#include <linux/ctype.h>
#include <linux/list.h>
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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章