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

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),扶風(fēng)企業(yè)網(wǎng)站建設(shè),扶風(fēng)品牌網(wǎng)站建設(shè),網(wǎng)站定制,扶風(fēng)網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,扶風(fēng)網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
系列文章索引
-
準(zhǔn)備環(huán)境
-
斷點(diǎn)
-
寄存器和內(nèi)存
-
ELF 和 DWARF
-
源碼和信號(hào)
-
源碼級(jí)逐步執(zhí)行
-
源碼級(jí)斷點(diǎn)
-
堆棧展開(kāi)
-
處理變量
-
高級(jí)話題
在開(kāi)始之前,請(qǐng)確保你使用的 libelfin 版本是我分支上的 fbreg。這包含了一些 hack 來(lái)支持獲取當(dāng)前堆棧幀的基址并評(píng)估位置列表,這些都不是由原生的 libelfin 提供的。你可能需要給 GCC 傳遞 -gdwarf-2 參數(shù)使其生成兼容的 DWARF 信息。但是在實(shí)現(xiàn)之前,我將詳細(xì)說(shuō)明 DWARF 5 最新規(guī)范中的位置編碼方式。如果你想要了解更多信息,那么你可以從這里獲取該標(biāo)準(zhǔn)。
DWARF 位置
某一給定時(shí)刻的內(nèi)存中變量的位置使用 DW_AT_location 屬性編碼在 DWARF 信息中。位置描述可以是單個(gè)位置描述、復(fù)合位置描述或位置列表。
-
簡(jiǎn)單位置描述:描述了對(duì)象的一個(gè)連續(xù)的部分(通常是所有部分)的位置。簡(jiǎn)單位置描述可以描述可尋址存儲(chǔ)器或寄存器中的位置,或缺少位置(具有或不具有已知值)。比如,DW_OP_fbreg -32: 一個(gè)整個(gè)存儲(chǔ)的變量 – 從堆棧幀基址開(kāi)始的32個(gè)字節(jié)。
-
復(fù)合位置描述:根據(jù)片段描述對(duì)象,每個(gè)對(duì)象可以包含在寄存器的一部分中或存儲(chǔ)在與其他片段無(wú)關(guān)的存儲(chǔ)器位置中。比如, DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2:前四個(gè)字節(jié)位于寄存器 3 中,后兩個(gè)字節(jié)位于寄存器 10 中的一個(gè)變量。
-
位置列表:描述了具有有限生存期或在生存期內(nèi)更改位置的對(duì)象。比如:
-
-
[ 0] DW_OP_reg0
-
[ 1] DW_OP_reg3
-
[ 2] DW_OP_reg2
-
-
-
根據(jù)程序計(jì)數(shù)器的當(dāng)前值,位置在寄存器之間移動(dòng)的變量。
根據(jù)位置描述的種類,DW_AT_location 以三種不同的方式進(jìn)行編碼。exprloc 編碼簡(jiǎn)單和復(fù)合的位置描述。它們由一個(gè)字節(jié)長(zhǎng)度組成,后跟一個(gè) DWARF 表達(dá)式或位置描述。loclist 和 loclistptr 的編碼位置列表,它們?cè)?.debug_loclists 部分中提供索引或偏移量,該部分描述了實(shí)際的位置列表。
DWARF 表達(dá)式
使用 DWARF 表達(dá)式計(jì)算變量的實(shí)際位置。這包括操作堆棧值的一系列操作。有很多 DWARF 操作可用,所以我不會(huì)詳細(xì)解釋它們。相反,我會(huì)從每一個(gè)表達(dá)式中給出一些例子,給你一個(gè)可用的東西。另外,不要害怕這些;libelfin將為我們處理所有這些復(fù)雜性。
-
字面編碼
-
-
將無(wú)符號(hào)值壓入堆棧
-
將地址操作數(shù)壓入堆棧
-
將字面量壓入堆棧
-
DW_OP_lit0、DW_OP_lit1……DW_OP_lit31
-
DW_OP_addr
-
DW_OP_constu
-
-
寄存器值
-
-
將給定寄存器的內(nèi)容加上給定的偏移量壓入堆棧
-
壓入在堆棧幀基址找到的值,偏移給定值
-
DW_OP_fbreg
-
DW_OP_breg0、DW_OP_breg1…… DW_OP_breg31
-
-
堆棧操作
-
-
將堆棧頂部視為內(nèi)存地址,并將其替換為該地址的內(nèi)容
-
復(fù)制堆棧頂部的值
-
DW_OP_dup
-
DW_OP_deref
-
-
算術(shù)和邏輯運(yùn)算
-
-
與 DW_OP_and 相同,但是會(huì)添加值
-
彈出堆棧頂部的兩個(gè)值,并壓回它們的邏輯 AND
-
DW_OP_and
-
DW_OP_plus
-
-
控制流操作
-
-
條件分支:如果堆棧的頂部不是 0,則通過(guò) offset 在表達(dá)式中向后或向后跳過(guò)
-
彈出前兩個(gè)值,比較它們,并且如果條件為真,則壓入 1,否則為 0
-
DW_OP_le、DW_OP_eq、DW_OP_gt 等
-
DW_OP_bra
-
-
輸入轉(zhuǎn)化
-
-
將堆棧頂部的值轉(zhuǎn)換為不同的類型,它由給定偏移量的 DWARF 信息條目描述
-
DW_OP_convert
-
-
特殊操作
-
-
什么都不做!
-
DW_OP_nop
-
DWARF 類型
DWARF 類型的表示需要足夠強(qiáng)大來(lái)為調(diào)試器用戶提供有用的變量表示。用戶經(jīng)常希望能夠在應(yīng)用程序級(jí)別進(jìn)行調(diào)試,而不是在機(jī)器級(jí)別進(jìn)行調(diào)試,并且他們需要了解他們的變量正在做什么。
DWARF 類型與大多數(shù)其他調(diào)試信息一起編碼在 DIE 中。它們可以具有指示其名稱、編碼、大小、字節(jié)等的屬性。無(wú)數(shù)的類型標(biāo)簽可用于表示指針、數(shù)組、結(jié)構(gòu)體、typedef 以及 C 或 C++ 程序中可以看到的任何其他內(nèi)容。
以這個(gè)簡(jiǎn)單的結(jié)構(gòu)體為例:
struct test{
int i;
float j;
int k[42];
test* next;
};
這個(gè)結(jié)構(gòu)體的父 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
上面說(shuō)的是我們有一個(gè)叫做 test 的結(jié)構(gòu)體,大小為 0xb8,在 test.cpp 的第 1 行聲明。接下來(lái)有許多描述成員的子 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)
每個(gè)成員都有一個(gè)名稱、一個(gè)類型(它是一個(gè) DIE 偏移量)、一個(gè)聲明文件和行,以及一個(gè)指向其成員所在的結(jié)構(gòu)體的字節(jié)偏移。其類型指向如下。
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
如你所見(jiàn),我筆記本電腦上的 int 是一個(gè) 4 字節(jié)的有符號(hào)整數(shù)類型,float是一個(gè) 4 字節(jié)的浮點(diǎn)數(shù)。整數(shù)數(shù)組類型通過(guò)指向 int 類型作為其元素類型,sizetype(可以認(rèn)為是 size_t)作為索引類型,它具有 2a 個(gè)元素。 test * 類型是 DW_TAG_pointer_type,它引用 test DIE。
實(shí)現(xiàn)簡(jiǎn)單的變量讀取器
如上所述,libelfin 將為我們處理大部分復(fù)雜性。但是,它并沒(méi)有實(shí)現(xiàn)用于表示可變位置的所有方法,并且在我們的代碼中處理這些將變得非常復(fù)雜。因此,我現(xiàn)在選擇只支持 exprloc。請(qǐng)根據(jù)需要添加對(duì)更多類型表達(dá)式的支持。如果你真的有勇氣,請(qǐng)?zhí)峤谎a(bǔ)丁到 libelfin 中來(lái)幫助完成必要的支持!
處理變量主要是將不同部分定位在存儲(chǔ)器或寄存器中,讀取或?qū)懭肱c之前一樣。為了簡(jiǎn)單起見(jiàn),我只會(huì)告訴你如何實(shí)現(xiàn)讀取。
首先我們需要告訴 libelfin 如何從我們的進(jìn)程中讀取寄存器。我們創(chuàng)建一個(gè)繼承自 expr_context 的類并使用 ptrace 來(lái)處理所有內(nèi)容:
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 函數(shù)處理:
void debugger::read_variables() {
using namespace dwarf;
auto func = get_function_from_pc(get_pc());
//...
}
我們上面做的第一件事是找到我們目前進(jìn)入的函數(shù),然后我們需要循環(huán)訪問(wèn)該函數(shù)中的條目來(lái)尋找變量:
for (const auto& die : func) {
if (die.tag == DW_TAG::variable) {
//...
}
}
我們通過(guò)查找 DIE 中的 DW_AT_location 條目獲取位置信息:
auto loc_val = die[DW_AT::location];
接著我們確保它是一個(gè) exprloc,并請(qǐng)求 libelfin 來(lái)評(píng)估我們的表達(dá)式:
if (loc_val.get_type() == value::type::exprloc) {ptrace_expr_context context {m_pid};auto result = loc_val.as_exprloc().evaluate(&context);
現(xiàn)在我們已經(jīng)評(píng)估了表達(dá)式,我們需要讀取變量的內(nèi)容。它可以在內(nèi)存或寄存器中,因此我們將處理這兩種情況:
switch (result.location_type) {
case expr_result::type::address:
{
auto value = read_memory(result.value);
std::cout " (0x" ") = "
break;
}
case expr_result::type::reg:
{
auto value = get_register_value_from_dwarf_register(m_pid, result.value);
std::cout " (reg " ") = "
break;
}
default:
throw std::runtime_error{"Unhandled variable location"};
}
你可以看到,我根據(jù)變量的類型,打印輸出了值而沒(méi)有解釋。希望通過(guò)這個(gè)代碼,你可以看到如何支持編寫(xiě)變量,或者用給定的名字搜索變量。
最后我們可以將它添加到我們的命令解析器中:
else if(is_prefix(command, "variables")) {
read_variables();
}
測(cè)試一下
編寫(xiě)一些具有一些變量的小功能,不用優(yōu)化并帶有調(diào)試信息編譯它,然后查看是否可以讀取變量的值。嘗試寫(xiě)入存儲(chǔ)變量的內(nèi)存地址,并查看程序改變的行為。
分享文章:詳解Linux調(diào)試器中處理變量
當(dāng)前網(wǎng)址:http://www.5511xx.com/article/cdsoppg.html


咨詢
建站咨詢
