自己動(dòng)手編寫(xiě)一個(gè)Linux調(diào)試器系列之1 準(zhǔn)備工作 by lantie@15PB

自己動(dòng)手編寫(xiě)一個(gè)Linux調(diào)試器系列之1 準(zhǔn)備工作 by lantie@15PB

Paste_Image.png

我想每個(gè)人都會(huì)編寫(xiě)不止一個(gè) hello world 程序, 并且使用調(diào)試器來(lái)調(diào)試這些程序(如果你沒(méi)有慨代,那放下你手上的活兒僻族,來(lái)學(xué)習(xí)使用調(diào)試器吧)堤如。然而撤逢,盡管調(diào)試器使用如此廣泛,但卻沒(méi)有很多資料可以告訴我們它的工作原理寓辱,以及如何編寫(xiě)一個(gè)調(diào)試器艘绍。特別是與編程時(shí)的其他工具技術(shù)(如編譯器)比起來(lái)。在這個(gè)系列的文章中秫筏,我們將會(huì)學(xué)習(xí)調(diào)試器的原理并編寫(xiě)一個(gè)調(diào)試器去調(diào)試Linux程序诱鞠。

我們將支持以下功能:

  • 啟動(dòng)、停止并繼續(xù)執(zhí)行
  • 設(shè)置各種斷點(diǎn)
    • 內(nèi)存地址
    • 源代碼行
    • 函數(shù)入口處
  • 讀取和寫(xiě)入寄存器和內(nèi)存
  • 單步跟蹤
    • 指令
    • 單步步入
    • 單步跳過(guò)
    • 單步步過(guò)
  • 打印當(dāng)前源碼位置
  • 打印椪饩矗回溯信息
  • 打印簡(jiǎn)單的值信息

最后我還會(huì)概述如何將以下功能添加到編寫(xiě)的調(diào)試器中:

  • 遠(yuǎn)程調(diào)試
  • 共享庫(kù)和動(dòng)態(tài)加載的支持
  • 表達(dá)式求值
  • 多線程調(diào)試的支持

我將使用C和C++來(lái)編寫(xiě)這個(gè)項(xiàng)目航夺,但這個(gè)項(xiàng)目同樣也適用于編譯成機(jī)器代碼和輸出標(biāo)準(zhǔn)的DWARF調(diào)試信息的編程語(yǔ)言。(如果你不知道這是什么崔涂,不要擔(dān)心阳掐,馬上就會(huì)清楚了)
此外, 我們的主要目的是在大多數(shù)情況下冷蚂,使程序都能正常運(yùn)行缭保,因此健壯的錯(cuò)誤處理會(huì)使編寫(xiě)變得更簡(jiǎn)單。


系列索引

  1. 準(zhǔn)備工作
  2. 斷點(diǎn)
  3. 寄存器和內(nèi)存
  4. ELF文件和調(diào)試信息
  5. 源碼和信號(hào)
  6. 源碼級(jí)單步
  7. 源碼級(jí)斷點(diǎn)
  8. 堆棧解除
  9. 處理變量
  10. 高級(jí)主題

開(kāi)始設(shè)置

在我們開(kāi)始討論之前蝙茶,讓我們先建立環(huán)境艺骂。在本教程中,我們將使用兩個(gè)依賴(lài)項(xiàng):

  • Linenoise 用于處理我們的命令行輸入
  • libelfin 用于解析調(diào)試信息隆夯。

你可以使用比較傳統(tǒng)的libdwarf而不是libelfin钳恕,但是其接口遠(yuǎn)沒(méi)有那么好,libelfin還提供了一個(gè)基本完整的DWARF表達(dá)式求值工具蹄衷,如果您想要讀取變量的話忧额,這將節(jié)省您很多時(shí)間。請(qǐng)務(wù)必您使用我的libelfinfbreg分支愧口,因?yàn)樗鼮閤86上的讀取變量提供了一些額外的支持睦番。

一旦你在系統(tǒng)中安裝了這些工具,或者在你的系統(tǒng)上編譯了相關(guān)的依賴(lài)項(xiàng)调卑,就可以開(kāi)始了抡砂。我只是將它們與我的CMake文件中的其他代碼一起編譯大咱。

啟動(dòng)程序

在我們調(diào)試一個(gè)程序時(shí)恬涧,首先我們需要先系統(tǒng)一個(gè)要調(diào)試的程序。我們可以使用經(jīng)典的 fork/exec 模式碴巾。

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Program name not specified";
        return -1;
    }

    auto prog = argv[1];

    auto pid = fork();
    if (pid == 0) {
        // 我們?cè)谧舆M(jìn)程中
        // 執(zhí)行要調(diào)試的程序

    }
    else if (pid >= 1)  {
        // 我們?cè)诟高M(jìn)程中
        // 執(zhí)行調(diào)試器
    }

我們調(diào)用fork會(huì)使我們的程序分為兩個(gè)進(jìn)程溯捆,如果我們?cè)谧舆M(jìn)程中fork返回0,如果我們?cè)诟高M(jìn)程中,則返回子進(jìn)程的進(jìn)程ID提揍。

如果我們?cè)谧舆M(jìn)程中啤月,我們想用我們要調(diào)試的程序替換當(dāng)前正在執(zhí)行的程序,從而達(dá)到調(diào)試程序的目的劳跃。

   ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);
   execl(prog, prog, nullptr);

這里我們第一次使用ptrace谎仲,它將在編寫(xiě)調(diào)試器時(shí)成為我們最好的朋友。ptrace允許我們通過(guò)讀取寄存器刨仑,讀取內(nèi)存郑诺,單步執(zhí)行等來(lái)觀察和控制另一個(gè)進(jìn)程的執(zhí)行。
這個(gè)API非常難看杉武,它是一個(gè)單一的函數(shù)辙诞,其中提了一些枚舉值可以使用,還有一些參數(shù)可以根據(jù)你提供的值使用或是忽略轻抱。函數(shù)的簽名如下所示:

long ptrace(enum __ptrace_request request, pid_t pid,
            void *addr, void *data);
  • request是我們對(duì)想要去跟蹤的進(jìn)程能做什么飞涂。
  • pid是跟蹤進(jìn)程的進(jìn)程id。
  • addr是內(nèi)存地址祈搜,這是用在跟蹤時(shí)一些調(diào)用指定地址较店。
  • data是某些請(qǐng)求特定的資源。
  • 返回值通常會(huì)提供錯(cuò)誤信息容燕,因此需要在編寫(xiě)代碼時(shí)對(duì)返回值進(jìn)行檢查泽西,更多信息可以查閱man手冊(cè)。

在上面的代碼中 request的值是PTRACE_TRACEME時(shí) 表面這個(gè)進(jìn)程應(yīng)該允許其父進(jìn)程跟蹤它缰趋,所有其他參數(shù)可以被忽略捧杉,因?yàn)锳PI設(shè)計(jì)的參數(shù)就不太重要。

下一步秘血,我們調(diào)用 execl味抖,這是許多 exec的類(lèi)似函數(shù)的其中一個(gè)。我們執(zhí)行給定的程序灰粮,通過(guò)它的名稱(chēng)作為命令行參數(shù)和一個(gè)nullptr終止參數(shù)列表仔涩。如果你愿意,你可以將nullptr替換為你的程序所需的任何其他參數(shù)粘舟。

在我們完成這項(xiàng)工作之后熔脂,我們完成了子進(jìn)程,我們將讓它繼續(xù)運(yùn)行柑肴,直到我們完成它為止霞揉。

添加調(diào)試器循環(huán)

現(xiàn)在我們已經(jīng)啟動(dòng)了子進(jìn)程,我們希望能夠與它進(jìn)行交互晰骑。 為此适秩,我們將創(chuàng)建一個(gè)debugger類(lèi),為其提供一個(gè)用于監(jiān)聽(tīng)用戶輸入的循環(huán),并從我們的main函數(shù)中父進(jìn)程的fork之后開(kāi)始秽荞。

else if (pid >= 1)  {
    //parent
    debugger dbg{prog, pid};
    dbg.run();
}
class debugger {
public:
    debugger (std::string prog_name, pid_t pid)
        : m_prog_name{std::move(prog_name)}, m_pid{pid} {}

    void run();

private:
    std::string m_prog_name;
    pid_t m_pid;
};

在我們的run函數(shù)中骤公,我們需要等待子進(jìn)程完成啟動(dòng),然后繼續(xù)從linenoise獲取輸入扬跋,直到得到一個(gè)EOF(ctrl + d)阶捆。

void debugger::run() {
    int wait_status;
    auto options = 0;
    waitpid(m_pid, &wait_status, options);

    char* line = nullptr;
    while((line = linenoise("minidbg> ")) != nullptr) {
        handle_command(line);
        linenoiseHistoryAdd(line);
        linenoiseFree(line);
    }
}

當(dāng)跟蹤進(jìn)程啟動(dòng)時(shí),將發(fā)送一個(gè)SIGTRAP信號(hào)钦听,它是一個(gè)跟蹤或斷點(diǎn)陷阱趁猴。 我們可以等到這個(gè)信號(hào)使用 waitpid 函數(shù)發(fā)送。

在我們知道這個(gè)進(jìn)程已經(jīng)準(zhǔn)備好進(jìn)行調(diào)試之后彪见,我們會(huì)監(jiān)聽(tīng)用戶的輸入儡司。linenoise 函數(shù)自動(dòng)顯示和處理用戶輸入的提示。 這意味著我們得到一個(gè)很好的命令行與歷史和導(dǎo)航命令余指,而不需要做太多的工作捕犬。 當(dāng)我們得到輸入時(shí),我們給一個(gè)handle_command函數(shù)給出這個(gè)命令酵镜,我們將很快寫(xiě)入碉碉,然后我們將這個(gè)命令添加到linenoise歷史中并釋放資源。

處理輸入

我們的命令將遵循與gdblldb類(lèi)似的格式淮韭。 要繼續(xù)該程序垢粮,用戶將鍵入continuecont或甚至c。 如果他們想在地址上設(shè)置一個(gè)斷點(diǎn)靠粪,它們會(huì)寫(xiě)入break 0xDEADBEEF蜡吧,其中0xDEADBEEF是十六進(jìn)制格式的所需地址。 我們添加對(duì)這些命令的支持占键。

void debugger::handle_command(const std::string& line) {
    auto args = split(line,' ');
    auto command = args[0];

    if (is_prefix(command, "continue")) {
        continue_execution();
    }
    else {
        std::cerr << "Unknown command\n";
    }
}

splitis_prefix是一些小的幫助函數(shù):

std::vector<std::string> split(const std::string &s, char delimiter) {
    std::vector<std::string> out{};
    std::stringstream ss {s};
    std::string item;

    while (std::getline(ss,item,delimiter)) {
        out.push_back(item);
    }

    return out;
}

bool is_prefix(const std::string& s, const std::string& of) {
    if (s.size() > of.size()) return false;
    return std::equal(s.begin(), s.end(), of.begin());
}

我們將在debugger類(lèi)中添加continue_execution昔善。

void debugger::continue_execution() {
    ptrace(PTRACE_CONT, m_pid, nullptr, nullptr);

    int wait_status;
    auto options = 0;
    waitpid(m_pid, &wait_status, options);
}

現(xiàn)在我們的continue_execution函數(shù)只是使用ptrace來(lái)告訴進(jìn)程繼續(xù),然后調(diào)用waitpid直到它發(fā)出信號(hào)畔乙。

完成準(zhǔn)備工作

現(xiàn)在君仆,你應(yīng)該可以編譯一些C或C++程序,通過(guò)自己寫(xiě)的調(diào)試器運(yùn)行它牲距,看到它停止輸入返咱,并能夠從調(diào)試器繼續(xù)執(zhí)行。 在下一部分中牍鞠,我們將學(xué)習(xí)如何讓我們的調(diào)試器設(shè)置斷點(diǎn)咖摹。 如果遇到任何問(wèn)題,請(qǐng)?jiān)谠u(píng)論中通知我皮服!

你可以在這里找到這篇文章的代碼楞艾。

說(shuō)明

原文來(lái)自:https://blog.tartanllama.xyz/writing-a-linux-debugger-setup/
翻譯來(lái)自:lantie@15PB, 15PB信息安全教育,主頁(yè):http://www.15pb.com.cn

運(yùn)行截圖

使用Clion編寫(xiě)的代碼龄广,在控制臺(tái)中的運(yùn)行結(jié)果


code
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硫眯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子择同,更是在濱河造成了極大的恐慌两入,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敲才,死亡現(xiàn)場(chǎng)離奇詭異裹纳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)紧武,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)剃氧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人阻星,你說(shuō)我怎么就攤上這事朋鞍。” “怎么了妥箕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵滥酥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我畦幢,道長(zhǎng)坎吻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任宇葱,我火速辦了婚禮瘦真,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黍瞧。我一直安慰自己吗氏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般折欠。 火紅的嫁衣襯著肌膚如雪哑子。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天蝶防,我揣著相機(jī)與錄音,去河邊找鬼。 笑死仿村,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兴喂。 我是一名探鬼主播蔼囊,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼焚志,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了畏鼓?” 一聲冷哼從身側(cè)響起酱酬,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎云矫,沒(méi)想到半個(gè)月后膳沽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡让禀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年挑社,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巡揍。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痛阻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腮敌,到底是詐尸還是另有隱情录平,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布缀皱,位于F島的核電站斗这,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啤斗。R本人自食惡果不足惜表箭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钮莲。 院中可真熱鬧免钻,春花似錦、人聲如沸崔拥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)链瓦。三九已至拆魏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慈俯,已是汗流浹背渤刃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贴膘,地道東北人卖子。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像刑峡,于是被迫代替她去往敵國(guó)和親洋闽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玄柠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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