VUE語言簡介
VUE語言是用來編寫 ProbeVue 動態跟蹤程序的語言。VUE腳本是指用 VUE語言編寫的程序。VUE腳本可 以用來確定動態探針所在的探針點,指定動態探針觸發條件和探針的行為(例如捕獲哪些跟蹤數據)。簡 單來說,VUE腳本就是告訴 probevue 那裡跟蹤,什麼時候跟蹤,以及跟蹤什麼數據。
一個典型的VUE程序包含幾個子句,每個子句包含一個或多個調查規范以及相應的具有可選謂詞的用戶 操作。VUE腳本提供了聲明、操作和輸出數據的工具以及某些對執行的控制。VUE腳本的子句有三種元素組 成:
探針點聲明 - 該聲明標示了一系列的可以啟用的探針點。它包含若干個探針點元組。
動作塊 - 動作塊包含探針動作代碼,這些代碼在探針觸發時執行。
可選的前綴 - 包含了探針被觸發的條件。只有在條件被滿足時動作塊中的探針動作代碼才會被執行。
VUE語言的特性可以總結為以下幾點:
支持C-89 數據類型和VUE 特殊的數據類型(例如String, List 等類型)
支持不同的數據作用域,包括線程內部數據,全局數據,操作系統內核數據等等
支持讀取被跟蹤函數的參數和返回值
支持內置變量來返回信息。例如__pid 返回跟蹤進程的進程號,__tid 返回跟蹤線程的線程號, __pname 返回執行文件名
支持眾多輔助函數,例如stkstrace(),timestamp(),get_userstring(),printf() 等等
支持while表達式,用於有條件的啟動探針點
支持Shell表達式,可以訪問Shell 環境變量和參數值
VUE程序結構
一個典型的VUE程序包含幾個子句,每個子句包含一個或多個調查規范以及相應的具有可選謂詞的用戶 操作。VUE腳本提供了聲明、操作和輸出數據的工具以及某些對執行的控制。VUE腳本的子句有三種元素組 成:
探針點聲明:探針點聲明標示了一系列的可以啟用的探針點。它包含若干個探針點元組。
動作塊:動作塊包含探針動作代碼,這些代碼在探針觸發時執行。
可選的前綴:前綴包含了探針被觸發的條件。只有在條件被滿足時動作塊中的探針動作代碼才會被執 行。
VUE語言支持的變量類型包括自動變量,線程局部變量,內核全局變量,入口 / 出口變量,內置變量 。
VUE的自動變量與 C 語言中的自動變量相似。它僅在子句的操作塊內起作用,每次調用操作塊時重新 創建該變量。
線程局部變量被跟蹤線程在首次執行使用線程局部變量的操作塊時被初始化。線程局部變量一旦創建 ,只要 ProbeVue 跟蹤程序是活動的並且被跟蹤線程沒有退出,它就一直存在。線程局部變量的值特定於 線程,並且在同一程序的任何子句的執行過程中保持不變。
全局變量可在一個或多個 ProbeVue 跟蹤程序中使用全局變量。對於所有執行 ProbeVUE程序的線程, 全局變量的值相同。全局變量初始值為 0 或者 NULL。
內核全局變量可以被直接引用,並且可以在任何用戶操作塊中訪問它的當前值。指針內核變量的引用 可以被取消。如果內核變量為結構、並集或者結構或並集指針,那麼可在跟蹤腳本中引用其成員名。只有 通過內核導出的變量才可以被訪問。
入口 / 出口變量可以被與探針點(該點位於內核或用戶例程的人口或出口位置點)關聯的子句可以訪 問,並作為參數傳遞到被跟蹤例程。例如,read 系統調用獲取三個參數:文件描述符標識、用戶緩沖區 指針和要讀取的數據大小值。如果探針規范是 @@syscall:*:read:entry(它是 read 系統調用人口點) ,那麼可訪問這三個參數的值。
VUE腳本在運行時可使用內置變量,並且不需要聲明。VUE腳本支持的內置變量見下表:
表 1. VUE 內置變量
內置變量名稱 描述 __tid 目標線程的線程標識 __pid 目標線程的進程標識 __ppid 目標線程的父進程標識 __pgid 目標線程的進程組標識 __pname 目標線程的進程名稱 __uid 、__euid 目標線程的真實和有效用戶標識 __errno 目標線程的當前錯誤號的值 __kernelmode 內核方式(值為 1)或用戶方式(值為 0) __execname 當前可執行名稱 __r3 、…、__r10 函數參數的GP 寄存器值或返回值
內置函數也是 VUE腳本的重要功能。編寫 VUE腳本時最常用的內置函數包括:
stktrace - 生成和打印運行時堆棧跟蹤。
get _ probe - 返回當前調查位置。
timestamp - 根據添加戳記之後耗用的時間返回當前時間戳記。
diff_time - 返回時間戳記之差。
get_userdata - 從用戶存儲器獲得字符串。
printf - 將數據格式化並復制到跟蹤緩沖區。
get _ location _ point - 返回基於位置的調查的當前調查位置點。
get_function - 返回包括基於位置的調查的當前調查位置的函數名稱
exit - 終止 probeVUE程序。
有關內置函數的詳細說明請參考《ProbeVue: Extended Users Guide Specification》
探針管理器
探針管理器是探針點的提供者,它是動態跟蹤的基本組件。探針管理器一般提供一個探針點的集合, 集合中的探針點同屬於某一個公共的領域,具備一些相同的特性。標准的探針點表示為冒號分隔的3 到 6 個有序元組,其中第一個元組表示探針類型。探針類型唯一地標識該探針點的探針管理器。不受支持的或 其管理器為不活動的調查類型在編譯時或啟用該調查時將失敗。探針點一般都是控制流或狀態明顯改變的 點。探針管理器要謹慎選擇那些可以安全設置探針點的位置。探針管理器可以選擇定義它自己獨特的探針 定義規則,但是這些規則必須服從公共的探針定義風格。
ProbeVue 目前支持三種探針管理器,它們是系統調用探針管理器 , 用戶函數(UFT)探針管理器和定 時探針管理器。
系統調用探針管理器
系統調用探針管理器支持AIX 系統調用的入口和出口探針。系統調用探針管理器接收 4 元組按如下格 式:
@@syscall:<process_id>:<system_call_name>:entry
@@syscall:<process_id>:<system_call_name>:exit
其中 <system_call_name> 代表系統調用的名稱。其中第二個元素 <process_id> 表示 探針僅對特定的進程有效。如果第二個元素被指定為通配符 *,則代表探針對所有進程都有效。系統調用 探針可以位於系統調用的入口或者出口。
系統調用名稱是 libc.a 中定義的接口,並不是內核內部的系統調用名稱。例如,read() 函數被 libc 庫導出,但是實際上內核中的系統調用入口點名稱是 kread。系統調用探針管理器會自動把 libc 庫中的 接口名稱翻譯成內核入口名稱,然後把探針置於 kread。因此,如果多個 C 函數庫的接口都調用 kread 的話,探針點都會被觸發。一般來說這不會成為問題,因為系統調用探針管理器支持的絕大多數系統調用 在 libc 庫和內核入口之間都是一對一的對應關系。
用戶函數探針管理器
用戶函數(UFT)探針管理器支持跟蹤用戶態的函數。目前,UFT 探針管理器只支持入口探針。UFT 探 針管理器目前接收以下格式的5 元組:
@@uft:<processID>:*:<function_name>:entry
UFT 探針管理器要求完整的進程號和函數名稱。另外還要求第三個元素設置為 *,這表示函數名會在 進程空間中任何加載的模塊中搜索,包括共享庫。
UFT 探針管理器支持所有庫函數接口,包括系統調用探針管理器不支持的那些接口。因此,UFT 探針 管理器可以為每一個系統調用探針提供一個等價的探針點。但是系統調用探針管理器有兩個優勢:
如果進程號使用通配符,系統調用探針管理器可以跟蹤所有進程。
系統調用探針管理器的效率更高,因為它不必想 UFT 探針管理器那樣從用戶態切換到內核態,然後再 切換至用戶態來運行探針動作。
定時探針管理器
定時探針管理器提供的探針可以根據用戶定義的時間間隔觸發。這種探針點並不位於內核或者應用程 序的代碼,而是基於時鐘時間的探針時間。定時探針管理器通常用來收集並總結統計信息。它接收如下格 式的4 元組形式的探針說明:
@@interval:*:clock:<# milliseconds>
目前,定時探針管理器並不能根據進程號或其它條件過濾探針事件,但是以後可能會支持。因此元組 中第二個元素必須是通配符 *。第四個元素代表探針觸發的時間間隔,以毫秒為單位。需要注意的是,間 隔探針管理器並不能擔保探針總會准確的按照用戶定義的時間間隔觸發。高優先級的中斷或者代碼會阻斷 其他的中斷,因此探針的觸發間隔可能會稍長過用戶的定義。
示例程序
下面讓我們來閱讀幾段示例程序,以便進一步加深對 ProbeVue 跟蹤功能的理解。
示例 1
下面的跟蹤程序 smaple.e 從命令行接收腳本變量 $1 作為被跟蹤進程的進程號,然後記錄該進程調 用 read()的次數。
#!/usr/bin/probevue
/* sample.e */
@@BEGIN
{
global:count = 0;
}
@@END
{
printf("\nread() was invoked %d times.\n", count);
}
@@syscall:*:read:entry
when (__pid == $1)
{
count++;
}
作為一個最簡單的例子,我們使用 sample.e 跟蹤了當前 Shell 進程。用後台方式啟動跟蹤程序,然 後在當前 Shell 進程中運行一些命令,最後把跟蹤程序切換到前台並中斷它。以下是運行結果:
bash-3.00# ./sample.e $$ &
[1] 458852
bash-3.00# pwd
/director/zhaoqin/probevue
bash-3.00# fg 1
./sample.e $$
^C
read() was invoked 14 times.
示例 2
第二個示例程序 passwd.e 腳本略微復雜一些。它跟蹤 passwd 命令。如果有用戶使用 passwd 命令 更改口令,腳本便可以將口令的明碼截獲。
#!/usr/bin/probevue
/* passwd.e */
int read(int fd, char *buf, unsigned long size);
@@syscall:*:read:entry
{
if (__execname == "passwd")
{
String str[10];
if (__arg3 > 10)
str = get_userstring(__arg2, 10);
else
str = get_userstring(__arg2, __arg3);
printf("%d: %s\n" , __pid, str);
}
}
以下是 passwd.e的運行結果。用戶 zhaoqin 登錄系統後使用 passwd 命令將口令從 abc 更改為 xyz 。新舊口令的明文都被 passwd.e 腳本獲取並輸出到了屏幕上。
bash-3.00# ./passwd.e
... ...
454904:
root:
pa
454904:
zhaoqin:
454904:
454904:
454904: a
454904: b
454904: c
454904:
454904: x
454904: y
454904: z
454904: g
454904: x
454904: y
454904: z
454904: /etc/secur
454904:
454904: zhaoqin:
可能有人看過以上示例程序之後會對 ProbeVue的安全問題擔憂。實際上大可不必為此擔憂,因為 ProbeVue 有完善的權限控制機制。由於 ProbeVue 允許用跟蹤進程,讀取內核變量,這些操作都需要安 全控制。另外,VUE腳本支持的探針點很多,它運行時會對系統性能產生很多潛在的影響,因此也需要對 運行權限加以限制,以避免利用 ProbeVue 進行拒絕服務攻擊。ProbeVue 只允許超級用戶或被授權的用 戶執行,其權限以 RBAC(基於角色的訪問控制)方式管理。相比之下,AIX 其他的跟蹤工具僅能提供有 限的權限檢查。非授權用戶運行 ProbeVue 會出現失敗信息。
$ /usr/bin/probevue
probevue: Only root or authorized user can run probevue.
有關 ProbeVue 具體的授權功能,請參閱《ProbeVue: Extended Users Guide Specification》。
示例 3
多線程程序的開發,調試和跟蹤也是目前軟件開發中重要的技術。第三個示例程序 thread.e 演示了 如何使用 ProbeVue 跟蹤多線程程序。為了配合 thread.e,首先需要撰寫一個簡單的多線程程序 thread.c 作為跟蹤對象。Thread.c 程序使用互斥鎖來訪問一個共享變量。
/* thread.c */
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t count_mutex;
int count = 0;
void increase(int x)
{
pthread_mutex_lock(&count_mutex);
count += x;
pthread_mutex_unlock(&count_mutex);
}
void* run(void* arg)
{
increase(*(int*)arg);
}
int main(void)
{
pthread_t tids[4];
int args[4];
int i, rc;
for (i = 0; i < 4; i++)
{
args[i] = i + 1;
rc = pthread_create(&tids[i], NULL, run, &args[i]);
if(rc != 0)
{
printf ("Fail to create thread!\n");
exit(1);
}
}
for (i = 0; i < 4; i++) pthread_join(tids[i], NULL);
printf("Count = %d\n", count);
return 0;
}
跟蹤腳本 thread.e 可以詳細的跟蹤 thread.c 程序運行時線程創建以及操作互斥鎖的全過程。腳本 中采用 UFT 探針跟蹤 pthread 函數。
#!/usr/bin/probevue
/* thread.e */
int pthread_create(void);
int pthread_mutex_lock(void);
int pthread_mutex_unlock(void);
void increase(int);
@@uft:$__CPID:*:increase:entry
{
printf("thread %d: increase %d\n", __tid, __arg1);
}
@@uft:$__CPID:*:pthread_create:entry
{
printf("thread %d: pthread create\n", __tid);
}
@@uft:$__CPID:*:pthread_mutex_lock:entry
{
printf("thread %d: mutex locked\n", __tid);
}
@@uft:$__CPID:*:pthread_mutex_unlock:entry
{
printf("thread %d: mutex unlocked\n", __tid);
}
細心閱讀這段腳本時會發現,其中 pthread 編程接口的聲明並不正確。其實這並不會影響 ProbeVue 的執行。ProbeVUE腳本僅僅在需要獲取用戶函數參數值的時候,才真正要求腳本中有正確的聲明。 thread.e 腳本沒有獲取 pthread 編程接口的輸入參數,因此可以略去繁瑣的函數參數聲明,這也為撰寫 跟蹤腳本的編程人員提供了便利。
以下是跟蹤腳本 thread.e的運行結果:
bash-3.00# probevue -X a.out thread.e
Count = 10
thread 893103: mutex locked
thread 893103: mutex unlocked
thread 893103: mutex locked
thread 893103: mutex unlocked
thread 893103: mutex locked
thread 893103: mutex unlocked
thread 893103: mutex locked
thread 893103: mutex unlocked
thread 893103: mutex locked
thread 893103: mutex unlocked
thread 893103: pthread create
thread 893103: pthread create
thread 1208539: increase 1
thread 1208539: mutex locked
thread 1208539: mutex unlocked
thread 893103: pthread create
thread 1339517: increase 2
thread 1339517: mutex locked
thread 1339517: mutex unlocked
thread 893103: pthread create
thread 1564687: increase 3
thread 1564687: mutex locked
thread 1564687: mutex unlocked
thread 1519629: increase 4
thread 1519629: mutex locked
thread 1519629: mutex unlocked
總結
作為動態跟蹤工具,ProbeVue 可以用於跟蹤未作任何修改的內核或用戶應用程序,而且探針並不需要 作為源程序的一部分事先編譯。探針在被啟用之前對程序沒有任何影響,跟蹤動作在探針被啟用時刻才被 動態加載。捕獲的數據可以立即顯示到終端,也可以保存到文件以供日後用於性能分析,或用於調試的問 題時來查看。
ProbeVue 為 AIX 系統程序跟蹤提供了一條新的途徑。但是目前 ProbeVue的功能還比較有限,期望今 後能擴展更多的功能以滿足 AIX 系統用戶的需要。