<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è)GDB|設(shè)置斷點(diǎn)(原理篇)

          共 3708字,需瀏覽 8分鐘

           ·

          2022-05-16 01:38

          在上一篇文章《自己動(dòng)手寫一個(gè)GDB|基礎(chǔ)功能》中,我們介紹了怎么使用?ptrace()?系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單進(jìn)程追蹤程序,本文主要介紹怎么實(shí)現(xiàn)斷點(diǎn)設(shè)置功能。

          什么是斷點(diǎn)

          當(dāng)使用 GDB 調(diào)試程序時(shí),如果想在程序執(zhí)行到某個(gè)位置(某一行代碼)時(shí)停止運(yùn)行,我們可以通過(guò)在此處位置設(shè)置一個(gè)?斷點(diǎn)?來(lái)實(shí)現(xiàn)。

          當(dāng)程序執(zhí)行到斷點(diǎn)的位置時(shí),會(huì)停止運(yùn)行。這時(shí),我們可以對(duì)進(jìn)程進(jìn)行調(diào)試,比如打印當(dāng)前進(jìn)程的堆棧信息或者打印變量的值等。如下圖所示:

          斷點(diǎn)原理

          要說(shuō)明?斷點(diǎn)?的原理,我們首先需要了解下什么是?中斷。本公眾號(hào)以前也寫過(guò)很多關(guān)于?中斷?的文章,例如:《一文看懂|Linux中斷處理》。

          想深入了解中斷原理的,可以看看上文。下面簡(jiǎn)單介紹一下什么是中斷:

          中斷?是為了解決外部設(shè)備完成某些工作后通知CPU的一種機(jī)制(譬如硬盤完成讀寫操作后通過(guò)中斷告知CPU已經(jīng)完成)。

          從物理學(xué)的角度看,中斷是一種電信號(hào),由硬件設(shè)備產(chǎn)生,并直接送入中斷控制器(如 8259A)的輸入引腳上,然后再由中斷控制器向處理器發(fā)送相應(yīng)的信號(hào)。處理器一經(jīng)檢測(cè)到該信號(hào),便中斷自己當(dāng)前正在處理的工作,轉(zhuǎn)而去處理中斷。此后,處理器會(huì)通知 OS 已經(jīng)產(chǎn)生中斷。這樣,OS 就可以對(duì)這個(gè)中斷進(jìn)行適當(dāng)?shù)奶幚怼2煌脑O(shè)備對(duì)應(yīng)的中斷不同,而每個(gè)中斷都通過(guò)一個(gè)唯一的數(shù)字標(biāo)識(shí),這些值通常被稱為中斷請(qǐng)求線。

          如果進(jìn)程在運(yùn)行的過(guò)程中,發(fā)生了中斷,CPU 將會(huì)停止運(yùn)行當(dāng)前進(jìn)程,轉(zhuǎn)而執(zhí)行內(nèi)核設(shè)置好的?中斷服務(wù)例程。如下圖所示:

          軟中斷

          大概了解中斷的原理后,接下來(lái)我們將會(huì)介紹?斷點(diǎn)?會(huì)用到的?軟中斷?功能。軟中斷跟上面介紹的中斷(也稱為?硬中斷)類似,不過(guò)軟中斷并不是由外部設(shè)備產(chǎn)生,而是有特殊的指令觸發(fā),這個(gè)特殊的指令稱為?int3

          int3?是一個(gè)單節(jié)的操作碼(十六進(jìn)制為?0xcc)。當(dāng) CPU 執(zhí)行到?int3?指令時(shí),將會(huì)停止運(yùn)行當(dāng)前進(jìn)程,轉(zhuǎn)而執(zhí)行內(nèi)核定義好的 int3 中斷處理例程:do_int3()

          do_int3()?例程會(huì)向當(dāng)前進(jìn)程發(fā)送一個(gè)?SIGTRAP?信號(hào),當(dāng)進(jìn)程接收到?SIGTRAP?信號(hào)后,CPU 將會(huì)停止執(zhí)行當(dāng)前進(jìn)程。這時(shí)調(diào)試進(jìn)程(GDB)就可以對(duì)進(jìn)程進(jìn)行調(diào)試,如:打印變量的值、打印堆棧信息等。

          設(shè)置斷點(diǎn)

          從上面的介紹可知,設(shè)置斷點(diǎn)的目的是讓進(jìn)程停止運(yùn)行,從而調(diào)試進(jìn)程(GDB)就可以對(duì)其進(jìn)行調(diào)試。

          接下來(lái),我們將會(huì)介紹如何設(shè)置一個(gè)斷點(diǎn)。

          我們知道,當(dāng) CPU 執(zhí)行到?int3?指令(0xcc)時(shí)會(huì)停止運(yùn)行當(dāng)前進(jìn)程。所以,我們只需要在要進(jìn)行設(shè)置斷點(diǎn)的位置改為?int3?指令即可。如下圖所示:

          從上圖可以看出,設(shè)置斷點(diǎn)時(shí),只需要在要設(shè)置斷點(diǎn)的位置修改為?int3?指令即可。但我們還需要保存原來(lái)被替換的指令,因?yàn)檎{(diào)試完畢后,我們還需要把?int3?指令修改為原來(lái)的指令,這樣程序才能正常運(yùn)行。

          斷點(diǎn)實(shí)現(xiàn)

          既然,我們已經(jīng)知道了斷點(diǎn)的原理。那么,現(xiàn)在是時(shí)候介紹怎么實(shí)現(xiàn)斷點(diǎn)功能了。

          我們來(lái)說(shuō)說(shuō)設(shè)置斷點(diǎn)的步驟吧:

          • 第一步:找到要設(shè)置斷點(diǎn)的地址。
          • 第二步:保存此地址處的數(shù)據(jù)(為了調(diào)試完能夠恢復(fù)原來(lái)的指令)。
          • 第三步:我們把此地址處的指令替換成 int3 指令。
          • 第四步:讓被調(diào)試的進(jìn)程繼續(xù)運(yùn)行,直到執(zhí)行到 int3 指令(也就是斷點(diǎn))。此時(shí),被調(diào)試進(jìn)程會(huì)停止運(yùn)行,調(diào)試進(jìn)程(GDB)就可以對(duì)進(jìn)程進(jìn)行調(diào)試。
          • 第五步:調(diào)試完畢后,恢復(fù)斷點(diǎn)處原來(lái)的指令,并且讓 IP 寄存器回退一個(gè)字節(jié)(因?yàn)閿帱c(diǎn)處原來(lái)的代碼還沒(méi)執(zhí)行)。
          • 第六步:把被調(diào)試進(jìn)程設(shè)置為單步調(diào)試模式,這是因?yàn)橐趫?zhí)行完斷點(diǎn)處原來(lái)的指令后,重新設(shè)置斷點(diǎn)(為什么?這是因?yàn)樵谝恍┭h(huán)語(yǔ)句中,可能需要重新執(zhí)行原來(lái)的斷點(diǎn))。

          知道斷點(diǎn)實(shí)現(xiàn)的步驟后,我們可以開始編寫代碼了。

          我們定義一個(gè)結(jié)構(gòu)體?breakpoint_context?用于保存斷點(diǎn)被設(shè)置前的信息:

          struct?breakpoint_context
          {

          ????void?*addr;?//?設(shè)置斷點(diǎn)的地址
          ????long?data;??//?斷點(diǎn)原來(lái)的數(shù)據(jù)
          };

          圍繞?breakpoint_context?結(jié)構(gòu),我們定義幾個(gè)輔助函數(shù),分別是:

          • create_breakpoint():用于創(chuàng)建一個(gè)斷點(diǎn)。
          • enable_breakpoint():用于啟用斷點(diǎn)。
          • disable_breakpoint():用于禁用斷點(diǎn)。
          • free_breakpoint():用于釋放斷點(diǎn)。

          現(xiàn)在我們來(lái)實(shí)現(xiàn)這幾個(gè)輔助函數(shù)。

          1. 創(chuàng)建斷點(diǎn)

          首先,我們來(lái)實(shí)現(xiàn)用于創(chuàng)建一個(gè)斷點(diǎn)的輔助函數(shù)?create_breakpoint()

          breakpoint_context?*create_breakpoint(void?*addr)
          {
          ????breakpoint_context?*ctx?=?malloc(sizeof(*ctx));
          ????if?(ctx)?{
          ????????ctx->addr?=?addr;
          ????????ctx->data?=?NULL;
          ????}

          ????return?ctx;
          }

          create_breakpoint()?函數(shù)需要提供一個(gè)類型為?void *?的參數(shù),表示要設(shè)置的斷點(diǎn)地址。

          create_breakpoint()?函數(shù)的實(shí)現(xiàn)比較簡(jiǎn)單,首先調(diào)用?malloc()?函數(shù)申請(qǐng)一個(gè)?breakpoint_context?結(jié)構(gòu),然后把?addr?字段設(shè)置為斷點(diǎn)的地址,并且把?data?字段設(shè)置為 NULL。

          2. 啟用斷點(diǎn)

          啟用斷點(diǎn)的原理是:首先讀取斷點(diǎn)處的數(shù)據(jù),并且保存到?breakpoint_context?結(jié)構(gòu)的?data?字段中。然后將斷點(diǎn)處的指令設(shè)置為?int3?指令。

          獲取某個(gè)內(nèi)存地址處的數(shù)據(jù)可以使用?ptrace(PTRACE_PEEKTEXT,...)?函數(shù)來(lái)實(shí)現(xiàn),如下所示:

          long?data?=?ptrace(PTRACE_PEEKTEXT,?pid,?address,?0);

          在上面代碼中,pid?參數(shù)指定了目標(biāo)進(jìn)程的PID,而?address?參數(shù)指定了要獲取此內(nèi)存地址處的數(shù)據(jù)。

          而要將某內(nèi)存地址處設(shè)置為制定的值,可以使用?ptrace(PTRACE_POKETEXT,...)?函數(shù)來(lái)實(shí)現(xiàn),如下所示:

          ptrace(PTRACE_POKETEXT,?pid,?address,?data);

          在上面代碼中,pid?參數(shù)指定了目標(biāo)進(jìn)程的PID,而?address?參數(shù)指定了要將此內(nèi)存地址處的值設(shè)置為?data

          有了上面的基礎(chǔ),現(xiàn)在我們可以來(lái)編寫?enable_breakpoint()?函數(shù)的代碼了:

          void?enable_breakpoint(pid_t?pid,?breakpoint_context?*ctx)
          {
          ????//?1.?獲取斷點(diǎn)處的數(shù)據(jù),?并且保存到?breakpoint_context?結(jié)構(gòu)的?data?字段中
          ????ctx->data?=?ptrace(PTRACE_PEEKTEXT,?pid,?ctx->addr,?0);

          ????//?2.?把斷點(diǎn)處的值設(shè)置為?int3?指令(0xCC)
          ????ptrace(PTRACE_POKETEXT,?pid,?ctx->addr,?(ctx->data?&?0xFFFFFF00)?|?0xCC);
          }

          enable_breakpoint()?函數(shù)的原理,上面已經(jīng)詳細(xì)介紹過(guò)了。

          不過(guò)有一點(diǎn)我們需要注意的,就是使用?ptrace()?函數(shù)一次只能獲取和設(shè)置一個(gè) 4 字節(jié)大小的長(zhǎng)整型數(shù)據(jù)。但是?int3?指令是一個(gè)單子節(jié)指令,所以設(shè)置斷點(diǎn)時(shí),需要對(duì)設(shè)置的數(shù)據(jù)進(jìn)行處理。如下圖所示:

          3. 禁用斷點(diǎn)

          禁用斷點(diǎn)的原理與啟用斷點(diǎn)剛好相反,就是把斷點(diǎn)處的?int3?指令替換成原來(lái)的指令,原理如下圖所示:

          由于?breakpoint_context?結(jié)構(gòu)的?data?字段保存了斷點(diǎn)處原來(lái)的指令,所以我們只需要把斷點(diǎn)處的指令替換成?data?字段的數(shù)據(jù)即可,代碼如下:

          void?disable_breakpoint(pid_t?pid,?breakpoint_context?*ctx)
          {
          ????long?data?=?ptrace(PTRACE_PEEKTEXT,?pid,?ctx->addr,?0);
          ????ptrace(PTRACE_POKETEXT,?pid,?ctx->addr,?(data?&?0xFFFFFF00)?|?(ctx->data?&?0xFF));
          }

          4. 釋放斷點(diǎn)

          釋放斷點(diǎn)的實(shí)現(xiàn)就非常簡(jiǎn)單了,只需要調(diào)用?free()?函數(shù)把?breakpoint_context?結(jié)構(gòu)占用的內(nèi)存釋放掉即可,代碼如下:

          void?free_breakpoint(breakpoint_context?*ctx)
          {
          ????free(ctx);
          }

          總結(jié)

          本來(lái)想一口氣把斷點(diǎn)的原理和實(shí)現(xiàn)都在本文寫完的,但寫著寫著發(fā)現(xiàn)篇幅有點(diǎn)長(zhǎng)。所以,決定把斷點(diǎn)分為原理篇和實(shí)現(xiàn)篇。

          本文是斷點(diǎn)設(shè)置的原理篇,下一篇文章中,我們將會(huì)介紹如何使用上面介紹的知識(shí)點(diǎn)和輔助函數(shù)來(lái)實(shí)現(xiàn)我們的斷點(diǎn)設(shè)置功能,敬請(qǐng)期待。


          瀏覽 91
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  日韩一级无码特黄AAA片 | 欧美成人一区二区三区片 | 六月婷婷色 | 思思热高清无码播放 | 日本三级网站在线播放 |