<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自己動(dòng)手寫一個(gè) strace

          共 9101字,需瀏覽 19分鐘

           ·

          2020-11-19 03:05

          這次主要分享一下一個(gè)動(dòng)手的東西,就是自己動(dòng)手寫一個(gè)?strace?工具。

          用過?strace?的同學(xué)都知道,strace?是用來跟蹤進(jìn)程調(diào)用的?系統(tǒng)調(diào)用,還可以統(tǒng)計(jì)進(jìn)程對?系統(tǒng)調(diào)用?的統(tǒng)計(jì)等。strace?的使用方式有兩種,如下:

          • strace?執(zhí)行的程序

          • strace -p?進(jìn)程pid

          第一種用于跟蹤將要執(zhí)行的程序,而第二種用于跟蹤一個(gè)運(yùn)行中的進(jìn)程。

          下圖就是使用?strace?對?ls?命令跟蹤的結(jié)果:

          ptrace系統(tǒng)調(diào)用

          要自己動(dòng)手寫?strace?的第一步就是了解?ptrace()?系統(tǒng)調(diào)用的使用,我們來看看?ptrace()?系統(tǒng)調(diào)用的定義:

          int ptrace(long request, long pid, long addr, long data);

          ptrace()?系統(tǒng)調(diào)用用于跟蹤進(jìn)程的運(yùn)行情況,下面介紹一下其各個(gè)參數(shù)的含義:

          • request:指定跟蹤的動(dòng)作。也就是說,通過傳入不同的?request?參數(shù)可以對進(jìn)程進(jìn)行不同的跟蹤操作。其可選值有:

            • PTRACE_TRACEME

            • PTRACE_PEEKTEXT

            • PTRACE_POKETEXT

            • PTRACE_CONT

            • PTRACE_SINGLESTEP

            • ...

          • pid:指定要跟蹤的進(jìn)程PID。

          • addr:指定要讀取或者修改的內(nèi)存地址。

          • data:對于不同的?request?操作,data?有不同的作用,下面會(huì)介紹。

          前面介紹過,使用?strace?跟蹤進(jìn)程有兩種方式,一種是通過?strace?命令啟動(dòng)進(jìn)程,另外一種是通過?-p?指定要跟蹤的進(jìn)程。

          ptrace()?系統(tǒng)調(diào)用也提供了兩種?request?來實(shí)現(xiàn)上面兩種方式:

          • 第一種通過?PTRACE_TRACEME?來實(shí)現(xiàn)

          • 第二種通過?PTRACE_ATTACH?來實(shí)現(xiàn)

          本文我們主要介紹使用第一種方式。由于第一種方式使用跟蹤程序來啟動(dòng)被跟蹤的程序,所以需要啟動(dòng)兩個(gè)進(jìn)程。通常要?jiǎng)?chuàng)建新進(jìn)程可以使用?fork()?系統(tǒng)調(diào)用,所以自然而然地我們也使用?fork()?系統(tǒng)調(diào)用。

          我們新建一個(gè)文件?strace.c,輸入代碼如下:

          int main(int argc, char *argv[]){    pid_t child;
          child = fork(); if (child == 0) { // 子進(jìn)程... } else { // 父進(jìn)程... }
          return 0;}

          上面的代碼通過調(diào)用?fork()?來創(chuàng)建一個(gè)子進(jìn)程,但是沒有做任何事情。之后,我們就會(huì)在?子進(jìn)程?中運(yùn)行被跟蹤的程序,而在?父進(jìn)程?中運(yùn)行跟蹤進(jìn)程的代碼。

          運(yùn)行被跟蹤程序

          前面說過,被跟蹤的程序需要在子進(jìn)程中運(yùn)行,而要運(yùn)行一個(gè)程序,可以通過調(diào)用?execl()?系統(tǒng)調(diào)用。所以可以通過下面的代碼,在子進(jìn)程中運(yùn)行?ls?命令:

          #include #include 
          int main(int argc, char *argv[]){ pid_t child;
          child = fork(); if (child == 0) { execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { // 父進(jìn)程... }
          return 0;}

          execl()?用于執(zhí)行指定的程序,如果執(zhí)行成功就不會(huì)返回,所以?execl(...)?的下一行代碼?exit(0)?不會(huì)被執(zhí)行到。

          由于我們需要跟蹤?ls?命令,所以在執(zhí)行?ls?命令前,必須調(diào)用?ptrace(PTRACE_TRACEME, 0, NULL, NULL)?來告訴系統(tǒng)需要跟蹤這個(gè)進(jìn)程,代碼如下:

          #include #include #include 
          int main(int argc, char *argv[]){ pid_t child;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { // 父進(jìn)程... }
          return 0;}

          這樣,被跟蹤進(jìn)程部分的代碼就完成了,接下來開始編寫跟蹤進(jìn)程部分代碼。

          編寫跟蹤進(jìn)程代碼

          如果編譯運(yùn)行上面的代碼,會(huì)發(fā)現(xiàn)什么效果也沒有。這是因?yàn)楫?dāng)在子進(jìn)程調(diào)用?ptrace(PTRACE_TRACEME, 0, NULL, NULL)?后,并且調(diào)用?execl()?系統(tǒng)調(diào)用,那么子進(jìn)程會(huì)發(fā)送一個(gè)?SIGCHLD?信號給父進(jìn)程(跟蹤進(jìn)程)并且自己停止運(yùn)行,直到父進(jìn)程發(fā)送調(diào)試命令,才會(huì)繼續(xù)運(yùn)行。

          由于上面的代碼中,父進(jìn)程(跟蹤進(jìn)程)并沒有發(fā)送任何調(diào)試命令就退出運(yùn)行,所以子進(jìn)程(被跟蹤進(jìn)程)在沒有運(yùn)行的情況下就跟著父進(jìn)程一起退出了,那么就不會(huì)看到任何效果。

          現(xiàn)在我們開始編寫跟蹤進(jìn)程的代碼。

          由于被跟蹤進(jìn)程會(huì)發(fā)送一個(gè)?SIGCHLD?信息給跟蹤進(jìn)程,所以我們先要在跟蹤進(jìn)程的代碼中接收?SIGCHLD?信號,接收信號通過使用?wait()?系統(tǒng)調(diào)用完成,代碼如下:

          #include #include #include #include 
          int main(int argc, char *argv[]){ pid_t child; int status;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 }
          return 0;}

          上面的代碼通過調(diào)用?wait()?系統(tǒng)調(diào)用來接收被跟蹤進(jìn)程發(fā)送過來的?SIGCHLD?信號,接下來需要開始向被跟蹤進(jìn)程發(fā)送調(diào)試命令,來對被跟蹤進(jìn)程進(jìn)行調(diào)試。

          由于本文介紹怎么跟蹤進(jìn)程調(diào)用了哪些?系統(tǒng)調(diào)用,所以我們需要使用?ptrace()?的?PTRACE_SYSCALL?命令,代碼如下:

          #include #include #include #include #include 
          int main(int argc, char *argv[]){ pid_t child; int status; struct user_regs_struct regs; int orig_rax;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          // 1. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用前,可以獲取系統(tǒng)調(diào)用的參數(shù)) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          // 2. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用后,可以獲取系統(tǒng)調(diào)用的返回值) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 }
          return 0;}

          從上面的代碼可以發(fā)現(xiàn),我們調(diào)用了兩次?ptrace(PTRACE_SYSCALL, child, NULL, NULL),這是因?yàn)楦櫹到y(tǒng)調(diào)用時(shí),需要跟蹤系統(tǒng)調(diào)用前的環(huán)境(比如獲取系統(tǒng)調(diào)用的參數(shù))和系統(tǒng)調(diào)用后的環(huán)境(比如獲取系統(tǒng)調(diào)用的返回值),所以就需要調(diào)用兩次?ptrace(PTRACE_SYSCALL, child, NULL, NULL)

          獲取進(jìn)程寄存器的值

          Linux系統(tǒng)調(diào)用是通過?CPU寄存器?來傳遞參數(shù)的,所以要想獲取調(diào)用了哪個(gè)系統(tǒng)調(diào)用,必須獲取進(jìn)程寄存器的值。獲取進(jìn)程寄存器的值,可以通過?ptrace()?系統(tǒng)調(diào)用的?PTRACE_GETREGS?命令來實(shí)現(xiàn),代碼如下:

          #include #include #include #include #include 
          int main(int argc, char *argv[]){ pid_t child; int status; struct user_regs_struct regs; int orig_rax;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          // 1. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用前,可以獲取系統(tǒng)調(diào)用的參數(shù)) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          ptrace(PTRACE_GETREGS, child, 0, ®s); // 獲取被跟蹤進(jìn)程寄存器的值
          orig_rax = regs.orig_rax; // 獲取rax寄存器的值
          printf("orig_rax: %d\n", orig_rax); // 打印rax寄存器的值
          // 2. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用后,可以獲取系統(tǒng)調(diào)用的返回值) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 }
          return 0;}

          上面的代碼通過調(diào)用?ptrace(PTRACE_GETREGS, child, 0, ®s)?來獲取進(jìn)程寄存器的值,PTRACE_GETREGS?命令需要在?data?參數(shù)傳入類型為?user_regs_struct?結(jié)構(gòu)的指針,user_regs_struct?結(jié)構(gòu)定義如下(在文件?sys/user.h?中):

          struct user_regs_struct {    unsigned long r15,r14,r13,r12,rbp,rbx,r11,r10;    unsigned long r9,r8,rax,rcx,rdx,rsi,rdi,orig_rax;    unsigned long rip,cs,eflags;    unsigned long rsp,ss;    unsigned long fs_base, gs_base;    unsigned long ds,es,fs,gs;};

          其中?user_regs_struct?結(jié)構(gòu)的?orig_rax?保存了系統(tǒng)調(diào)用號,所以我們可以通過?orig_rax?的值來知道調(diào)用了哪個(gè)系統(tǒng)調(diào)用。

          編譯運(yùn)行上面的代碼,會(huì)輸出結(jié)果:orig_rax: 12,就是說當(dāng)前調(diào)用的是編號為 12 的系統(tǒng)調(diào)用。那么編號為 12 的系統(tǒng)調(diào)用是哪個(gè)系統(tǒng)調(diào)用呢?可以通過下面鏈接來查看:

          https://www.cnblogs.com/gavanwanggw/p/6920826.html

          通過查閱系統(tǒng)調(diào)用表,可以知道編號 12 的系統(tǒng)調(diào)用為?brk(),如下:

          系統(tǒng)調(diào)用號     函數(shù)名     入口點(diǎn)     源碼...12            brk       sys_brk    mm/mmap.c...

          上面的程序只跟蹤了一個(gè)系統(tǒng)調(diào)用,那么怎么跟蹤所有的系統(tǒng)調(diào)用呢?很簡單,只需要把跟蹤的代碼放到一個(gè)無限循環(huán)中即可。代碼如下:

          #include #include #include #include #include 
          int main(int argc, char *argv[]){ pid_t child; int status; struct user_regs_struct regs; int orig_rax;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          while (1) { // 1. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用前,可以獲取系統(tǒng)調(diào)用的參數(shù)) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 if (WIFEXITED(status)) { // 如果子進(jìn)程退出了, 那么終止跟蹤 break; }
          ptrace(PTRACE_GETREGS, child, 0, ®s); // 獲取被跟蹤進(jìn)程寄存器的值
          orig_rax = regs.orig_rax; // 獲取rax寄存器的值
          printf("orig_rax: %d\n", orig_rax); // 打印rax寄存器的值
          // 2. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用后,可以獲取系統(tǒng)調(diào)用的返回值) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 if (WIFEXITED(status)) { // 如果子進(jìn)程退出了, 那么終止跟蹤 break; } } }
          return 0;}

          if (WIFEXITED(status)) ...?這行代碼用于判斷子進(jìn)程(被跟蹤進(jìn)程)是否已經(jīng)退出,如果退出了就停止跟蹤。現(xiàn)在可以編譯并運(yùn)行這個(gè)程序,輸出結(jié)果如下:

          [root@localhost liexusong]$ ./straceorig_rax: 12orig_rax: 9orig_rax: 21orig_rax: 2orig_rax: 5orig_rax: 9orig_rax: 3orig_rax: 2orig_rax: 0orig_rax: 5orig_rax: 9orig_rax: 10orig_rax: 9orig_rax: 9orig_rax: 3orig_rax: 2orig_rax: 0orig_rax: 5orig_rax: 9orig_rax: 10...

          從執(zhí)行結(jié)果來看,只是打印系統(tǒng)調(diào)用號不太直觀,那么我們怎么優(yōu)化呢?

          我們可以定義一個(gè)系統(tǒng)調(diào)用號與系統(tǒng)調(diào)用名的對應(yīng)表來實(shí)現(xiàn)更清晰的輸出結(jié)果,如下:

          #include #include #include #include #include 
          struct syscall { int code; char *name;} syscall_table[] = { {0, "read"}, {1, "write"}, {2, "open"}, {3, "close"}, {4, "stat"}, {5, "fstat"}, {6, "lstat"}, {7, "poll"}, {8, "lseek"}, ... {-1, NULL},}
          char *find_syscall_symbol(int code) { struct syscall *sc;
          for (sc = syscall_table; sc->code >= 0; sc++) { if (sc->code == code) { return sc->name; } }
          return NULL;}
          int main(int argc, char *argv[]){ pid_t child; int status; struct user_regs_struct regs; int orig_rax;
          child = fork(); if (child == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "/bin/ls", NULL); exit(0); } else { wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號
          while (1) { // 1. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用前,可以獲取系統(tǒng)調(diào)用的參數(shù)) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 if(WIFEXITED(status)) { // 如果子進(jìn)程退出了, 那么終止跟蹤 break; }
          ptrace(PTRACE_GETREGS, child, 0, ®s); // 獲取被跟蹤進(jìn)程寄存器的值
          orig_rax = regs.orig_rax; // 獲取rax寄存器的值
          printf("syscall: %s()\n", find_syscall_symbol(orig_rax)); // 打印系統(tǒng)調(diào)用
          // 2. 發(fā)送 PTRACE_SYSCALL 命令給被跟蹤進(jìn)程 (調(diào)用系統(tǒng)調(diào)用后,可以獲取系統(tǒng)調(diào)用的返回值) ptrace(PTRACE_SYSCALL, child, NULL, NULL);
          wait(&status); // 接收被子進(jìn)程發(fā)送過來的 SIGCHLD 信號 if(WIFEXITED(status)) { // 如果子進(jìn)程退出了, 那么終止跟蹤 break; } } }
          return 0;}

          上面例子添加了一個(gè)函數(shù)?find_syscall_symbol()?來獲取系統(tǒng)調(diào)用號對應(yīng)的系統(tǒng)調(diào)用名,實(shí)現(xiàn)也比較簡單。編譯運(yùn)行后輸出結(jié)果如下:

          [root@localhost liexusong]$ ./stracesyscall: brk()syscall: mmap()syscall: access()syscall: open()syscall: fstat()syscall: mmap()syscall: close()syscall: open()syscall: read()syscall: fstat()syscall: mmap()syscall: mprotect()syscall: mmap()syscall: mmap()syscall: close()...

          從執(zhí)行結(jié)果來看,現(xiàn)在可以打印系統(tǒng)調(diào)用的名字了,但我們知道?strace?命令還會(huì)打印系統(tǒng)調(diào)用參數(shù)的值,我們可以通過?ptrace()?系統(tǒng)調(diào)用的?PTRACE_PEEKTEXT?和?PTRACE_PEEKDATA?來獲取參數(shù)的值,所以有興趣的就自己實(shí)現(xiàn)這個(gè)效果了。

          本文完整代碼在:

          https://github.com/liexusong/build-strace-by-myself/blob/main/strace.c


          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操人人操人人操人人 | 2025年亚洲国产精品视频 | 性高潮视频网站 | 色哟哟精品无码 | 18禁黄网站禁片免费看 |