How Debugger Works

  • from
    How Debugger Works

  • 核心是系統(tǒng)調(diào)用ptrace坷备,ptrace可以讓一個process窺探另一個process的內(nèi)部,甚至控制另一個process舅踪。

  • 首先創(chuàng)建兩個process辩恼,parent運行debugger,child運行target鱼鼓。target程序調(diào)用ptrace系統(tǒng)調(diào)用。參數(shù)PTRACE_TRACEME告訴內(nèi)核可以讓parent跟蹤自己该编。

void run_target(const char* programname)
{ 
        procmsg("target started. will run '%s'\n", programname); 

        /* Allow tracing of this process */ 
        if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { perror("ptrace"); return; } 
        /* Replace this process's image with the given program */ 
        execl(programname, programname, 0);
}

Indicates that this process is to be traced by its parent. Any signal (except SIGKILL) delivered to this process will cause it to stop and its parent to be notified via wait(). Also, all subsequent calls to exec() by this process will cause a SIGTRAP to be sent to it, giving the parent a chance to gain control before the new program begins execution. A process probably shouldn't make this request if its parent isn't expecting to trace it. (pid, addr, and data are ignored.)

  • 也就是說在child運行execl之前停止運行迄本,通知parent,發(fā)送一個signal课竣。此時parent
    等到了信號發(fā)生嘉赎,并且用WIFSTOPPED判斷是否是child是否停止。如果是就調(diào)用ptrace但是參數(shù)是PTRACE_SINGLESTEP于樟,讓child單步運行公条。單步運行意味著child每運行一個指令都要通知parent,這里的while循環(huán)就是這個意思迂曲,直到parent收到停止的信號靶橱。注意這里的icounter就是指令計數(shù)器,每收到一個信號就計數(shù)一次。
void run_debugger(pid_t child_pid)
{ 
    int wait_status; 
    unsigned icounter = 0; 

    procmsg("debugger started\n"); 

    /* Wait for child to stop on its first instruction */ 
    wait(&wait_status); 

    while (WIFSTOPPED(wait_status)) { 
        icounter++; 

        /* Make the child execute another instruction */ 
        if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) { perror("ptrace"); return;}
        
        /* Wait for child to stop on its next instruction */ 
        wait(&wait_status); 
    } 
    procmsg("the child executed %u instructions\n", icounter);
}
  • 另外关霸,還有其他的ptrace參數(shù)可以幫助獲得child的各種信息传黄,包括寄存器,指令等谒拴。
struct user_regs_struct regs; ptrace(PTRACE_GETREGS, child_pid, 0, &regs); unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0); procmsg("icounter = %u. EIP = 0x%08x. instr = 0x%08x\n", icounter, regs.eip, instr);

結(jié)果如下:

$ simple_tracer traced_helloworld
[5700] debugger started
[5701] target started. will run 'traced_helloworld'
[5700] icounter = 1.  EIP = 0x08048080.  instr = 0x00000eba
[5700] icounter = 2.  EIP = 0x08048085.  instr = 0x0490a0b9
[5700] icounter = 3.  EIP = 0x0804808a.  instr = 0x000001bb
[5700] icounter = 4.  EIP = 0x0804808f.  instr = 0x000004b8
[5700] icounter = 5.  EIP = 0x08048094.  instr = 0x01b880cd
Hello, world!
[5700] icounter = 6.  EIP = 0x08048096.  instr = 0x000001b8
[5700] icounter = 7.  EIP = 0x0804809b.  instr = 0x000080cd
[5700] the child executed 7 instructions
  • attach也并不難尝江,只用給ptrace一個參數(shù)即可,PTRACE_ATTACH

  • 問題是每次單步實在是太費勁了英上,動輒幾千條指令炭序,這個時候breakpoint就是一個好的選擇了。

  • debugger的兩個基石苍日,一個是breakpoint惭聂,另一個是能夠探測debugged process的memory。

  • breakpoint就是一個軟中斷相恃,x86上面的3號軟中斷就是專門用來trap to debugger的

The INT 3 instruction generates a special one byte opcode (CC) that is intended for calling the debug exception handler. (This one byte form is valuable because it can be used to replace the first byte of any instruction with a breakpoint, including other one byte instructions, without over-writing other code).

  • Linux接收到int 3的時候會想process發(fā)送SIGTRAP辜纲,聯(lián)系到之前的child會自動向parent發(fā)送停止信號,就是因為先收到SIGTRAP拦耐。

  • 假設由如下匯編代碼耕腾,這個代碼先打印hello,再打印world杀糯,我們的目的是在打印hello之后暫停進程扫俺。我們只需要將指令mov edx, len2替換成int 3就行了。

section    .text
    ; The _start symbol must be declared for the linker (ld)
    global _start

_start:

    ; Prepare arguments for the sys_write system call:
    ;   - eax: system call number (sys_write)
    ;   - ebx: file descriptor (stdout)
    ;   - ecx: pointer to string
    ;   - edx: string length
    mov     edx, len1
    mov     ecx, msg1
    mov     ebx, 1
    mov     eax, 4

    ; Execute the sys_write system call
    int     0x80

    ; Now print the other message
    mov     edx, len2     <---- replaced by int 3
    mov     ecx, msg2
    mov     ebx, 1
    mov     eax, 4
    int     0x80

    ; Execute sys_exit
    mov     eax, 1
    int     0x80

section    .data

msg1    db      'Hello,', 0xa
len1    equ     $ - msg1
msg2    db      'world!', 0xa
len2    equ     $ - msg2

  • 實際上debugger要做兩件事:
  1. 記住原先的指令
  2. 將原先指令的第一個byte替換成int 3
  • 當debugger讓程序按照PTRACE_CONT運行固翰,運行到int 3的時候收到信號狼纬,然后通知debugger child已經(jīng)停止。debugger要做:
  1. 恢復原來的指令
  2. ip - 1
  3. 可以peek child的狀態(tài)骂际,比如寄存器疗琉,變量等
  4. 恢復breakpoint,因為用戶沒有要求delete breakpoint
/* Obtain and show child's instruction pointer */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
procmsg("Child started. EIP = 0x%08x\n", regs.eip);
/* Look at the word at the address we're interested in */
unsigned addr = 0x8048096;
unsigned data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0);
procmsg("Original data at 0x%08x: 0x%08x\n", addr, data);

[13028] Child started. EIP = 0x08048080
[13028] Original data at 0x08048096: 0x000007ba
/* Write the trap instruction 'int 3' into the address */
unsigned data_with_trap = (data & 0xFFFFFF00) | 0xCC;
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data_with_trap)
;
/* See what's there again... */
unsigned readback_data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0);
procmsg("After trap, data at 0x%08x: 0x%08x\n", addr, readback_data);

[13028] After trap, data at 0x08048096: 0x000007cc
/* Let the child run to the breakpoint and wait for it to** reach it*/
ptrace(PTRACE_CONT, child_pid, 0, 0);
wait(&wait_status);
if (WIFSTOPPED(wait_status)) { 
    procmsg("Child got a signal: %s\n", strsignal(WSTOPSIG(wait_status)));
}else { 
    perror("wait"); return;
}
/* See where the child is now */
ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
procmsg("Child stopped at EIP = 0x%08x\n", regs.eip);

This prints:
Hello,[13028] Child got a signal: Trace/breakpoint trap[13028] Child stopped at EIP = 0x08048097
/* Remove the breakpoint by restoring the previous data** at the target address, and unwind the EIP back by 1 to** let the CPU execute the original instruction that was** there.*/
ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data);
regs.eip -= 1;
ptrace(PTRACE_SETREGS, child_pid, 0, &regs);
/* The child can continue running now */
ptrace(PTRACE_CONT, child_pid, 0, 0);
  • int 3只占用一個字節(jié)是故意為之的歉铝,否則可能損毀后面的指令

  • 假設有下面的代碼盈简,我想在do_stuff的入口break。首先可以先差找到入口指令的地址太示,這里因為是個循環(huán)我們要使breakpoint能夠在觸發(fā)后恢復柠贤。

#include <stdio.h>
void do_stuff()
{ 
    printf("Hello, ");
}
int main()
{
    for (int i = 0; i < 4; ++i) 
        do_stuff(); 
    printf("world!\n"); 
    return 0;
}

080483e4 <do_stuff>:
 80483e4:     55                      push   %ebp
 80483e5:     89 e5                   mov    %esp,%ebp
 80483e7:     83 ec 18                sub    $0x18,%esp
 80483ea:     c7 04 24 f0 84 04 08    movl   $0x80484f0,(%esp)
 80483f1:     e8 22 ff ff ff          call   8048318 <puts@plt>
 80483f6:     c9                      leave
 80483f7:     c3                      ret
void run_debugger(pid_t child_pid)
{
    procmsg("debugger started\n");

    /* Wait for child to stop on its first instruction */
    wait(0);
    procmsg("child now at EIP = 0x%08x\n", get_child_eip(child_pid));

    /* Create breakpoint and run to it*/
    debug_breakpoint* bp = create_breakpoint(child_pid, (void*)0x080483e4);
    procmsg("breakpoint created\n");
    ptrace(PTRACE_CONT, child_pid, 0, 0);
    wait(0);

    /* Loop as long as the child didn't exit */
    while (1) {
        /* The child is stopped at a breakpoint here. Resume its
        ** execution until it either exits or hits the
        ** breakpoint again.
        */
        procmsg("child stopped at breakpoint. EIP = 0x%08X\n", get_child_eip(child_pid));
        procmsg("resuming\n");
        int rc = resume_from_breakpoint(child_pid, bp);

        if (rc == 0) {
            procmsg("child exited\n");
            break;
        }
        else if (rc == 1) {
            continue;
        }
        else {
            procmsg("unexpected: %d\n", rc);
            break;
        }
    }


    cleanup_breakpoint(bp);
}
int resume_from_breakpoint(pid_t pid, debug_breakpoint* bp)

{

struct user_regs_struct regs;

int wait_status;

ptrace(PTRACE_GETREGS, pid, 0, &regs);

/* Make sure we indeed are stopped at bp */

assert(regs.eip == (long) bp->addr + 1);

/* Now disable the breakpoint, rewind EIP back to the original instruction

 ** and single-step the process. This executes the original instruction that

 ** was replaced by the breakpoint.

 */

regs.eip = (long) bp->addr;

ptrace(PTRACE_SETREGS, pid, 0, &regs);

disable_breakpoint(pid, bp);

if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) {

perror("ptrace");

return -1;

}

wait(&wait_status);

if (WIFEXITED(wait_status))

return 0;

/* Re-enable the breakpoint and let the process run.

 */

enable_breakpoint(pid, bp);

if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) {

perror("ptrace");

return -1;

}

wait(&wait_status);

if (WIFEXITED(wait_status))

return 0;

else if (WIFSTOPPED(wait_status)) {

return 1;

}

else

return -1;

}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市先匪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弃衍,老刑警劉巖呀非,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡岸裙,警方通過查閱死者的電腦和手機猖败,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來降允,“玉大人恩闻,你說我怎么就攤上這事【缍” “怎么了幢尚?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翅楼。 經(jīng)常有香客問我尉剩,道長,這世上最難降的妖魔是什么毅臊? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任理茎,我火速辦了婚禮,結(jié)果婚禮上管嬉,老公的妹妹穿的比我還像新娘皂林。我一直安慰自己,他們只是感情好蚯撩,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布础倍。 她就那樣靜靜地躺著,像睡著了一般求厕。 火紅的嫁衣襯著肌膚如雪著隆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天呀癣,我揣著相機與錄音美浦,去河邊找鬼。 笑死项栏,一個胖子當著我的面吹牛浦辨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沼沈,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼流酬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了列另?” 一聲冷哼從身側(cè)響起芽腾,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎页衙,沒想到半個月后摊滔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阴绢,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年艰躺,在試婚紗的時候發(fā)現(xiàn)自己被綠了呻袭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡腺兴,死狀恐怖左电,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情页响,我是刑警寧澤篓足,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站拘泞,受9級特大地震影響纷纫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陪腌,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一辱魁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诗鸭,春花似錦染簇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝌箍,卻和暖如春青灼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妓盲。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工杂拨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悯衬。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓弹沽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親筋粗。 傳聞我的和親對象是個殘疾皇子策橘,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,748評論 0 33
  • 1. Java基礎部分 基礎部分的順序:基本語法娜亿,類相關的語法丽已,內(nèi)部類的語法,繼承相關的語法买决,異常的語法沛婴,線程的語...
    子非魚_t_閱讀 31,667評論 18 399
  • 簡介: 提供一個讓有限的窗口變成一個大數(shù)據(jù)集的靈活視圖辰斋。 術語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,181評論 0 16
  • 幾個朋友小聚,家長里短地談工作瘸味,說生活,分享開心喜悅够挂,吐槽壓力糾結(jié)旁仿。 我說我每天從進到學校那一刻起,到離開學校止孽糖,...
    可比克克閱讀 430評論 10 10
  • 嘆息年少無事處枯冈, 思緒心頭萬物蘇。 感傷日后不得勢办悟, 懷中優(yōu)渥報復中尘奏。
    遨游于天際閱讀 311評論 0 3