自己動手編寫一個Linux調試器系列之1 準備工作 by lantie@15PB

自己動手編寫一個Linux調試器系列之1 準備工作 by lantie@15PB

Paste_Image.png

我想每個人都會編寫不止一個 hello world 程序, 并且使用調試器來調試這些程序(如果你沒有,那放下你手上的活兒,來學習使用調試器吧)。然而,盡管調試器使用如此廣泛,但卻沒有很多資料可以告訴我們它的工作原理,以及如何編寫一個調試器。特別是與編程時的其他工具技術(如編譯器)比起來。在這個系列的文章中,我們將會學習調試器的原理并編寫一個調試器去調試Linux程序。

我們將支持以下功能:

  • 啟動、停止并繼續執行
  • 設置各種斷點
    • 內存地址
    • 源代碼行
    • 函數入口處
  • 讀取和寫入寄存器和內存
  • 單步跟蹤
    • 指令
    • 單步步入
    • 單步跳過
    • 單步步過
  • 打印當前源碼位置
  • 打印棧回溯信息
  • 打印簡單的值信息

最后我還會概述如何將以下功能添加到編寫的調試器中:

  • 遠程調試
  • 共享庫和動態加載的支持
  • 表達式求值
  • 多線程調試的支持

我將使用C和C++來編寫這個項目,但這個項目同樣也適用于編譯成機器代碼和輸出標準的DWARF調試信息的編程語言。(如果你不知道這是什么,不要擔心,馬上就會清楚了)
此外, 我們的主要目的是在大多數情況下,使程序都能正常運行,因此健壯的錯誤處理會使編寫變得更簡單。


系列索引

  1. 準備工作
  2. 斷點
  3. 寄存器和內存
  4. ELF文件和調試信息
  5. 源碼和信號
  6. 源碼級單步
  7. 源碼級斷點
  8. 堆棧解除
  9. 處理變量
  10. 高級主題

開始設置

在我們開始討論之前,讓我們先建立環境。在本教程中,我們將使用兩個依賴項:

  • Linenoise 用于處理我們的命令行輸入
  • libelfin 用于解析調試信息。

你可以使用比較傳統的libdwarf而不是libelfin,但是其接口遠沒有那么好,libelfin還提供了一個基本完整的DWARF表達式求值工具,如果您想要讀取變量的話,這將節省您很多時間。請務必您使用我的libelfinfbreg分支,因為它為x86上的讀取變量提供了一些額外的支持。

一旦你在系統中安裝了這些工具,或者在你的系統上編譯了相關的依賴項,就可以開始了。我只是將它們與我的CMake文件中的其他代碼一起編譯。

啟動程序

在我們調試一個程序時,首先我們需要先系統一個要調試的程序。我們可以使用經典的 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) {
        // 我們在子進程中
        // 執行要調試的程序

    }
    else if (pid >= 1)  {
        // 我們在父進程中
        // 執行調試器
    }

我們調用fork會使我們的程序分為兩個進程,如果我們在子進程中fork返回0,如果我們在父進程中,則返回子進程的進程ID。

如果我們在子進程中,我們想用我們要調試的程序替換當前正在執行的程序,從而達到調試程序的目的。

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

這里我們第一次使用ptrace,它將在編寫調試器時成為我們最好的朋友。ptrace允許我們通過讀取寄存器,讀取內存,單步執行等來觀察和控制另一個進程的執行。
這個API非常難看,它是一個單一的函數,其中提了一些枚舉值可以使用,還有一些參數可以根據你提供的值使用或是忽略。函數的簽名如下所示:

long ptrace(enum __ptrace_request request, pid_t pid,
            void *addr, void *data);
  • request是我們對想要去跟蹤的進程能做什么。
  • pid是跟蹤進程的進程id。
  • addr是內存地址,這是用在跟蹤時一些調用指定地址。
  • data是某些請求特定的資源。
  • 返回值通常會提供錯誤信息,因此需要在編寫代碼時對返回值進行檢查,更多信息可以查閱man手冊。

在上面的代碼中 request的值是PTRACE_TRACEME時 表面這個進程應該允許其父進程跟蹤它,所有其他參數可以被忽略,因為API設計的參數就不太重要。

下一步,我們調用 execl,這是許多 exec的類似函數的其中一個。我們執行給定的程序,通過它的名稱作為命令行參數和一個nullptr終止參數列表。如果你愿意,你可以將nullptr替換為你的程序所需的任何其他參數。

在我們完成這項工作之后,我們完成了子進程,我們將讓它繼續運行,直到我們完成它為止。

添加調試器循環

現在我們已經啟動了子進程,我們希望能夠與它進行交互。 為此,我們將創建一個debugger類,為其提供一個用于監聽用戶輸入的循環,并從我們的main函數中父進程的fork之后開始。

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函數中,我們需要等待子進程完成啟動,然后繼續從linenoise獲取輸入,直到得到一個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);
    }
}

當跟蹤進程啟動時,將發送一個SIGTRAP信號,它是一個跟蹤或斷點陷阱。 我們可以等到這個信號使用 waitpid 函數發送。

在我們知道這個進程已經準備好進行調試之后,我們會監聽用戶的輸入。linenoise 函數自動顯示和處理用戶輸入的提示。 這意味著我們得到一個很好的命令行與歷史和導航命令,而不需要做太多的工作。 當我們得到輸入時,我們給一個handle_command函數給出這個命令,我們將很快寫入,然后我們將這個命令添加到linenoise歷史中并釋放資源。

處理輸入

我們的命令將遵循與gdblldb類似的格式。 要繼續該程序,用戶將鍵入continuecont或甚至c。 如果他們想在地址上設置一個斷點,它們會寫入break 0xDEADBEEF,其中0xDEADBEEF是十六進制格式的所需地址。 我們添加對這些命令的支持。

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是一些小的幫助函數:

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類中添加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);
}

現在我們的continue_execution函數只是使用ptrace來告訴進程繼續,然后調用waitpid直到它發出信號。

完成準備工作

現在,你應該可以編譯一些C或C++程序,通過自己寫的調試器運行它,看到它停止輸入,并能夠從調試器繼續執行。 在下一部分中,我們將學習如何讓我們的調試器設置斷點。 如果遇到任何問題,請在評論中通知我!

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

說明

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

運行截圖

使用Clion編寫的代碼,在控制臺中的運行結果


code
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容