導讀 | 變數是偷偷摸摸的。有時,它們會很高興地留在暫存器中,但是一轉頭就會跑到堆疊中。為了優化,編譯器可能會完全將它們從視窗中拋出。無論變數在記憶體中的如何移動,我們都需要一些方法在調試器中追蹤和操作它們。這篇文章將會教你如何處理偵錯器中的變量,並使用 libelfin 來示範一個簡單的實作。 |
- 準備環境
- 斷點
- 暫存器和記憶體
- ELF 和 DWARF
- 原始碼和訊號
- 源碼級逐步執行
- 原始碼級斷點
- 堆疊展開
- 處理變數
- 高階話題
在開始之前,請確保你使用的 libelfin 版本是我分支上的 fbreg。這包含了一些 hack 來支援獲取當前堆疊幀的基址並評估位置列表,這些都不是由原生的 libelfin 提供的。你可能需要給 GCC 傳遞 -gdwarf-2 參數使其產生相容的 DWARF 資訊。但是在實作之前,我將詳細說明 DWARF 5 最新規範中的位置編碼方式。如果你想要了解更多信息,那麼你可以從這裡獲取該標準。
DWARF 位置某一給定時刻的記憶體中變數的位置使用 DW_AT_location 屬性編碼在 DWARF 資訊中。位置描述可以是單一位置描述、複合位置描述或位置清單。
- 簡單位置描述:描述了物件的一個連續的部分(通常是所有部分)的位置。簡單位置描述可以描述可尋址記憶體或暫存器中的位置,或缺少位置(具有或不具有已知值)。例如,DW_OP_fbreg -32: 一個整個儲存的變數 - 從堆疊幀基址開始的32個位元組。
- 複合位置描述:根據片段描述對象,每個對象可以包含在暫存器的一部分或儲存在與其他片段無關的記憶體位置。例如, DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2:前四個位元組位於暫存器 3 中,後兩個位元組位於暫存器 10 中的一個變數。
- 位置清單:描述了具有有限生存期或在生存期內更改位置的物件。比如:
- [ 0]
DW_OP_reg0 - [ 1]
DW_OP_reg3 - [ 2]
DW_OP_reg2
- [ 0]
- 根據程式計數器的目前值,位置在暫存器之間移動的變數。
根據位置描述的種類,DW_AT_location 以三種不同的方式進行編碼。 exprloc 編碼簡單和複合的位置描述。它們由一個位元組長度組成,後面跟著一個 DWARF 表達式或位置描述。 loclist 和 loclistptr 的編碼位置列表,它們在 .debug_loclists 部分中提供索引或偏移量,該部分描述了實際的位置列表。
DWARF 表達式使用 DWARF 表達式計算變數的實際位置。這包括操作堆疊值的一系列操作。有很多 DWARF 操作可用,所以我不會詳細解釋它們。相反,我會從每一個表達式中給出一些例子,給你一個可用的東西。另外,不要害怕這些;libelfin將為我們處理所有這些複雜性。
- 字面編碼
- DW_OP_lit0、DW_OP_lit1……DW_OP_lit31
- 將字面量壓入堆疊
- DW_OP_addr
- 將位址運算元壓入堆疊
- DW_OP_constu
- 將無符號值壓入堆疊
- DW_OP_lit0、DW_OP_lit1……DW_OP_lit31
- 暫存器值
- DW_OP_fbreg
- 壓入在堆疊幀基址找到的值,偏移給定值
- DW_OP_breg0、DW_OP_breg1…… DW_OP_breg31
- 將給定暫存器的內容加上給定的偏移量壓入堆疊
- DW_OP_fbreg
- 堆疊操作
- DW_OP_dup
- 複製堆疊頂端的值
- DW_OP_deref
- 將堆疊頂部視為記憶體位址,並將其替換為該位址的內容
- DW_OP_dup
- 算術和邏輯運算
- DW_OP_and
- 彈出堆疊頂端的兩個值,並壓回它們的邏輯 AND
- DW_OP_plus
- 與 DW_OP_and 相同,但會加值
- DW_OP_and
- 控制流程操作
- DW_OP_le、DW_OP_eq、DW_OP_gt 等
- 彈出前兩個值,比較它們,如果條件為真,則壓入 1,否則為 0
- DW_OP_bra
- 條件分支:如果堆疊的頂部不是 0,則透過 offset 在表達式中向後或向後跳過
- DW_OP_le、DW_OP_eq、DW_OP_gt 等
- 輸入轉換
- DW_OP_convert
- 將堆疊頂部的值轉換為不同的類型,它由給定偏移量的 DWARF 資訊條目描述
- DW_OP_convert
- 特殊操作
- DW_OP_nop
- 什麼都不做!
- DW_OP_nop
DWARF 類型的表示需要足夠強大來為偵錯器使用者提供有用的變數表示。使用者經常希望能夠在應用程式層級進行調試,而不是在機器層級進行調試,並且他們需要了解他們的變數正在做什麼。
DWARF 類型與大多數其他調試資訊一起編碼在 DIE 中。它們可以具有指示其名稱、編碼、大小、位元組等的屬性。無數的類型標籤可用於表示指標、陣列、結構體、typedef 以及 C 或 C 程式中可以看到的任何其他內容。
以這個簡單的結構體為例:
struct test{ int i; float j; int k[42]; test* next; };
這個結構體的父 DIE 是這樣的:
DW_TAG_structure_type DW_AT_name "test" DW_AT_byte_size 0x000000b8 DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000001
上面說的是我們有一個叫做 test 的結構體,大小為 0xb8,在 test.cpp 的第 1 行宣告。接下來有許多描述成員的子 DIE。
DW_TAG_member DW_AT_name "i" DW_AT_type DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000002 DW_AT_data_member_location 0 DW_TAG_member DW_AT_name "j" DW_AT_type DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000003 DW_AT_data_member_location 4 DW_TAG_member DW_AT_name "k" DW_AT_type DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000004 DW_AT_data_member_location 8 DW_TAG_member DW_AT_name "next" DW_AT_type DW_AT_decl_file 0x00000001 test.cpp DW_AT_decl_line 0x00000005 DW_AT_data_member_location 176(as signed = -80)
每個成員都有一個名稱、一個類型(它是一個 DIE 偏移量)、一個宣告檔案和行,以及一個指向其成員所在的結構體的位元組偏移。其類型指向如下。
DW_TAG_base_type DW_AT_name "int" DW_AT_encoding DW_ATE_signed DW_AT_byte_size 0x00000004 DW_TAG_base_type DW_AT_name "float" DW_AT_encoding DW_ATE_float DW_AT_byte_size 0x00000004 DW_TAG_array_type DW_AT_type DW_TAG_subrange_type DW_AT_type DW_AT_count 0x0000002a DW_TAG_base_type DW_AT_name "sizetype" DW_AT_byte_size 0x00000008 DW_AT_encoding DW_ATE_unsigned DW_TAG_pointer_type DW_AT_type
如你所見,我筆記型電腦上的 int 是一個 4 位元組的有符號整數類型,float是一個 4 位元組的浮點數。整數數組類型透過指向 int 類型作為其元素類型,sizetype(可以認為是 size_t)作為索引類型,它具有 2a 個元素。 test * 類型是 DW_TAG_pointer_type,它引用 test DIE。
實作簡單的變數讀取器如上所述,libelfin 將為我們處理大部分複雜性。但是,它並沒有實現用於表示可變位置的所有方法,並且在我們的程式碼中處理這些將變得非常複雜。因此,我現在選擇只支援 exprloc。請根據需要新增對更多類型表達式的支援。如果你真的有勇氣,請提交補丁到 libelfin 中來幫助完成必要的支援!
處理變數主要是將不同部分定位在記憶體或暫存器中,讀取或寫入與之前相同。為了簡單起見,我只會告訴你如何實作讀取。
首先我們需要告訴 libelfin 如何從我們的進程中讀取暫存器。我們建立一個繼承自 expr_context 的類別並使用 ptrace 來處理所有內容:
class ptrace_expr_context : public dwarf::expr_context { public: ptrace_expr_context (pid_t pid) : m_pid{pid} {} dwarf::taddr reg (unsigned regnum) override { return get_register_value_from_dwarf_register(m_pid, regnum); } dwarf::taddr pc() override { struct user_regs_struct regs; ptrace(PTRACE_GETREGS, m_pid, nullptr, ®s); return regs.rip; } dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override { //TODO take into account size return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr); } private: pid_t m_pid; };
讀取將由我們 debugger 類別中的 read_variables 函數處理:
void debugger::read_variables() { using namespace dwarf; auto func = get_function_from_pc(get_pc()); //... }
我們上面做的第一件事是找到我們目前進入的函數,然後我們需要循環訪問該函數中的條目來尋找變數:
for (const auto& die : func) { if (die.tag == DW_TAG::variable) { //... } }
我們透過尋找 DIE 中的 DW_AT_location 條目來取得位置資訊:
auto loc_val = die[DW_AT::location];
接著我們確保它是一個 exprloc,並請求 libelfin 來評估我們的表達式:
if (loc_val.get_type() == value::type::exprloc) { ptrace_expr_context context {m_pid}; auto result = loc_val.as_exprloc().evaluate(&context);
現在我們已經評估了表達式,我們需要讀取變數的內容。它可以在記憶體或暫存器中,因此我們將處理這兩種情況:
switch (result.location_type) { case expr_result::type::address: { auto value = read_memory(result.value); std::cout <p>你可以看到,我根據變數的類型,列印輸出了值而沒有解釋。希望透過這個程式碼,你可以看到如何支援編寫變量,或用給定的名字搜尋變數。 </p> <p>最後我們可以將它加入我們的命令解析器中:</p> <pre class="brush:php;toolbar:false">else if(is_prefix(command, "variables")) { read_variables(); }測試一下
寫一些具有一些變數的小功能,不用最佳化並帶有偵錯資訊編譯它,然後查看是否可以讀取變數的值。嘗試寫入儲存變數的記憶體位址,並查看程式改變的行為。
已經有九篇文章了,剩下最後一篇!下一次我會討論一些你可能會感興趣的更高級的概念。現在你可以在這裡找到這個帖子的程式碼。
以上是探索Linux調試器中的變數處理技巧!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

在我們的尷尬系列的第六部分中,我們將探索下一個命令,該命令可以通過跳過冗餘處理步驟來提高腳本執行的效率。下一個命令是什麼? awk instruc中的下一個命令

在Linux系統中傳輸文件是一項常見任務,每個系統管理員都應掌握,尤其是涉及本地或遠程系統間的網絡傳輸。 Linux提供了兩種常用的工具來完成此任務:SCP(安全複製)和Rsync。兩者都提供了一種安全且便捷的方式,用於在本地或遠程機器之間傳輸文件。本文將詳細介紹如何使用SCP和Rsync命令來傳輸文件,包括本地和遠程文件傳輸。了解Linux中的scp(安全複製協議)scp命令是一個命令行程序,用於在兩個主機之間通過SSH(安全 Shell)安全地複製文件和目錄,這意味著在文件通過互聯網傳輸時,數

與Windows和Mac OS X相比,Linux的一個引人入勝的功能是對各種桌面環境的支持。這使桌面用戶可以根據其計算要求選擇最合適且最合適的桌面環境。 A

Libreoffice脫穎而出,是一個健壯的開源辦公室套件,適用於Linux,Windows和Mac平台。它擁有一系列用於處理Word文檔,電子表格,演示,圖紙,計算和Mathematica的高級功能

在AWK命令系列的較早部分中,我們的重點主要是從文件中讀取輸入。但是,如果您需要從stdin中讀取輸入怎麼辦?在Awk系列的第7部分中,我們將探索幾個示例,您可以在其中使用O的OF O的輸出。

Clifm是一個獨特而令人難以置信的迅速命令行文件管理器,在類似殼的界面的基礎上設計。這意味著用戶可以使用他們已經熟悉的命令與他們的文件系統互動。

如果您不想執行Linux Mint 22 Wilma的新安裝,則可以選擇從以前的版本中升級。在本指南中,我們將詳細介紹從Linux Mint 21.3升級的過程(21.x系列的最新次要版本


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。