五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

如何讀懂GDB底層實(shí)現(xiàn)原理(從這幾點(diǎn)入手~)

2022-05-20 14:15 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿
  • 在程序出現(xiàn)bug的時候,最好的解決辦法就是通過 GDB 調(diào)試程序,然后找到程序出現(xiàn)問題的地方。比如程序出現(xiàn) 段錯誤(內(nèi)存地址不合法)時,就可以通過 GDB 找到程序哪里訪問了不合法的內(nèi)存地址而導(dǎo)致的。

  • 本文不是介紹 GDB 的使用方式,而是大概介紹 GDB 的實(shí)現(xiàn)原理,當(dāng)然 GDB 是一個龐大而復(fù)雜的項(xiàng)目,不可能只通過一篇文章就能解釋清楚,所以本文主要是介紹 GDB 使用的核心的技術(shù) - ptrace。

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

  • ptrace() 系統(tǒng)調(diào)用是 Linux 提供的一個調(diào)試進(jìn)程的工具,ptrace() 系統(tǒng)調(diào)用非常強(qiáng)大,它提供非常多的調(diào)試方式讓我們?nèi)フ{(diào)試某一個進(jìn)程,下面是 ptrace() 系統(tǒng)調(diào)用的定義:

  • 下面解釋一下 ptrace() 各個參數(shù)的作用:

  1. request:指定調(diào)試的指令,指令的類型很多,如:PTRACE_TRACEME、PTRACE_PEEKUSER、PTRACE_CONT、PTRACE_GETREGS等等,下面會介紹不同指令的作用。

  2. pid:進(jìn)程的ID(這個不用解釋了)。

  3. addr:進(jìn)程的某個地址空間,可以通過這個參數(shù)對進(jìn)程的某個地址進(jìn)行讀或?qū)懖僮鳌?/p>

  4. data:根據(jù)不同的指令,有不同的用途,下面會介紹。

  • 下面通過一個簡單例子來說明 ptrace() 系統(tǒng)調(diào)用的使用,這個例子主要介紹怎么使用 ptrace() 系統(tǒng)調(diào)用獲取當(dāng)前被調(diào)試(追蹤)進(jìn)程的各個寄存器的值,代碼如下(ptrace.c):

通過命令 gcc ptrace.c -o ptrace 編譯并運(yùn)行上面的程序會輸出如下結(jié)果:

  • 上面結(jié)果的第一行是由父進(jìn)程輸出的,主要是打印了子進(jìn)程執(zhí)行 /bin/ls 程序后各個寄存器的值。而第二行是由子進(jìn)程輸出的,主要是打印了執(zhí)行 /bin/ls 程序后輸出的結(jié)果。

  • 下面解釋一下上面程序的執(zhí)行流程:

  1. 主進(jìn)程調(diào)用 fork() 系統(tǒng)調(diào)用創(chuàng)建一個子進(jìn)程。

  2. 子進(jìn)程調(diào)用 ptrace(PTRACE_TRACEME,...) 把自己設(shè)置為被追蹤狀態(tài),并且調(diào)用 execl() 執(zhí)行 /bin/ls 程序。

  3. 被設(shè)置為追蹤(TRACE)狀態(tài)的子進(jìn)程執(zhí)行 execl() 的程序后,會向父進(jìn)程發(fā)送 SIGCHLD 信號,并且暫停自身的執(zhí)行。

  4. 父進(jìn)程通過調(diào)用 wait() 接收子進(jìn)程發(fā)送過來的信號,并且開始追蹤子進(jìn)程。

  5. 父進(jìn)程通過調(diào)用 ptrace(PTRACE_GETREGS, child, ...) 來獲取到子進(jìn)程各個寄存器的值,并且打印寄存器的值。

  6. 父進(jìn)程通過調(diào)用 ptrace(PTRACE_CONT, child, ...) 讓子進(jìn)程繼續(xù)執(zhí)行下去。

  • 從上面的例子可以知道,通過向 ptrace() 函數(shù)的 request 參數(shù)傳入不同的值時,就有不同的效果。比如傳入 PTRACE_TRACEME 就可以讓進(jìn)程進(jìn)入被追蹤狀態(tài),而傳入 PTRACE_GETREGS 時,就可以獲取被追蹤的子進(jìn)程各個寄存器的值等。

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


本文使用的 Linux 2.4.16 版本的內(nèi)核 看懂本文需要的基礎(chǔ):進(jìn)程調(diào)度,內(nèi)存管理和信號處理相關(guān)知識。

  • 調(diào)用 ptrace() 系統(tǒng)函數(shù)時會觸發(fā)調(diào)用內(nèi)核的 sys_ptrace() 函數(shù),由于不同的 CPU 架構(gòu)有著不同的調(diào)試方式,所以 Linux 為每種不同的 CPU 架構(gòu)實(shí)現(xiàn)了不同的 sys_ptrace() 函數(shù),而本文主要介紹的是 X86 CPU 的調(diào)試方式,所以 sys_ptrace() 函數(shù)所在文件是 linux-2.4.16/arch/i386/kernel/ptrace.c。

  • sys_ptrace() 函數(shù)的主體是一個 switch 語句,會傳入的 request 參數(shù)不同進(jìn)行不同的操作,如下:

  • 從上面的代碼可以看出,sys_ptrace() 函數(shù)首先根據(jù)進(jìn)程的 pid 獲取到進(jìn)程的 task_struct 對象。然后根據(jù)傳入不同的 request 參數(shù)在 switch 語句中進(jìn)行不同的操作。

  • ptrace() 支持的所有 request 操作定義在 linux-2.4.16/include/linux/ptrace.h 文件中,如下:

  • 由于 ptrace() 提供的操作比較多,所以本文只會挑選一些比較有代表性的操作進(jìn)行解說,比如 PTRACE_TRACEME、PTRACE_SINGLESTEP、PTRACE_PEEKTEXT、PTRACE_PEEKDATA 和 PTRACE_CONT 等,而其他的操作,有興趣的朋友可以自己去分析其實(shí)現(xiàn)原理。

進(jìn)入被追蹤模式(PTRACE_TRACEME操作)

  • 當(dāng)要調(diào)試一個進(jìn)程時,需要使進(jìn)程進(jìn)入被追蹤模式,怎么使進(jìn)程進(jìn)入被追蹤模式呢?有兩個方法:

  1. 被調(diào)試的進(jìn)程調(diào)用 ptrace(PTRACE_TRACEME, ...) 來使自己進(jìn)入被追蹤模式。

  2. 調(diào)試進(jìn)程(如GDB)調(diào)用 ptrace(PTRACE_ATTACH, pid, ...) 來使指定的進(jìn)程進(jìn)入被追蹤模式。

  • 第一種方式是進(jìn)程自己主動進(jìn)入被追蹤模式,而第二種是進(jìn)程被動進(jìn)入被追蹤模式。

  • 被調(diào)試的進(jìn)程必須進(jìn)入被追蹤模式才能進(jìn)行調(diào)試,因?yàn)?Linux 會對被追蹤的進(jìn)程進(jìn)行一些特殊的處理。下面我們主要介紹第一種進(jìn)入被追蹤模式的實(shí)現(xiàn),就是 PTRACE_TRACEME 的操作過程,代碼如下:

  • 從上面的代碼可以發(fā)現(xiàn),ptrace() 對 PTRACE_TRACEME 的處理就是把當(dāng)前進(jìn)程標(biāo)志為 PTRACE 狀態(tài)。

  • 當(dāng)然事情不會這么簡單,因?yàn)楫?dāng)一個進(jìn)程被標(biāo)記為 PTRACE 狀態(tài)后,當(dāng)調(diào)用 exec() 函數(shù)去執(zhí)行一個外部程序時,將會暫停當(dāng)前進(jìn)程的運(yùn)行,并且發(fā)送一個 SIGCHLD 給父進(jìn)程。父進(jìn)程接收到 SIGCHLD 信號后就可以對被調(diào)試的進(jìn)程進(jìn)行調(diào)試。

  • 我們來看看 exec() 函數(shù)是怎樣實(shí)現(xiàn)上述功能的,exec() 函數(shù)的執(zhí)行過程為 sys_execve() -> do_execve() -> load_elf_binary():

  • 從上面代碼可以看出,當(dāng)進(jìn)程被標(biāo)記為 PTRACE 狀態(tài)時,執(zhí)行 exec() 函數(shù)后便會發(fā)送一個 SIGTrap 的信號給當(dāng)前進(jìn)程。

  • 我們再來看看,進(jìn)程是怎么處理 SIGTrap 信號的。信號是通過 do_signal() 函數(shù)進(jìn)行處理的,而對 SIGTrap 信號的處理邏輯如下:

  • 上面的代碼主要做了3件事:

  1. 如果當(dāng)前進(jìn)程被標(biāo)記為 PTRACE 狀態(tài),那么就使自己進(jìn)入停止運(yùn)行狀態(tài)。

  2. 發(fā)送 SIGCHLD 信號給父進(jìn)程。

  3. 讓出 CPU 的執(zhí)行權(quán)限,使 CPU 執(zhí)行其他進(jìn)程。

  • 執(zhí)行以上過程后,被追蹤進(jìn)程便進(jìn)入了調(diào)試模式,過程如下圖:

  • 當(dāng)父進(jìn)程(調(diào)試進(jìn)程)接收到 SIGCHLD 信號后,表示被調(diào)試進(jìn)程已經(jīng)標(biāo)記為被追蹤狀態(tài)并且停止運(yùn)行,那么調(diào)試進(jìn)程就可以開始進(jìn)行調(diào)試了。

獲取被調(diào)試進(jìn)程的內(nèi)存數(shù)據(jù)(PTRACE_PEEKTEXT / PTRACE_PEEKDATA)

  • 調(diào)試進(jìn)程(如GDB)可以通過調(diào)用 ptrace(PTRACE_PEEKDATA, pid, addr, data) 來獲取被調(diào)試進(jìn)程 addr 處虛擬內(nèi)存地址的數(shù)據(jù),但每次只能讀取一個大小為 4字節(jié)的數(shù)據(jù)。

  • 我們來看看 ptrace() 對 PTRACE_PEEKDATA 操作的處理過程,代碼如下:

  • 從上面代碼可以看出,對 PTRACE_PEEKTEXT 和 PTRACE_PEEKDATA 的處理是相同的,主要是通過調(diào)用 access_process_vm() 函數(shù)來讀取被調(diào)試進(jìn)程 addr 處的虛擬內(nèi)存地址的數(shù)據(jù)。

  • access_process_vm() 函數(shù)的實(shí)現(xiàn)主要涉及到 內(nèi)存管理 相關(guān)的知識,可以參考我以前對內(nèi)存管理分析的文章,這里主要大概說明一下 access_process_vm() 的原理。

  • 我們知道每個進(jìn)程都有個 mm_struct 的內(nèi)存管理對象,而 mm_struct 對象有個表示虛擬內(nèi)存與物理內(nèi)存映射關(guān)系的頁目錄的指針 pgd。如下:

  • 而 access_process_vm() 函數(shù)就是通過進(jìn)程的頁目錄來找到 addr 虛擬內(nèi)存地址映射的物理內(nèi)存地址,然后把此物理內(nèi)存地址處的數(shù)據(jù)復(fù)制到 data 變量中。如下圖所示:

  • access_process_vm() 函數(shù)的實(shí)現(xiàn)這里就不分析了,有興趣的讀者可以參考我之前對內(nèi)存管理分析的文章自行進(jìn)行分析。

單步調(diào)試模式(PTRACE_SINGLESTEP)

  • 單步調(diào)試是一個比較有趣的功能,當(dāng)把被調(diào)試進(jìn)程設(shè)置為單步調(diào)試模式后,被調(diào)試進(jìn)程沒執(zhí)行一條CPU指令都會停止執(zhí)行,并且向父進(jìn)程(調(diào)試進(jìn)程)發(fā)送一個 SIGCHLD 信號。

  • 我們來看看 ptrace() 函數(shù)對 PTRACE_SINGLESTEP 操作的處理過程,代碼如下:

  • 要把被調(diào)試的進(jìn)程設(shè)置為單步調(diào)試模式,英特爾的 X86 CPU 提供了一個硬件的機(jī)制,就是通過把 eflags 寄存器的 Trap Flag 設(shè)置為1即可。

  • 當(dāng)把 eflags 寄存器的 Trap Flag 設(shè)置為1后,CPU 每執(zhí)行一條指令便會產(chǎn)生一個異常,然后會觸發(fā) Linux 的異常處理,Linux 便會發(fā)送一個 SIGTrap 信號給被調(diào)試的進(jìn)程。eflags 寄存器的各個標(biāo)志如下圖:

  • 從上圖可知,eflags 寄存器的第8位就是單步調(diào)試模式的標(biāo)志。

  • 所以 ptrace() 函數(shù)的以下2行代碼就是設(shè)置 eflags 進(jìn)程的單步調(diào)試標(biāo)志:

  • 而 get_stack_long(proccess, offset) 函數(shù)用于獲取進(jìn)程棧 offset 處的值,而 EFL_OFFSET 偏移量就是 eflags 寄存器的值。所以上面兩行代碼的意思就是:

  1. 獲取進(jìn)程的 eflags 寄存器的值,并且設(shè)置 Trap Flag 標(biāo)志。

  2. 把新的值設(shè)置到進(jìn)程的 eflags 寄存器中。

  • 設(shè)置完 eflags 寄存器的值后,就調(diào)用 wake_up_process() 函數(shù)把被調(diào)試的進(jìn)程喚醒,讓其進(jìn)入運(yùn)行狀態(tài)。單步調(diào)試過程如下圖:

  • 處于單步調(diào)試模式時,被調(diào)試進(jìn)程每執(zhí)行一條指令都會觸發(fā)一次 SIGTrap 信號,而被調(diào)試進(jìn)程處理 SIGTrap 信號時會發(fā)送一個 SIGCHLD 信號給父進(jìn)程(調(diào)試進(jìn)程),并且讓自己停止執(zhí)行。

  • 而父進(jìn)程(調(diào)試進(jìn)程)接收到 SIGCHLD 后,就可以對被調(diào)試的進(jìn)程進(jìn)行各種操作,比如讀取被調(diào)試進(jìn)程內(nèi)存的數(shù)據(jù)和寄存器的數(shù)據(jù),或者通過調(diào)用 ptrace(PTRACE_CONT, child,...) 來讓被調(diào)試進(jìn)程進(jìn)行運(yùn)行等。

小結(jié)

  • 由于 ptrace() 的功能十分強(qiáng)大,所以本文只能拋磚引玉,沒能對其所有功能進(jìn)行分析。另外斷點(diǎn)功能并不是通過 ptrace() 函數(shù)實(shí)現(xiàn)的,而是通過 int3 指令來實(shí)現(xiàn)的,在 Eli Bendersky 大神的文章有介紹。而對于 ptrace() 的所有功能,只能讀者自己慢慢看代碼來體會了。


如何讀懂GDB底層實(shí)現(xiàn)原理(從這幾點(diǎn)入手~)的評論 (共 條)

分享到微博請遵守國家法律
咸丰县| 江都市| 安达市| 和林格尔县| 彭泽县| 临泉县| 卢湾区| 丰镇市| 荣成市| 新兴县| 萨嘎县| 柞水县| 四会市| 莆田市| 安丘市| 翼城县| 崇信县| 望城县| 新巴尔虎左旗| 秦皇岛市| 贡嘎县| 婺源县| 彰化县| 大冶市| 新巴尔虎左旗| 吉首市| 同江市| 堆龙德庆县| 江北区| 贺州市| 汨罗市| 门源| 华容县| 临西县| 吴川市| 香港| 务川| 合肥市| 黎平县| 连南| 武义县|