自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

開發(fā)一個Linux調(diào)試器(九):處理變量

系統(tǒng) Linux
變量是偷偷摸摸的。有時,它們會很高興地呆在寄存器中,但是一轉(zhuǎn)頭就會跑到堆棧中。為了優(yōu)化,編譯器可能會完全將它們從窗口中拋出。無論變量在內(nèi)存中的如何移動,我們都需要一些方法在調(diào)試器中跟蹤和操作它們。這篇文章將會教你如何處理調(diào)試器中的變量,并使用 libelfin 演示一個簡單的實現(xiàn)。

[[205557]]

變量是偷偷摸摸的。有時,它們會很高興地呆在寄存器中,但是一轉(zhuǎn)頭就會跑到堆棧中。為了優(yōu)化,編譯器可能會完全將它們從窗口中拋出。無論變量在內(nèi)存中的如何移動,我們都需要一些方法在調(diào)試器中跟蹤和操作它們。這篇文章將會教你如何處理調(diào)試器中的變量,并使用 libelfin 演示一個簡單的實現(xiàn)。

系列文章索引

  1. 準(zhǔn)備環(huán)境
  2. 斷點
  3. 寄存器和內(nèi)存
  4. ELF 和 DWARF
  5. 源碼和信號
  6. 源碼級逐步執(zhí)行
  7. 源碼級斷點
  8. 堆棧展開
  9. 處理變量
  10. 高級話題

在開始之前,請確保你使用的 libelfin 版本是我分支上的 fbreg。這包含了一些 hack 來支持獲取當(dāng)前堆棧幀的基址并評估位置列表,這些都不是由原生的 libelfin 提供的。你可能需要給 GCC 傳遞 -gdwarf-2 參數(shù)使其生成兼容的 DWARF 信息。但是在實現(xiàn)之前,我將詳細(xì)說明 DWARF 5 ***規(guī)范中的位置編碼方式。如果你想要了解更多信息,那么你可以從這里獲取該標(biāo)準(zhǔn)。

DWARF 位置

某一給定時刻的內(nèi)存中變量的位置使用 DW_AT_location 屬性編碼在 DWARF 信息中。位置描述可以是單個位置描述、復(fù)合位置描述或位置列表。

  • 簡單位置描述:描述了對象的一個連續(xù)的部分(通常是所有部分)的位置。簡單位置描述可以描述可尋址存儲器或寄存器中的位置,或缺少位置(具有或不具有已知值)。比如,DW_OP_fbreg -32: 一個整個存儲的變量 - 從堆棧幀基址開始的32個字節(jié)。
  • 復(fù)合位置描述:根據(jù)片段描述對象,每個對象可以包含在寄存器的一部分中或存儲在與其他片段無關(guān)的存儲器位置中。比如, DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2:前四個字節(jié)位于寄存器 3 中,后兩個字節(jié)位于寄存器 10 中的一個變量。
  • 位置列表:描述了具有有限生存期或在生存期內(nèi)更改位置的對象。比如:
    • <loclist with 3 entries follows>
      • [ 0]<lowpc=0x2e00><highpc=0x2e19>DW_OP_reg0
      • [ 1]<lowpc=0x2e19><highpc=0x2e3f>DW_OP_reg3
      • [ 2]<lowpc=0x2ec4><highpc=0x2ec7>DW_OP_reg2
    • 根據(jù)程序計數(shù)器的當(dāng)前值,位置在寄存器之間移動的變量。

根據(jù)位置描述的種類,DW_AT_location 以三種不同的方式進(jìn)行編碼。exprloc 編碼簡單和復(fù)合的位置描述。它們由一個字節(jié)長度組成,后跟一個 DWARF 表達(dá)式或位置描述。loclist 和 loclistptr 的編碼位置列表,它們在 .debug_loclists 部分中提供索引或偏移量,該部分描述了實際的位置列表。

DWARF 表達(dá)式

使用 DWARF 表達(dá)式計算變量的實際位置。這包括操作堆棧值的一系列操作。有很多 DWARF 操作可用,所以我不會詳細(xì)解釋它們。相反,我會從每一個表達(dá)式中給出一些例子,給你一個可用的東西。另外,不要害怕這些;libelfin 將為我們處理所有這些復(fù)雜性。

  • 字面編碼
    • DW_OP_lit0、DW_OP_lit1……DW_OP_lit31
      • 將字面量壓入堆棧
    • DW_OP_addr <addr>
      • 將地址操作數(shù)壓入堆棧
    • DW_OP_constu <unsigned>
      • 將無符號值壓入堆棧
  • 寄存器值
    • DW_OP_fbreg <offset>
      • 壓入在堆棧幀基址找到的值,偏移給定值
    • DW_OP_breg0、DW_OP_breg1…… DW_OP_breg31 <offset>
      • 將給定寄存器的內(nèi)容加上給定的偏移量壓入堆棧
  • 堆棧操作
    • DW_OP_dup
      • 復(fù)制堆棧頂部的值
    • DW_OP_deref
      • 將堆棧頂部視為內(nèi)存地址,并將其替換為該地址的內(nèi)容
  • 算術(shù)和邏輯運算
    • DW_OP_and
      • 彈出堆棧頂部的兩個值,并壓回它們的邏輯 AND
    • DW_OP_plus
      • 與 DW_OP_and 相同,但是會添加值
  • 控制流操作
    • DW_OP_le、DW_OP_eq、DW_OP_gt 等
      • 彈出前兩個值,比較它們,并且如果條件為真,則壓入 1,否則為 0
    • DW_OP_bra <offset>
      • 條件分支:如果堆棧的頂部不是 0,則通過 offset 在表達(dá)式中向后或向后跳過
  • 輸入轉(zhuǎn)化
    • DW_OP_convert <DIE offset>
      • 將堆棧頂部的值轉(zhuǎn)換為不同的類型,它由給定偏移量的 DWARF 信息條目描述
  • 特殊操作
    • DW_OP_nop
      • 什么都不做!

DWARF 類型

DWARF 類型的表示需要足夠強(qiáng)大來為調(diào)試器用戶提供有用的變量表示。用戶經(jīng)常希望能夠在應(yīng)用程序級別進(jìn)行調(diào)試,而不是在機(jī)器級別進(jìn)行調(diào)試,并且他們需要了解他們的變量正在做什么。

DWARF 類型與大多數(shù)其他調(diào)試信息一起編碼在 DIE 中。它們可以具有指示其名稱、編碼、大小、字節(jié)等的屬性。無數(shù)的類型標(biāo)簽可用于表示指針、數(shù)組、結(jié)構(gòu)體、typedef 以及 C 或 C++ 程序中可以看到的任何其他內(nèi)容。

以這個簡單的結(jié)構(gòu)體為例:

  1. struct test{ 
  2.     int i; 
  3.     float j; 
  4.     int k[42]; 
  5.     test* next
  6. }; 

這個結(jié)構(gòu)體的父 DIE 是這樣的:

  1. < 1><0x0000002a>    DW_TAG_structure_type 
  2.                       DW_AT_name                  "test" 
  3.                       DW_AT_byte_size             0x000000b8 
  4.                       DW_AT_decl_file             0x00000001 test.cpp 
  5.                       DW_AT_decl_line             0x00000001 

上面說的是我們有一個叫做 test 的結(jié)構(gòu)體,大小為 0xb8,在 test.cpp 的第 1 行聲明。接下來有許多描述成員的子 DIE。

  1. < 2><0x00000032>      DW_TAG_member 
  2.                         DW_AT_name                  "i" 
  3.                         DW_AT_type                  <0x00000063> 
  4.                         DW_AT_decl_file             0x00000001 test.cpp 
  5.                         DW_AT_decl_line             0x00000002 
  6.                         DW_AT_data_member_location  0 
  7. < 2><0x0000003e>      DW_TAG_member 
  8.                         DW_AT_name                  "j" 
  9.                         DW_AT_type                  <0x0000006a> 
  10.                         DW_AT_decl_file             0x00000001 test.cpp 
  11.                         DW_AT_decl_line             0x00000003 
  12.                         DW_AT_data_member_location  4 
  13. < 2><0x0000004a>      DW_TAG_member 
  14.                         DW_AT_name                  "k" 
  15.                         DW_AT_type                  <0x00000071> 
  16.                         DW_AT_decl_file             0x00000001 test.cpp 
  17.                         DW_AT_decl_line             0x00000004 
  18.                         DW_AT_data_member_location  8 
  19. < 2><0x00000056>      DW_TAG_member 
  20.                         DW_AT_name                  "next" 
  21.                         DW_AT_type                  <0x00000084> 
  22.                         DW_AT_decl_file             0x00000001 test.cpp 
  23.                         DW_AT_decl_line             0x00000005 
  24.                         DW_AT_data_member_location  176(as signed = -80) 

每個成員都有一個名稱、一個類型(它是一個 DIE 偏移量)、一個聲明文件和行,以及一個指向其成員所在的結(jié)構(gòu)體的字節(jié)偏移。其類型指向如下。

  1. < 1><0x00000063>    DW_TAG_base_type 
  2.                       DW_AT_name                  "int" 
  3.                       DW_AT_encoding              DW_ATE_signed 
  4.                       DW_AT_byte_size             0x00000004 
  5. < 1><0x0000006a>    DW_TAG_base_type 
  6.                       DW_AT_name                  "float" 
  7.                       DW_AT_encoding              DW_ATE_float 
  8.                       DW_AT_byte_size             0x00000004 
  9. < 1><0x00000071>    DW_TAG_array_type 
  10.                       DW_AT_type                  <0x00000063> 
  11. < 2><0x00000076>      DW_TAG_subrange_type 
  12.                         DW_AT_type                  <0x0000007d> 
  13.                         DW_AT_count                 0x0000002a 
  14. < 1><0x0000007d>    DW_TAG_base_type 
  15.                       DW_AT_name                  "sizetype" 
  16.                       DW_AT_byte_size             0x00000008 
  17.                       DW_AT_encoding              DW_ATE_unsigned 
  18. < 1><0x00000084>    DW_TAG_pointer_type 
  19.                       DW_AT_type                  <0x0000002a> 

如你所見,我筆記本電腦上的 int 是一個 4 字節(jié)的有符號整數(shù)類型,float是一個 4 字節(jié)的浮點數(shù)。整數(shù)數(shù)組類型通過指向 int 類型作為其元素類型,sizetype(可以認(rèn)為是 size_t)作為索引類型,它具有 2a 個元素。 test * 類型是 DW_TAG_pointer_type,它引用 test DIE。

實現(xiàn)簡單的變量讀取器

如上所述,libelfin 將為我們處理大部分復(fù)雜性。但是,它并沒有實現(xiàn)用于表示可變位置的所有方法,并且在我們的代碼中處理這些將變得非常復(fù)雜。因此,我現(xiàn)在選擇只支持 exprloc。請根據(jù)需要添加對更多類型表達(dá)式的支持。如果你真的有勇氣,請?zhí)峤谎a(bǔ)丁到 libelfin 中來幫助完成必要的支持!

處理變量主要是將不同部分定位在存儲器或寄存器中,讀取或?qū)懭肱c之前一樣。為了簡單起見,我只會告訴你如何實現(xiàn)讀取。

首先我們需要告訴 libelfin 如何從我們的進(jìn)程中讀取寄存器。我們創(chuàng)建一個繼承自 expr_context 的類并使用 ptrace 來處理所有內(nèi)容:

  1. class ptrace_expr_context : public dwarf::expr_context { 
  2. public
  3.     ptrace_expr_context (pid_t pid) : m_pid{pid} {} 
  4.     dwarf::taddr reg (unsigned regnum) override { 
  5.         return get_register_value_from_dwarf_register(m_pid, regnum); 
  6.     } 
  7.     dwarf::taddr pc() override { 
  8.         struct user_regs_struct regs; 
  9.         ptrace(PTRACE_GETREGS, m_pid, nullptr, &regs); 
  10.         return regs.rip; 
  11.     } 
  12.     dwarf::taddr deref_size (dwarf::taddr address, unsigned size) override { 
  13.         //TODO take into account size 
  14.         return ptrace(PTRACE_PEEKDATA, m_pid, address, nullptr); 
  15.     } 
  16. private: 
  17.     pid_t m_pid; 
  18. }; 

讀取將由我們 debugger 類中的 read_variables 函數(shù)處理:

  1. void debugger::read_variables() { 
  2.     using namespace dwarf; 
  3.     auto func = get_function_from_pc(get_pc()); 
  4.     //... 

我們上面做的***件事是找到我們目前進(jìn)入的函數(shù),然后我們需要循環(huán)訪問該函數(shù)中的條目來尋找變量:

  1. for (const auto& die : func) { 
  2.     if (die.tag == DW_TAG::variable) { 
  3.         //... 
  4.     } 

我們通過查找 DIE 中的 DW_AT_location 條目獲取位置信息:

  1. auto loc_val = die[DW_AT::location]; 

接著我們確保它是一個 exprloc,并請求 libelfin 來評估我們的表達(dá)式:

  1. if (loc_val.get_type() == value::type::exprloc) { 
  2.     ptrace_expr_context context {m_pid}; 
  3.     auto result = loc_val.as_exprloc().evaluate(&context); 

現(xiàn)在我們已經(jīng)評估了表達(dá)式,我們需要讀取變量的內(nèi)容。它可以在內(nèi)存或寄存器中,因此我們將處理這兩種情況:

  1. switch (result.location_type) { 
  2.                 case expr_result::type::address: 
  3.                 { 
  4.                     auto value = read_memory(result.value); 
  5.                     std::cout << at_name(die) << " (0x" << std::hex << result.value << ") = " 
  6.                               << value << std::endl; 
  7.                     break; 
  8.                 } 
  9.                 case expr_result::type::reg: 
  10.                 { 
  11.                     auto value = get_register_value_from_dwarf_register(m_pid, result.value); 
  12.                     std::cout << at_name(die) << " (reg " << result.value << ") = " 
  13.                               << value << std::endl; 
  14.                     break; 
  15.                 } 
  16.                 default
  17.                     throw std::runtime_error{"Unhandled variable location"}; 
  18.                 } 

你可以看到,我根據(jù)變量的類型,打印輸出了值而沒有解釋。希望通過這個代碼,你可以看到如何支持編寫變量,或者用給定的名字搜索變量。

***我們可以將它添加到我們的命令解析器中:

  1. else if(is_prefix(command, "variables")) { 
  2.     read_variables(); 

測試一下

編寫一些具有一些變量的小功能,不用優(yōu)化并帶有調(diào)試信息編譯它,然后查看是否可以讀取變量的值。嘗試寫入存儲變量的內(nèi)存地址,并查看程序改變的行為。

已經(jīng)有九篇文章了,還剩***一篇!下一次我會討論一些你可能會感興趣的更高級的概念。現(xiàn)在你可以在這里找到這個帖子的代碼。 

責(zé)任編輯:龐桂玉 來源: Linux中國
點贊
收藏

51CTO技術(shù)棧公眾號