【技術乾貨】聽阿里雲CDN安防技術專家金九講SystemTap使用技巧

小資一夏發表於2017-08-22

1.簡介


     SystemTap是一個Linux非常有用的除錯(跟蹤/探測)工具,常用於Linux

     核心或者應用程式的資訊採集,比如:獲取一個函式里面執行時的變

     量、呼叫堆疊,甚至可以直接修改變數的值,對診斷效能或功能問題非

     常有幫助。SystemTap提供非常簡單的命令列介面和很簡潔的指令碼語

     言,以及非常豐富的tapset和例子。  


2.何時使用


    定位(核心)函式位置

    檢視函式被呼叫時的呼叫堆疊、區域性變數、引數

    檢視函式指標變數實際指的是哪個函式

    檢視程式碼的執行軌跡(哪些行被執行了)

    檢視核心或者程式的執行流程

    除錯記憶體洩露或者記憶體重複釋放

    統計函式呼叫次數

    ......


3.原理


在網上找了個原理圖:


![systemtap]()



SystemTap的處理流程有5個步驟:解析script檔案(parse)、細化(elaborate)、script檔案翻譯成C語言程式碼(translate)、編譯C語言程式碼(生成核心模組)(build)、載入核心模組(run)


![systemtap_phase]()



4.安裝


SystemTap依賴的package:

elfutils、gcc、kernel-devel、kernel-debuginfo

如果呼叫使用者態程式,還需要該程式有除錯符號,否則無法除錯。

推薦使用最新穩定版的SystemTap,目前最新穩定版為:systemtap-2.9.tar.gz


5.入門

5.1 stap命令

stap [OPTIONS] FILENAME [ARGUMENTS]
stap [OPTIONS] - [ARGUMENTS]
stap [OPTIONS] –e SCRIPT [ARGUMENTS]
比較常用和有用的引數:
-e SCRIPT               Run given script.
-l PROBE                List matching probes.
-L PROBE                List matching probes and local variables.
-g                      guru mode 
-D NM=VAL               emit macro definition into generated C code
-o FILE                 send script output to file, instead of stdout.
-x PID                  sets target() to PID




Hello World:

oot@j9 ~/stp# cat hello-world.stp
probe begin {
    print("===Hello World===\n")
}
probe end {
    print("===GunLe===\n")
}
root@j9 ~/stp# stap hello-world.stp 
===Hello World===
^C===GunLe===
root@j9 ~/stp# stap -e 'probe begin { printf("Hello World!\n") exit() }'   
Hello World!
root@j9 ~/stp#




5.2 staprun命令


staprun [OPTIONS] MODULE [MODULE-OPTIONS]



stap命令與staprun命令的區別在於:

stap命令的操作物件是stp檔案或script命令等,而staprun命令的操作物件是編譯生成的核心模組。


6.指令碼語言


6.1 probe


“probe” <=> “探測”, 是SystemTap進行具體地收集資料的關鍵字。

![systemtap_probe]()


“probe point” 是probe動作的時機,也稱探測點。也就是probe程式監視的某事件點,一旦偵測的事件觸發了,則probe將從此處插入核心或者使用者程式中。

“probe handle” 是當probe插入核心或者使用者程式後所做的具體動作。


probe用法:


probe probe-point { statement }



在Hello World例子中begin和end就是probe-point, statement就是該探測點的處理邏輯,在Hello World例子中statement只有一行print,statement可以是複雜的程式碼塊。

探測點語法:

kernel.function(PATTERN)
kernel.function(PATTERN).call
kernel.function(PATTERN).return
kernel.function(PATTERN).return.maxactive(VALUE)
kernel.function(PATTERN).inline
kernel.function(PATTERN).label(LPATTERN)
module(MPATTERN).function(PATTERN)
module(MPATTERN).function(PATTERN).call
module(MPATTERN).function(PATTERN).return.maxactive(VALUE)
module(MPATTERN).function(PATTERN).inline
kernel.statement(PATTERN)
kernel.statement(ADDRESS).absolute
module(MPATTERN).statement(PATTERN)
process(PROCESSPATH).function(PATTERN)
process(PROCESSPATH).function(PATTERN).call
process(PROCESSPATH).function(PATTERN).return
process(PROCESSPATH).function(PATTERN).inline
process(PROCESSPATH).statement(PATTERN)



PATTERN語法為:


func[@file]
func@file:linenumber

 


例如:


kernel.function("*init*")
module("ext3").function("*")
kernel.statement("*@kernel/time.c:296")
process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request")



在return探測點可以用\$return獲取該函式的返回值。

inline函式無法安裝.return探測點,也無法用$return獲取其返回值。


6.2 基本語法


SystemTap指令碼語法比較簡單,與C語言類似,只是每一行結尾";"是可選的。主要語句如下:

if/else、while、for/foreach、break/continue、return、next、delete、try/catch

其中:

next:主要在probe探測點邏輯處理中使用,呼叫此語句時,立刻從呼叫函式中退出。不同於exit()的是,next只是退出當前的呼叫函式,而此SystemTap並沒有終了,但exit()則會終止SystemTap。


6.2.1 變數


不需要明確宣告變數型別,指令碼語言會根據函式引數等自動判斷變數是什麼型別的。

區域性變數:在宣告的probe和block(”{ }“範圍內的部分)內有效。

全域性變數:用”global“宣告的變數,在此SystemTap的整個動作過程中都有效。全域性變數的宣告位置沒有具體要求。需要注意的是,全域性變數預設有鎖保護,使用過多會有效能損失,如果用全域性變數儲存指標,可能出現指標所指的內容被程式修改,在探測點中拿不到真正的資料。

獲取程式中的變數(全域性變數、區域性變數、引數)直接在變數名前面加$即可(後面會有例子)


6.2.2 註釋



# ...... : Shell語言風格    
//...... : C++語言風格    
 /*......*/ : C語言風格




6.2.3 運算子


比較運算子、算數運算子基本上與C語言一樣,需要特別指出的是:

(1)、.運算子:連線兩個字串,類似於php;

(2)、=~和!~:正則匹配和正則不匹配;


6.2.4 函式


函式定義例子:

function indent:string (delta:long){
  return _generic_indent(-1, "",  delta)
}
function _generic_indent (idx, desc, delta)
{
  ts = __indent_timestamp ()
  if (! _indent_counters[idx]) _indent_timestamps[idx] = ts
  depth = _generic_indent_depth(idx, delta)
  return sprintf("%6d (%d:%d) %s:%-*s", (ts - _indent_timestamps[idx]), depth, delta, desc, depth, "")
}  
function strlen:long(s:string) %{
    STAP_RETURN(strlen(STAP_ARG_s));
%}



官方有很多很有用的函式,詳情請參考:

以及在本機安裝了SystemTap之後在目錄/usr/local/share/systemtap/tapset/下也可以看具體函式的實現以及一些奇特的用法。



7.技巧


7.1 定位函式位置


在一個大型專案中找出函式在哪裡定義有時很有用,特別是一些比較難找出在哪裡定義的函式,比如核心或者glibc中的某個函式想要看其實現時,首先得找出其在哪個檔案的哪一行定義,用SystemTap一行命令就可以搞定。

比如要看printf在glibc中哪裡定義的:


root@j9 ~# stap -l 'process("/lib/x86_64-linux-gnu/libc.so.6").function("printf")' 
process("/lib/x86_64-linux-gnu/libc-2.15.so").function("__printf@/build/buildd/eglibc-2.15/stdio-common/printf.c:29")



可以看出printf是在printf.c第29行定義的。

再比如要看核心中recv系統的呼叫是在哪裡定義的:

root@j9 ~# stap -l 'kernel.function("sys_recv")'
kernel.function("sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/socket.c:1868")



可以看出recv是在socket.c第1868行定義的。

甚至可以*號來模糊查詢:


root@j9 ~# stap -l 'kernel.function("*recv")'   
kernel.function("__audit_mq_sendrecv@/build/buildd/linux-lts-trusty-3.13.0/kernel/auditsc.c:2062")
kernel.function("audit_mq_sendrecv@/build/buildd/linux-lts-trusty-3.13.0/include/linux/audit.h:263")
kernel.function("compat_sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/compat.c:762")
kernel.function("i2c_master_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/i2c/i2c-core.c:1827")
kernel.function("ip_cmsg_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/ip_sockglue.c:147")
kernel.function("kgdb_tty_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/tty/serial/kgdb_nmi.c:109")
kernel.function("ppp_do_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/net/ppp/ppp_generic.c:1617")
kernel.function("scm_recv@/build/buildd/linux-lts-trusty-3.13.0/include/net/scm.h:109")
kernel.function("sys_recv@/build/buildd/linux-lts-trusty-3.13.0/net/socket.c:1868")
kernel.function("tcp_event_data_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/tcp_input.c:615")
kernel.function("tcp_splice_data_recv@/build/buildd/linux-lts-trusty-3.13.0/net/ipv4/tcp.c:637")
kernel.function("tpm_tis_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/char/tpm/tpm_tis.c:231")
kernel.function("try_fill_recv@/build/buildd/linux-lts-trusty-3.13.0/drivers/net/virtio_net.c:615")



同理,也可以用來定位使用者程式的函式位置:

比如tengine的檔案ngx_shmem.c裡面為了相容各個作業系統而實現了三個版本的ngx_shm_alloc,用#if (NGX_HAVE_MAP_ANON)、#elif (NGX_HAVE_MAP_DEVZERO)、#elif (NGX_HAVE_SYSVSHM)、#endif來做條件編譯,那怎麼知道編譯出來的是哪個版本呢,用SystemTap的話就很簡單了,否則要去grep一下這幾宏有沒有定義才知道了。


[root@cache4 tengine]# stap -l 'process("/home/admin/tengine/bin/nginx").function("ngx_shm_alloc")'
process("/home/admin/tengine/bin/nginx").function("ngx_shm_alloc@src/os/unix/ngx_shmem.c:15")

  



7.2 檢視可用探測點以及該探測點上可用的變數


在一些探測點上能獲取的變數比較有限,這是因為這些變數可能已經被編譯器最佳化掉了,最佳化掉的變數就獲取不到了。一般先用-L引數來看看有哪些變數可以直接使用:


[root@cache4 tengine]# stap -L 'process("/home/admin/tengine/bin/nginx").function("ngx_shm_alloc")' 
process("/home/admin/tengine/bin/nginx").function("ngx_shm_alloc@src/os/unix/ngx_shmem.c:15") $shm:ngx_shm_t*




可見在該探測點上可以直接使用$shm這個變數,其型別是ngx_shm_t*。

statement探測點也類似:


[root@cache4 tengine]# stap -L 'process("/home/admin/tengine/bin/nginx").statement("ngx_pcalloc@src/core/ngx_palloc.c:*")'                   
process("/home/admin/tengine/bin/nginx").statement("ngx_pcalloc@src/core/ngx_palloc.c:395") $pool:ngx_pool_t* $size:size_t
process("/home/admin/tengine/bin/nginx").statement("ngx_pcalloc@src/core/ngx_palloc.c:398") $pool:ngx_pool_t* $size:size_t
process("/home/admin/tengine/bin/nginx").statement("ngx_pcalloc@src/core/ngx_palloc.c:399") $size:size_t
process("/home/admin/tengine/bin/nginx").statement("ngx_pcalloc@src/core/ngx_palloc.c:404") $size:size_t $p:void*




7.3 輸出呼叫堆疊


使用者態探測點堆疊:print_ubacktrace()、sprint_ubacktrace()

核心態探測點堆疊:print_backtrace()、sprint_backtrace()

不帶s和帶s的區別是前者直接輸出,後者是返回堆疊字串。

這幾個函式非常有用,在排查問題時可以根據一些特定條件來過濾函式被執行時是怎麼呼叫進來的,比如排查tengine返回5xx時的呼叫堆疊是怎樣的:


#cat debug_tengine_5xx.stp 
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_finalize_request").call {
    if ($rc >= 500) {
        printf("rc: %d\n", $rc)
        print_ubacktrace()
    }
}
#stap debug_tengine_5xx.stp 
rc: 502
 0x49af2e : ngx_http_finalize_request+0xe/0x480 [/home/admin/tengine/bin/nginx]
 0x543305 : ngx_http_video_flv_send_rest+0xf5/0x380 [/home/admin/tengine/bin/nginx]
 0x543187 : ngx_http_video_finalize_request+0x57/0xe0 [/home/admin/tengine/bin/nginx]
 0x49828f : ngx_http_terminate_request+0x4f/0xc0 [/home/admin/tengine/bin/nginx]
 0x49b760 : ngx_http_test_reading+0x50/0x130 [/home/admin/tengine/bin/nginx]
 0x49779f : ngx_http_request_handler+0x1f/0x40 [/home/admin/tengine/bin/nginx]
 0x47ea8f : ngx_epoll_process_events+0x2df/0x330 [/home/admin/tengine/bin/nginx]
 0x4753f9 : ngx_process_events_and_timers+0x69/0x1c0 [/home/admin/tengine/bin/nginx]
 0x47d4d8 : ngx_worker_process_cycle+0x138/0x260 [/home/admin/tengine/bin/nginx]
 0x47a38a : ngx_spawn_process+0x1ca/0x5e0 [/home/admin/tengine/bin/nginx]
 0x47c73c : ngx_start_worker_processes+0x7c/0x100 [/home/admin/tengine/bin/nginx]
 0x47db5f : ngx_master_process_cycle+0x3af/0x9b0 [/home/admin/tengine/bin/nginx]
 0x45a740 : main+0xa90/0xb50 [/home/admin/tengine/bin/nginx]
 0x3623e1ecdd [/lib64/libc-2.12.so+0x1ecdd/0x38d000]




比如看看核心是怎麼收包的:



root@jusse ~# cat netif_receive_skb.stp 
probe kernel.function("netif_receive_skb") 
{ 
    printf("--------------------------------------------------------\n"); 
    print_backtrace(); 
    printf("--------------------------------------------------------\n"); 
} 
root@jusse ~# 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.4 獲取函式引數


一些被編譯器最佳化掉的函式引數用-L去看的時候沒有找到,這樣的話在探測點裡面也不能直接用$方式獲取該引數變數,這時可以使用SystemTap提供的*_arg函式介面,*是根據型別指定的,比如pointer_arg是獲取指標型別引數,int_arg是獲取整型引數,類似的還有long_arg、longlong_arg、uint_arg、ulong_arg、ulonglong_arg、s32_arg、s64_arg、u32_arg、u64_arg:


![image]()


root@j9 ~# 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@j9 ~# cat sys_open.stp 
probe kernel.function("sys_open").call
{
    printf("filename: %p(%s), flags: %d, mode: %x\n", pointer_arg(1), kernel_string(pointer_arg(1)), int_arg(2), int_arg(3));
}
root@j9 ~# stap sys_open.stp 
filename: 0xc2081d2120(/proc/stat), flags: 524288, mode: 0
filename: 0x7facec00e838(/root/opt/libexec/systemtap/stapio), flags: 0, mode: 1b6
filename: 0x2219488(/var/log/auth.log), flags: 0, mode: 1b6
filename: 0x7facec00e838(/root/opt/libexec/systemtap/stapio), flags: 0, mode: 1b6
filename: 0x7fad10172c29(/etc/passwd), flags: 524288, mode: 1b6
^C



再比如兩個函式的函式引數型別相容也可以使用這種方法獲取:

![image]()


這兩個函式的引數完全相容,只是第二個引數命名不一樣而已,可以像下面這麼用:


#cat debug_tengine_5xx.stp 
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_finalize_request").call, process("/home/admin/tengine/bin/nginx").function("ngx_http_special_response_handler").call {
    rc = int_arg(2)
    if (rc >= 500) {
        printf("rc: %d\n", rc)
        print_ubacktrace()
    }
}



7.5 獲取全域性變數


有時候用$可以直接獲取到全域性變數,但有時候又獲取不到,那可以試試@var:

比如獲取nginx的全域性變數ngx_cycyle:


root@j9 ~# cat get_ngx_cycle.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_process_events_and_timers").call {
    printf("ngx_cycle->connections: %d\n", $ngx_cycle->connections)
    exit()
}
root@j9 ~# stap get_ngx_cycle.stp
semantic error: while processing probe process("/home/admin/tengine/bin/nginx").function("ngx_process_events_and_timers@src/event/ngx_event.c:225").call from: process("/home/admin/tengine/bin/nginx").function("ngx_process_events_and_timers").call
semantic error: unable to find local 'ngx_cycle', [man error::dwarf] dieoffset 0x73ca8 in /home/admin/tengine/bin/nginx, near pc 0x434152 in ngx_process_events_and_timers src/event/ngx_event.c (alternatives: $cycle, $delta, $timer, $flags)): identifier '$ngx_cycle' at get_ngx_cycle.stp:3:44
        source:     printf("ngx_cycle->connections: %d\n", $ngx_cycle->connections)
                                                           ^
Pass 2: analysis failed.  [man error::pass2]
root@j9 ~# cat get_ngx_cycle.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_process_events_and_timers").call {
    ngx_cycle = @var("ngx_cycle@src/core/ngx_cycle.c")
    printf("ngx_cycle->connections: %d\n", ngx_cycle->connections)
    exit()
}
root@j9 ~# stap get_ngx_cycle.stp
ngx_cycle->connections: 19507312



7.6 獲取資料結構成員用法


typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;
struct ngx_http_request_s {
    ......
    ngx_uint_t                        method;
    ngx_uint_t                        http_version;
    ngx_str_t                         request_line;
    ngx_str_t                         raw_uri;
    ngx_str_t                         uri;
    ......
};




上面這個是nginx裡面的http請求結構裡面的幾個成員,在C語言裡,如果r是struct ngx_http_request_t *,那麼要獲取uri的data是這樣的:r->uri.data,但在SystemTap裡面,不管是指標還是資料結構,都是用->訪問其成員:


#cat get_http_uri.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request").call {
    printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri.len, $r->uri.data)
}
#stap get_http_uri.stp
WARNING: never-assigned local variable 'len' (similar: data): identifier 'len' at get_http_uri.stp:2:57
 source:     printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri.len, $r->uri.data)
                                                                 ^
WARNING: never-assigned local variable 'data' (similar: len): identifier 'data' at :2:70
 source:     printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri.len, $r->uri.data)
                                                                              ^
semantic error: invalid operator: operator '.' at :2:56
        source:     printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri.len, $r->uri.data)
                                                                       ^
semantic error: type mismatch: expected long but found string: operator '.' at :2:56
        source:     printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri.len, $r->uri.data)
                                                                       ^
Pass 2: analysis failed.  [man error::pass2]
#cat get_http_uri.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request").call {
    printf("r->uri.len: %d, r->uri.data: %p\n", $r->uri->len, $r->uri->data)
}
#stap get_http_uri.stp
r->uri.len: 1, r->uri.data: 0x1276f94
r->uri.len: 1, r->uri.data: 0x11d5fc4
r->uri.len: 1, r->uri.data: 0x124fd24
^C



7.7 輸出整個資料結構


SystemTap有兩個語法可以輸出整個資料結構:在變數的後面加一個或者兩個


$即可,例子如下:


#cat get_r_pool.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request").call {
    printf("$r->pool$: %s\n$r->pool$$: %s\n", $r->pool$, $r->pool$$)
}
#stap get_r_pool.stp
$r->pool$: {.d={...}, .max=4016, .current=0x161acd0, .chain=0x0, .large=0x0, .cleanup=0x0, .log=0x161c690}
$r->pool$$: {.d={.last="a", .end="", .next=0x1617650, .failed=0}, .max=4016, .current=0x161acd0, .chain=0x0, .large=0x0, .cleanup=0x0, .log=0x161c690}

  



其中r->pool的結構如下:


typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;
    size_t                max;
    ngx_pool_t           *current;
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
#if  (NGX_DEBUG_POOL)
    size_t                size;
    ngx_pool_stat_t      *stat;
#endif
};



ngx_pool_s包含了結構ngx_pool_data_t。變數後面加和$的區別是後者展開了裡面的結構而前者不展開,此用法只輸出基本資料型別的值。


7.8 輸出字串指標


使用者態使用:user_string、user_string_n

核心態使用:kernel_string、kernel_string_n、user_string_quoted

#cat get_http_uri.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request").call {
    printf("r->uri: %s\nr->uri(n): %s\n", user_string($r->uri->data), user_string_n($r->uri->data, $r->uri->len))
}
#stap get_http_uri.stp
r->uri: /?id=1 HTTP/1.1
User-Agent
r->uri(n): /

 


user_string_quoted是獲取使用者態傳給核心的字串,程式碼中一般有__user宏標記:

![image]()


#cat sys_open.stp
probe kernel.function("sys_open")
{
    printf("filename: %s\n", user_string_quoted(pointer_arg(1)));
}
#stap sys_open.stp 
filename: "/var/log/auth.log"
filename: "/proc/stat"
filename: "/proc/uptime"



7.9 指標型別轉換


SystemTap提供@cast來實現指標型別轉換,比如可以將void *轉成自己需要的型別:

![image]()

![image]()

#cat get_c_fd.stp 
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request_line").call {
    printf("c->fd: %d\n", @cast($rev->data, "ngx_connection_t")->fd)
}
#stap get_c_fd.stp 
c->fd: 3
c->fd: 28
c->fd: 30
c->fd: 32
c->fd: 34
^C




7.10 定義某個型別的變數


同樣是用@cast,定義一個變數用來儲存其轉換後的地址即可,用法如下:


#cat get_c.stp 
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request_line").call {
    c = &@cast($rev->data, "ngx_connection_t")
    printf("c->fd: %d, c->requests: %d\n", c->fd, c->requests)
}
#stap get_c.stp 
c->fd: 3, c->requests: 1
c->fd: 28, c->requests: 1
c->fd: 30, c->requests: 1
^C




7.11 多級指標用法


root@j9 ~# cat cc_multi_pointer.c
#include <stdio.h>
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@j9 ~# gcc -Wall -g -o cc_multi_pointer ./cc_multi_pointer.c
root@j9 ~# 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@j9 ~# ./cc_multi_pointer
t.count: 5566, pt->count: 5566, ppt->count: 5566
root@j9 ~# 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




簡言之:透過[0]去解引用即可。


7.12 遍歷C語言陣列


下面是在nginx處理請求關閉時遍歷請求頭的例子:


#cat debug_http_header.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_finalize_request").call {
    i = 0
    headers_in_part = &$r->headers_in->headers->part
    headers = &@cast(headers_in_part->elts, "ngx_table_elt_t")[0]
    while (headers) {
        if (i >= headers_in_part->nelts) {
            if (!headers_in_part->next) {
                break
            }
            headers_in_part = headers_in_part->next;
            headers = &@cast(headers_in_part->elts, "ngx_table_elt_t")[0]
            i = 0
        }
        h = &@cast(headers, "ngx_table_elt_t")[i]
        printf("%s: %s\n", user_string_n(h->key->data, h->key->len), user_string_n(h->value->data, h->value->len))
        i += 1
    }
}
#stap debug_http_header.stp
User-Agent: curl/7.29.0
Host: 127.0.0.1:20090
Accept: */*

 



7.13 檢視函式指標所指的函式名


獲取一個地址所對應的符號:

使用者態:usymname

核心態:symname


#cat get_c_handler.stp
probe process("/home/admin/tengine/bin/nginx").function("ngx_http_process_request_line").call {
    c = &@cast($rev->data, "ngx_connection_t")
    printf("c->read->handlers: %s, c->write->handler: %s\n", usymname(c->read->handler), usymname(c->write->handler))
}
#stap get_c_handler.stp
c->read->handlers: ngx_http_process_request_line, c->write->handler: ngx_http_empty_handler
^C

 


7.14 修改程式中的變數


root@j9 ~# cat stap_set_var.c -n     
     1  #include <stdio.h>
     2
     3  typedef struct policy {
     4      int     id;
     5  } policy_t;
     6
     7  int main(int argc, char *argv[])
     8  {
     9      policy_t policy;
    10      policy_t *p = &policy;
    11      policy_t **pp;
    12
    13      p->id = 111;
    14
    15      printf("before stap set, p->id: %d\n", p->id);
    16
    17      pp = &p;
    18
    19      printf("after stap set, p->id: %d, (*pp)->id: %d\n", p->id, (*pp)->id);
    20
    21      return 0;
    22  }
root@j9 ~# gcc -Wall -g -o ./stap_set_var ./stap_set_var.c      
root@j9 ~# cat stap_set_var.stp
probe process("./stap_set_var").statement("main@./stap_set_var.c:17")
{
    $p->id = 222;
    printf("$p$: %s\n", $p$)
}
root@j9 ~# stap -g stap_set_var.stp -c ./stap_set_var         
before stap set, p->id: 111
after stap set, p->id: 222, (*pp)->id: 222
$p$: {.id=222}
root@j9 ~#



可以看出在第17行用SystemTap修改後的值在第19行就生效了。

需要注意的是stap要加-g引數在guru模式下才能修改變數的值。


7.15 跟蹤程式執行流程


thread_indent(n): 補充空格

ppfunc(): 當前探測點所在的函式

在call探測點呼叫thread_indent(4)補充4個空格,在return探測點呼叫thread_indent(-4)回退4個空格,效果如下:


#cat trace_nginx.stp
probe process("/home/admin/tengine/bin/nginx").function("*@src/http/ngx_http_*").call
{
    printf("%s -> %s\n", thread_indent(4), ppfunc());
}
probe process("/home/admin/tengine/bin/nginx").function("*@src/http/ngx_http_*").return
{
    printf("%s <- %s\n", thread_indent(-4), ppfunc());
}
#stap trace_nginx.stp
     0 nginx(11368):    -> ngx_http_init_connection
    21 nginx(11368):    <- ngx_http_init_connection
     0 nginx(11368):    -> ngx_http_wait_request_handler
    30 nginx(11368):        -> ngx_http_create_request
    41 nginx(11368):        <- ngx_http_create_request
    55 nginx(11368):        -> ngx_http_process_request_line
    72 nginx(11368):            -> ngx_http_read_request_header
    78 nginx(11368):            <- ngx_http_read_request_header
    91 nginx(11368):            -> ngx_http_parse_request_line
    99 nginx(11368):            <- ngx_http_parse_request_line
   109 nginx(11368):            -> ngx_http_process_request_uri
   115 nginx(11368):            <- ngx_http_process_request_uri
   127 nginx(11368):            -> ngx_http_process_request_headers
   138 nginx(11368):                -> ngx_http_read_request_header
   143 nginx(11368):                <- ngx_http_read_request_header
   155 nginx(11368):                -> ngx_http_parse_header_line
   163 nginx(11368):                <- ngx_http_parse_header_line
   178 nginx(11368):                -> ngx_http_process_user_agent
   185 nginx(11368):                <- ngx_http_process_user_agent
   192 nginx(11368):                -> ngx_http_parse_header_line
   198 nginx(11368):                <- ngx_http_parse_header_line
   208 nginx(11368):                -> ngx_http_process_host
   222 nginx(11368):                    -> ngx_http_validate_host
   229 nginx(11368):                    <- ngx_http_validate_host
   239 nginx(11368):                    -> ngx_http_set_virtual_server
   252 nginx(11368):                        -> ngx_http_find_virtual_server
   259 nginx(11368):                        <- ngx_http_find_virtual_server
   263 nginx(11368):                    <- ngx_http_set_virtual_server
   266 nginx(11368):                <- ngx_http_process_host
   274 nginx(11368):                -> ngx_http_parse_header_line
   279 nginx(11368):                <- ngx_http_parse_header_line
   287 nginx(11368):                -> ngx_http_parse_header_line
   292 nginx(11368):                <- ngx_http_parse_header_line
   .....
  2072 nginx(11368):                                <- ngx_http_finalize_request
  2076 nginx(11368):                            <- ngx_http_core_content_phase
  2079 nginx(11368):                        <- ngx_http_core_run_phases
  2083 nginx(11368):                    <- ngx_http_handler
  2093 nginx(11368):                    -> ngx_http_run_posted_requests
  2100 nginx(11368):                    <- ngx_http_run_posted_requests
  2103 nginx(11368):                <- ngx_http_process_request
  2107 nginx(11368):            <- ngx_http_process_request_headers
  2111 nginx(11368):        <- ngx_http_process_request_line
  2114 nginx(11368):    <- ngx_http_wait_request_handler
     0 nginx(11368):    -> ngx_http_keepalive_handler
    26 nginx(11368):        -> ngx_http_close_connection
    79 nginx(11368):        <- ngx_http_close_connection
    83 nginx(11368):    <- ngx_http_keepalive_handler



7.16 檢視程式碼執行路徑


pp(): 輸出當前被啟用的探測點


#cat ngx_http_process_request.stp
probe process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:*") {
    printf("%s\n", pp())
}
#stap ngx_http_process_request.stp 
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2762")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2768")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2771")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2773")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2774")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2783")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2835")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2840")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2841")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2842")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2843")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2846")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2847")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2848")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2850")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2852")
process("/home/admin/tengine/bin/nginx").statement("ngx_http_process_request@src/http/ngx_http_request.c:2853")
^C



可以看出該函式哪些行被執行了。


7.17 巧用正則匹配過濾


在排查問題時,可以利用一些正則匹配來獲取自己想要的資訊,比如下面是隻收集*.j9.com的堆疊:


#
cat debug_tengine_5xx.stp 
probe process("/home/admin/tengine/bin/t-coresystem-tengine-cdn").function("ngx_http_finalize_request").call {
    rc = $rc
    if (rc < 0) {
        host = "(null)"
        if ($r->headers_in->server->len != 0) {
            host = user_string_n($r->headers_in->server->data, $r->headers_in->server->len)
        } else {
            cscf = &@cast($r->srv_conf, "ngx_http_core_srv_conf_t")[@var("ngx_http_core_module@src/http/ngx_http_core_module.c")->ctx_index]
            if (cscf->server_name->len != 0) {
                 host = user_string_n(cscf->server_name->data, cscf->server_name->len)
            }
        }
        if (host =~ ".*\.j9\.com") {
            printf("rc: %d, host: %s\n", rc, host)
            print_ubacktrace()
        }
    }
}
#stap debug_tengine_5xx.stp
WARNING: Missing unwind data for module, rerun with 'stap -d /lib64/libc-2.12.so'
rc: -4, host: 
 0x49af2e : ngx_http_finalize_request+0xe/0x480 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x492eab : ngx_http_core_content_phase+0x2b/0x130 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x48e74d : ngx_http_core_run_phases+0x3d/0x50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x514c3c : ngx_http_lua_socket_tcp_read+0x44c/0x590 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x513150 : ngx_http_lua_socket_tcp_handler+0x30/0x50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x475b96 : ngx_event_process_posted+0x36/0x40 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47d4d8 : ngx_worker_process_cycle+0x138/0x260 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47a38a : ngx_spawn_process+0x1ca/0x5e0 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47c73c : ngx_start_worker_processes+0x7c/0x100 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47db5f : ngx_master_process_cycle+0x3af/0x9b0 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x45a740 : main+0xa90/0xb50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x3623e1ecdd [/lib64/libc-2.12.so+0x1ecdd/0x38d000]
rc: -4, host: cdn.j9.com
 0x49af2e : ngx_http_finalize_request+0xe/0x480 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x492eab : ngx_http_core_content_phase+0x2b/0x130 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x48e74d : ngx_http_core_run_phases+0x3d/0x50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x514c3c : ngx_http_lua_socket_tcp_read+0x44c/0x590 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x513150 : ngx_http_lua_socket_tcp_handler+0x30/0x50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x475b96 : ngx_event_process_posted+0x36/0x40 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47d4d8 : ngx_worker_process_cycle+0x138/0x260 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47a38a : ngx_spawn_process+0x1ca/0x5e0 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47c73c : ngx_start_worker_processes+0x7c/0x100 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x47db5f : ngx_master_process_cycle+0x3af/0x9b0 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x45a740 : main+0xa90/0xb50 [/home/admin/tengine/bin/t-coresystem-tengine-cdn]
 0x3623e1ecdd [/lib64/libc-2.12.so+0x1ecdd/0x38d000]




7.18 關聯陣列用法


SystemTap的關聯陣列必須是全域性變數,需要用global進行宣告,其索引可以支援多達9項索引域,各域間以逗號隔開。支援 =, ++ 與 +=操作,其預設的初始值為0。

例如:

root@j9 ~# cat stap_array.stp 
global reads
probe vfs.read {
    reads[execname(), pid()] ++
}
probe timer.s(3) {
    foreach ([execname, pid] in reads) {
        printf("%s(%d) : %d \n", execname, pid, reads[execname, pid])
    }
    print("============================\n")
    delete reads
}
root@j9 ~# stap stap_array.stp 
stapio(18716) : 16 
rsyslogd(770) : 1 
docker(743) : 3 
IFSWatch(5594) : 30 
QThread(5594) : 6 
AliYunDunUpdate(1057) : 4 
sshd(15118) : 1 
sshd(15191) : 1 
============================
stapio(18716) : 16 
sshd(15191) : 3 
docker(743) : 3 
IFSWatch(5594) : 30 
sshd(15118) : 2 
QThread(5594) : 12 
AliYunDunUpdate(1057) : 8 
============================
^C
root@j9 ~/systemtap#



也可以用+、-進行排序:


root@j9 ~# cat stap_array.stp
global reads
probe vfs.read {
    reads[execname(), pid()] ++
}
probe timer.s(3) {
    foreach ([execname, pid+] in reads) {
        printf("%s(%d) : %d \n", execname, pid, reads[execname, pid])
    }
    print("============================\n")
    delete reads
}
root@j9 ~# stap stap_array.stp 
docker(743) : 3 
rsyslogd(770) : 1 
AliYunDunUpdate(1057) : 12 
IFSWatch(5594) : 30 
QThread(5594) : 12 
sshd(15118) : 2 
sshd(15191) : 2 
stapio(19021) : 16 
============================
docker(743) : 3 
AliYunDunUpdate(1057) : 12 
IFSWatch(5594) : 30 
QThread(5594) : 6 
sshd(15118) : 1 
sshd(15191) : 19 
stapio(19021) : 16 
============================
^C
root@j9 ~#




7.19 除錯記憶體洩漏以及記憶體重複釋放


probe begin {
    printf("=============begin============\n")
}
//記錄記憶體分配和釋放的計數關聯陣列
global g_mem_ref_tbl
//記錄記憶體分配和釋放的呼叫堆疊關聯陣列
global g_mem_bt_tbl
probe process("/lib/x86_64-linux-gnu/libc.so.6").function("__libc_malloc").return, process("/lib/x86_64-linux-gnu/libc.so.6").function("__libc_calloc").return {
    if (target() == pid()) {
        if (g_mem_ref_tbl[$return] == 0) {
            g_mem_ref_tbl[$return]++
            g_mem_bt_tbl[$return] = sprint_ubacktrace()
        }
    }
}
probe process("/lib/x86_64-linux-gnu/libc.so.6").function("__libc_free").call {
    if (target() == pid()) {
        g_mem_ref_tbl[$mem]--
        if (g_mem_ref_tbl[$mem] == 0) {
            if ($mem != 0) {
                //記錄上次釋放的呼叫堆疊
                g_mem_bt_tbl[$mem] = sprint_ubacktrace()
            }
        } else if (g_mem_ref_tbl[$mem] < 0 && $mem != 0) {
            //如果呼叫free已經失衡,那就出現了重複釋放記憶體的問題,這裡輸出當前呼叫堆疊,以及這個地址上次釋放的呼叫堆疊
            printf("MMMMMMMMMMMMMMMMMMMMMMMMMMMM\n")
            printf("g_mem_ref_tbl[%p]: %d\n", $mem, g_mem_ref_tbl[$mem])
            print_ubacktrace()
            printf("last free backtrace:\n%s\n", g_mem_bt_tbl[$mem])
            printf("WWWWWWWWWWWWWWWWWWWWWWWWWWWW\n")
        }
    }
}
probe end {
    //最後輸出產生洩漏的記憶體是在哪裡分配的
    printf("=============end============\n")
    foreach(mem in g_mem_ref_tbl) {
        if (g_mem_ref_tbl[mem] > 0) {
            printf("%s\n", g_mem_bt_tbl[mem])
        }
    }
}




詳細請看:http://blog.csdn.net/wangzuxi/article/details/44901285


7.20 嵌入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裡面。


7.21 除錯核心模組


這小節就不細講了,這篇部落格 ([http://blog.chinaunix.net/uid-14528823-id-4726046.html](http://blog.chinaunix.net/uid-14528823-id-4726046.html)) 寫得很詳細,這裡只copy兩個關鍵點過來記錄一下:

要除錯自己的核心模組,需要注意的有兩個關鍵點:

1)、使用SystemTap除錯核心模組,探測點的編寫格式示例為:

module("ext3").function("ext3_*")

2)、需要將自己的模組cp到/lib/modules/uname -r/extra目錄中,否則找不到符號,如果/lib/modules/uname -r/目錄下沒有extra這個目錄,自己mkdir一下就可以。


7.22 一些錯誤提示及解決辦法


錯誤提示1:


ERROR: MAXACTION exceeded near keyword at debug_connection.stp:86:9
ERROR: MAXACTION exceeded near operator '->' at debug_connection.stp:84:30

解決辦法:

加上stap引數:-DMAXACTION=102400,如果還報這種型別的錯誤,只需把102400調成更大的值即可。


錯誤提示2:


WARNING: Number of errors: 0, skipped probes: 82


解決辦法:

加上-DMAXSKIPPED=102400和-DSTP_NO_OVERLOAD引數


還有一些可以去掉限制的宏:


MAXSTRINGLEN:這個宏會影響sprintf的buffer大小,預設為512位元組。

MAXTRYLOCK:對全域性變數進行try lock操作的次數,超過則次數還拿不到鎖則放棄和跳過該探測點,預設值為1000.全域性變數多的時候可以把這個宏開大一點。


(完)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29779867/viewspace-2143961/,如需轉載,請註明出處,否則將追究法律責任。

相關文章