成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

揭開 Strace 命令捕獲系統調用的神秘面紗

系統 其他OS
strace 命令執行的過程中,會讓目標進程執行到系統調用時暫停運行,從而導致比較頻繁的上下文切換,會增加目標進程 的運行時間。所以,如果是在生產環境中,使用 strace 命令的時候還是要小心一點。更萬萬不可當成一個長期的線上監控工具來使用。

在性能觀測領域,strace 命令是一個雖然很古老,但很常用的命令。使用它我們可以非常方便地觀察某個進程正在執行什么系統調用。

這個命令的使用方式也很簡單,想觀察哪個進程,直接將其 pid 作為參數傳給 strace 命令即可。

# strace -p {pid}
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@k\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260A\2\0\0\0\0\0"..., 832) = 832
write(1, "anycast6   dev_snmp6\t if_inet6\ti"..., 137anycast6   dev_snmp6  if_inet6 ip6_mr_vif     ip_mr_vif        mcfilter   nf_conntrack        ptype  rt6_stats  sockstat      tcp6  unix
) = 137
......

然而我們都知道,正常來講操作系統中的各個進程之間是互相隔離的。那么 strace 命令是如何做到能獲取其他進程執行的系統調用信息的呢,我們今天就來揭開這個謎底。

一、手工實現一個 strace

要想理解清楚 strace 命令原理,我想最有效的辦法是我們自己親手寫一個簡單程序來模擬 strace 的工作過程。

為了方便大家理解,我這里只把這個程序的核心邏輯列出來。完整的程序源碼請大家查看strace配套源碼 https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/cpu/test11/main.c

int main(int argc, char *argv[]) {

 // 1.attach 到 pid 指定的目標進程上
 ptrace(PTRACE_ATTACH, pid, NULL, NULL)

 while (1) {
  // 2.等待目標進程的 PTRACE_SYSCALL
  // 2.1 指定要捕獲目標進程的 PTRACE_SYSCALL
  ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
  // 2.2 當目標進程有 SYSCALL 發生時醒來處理
  waitpid(pid, &status, 0)

  // 3.讀取并解析系統調用
  // 3.1 讀取目標進程正在執行的系統調用號
  syscall_number = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);  、
  // 3.2 將系統調用號轉為系統調用名稱
  switch (syscall_number) {
   case 5: syscall_name = "read"; break;
   case 6: syscall_name = "write"; break;
   case 10: syscall_name = "open"; break;
   case 11: syscall_name = "close"; break;
   ......
   default: syscall_name = "unknown"; break;
  }
  // 3.3 打印系統調用名稱
  printf("Syscall: %s (number: %ld)\n", syscall_name, syscall_number);
 }
}

通過上面二十多行核心代碼,我們就實現了一個模擬 strace 命令跟蹤系統調用功能的簡易程序。在這個程序中,主要是通過三塊邏輯來實現:

第一,attach 到目標進程。在 C 標準庫 中有一個 ptrace 函數 , 該函數是一個系統調用方法。通過指定它的第一個參數為 PTRACE_ATTACH  pid,這樣就可以建立當前程序和目標進程的跟蹤關系了。要注意的是,這一步必須得有 root 權限才可以。

第二,將自己注冊為目標進程的 syscall 調試器。這次還是使用 ptrace 函數。但第一個參數設為 PTRACE_SYSCALL,這樣就在告訴內核要將自己注冊為目標進程的 syscall 調試器。每當目標進程發生系統調用的時候,都會通知當前程序。

第三,讀取目標進程系統調用名。這里涉及到一個基礎知識,Linux 內核在幫用戶進程執行系統調用的時候,會將系統調用號保存到 ORIG_RAX 寄存器中。

ptrace 函數第一個參數設為 PTRACE_PEEKUSER,這是在告訴內核幫忙讀取目標進程的用戶區域的數據。第三個參數指定要讀取目標進程的 ORIG_RAX 寄存器中保存的系統調用號。在 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 中可以查看到所有的系統調用號信息。將其轉為系統調用名后輸出即可。

整個程序是一個循環,每當目標程序有系統調用發生的時候,都會通知到當前程序。當前程序再將其正在執行的系統調用信息輸出出來。這樣就實現了對目標進程實時行為的動態跟蹤。

圖片

接下來我們分三個部分,從內核視角深入地探究一下底層工作原理。

二、attach 到目標進程

要想實現對目標程序的跟蹤,首先第一步準備工作便是調用 ptrace 把自己 attach 到目標進程上。

int main(int argc, char *argv[]) {
 // 1.attach 到 pid 指定的目標進程上
 ptrace(PTRACE_ATTACH, pid, NULL, NULL)
 ...
}

圖片

我們來看下這個所謂的 attach ,在 Linux 內部究竟是干了點啥。找來 ptrace 系統調用的源碼。

//file:kernel/ptrace.c
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, ...)
{ 
 // 1. 根據 pid 查找目標進程內核對象
 struct task_struct *child;
 child = find_get_task_by_vpid(pid);
 ......

 // 2. 執行 ptrace_attach
 if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
  ret = ptrace_attach(child, request, addr, data);
  ...
 }
 ......
}

在 ptrace 系統調用源碼中,第一步比較簡單,根據參數中的 pid 查找目標進程在內核中的 task_struct 內核對象。第二步操作中的 ptrace_attach,是 attach 到目標進程的核心函數。

//file:kernel/ptrace.c
static int ptrace_attach(struct task_struct *task, long request, ...)
{
 ...
 // 1.權限檢查
 if (unlikely(task->flags & PF_KTHREAD))
  goto out;
 ...
 // 2.狀態設置
 ptrace_link(task, current);
 ...
}

在 ptrace_attach 中先要進行一些權限檢查,例如內核線程是不允許被 attach 的。接著調用 ptrace_link 來修改當前進程,以及要跟蹤的目標進程的內核對象相關字段。ptrace_link 的主要實現是 __ptrace_link。

//file:kernel/ptrace.c
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent, ...)
{
 list_add(&child->ptrace_entry, &new_parent->ptraced);
 child->parent = new_parent;
 ...
}

在 ptrace_link 函數中,先是通過 list_add 函數,將目標進程(child)的 ptrace_entry 被插入到當前進程(new_parent)的 ptraced 鏈表的頭部。這樣當前進程(new_parent) 就可以通過 ptraced 鏈表來跟蹤和管理所有在跟蹤的進程。

接著調用 child->parent = new_parent 把當前進程設置成了目標進程(child)的 parent。目的是使當前進程能夠通過調用 waitpid 獲取到目標進程的 SIGTRAP 信號。

這樣就完成了到目標進程的 attach,當前進程通過 ptraced 來管理目標進程,目標進程也可以發出 SIGTRAP 信號來和當前進程進行消息傳遞。

三、捕獲目標進程 SYSCALL

3.1 設置等待目標進程 SYSCALL

在完成當前進程和目標進程的 attach 關聯后。接著下一步操作是告訴 Linux 內核,要等待和捕獲目標進程的系統調用。

int main(int argc, char *argv[]) {

 // 1.attach 到 pid 指定的目標進程上
 ...

 while (1) {
  // 2.等待目標進程的 PTRACE_SYSCALL
  // 2.1 指定要捕獲目標進程的 PTRACE_SYSCALL
  ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
  // 2.2 當目標進程有 SYSCALL 發生時醒來處理
  waitpid(pid, &status, 0)
  ...
 }
}

圖片

在 ptrace 系統調用中,由于這次傳入的第一個參數是 PTRACE_SYSCALL。所以其執行的核心函數提煉后如下所示:

//file:kernel/ptrace.c
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
  unsigned long, data)
{ 
 struct task_struct *child;
 child = find_get_task_by_vpid(pid);
 ......
 ret = arch_ptrace(child, request, addr, data)
 ......
}

首先還是先根據 pid 獲取目標進程的 task_struct 內核對象。接著執行 arch_ptrace 來為目標進程內核對象添加一個標記 SYSCALL_TRACE。具體設置是在 ptrace_resume 函數中執行的( arch_ptrace -> ptrace_request -> ptrace_resume )。我們直接來看 ptrace_resume 源碼。

//file:kernel/ptrace.c
static int ptrace_resume(struct task_struct *child, long request,
    unsigned long data)
{
 if (request == PTRACE_SYSCALL)
  set_task_syscall_work(child, SYSCALL_TRACE);
 ...
}

set_task_syscall_work 函數就是在給目標進程的設置了一個 SYSCALL_TRACE 標記位。

//file:include/linux/thread_info.h
#define set_task_syscall_work(t, fl) \
 set_bit(SYSCALL_WORK_BIT_##fl, &task_thread_info(t)->syscall_work)

這樣后面當該進程再執行系統調用的時候,通過判斷該標記就能發現有進程在跟蹤它了。

3.2 等待目標進程信號發生

當前進程在設置完要對目標進程的 SYSCALL 進行觀察后,接著就調用 waitpid 進入了睡眠狀態。

int main(int argc, char *argv[]) {

 // 1.attach 到 pid 指定的目標進程上
 ...
 
 while (1) {
  // 2.等待目標進程的 PTRACE_SYSCALL
  // 2.1 指定要捕獲目標進程的 PTRACE_SYSCALL
  ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
  // 2.2 當目標進程有 SYSCALL 發生時醒來處理
  waitpid(pid, &status, 0)
  ...
 }
}

waitpid 也是一個系統調用。

//file:kernel/exit.c
SYSCALL_DEFINE3(waitpid, pid_t, pid, int __user *, stat_addr, int, options)
{
 return kernel_wait4(pid, stat_addr, options, NULL);
}

在這個系統調用中,依次調用 kernel_wait4、do_wait 等內核函數,最后在 add_wait_queue 函數中,將當前進程加入到等待隊列中,更新進程狀態為 TASK_INTERRUPTIBLE(可中斷睡眠狀態),等待子進程信號。

// file:kernel/exit.c
static long do_wait(struct wait_opts *wo)
{
 ...
 init_waitqueue_func_entry(&wo->child_wait, child_wait_callback);
 wo->child_wait.private = current;
 add_wait_queue(¤t->signal->wait_chldexit, &wo->child_wait);
...
}

當子進程退出時,內核會向父進程發送一個信號,父進程的信號處理程序會喚醒等待隊列中的進程,使它們重新進入可運行狀態,等待被調度器調度執行。

四、等待并讀取目標進程系統調用

圖片

4.1 目標進程系統調用發生

當目標進程系統調用發生的時候,會檢查是否有被設置 SYSCALL_TRACE  標記位。如果有,那就說明有進程正在跟蹤它。具體的檢測是在 syscall_trace_enter 內核函數中做的

//file:arch/m68k/kernel/entry.S
ENTRY(system_call)
 ...
 jbsr syscall_trace_enter
//file:arch/m68k/kernel/ptrace.c
asmlinkage int syscall_trace_enter(void)
{
 int ret = 0;

 if (test_thread_flag(TIF_SYSCALL_TRACE))
  ret = ptrace_report_syscall_entry(task_pt_regs(current));
 return ret;
}

如果有 SYSCALL_TRACE 標志,那就會設置一下退出碼,發出 SIGTRAP 信號,喚醒正在追蹤它的進程,最后暫停當前程序的運行。具體的內核函數調用過程是經過 ptrace_report_syscall_entry -> ptrace_report_syscall -> ptrace_notify -> ptrace_do_notify 這么一條長的調用鏈后,最終在 ptrace_stop 內核函數中執行的。我們直接來看這個最關鍵的 ptrace_stop 函數。

//file:kernel/signal.c
static int ptrace_stop(int exit_code, int why, unsigned long message,
         kernel_siginfo_t *info)
 __releases(¤t->sighand->siglock)
 __acquires(¤t->sighand->siglock)
{
 ......
 // 1.
 set_special_state(TASK_TRACED);


 // 2.設置當前進程的 exit_code
 current->ptrace_message = message;
 current->last_siginfo = info;
 current->exit_code = exit_code;


 // 3. 
 if (current->ptrace)
  do_notify_parent_cldstop(current, true, why);
 if (gstop_done && (!current->ptrace || ptrace_reparented(current)))
  do_notify_parent_cldstop(current, false, why);

 cgroup_enter_frozen();
 schedule();
 ......

}

在這個函數中做了這么幾件事情。

第一,調用 set_special_state(TASK_TRACED) 將當前進程(被跟蹤進程)的狀態設置為 TASK_TRACED,表示進程已被 ptrace 停止。該狀態意味著進程將不會在下次調度時被調度執行,因為它現在處于被跟蹤狀態。

第二,設置自己的退出碼,到struct task_struct 的成員 exit_code 上。

第三,調用 do_notify_parent_cldstop()-->__wake_up_parent()喚醒跟蹤進程(strace)

第四,調用 schedule 掛起自己讓出 CPU。

4.2 跟蹤進程 waitpid 返回

接下來跟蹤進程收到信號,會被內核喚醒,并中 waitpid 函數中返回。

//file:kernel/exit.c
SYSCALL_DEFINE3(waitpid, pid_t, pid, int __user *, stat_addr, int, options)
{
 return kernel_wait4(pid, stat_addr, options, NULL);
}

long kernel_wait4(pid_t upid, int __user *stat_addr, int options,
    struct rusage *ru)
{
 ...
 ret = do_wait(&wo);
 put_pid(pid);

 //將進程狀態保存到用戶傳入的地址中
 if (ret > 0 && stat_addr && put_user(wo.wo_stat, stat_addr))
  ret = -EFAULT;
 return ret;
}

這時,跟蹤進程就知道目標進程有系統調用發生了。下一步就可以讀取目標進程正在執行的系統調用信息了。

4.3 讀取目標進程系統調用號

在 Linux 內核中,ORIG_RAX 寄存器用于保存進程在執行系統調用是的調用號。跟蹤進程只需要訪問下目標進程的 ORIG_RAX 寄存器就可以知道目標進程正在執行哪個系統調用了。

圖片圖片

具體執行方式是調用 ptrace 系統調用。第一個參數設置為 PTRACE_PEEKUSER 表示要讀取目標進程的一些數據。第三個參數指定為 8*ORIG_RAX 表示要讀取 ORIG_RAX 寄存器。8*ORIG_RAX 計算出 ORIG_RAX 在用戶空間的偏移地址。

int main(int argc, char *argv[]) {

 // 1.attach 到 pid 指定的目標進程上
 ...

 while (1) {
  // 2.等待目標進程的 PTRACE_SYSCALL
  ...

  // 3.讀取并解析系統調用
  // 3.1 讀取目標進程正在執行的系統調用號
  syscall_number = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);  、
  // 3.2 將系統調用號轉為系統調用名稱
  switch (syscall_number) {
   case 5: syscall_name = "read"; break;
   case 6: syscall_name = "write"; break;
   case 10: syscall_name = "open"; break;
   case 11: syscall_name = "close"; break;
   ......
   default: syscall_name = "unknown"; break;
  }
  // 3.3 打印系統調用名稱
  printf("Syscall: %s (number: %ld)\n", syscall_name, syscall_number);
 }
}

內核執行 ptrace 系統調用的時候,會執行到 arch_ptrace 函數。

//file:arch/x86/kernel/ptrace.c
long arch_ptrace(struct task_struct *child, long request,
   unsigned long addr, unsigned long data)
{
 unsigned long __user *datap = (unsigned long __user *)data;
 ...
 switch (request) {
  ...
  case PTRACE_PEEKUSR: {
   tmp = 0;  /* Default return condition */
   if (addr < sizeof(struct user_regs_struct))
    tmp = getreg(child, addr);
   else if (addr >= offsetof(struct user, u_debugreg[0]) &&
    addr <= offsetof(struct user, u_debugreg[7])) {
    addr -= offsetof(struct user, u_debugreg[0]);
    tmp = ptrace_get_debugreg(child, addr / sizeof(data));
   }
   ret = put_user(tmp, datap);
   break;
  }
 }
}

在 arch_ptrace 判斷是 PTRACE_PEEKUSER 參數,會在計算一下目標進程數據地址 addr,然后將其讀取出來設置到跟蹤進程的用戶空間中。這樣,就讀取到系統調用號了。

接下來在 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 中可以查看到所有的系統調用號信息。將其轉為系統調用名后輸出即可。

五、總結

strace 命令跟蹤其它進程的系統調用的整個過程可以同下面一張圖來總結。

圖片圖片

首先是 strace 進程執行下面三步操作:

  • 調用 ptrace(ATTACH, ...) 設置關聯跟蹤進程和目標進程
  • 再調用 ptrace(SYSCALL, ...) 設置要要跟蹤目標進程的系統調用
  • 接著就調用 waitpid 去等待子進程的信號了,而先暫停執行了

再接下來當目標進程有系統調用發生時,

  • 檢查當前進程是否被設置了 SYSCALL_TRACE 標記
  • 如果有,那么設置一下當前進程的狀態,也暫停執行了
  • 通過信號機制喚醒跟蹤進程

跟蹤進程收到信號后會繼續執行:

  • 讀取目標進程 ORIG_RAX 寄存器,其中保存著目標進程的系統調用號
  • 將系統調用號轉換成系統調用名輸出

再接下來再調用 wait_pid,讓目標進程繼續運行。整體進入一個不斷獲取,不斷打印的循環中。

從以上的執行過程可以看出。strace 命令執行的過程中,會讓目標進程執行到系統調用時暫停運行,從而導致比較頻繁的上下文切換,會增加目標進程 的運行時間。所以,如果是在生產環境中,使用 strace 命令的時候還是要小心一點。更萬萬不可當成一個長期的線上監控工具來使用。

不過,strace 命令還是非常簡單好用的,你說呢!?

責任編輯:武曉燕 來源: 開發內功修煉
相關推薦

2015-08-20 13:43:17

NFV網絡功能虛擬化

2021-05-25 09:01:21

Linux命令Bash histor

2010-05-26 19:12:41

SVN沖突

2010-05-17 09:13:35

2021-06-07 08:18:12

云計算云端阿里云

2014-03-12 11:11:39

Storage vMo虛擬機

2009-06-01 09:04:44

Google WaveWeb

2018-03-01 09:33:05

軟件定義存儲

2009-09-15 15:34:33

Google Fast

2016-04-06 09:27:10

runtime解密學習

2023-11-02 09:55:40

2011-07-10 14:28:49

JAVAIO

2020-09-27 08:02:47

操作系統

2024-02-14 09:00:00

機器學習索引ChatGPT

2016-11-16 09:06:59

2025-01-07 15:07:13

2010-05-11 10:19:17

VMforceJava云計算

2017-10-16 05:56:00

2021-08-11 09:01:48

智能指針Box

2011-08-02 08:59:53

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲一区二区三区免费在线观看 | 亚洲高清在线视频 | 97人人爱 | 国产免费播放视频 | 亚洲综合一区二区三区 | 精品久久久久久久久久久院品网 | 亚洲福利av | 欧美日韩在线综合 | 久久久www成人免费无遮挡大片 | 一级黄色影片在线观看 | 日日夜夜操天天干 | 国产成人精品一区二区三区视频 | 欧美精品一区久久 | 国产美女免费视频 | 五月婷婷 六月丁香 | 日韩欧美一区二区三区免费观看 | 亚洲国产精品一区二区www | 黄色免费在线观看网址 | 狠狠艹| 古装人性做爰av网站 | 成人免费大片黄在线播放 | 中文字幕乱码一区二区三区 | 精品一区二区三区不卡 | 99精品在线观看 | 久久久综合色 | 日韩成人精品在线观看 | 欧美1页| 在线观看涩涩视频 | 日韩第1页 | 国产福利视频网站 | 日韩精品久久久久久 | 三级黄片毛片 | 欧美日韩精品一区二区三区视频 | 男女视频在线观看网站 | 狠狠色综合久久丁香婷婷 | 国产精品国产精品国产专区不蜜 | 9999视频 | 日韩在线观看一区 | 国产免费拔擦拔擦8x高清 | 国产亚韩 | 粉嫩一区二区三区性色av |