到目前為止,可能你已經(jīng)聽到了關(guān)于調(diào)試信息或者關(guān)于除了解析代碼以外的理解源代碼的方法的DWARF的只言片語。今天,我們將介紹源代碼級的調(diào)試信息的細(xì)節(jié),以備在該系列的余下部分使用它。
ELF和DWARF簡介
ELF和DWARF可能是在程序員日常生活中經(jīng)常使用但是可能卻沒有聽說過的兩個(gè)部件。ELF(Executable and Linkable Format)是Linux世界最廣泛中使用的一種Object File Format;它指定了一種將各部分?jǐn)?shù)據(jù)存儲在二進(jìn)制文件的方式,比如說代碼,靜態(tài)數(shù)據(jù),調(diào)試信息,以及一些字符串等這些數(shù)據(jù)。同時(shí),也告訴加載器以何種方式對待二進(jìn)制文件以及準(zhǔn)備好執(zhí)行,這涉及到將二進(jìn)制文件的不同部分加載到內(nèi)存中,以及根據(jù)其他一些組件的位置來修復(fù)(重定位)相關(guān)的數(shù)據(jù)位等等。我不會在文章中包含太多的ELF相關(guān)的知識,但是如果感興趣的話你可以看一下這個(gè)精彩的圖表或者這個(gè)ELF標(biāo)準(zhǔn)文檔。
DWARF是ELF文件通常使用的調(diào)試信息格式。通常來講DWARF對ELF來說并不是必須的,但是這兩者是被串聯(lián)開發(fā)在一起的,并且一起使用非常好。這個(gè)格式允許編譯器告訴調(diào)試器源代碼是如何與被執(zhí)行的二進(jìn)制文件相關(guān)的。調(diào)試信息被分割在ELF不同的區(qū)段中,每一部分都傳達(dá)了本區(qū)塊的相關(guān)信息。一下是一些預(yù)定義的一些區(qū)段,如果信息過時(shí)的話,可以從這里獲取最新信息,DWARF調(diào)試信息簡介:
.debug_abbrev在.debug_info中使用的縮寫
.debug_aranges內(nèi)存地址和匯編間的映射
.debug_frame調(diào)用棧幀信息
.debug_info包含DWARF信息入口(DIEs)的核心數(shù)據(jù)
.debug_line行號信息
.debug_loc?位置描述
.debug_macinfo宏定義描述
.debug_pubnames全局對象和函數(shù)查找表
.debug_pubtypes全局類型查找表
.debug_rangesDIEs引用地址范圍
.debug_str在.debug_info中使用的字符串表
.debug_types類型描述信息
我們最感興趣的是.debug_line和.debug_info區(qū)段,所以讓我們用一個(gè)簡單的程序來看一下一些DWARF信息吧:
int?main()?{????long?a?=?3;????long?b?=?2;????long?c?=?a?+?b;????a?=?4;}
DWARF行號表
如果在編譯程序的時(shí)候指定了-g選項(xiàng),然后通過dwarfdump運(yùn)行結(jié)果,應(yīng)該類似以下信息的行號區(qū)段:
.debug_line:?line?number?info?for?a?single?cuSource?lines?(from?CU-DIE?at?.debug_info?offset?0x0000000b):????????????NS?new?statement,?BB?new?basic?block,?ET?end?of?text?sequence????????????PE?prologue?end,?EB?epilogue?begin????????????IS=val?ISA?number,?DI=val?discriminator?value????????[lno,col]?NS?BB?ET?PE?EB?IS=?DI=?uri:?"filepath"0x00400670??[???1,?0]?NS?uri:?"/home/simon/play/MiniDbg/examples/variable.cpp"0x00400676??[???2,10]?NS?PE0x0040067e??[???3,10]?NS0x00400686??[???4,14]?NS0x0040068a??[???4,16]0x0040068e??[???4,10]0x00400692??[???5,?7]?NS0x0040069a??[???6,?1]?NS0x0040069c??[???6,?1]?NS?ET
開始的一大串信息是關(guān)于如何理解dump的一些說明,主行號信息從0x00400770這行開始。本質(zhì)上,它映射了代碼內(nèi)存地址和在文件中的行和列信息。NS表示該地址標(biāo)志著新語句的開始,這通常用于設(shè)置斷點(diǎn)或單步。PE標(biāo)志著函數(shù)頭部的結(jié)束,這有助于設(shè)置函數(shù)入口斷點(diǎn)。ET標(biāo)示該映射塊的結(jié)尾。信息實(shí)際上并不是像這樣編碼,實(shí)際的編碼是一種非常節(jié)省空間的程序,由它來建立這些行號信息。
那么,如果我們想在variable.cpp中的第4行下一個(gè)斷點(diǎn),應(yīng)該怎么做呢? 查找與該文件相對應(yīng)的條目,然后找到相關(guān)的行號,找到相關(guān)的地址,然后設(shè)置一個(gè)斷點(diǎn)就可以了。在我們的小程序中,就是這一條:
0x00400686??[???4,14]?NS
所以我們需要在0x00400686地址處設(shè)置一個(gè)斷點(diǎn)。如果你想嘗試一下,你可以用你已經(jīng)寫過的調(diào)試器手工完成。
相反的工作也是如此,如果我們有一個(gè)內(nèi)存位置 - 比如一個(gè)RIP,并且想要找出它在源代碼中的哪個(gè)位置,只需在行號信息表中找到最接近的映射地址,并從中獲取行號即可。
DWARF調(diào)試信息
.debug_info是DWARF的核心所在。它給了我們程序中存在的關(guān)于類型,功能,變量,希望和夢想的信息。該區(qū)段的基本單位是DWARF信息入口,也就是被親切地稱為DIE的東西。DIE包含一個(gè)標(biāo)簽,告訴你代表什么樣的源代碼級的條目,后面是一系列適用于該條目的屬性。以下是之前的那個(gè)簡單程序的.debug_info:
.debug_infoCOMPILE_UNIT
:0><0x0000000b>??DW_TAG_compile_unit????????????????????DW_AT_producer??????????????clang?version?3.9.1?(tags/RELEASE_391/final)????????????????????DW_AT_language??????????????DW_LANG_C_plus_plus????????????????????DW_AT_name??????????????????/super/secret/path/MiniDbg/examples/variable.cpp????????????????????DW_AT_stmt_list?????????????0x00000000????????????????????DW_AT_comp_dir??????????????/super/secret/path/MiniDbg/build????????????????????DW_AT_low_pc????????????????0x00400670????????????????????DW_AT_high_pc???????????????0x0040069cLOCAL_SYMBOLS:1><0x0000002e>????DW_TAG_subprogram??????????????????????DW_AT_low_pc????????????????0x00400670??????????????????????DW_AT_high_pc???????????????0x0040069c??????????????????????DW_AT_frame_base????????????DW_OP_reg6??????????????????????DW_AT_name??????????????????main??????????????????????DW_AT_decl_file?????????????0x00000001?/super/secret/path/MiniDbg/examples/variable.cpp??????????????????????DW_AT_decl_line?????????????0x00000001??????????????????????DW_AT_type??????????????????<0x00000077>??????????????????????DW_AT_external??????????????yes(1)2><0x0000004c>??????DW_TAG_variable????????????????????????DW_AT_location??????????????DW_OP_fbreg?-8????????????????????????DW_AT_name??????????????????a????????????????????????DW_AT_decl_file?????????????0x00000001?/super/secret/path/MiniDbg/examples/variable.cpp????????????????????????DW_AT_decl_line?????????????0x00000002????????????????????????DW_AT_type??????????????????<0x0000007e>2><0x0000005a>??????DW_TAG_variable????????????????????????DW_AT_location??????????????DW_OP_fbreg?-16????????????????????????DW_AT_name??????????????????b????????????????????????DW_AT_decl_file?????????????0x00000001?/super/secret/path/MiniDbg/examples/variable.cpp????????????????????????DW_AT_decl_line?????????????0x00000003????????????????????????DW_AT_type??????????????????<0x0000007e>2><0x00000068>??????DW_TAG_variable????????????????????????DW_AT_location??????????????DW_OP_fbreg?-24????????????????????????DW_AT_name??????????????????c????????????????????????DW_AT_decl_file?????????????0x00000001?/super/secret/path/MiniDbg/examples/variable.cpp????????????????????????DW_AT_decl_line?????????????0x00000004????????????????????????DW_AT_type??????????????????<0x0000007e>1><0x00000077>????DW_TAG_base_type??????????????????????DW_AT_name??????????????????int??????????????????????DW_AT_encoding??????????????DW_ATE_signed??????????????????????DW_AT_byte_size?????????????0x000000041><0x0000007e>????DW_TAG_base_type??????????????????????DW_AT_name??????????????????long?int??????????????????????DW_AT_encoding??????????????DW_ATE_signed??????????????????????DW_AT_byte_size?????????????0x00000008
第一個(gè)DIE表示一個(gè)編譯單元(CU),它本質(zhì)上是一個(gè)源文件,其中包含所有#include并且被解析的包含文件。以下是它們的包含注釋的屬性:
DW_AT_producer???clang?version?3.9.1?(tags/RELEASE_391/final)????<--?The?compiler?which?produced?????????????????????????????????????????????????????????????????????this?binaryDW_AT_language???DW_LANG_C_plus_plus?????????????????????????????<--?The?source?languageDW_AT_name???????/super/secret/path/MiniDbg/examples/variable.cpp??<--?The?name?of?the?file?which?????????????????????????????????????????????????????????????????????this?CU?representsDW_AT_stmt_list??0x00000000??????????????????????????????????????<--?An?offset?into?the?line?table?????????????????????????????????????????????????????????????????????which?tracks?this?CUDW_AT_comp_dir???/super/secret/path/MiniDbg/build??????????????????<--?The?compilation?directoryDW_AT_low_pc?????0x00400670??????????????????????????????????????<--?The?start?of?the?code?for?????????????????????????????????????????????????????????????????????this?CUDW_AT_high_pc????0x0040069c??????????????????????????????????????<--?The?end?of?the?code?for?????????????????????????????????????????????????????????????????????this?CU
其他DIE遵循類似的方案,你可以直觀地看出不同屬性的含義。
現(xiàn)在我們可以嘗試使用我們新發(fā)現(xiàn)的DWARF知識來解決一些實(shí)際問題。
此刻處于哪個(gè)函數(shù)中?
比如說我們有一個(gè)RIP,并想弄清楚我們處在那個(gè)函數(shù)中。一個(gè)簡單的算法是:
for?each?compile?unit:????if?the?pc?is?between?DW_AT_low_pc?and?DW_AT_high_pc:????????for?each?function?in?the?compile?unit:????????????if?the?pc?is?between?DW_AT_low_pc?and?DW_AT_high_pc:????????????????return?function?information
這可以用于大多數(shù)目標(biāo),但是在成員函數(shù)和內(nèi)聯(lián)存在的情況下,事情會變得更加困難。例如,存在內(nèi)聯(lián)的情況下,一旦我們發(fā)現(xiàn)某個(gè)函數(shù)范圍包含了RIP,需要對該DIE的子條目進(jìn)行遞歸,以查看是否有任何更匹配的內(nèi)聯(lián)函數(shù)。我不會在這個(gè)調(diào)試器的代碼中處理內(nèi)聯(lián),但是如果你喜歡,你可以添加對它的支持。
如何在函數(shù)上下斷點(diǎn)?
同樣的,這取決于是否要支持成員函數(shù),命名空間等。對于單獨(dú)的函數(shù),你可以在不同的編譯單元中的函數(shù)中迭代查找,直到找到具有正確名稱的函數(shù)。如果你的編譯器足夠友好的填寫了.debug_pubnames部分,則可以更有效地做到這一點(diǎn)。
一旦找到該函數(shù),就可以在給定的內(nèi)存地址DW_AT_low_pc上設(shè)置斷點(diǎn)。但是,這將會在在函數(shù)頭部開始時(shí)中斷,最好在用戶代碼開始時(shí)中斷。由于行表信息可以指定指定函數(shù)頭部結(jié)束的內(nèi)存地址,因此可以直接在行表中查找DW_AT_low_pc的值,然后繼續(xù)讀取,直到找到標(biāo)記為函數(shù)頭部結(jié)尾的條目。有些編譯器不會輸出這個(gè)信息,所以另外一個(gè)選擇是在該函數(shù)的第二行條目給出的地址上設(shè)置一個(gè)斷點(diǎn)。
假設(shè)我們要在示例程序中的main設(shè)置一個(gè)斷點(diǎn)。我們搜索main函數(shù),并得到這個(gè)DIE:
1><0x0000002e>????DW_TAG_subprogram??????????????????????DW_AT_low_pc????????????????0x00400670??????????????????????DW_AT_high_pc???????????????0x0040069c??????????????????????DW_AT_frame_base????????????DW_OP_reg6??????????????????????DW_AT_name??????????????????main??????????????????????DW_AT_decl_file?????????????0x00000001?/super/secret/path/MiniDbg/examples/variable.cpp??????????????????????DW_AT_decl_line?????????????0x00000001??????????????????????DW_AT_type??????????????????<0x00000077>??????????????????????DW_AT_external??????????????yes(1)
這告訴我們,函數(shù)從0x00400670開始。如果我們在行號表中查看,我們得到這個(gè)條目:
0x00400670??[???1,?0]?NS?uri:?"/super/secret/path/MiniDbg/examples/variable.cpp"
我們想跳過函數(shù)頭部,所以我們讀取下一個(gè)條目:
0x00400676??[???2,10]?NS?PE
Clang在這個(gè)條目中包含了頭部結(jié)尾標(biāo)志,所以我們知道在這里停下來,并在地址0x00400676上設(shè)置一個(gè)斷點(diǎn)。
如何讀取變量內(nèi)容?
讀取變量可能非常復(fù)雜。它們是可以在整個(gè)函數(shù)中變化的難以捉摸的東西,存儲在寄存器中,放在內(nèi)存中,被優(yōu)化,被隱藏在角落里,等等等等亂七八糟。還好,我們簡單的例子確實(shí)很簡單。如果我們想要讀取變量a的內(nèi)容,則需要查看一下它的DW_AT_location?屬性。
DW_AT_location??????????????DW_OP_fbreg?-8
reg6?在x86架構(gòu)上是RBP,由System V x86_64 ABI指定?,F(xiàn)在我們讀取RBP的內(nèi)容,從中減去8,就找到了我們的變量。如果我們想實(shí)際上的理解這個(gè)變量,還需要查看它的類型:
2><0x0000004c>??????DW_TAG_variable????????????????????????DW_AT_name??????????????????a????????????????????????DW_AT_type??????????????????<0x0000007e>
如果在調(diào)試信息中查找這種類型,我們得到這個(gè)DIE:
1><0x0000007e>????DW_TAG_base_type??????????????????????DW_AT_name??????????????????long?int??????????????????????DW_AT_encoding??????????????DW_ATE_signed??????????????????????DW_AT_byte_size?????????????0x00000008
這告訴我們,該類型是一個(gè)8字節(jié)(64位)有符號整數(shù)類型,因此我們可以直接將這些字節(jié)解釋為int64_t并將其顯示給用戶。
當(dāng)然,這些類型可能會比這更復(fù)雜,因?yàn)樗鼈儽仨毮軌虮磉_(dá)類似于C ++類型的東西,但是這給出了它們?nèi)绾喂ぷ鞯幕舅枷搿?/p>
暫時(shí)回到RBP,Clang可以很好地根據(jù)RBP來追蹤幀基址。最近版本的GCC更傾向于DW_OP_call_frame_cfa,它涉及解析.eh_frame ELF部分,這是一個(gè)完全不同的文章,我并不打算寫。如果你告訴GCC使用DWARF 2而不是更新的版本,它會傾向于輸出位置列表,這更容易閱讀:
DW_AT_frame_base?????????????low-off?:?0x00000000?addr??0x00400696?high-off??0x00000001?addr?0x00400697>DW_OP_breg7+8?low-off?:?0x00000001?addr??0x00400697?high-off??0x00000004?addr?0x0040069a>DW_OP_breg7+16?low-off?:?0x00000004?addr??0x0040069a?high-off??0x00000031?addr?0x004006c7>DW_OP_breg6+16?low-off?:?0x00000031?addr??0x004006c7?high-off??0x00000032?addr?0x004006c8>DW_OP_breg7+8
位置列表根據(jù)RIP給出不同的位置。這個(gè)例子展示了如果RIP位于距DW_AT_low_pc的0x0偏移的位置,那么幀基址距離寄存器7中存儲的值的偏移量為8,如果它位于0x1和0x4之間,那么它距離寄存器7中存儲的值偏移為16,等等。
休息休息
這么多信息會讓你的頭腦暈暈乎乎,但好消息是,在接下來的幾篇文章中,我們將有一個(gè)庫來為我們完成這些艱難的工作。理解實(shí)際操作中的內(nèi)容,特別是在出現(xiàn)問題時(shí),或者你希望支持一些DWARF內(nèi)容(在使用的任何DWARF庫中未實(shí)現(xiàn))時(shí)仍然有用。
如果你想了解有關(guān)DWARF的更多信息,那么可以從這里獲取相關(guān)標(biāo)準(zhǔn)。在撰寫本文時(shí),DWARF 5剛剛被發(fā)布,但是DWARF 4更受歡迎。
Linux平臺下調(diào)試器的編寫(五):源碼和信號
在之前的幾部分中我們學(xué)習(xí)了關(guān)于DWARF信息以及這些信息是如何在被執(zhí)行的機(jī)器碼和高級語言之間建立起聯(lián)系的。在這部分中,我們將實(shí)現(xiàn)一些能夠被調(diào)試器使用的DWARF相關(guān)原語。我們還將借此機(jī)會讓調(diào)試器在命中斷點(diǎn)之時(shí)輸出當(dāng)前源代碼的上下文信息。
建立DWAR解析器
正如在再還系列的開始時(shí)所提到的,我們將會使用libelfin來處理DWARF信息。希望你在我的第一篇文章時(shí)就已經(jīng)得到了該工具,如果沒有的話,你可使用我從倉庫fork出的fbreg分支。
一旦弄好了libelfin,就是時(shí)候把它加入到我們的調(diào)試器中了。第一步,解析ELF可執(zhí)行文件并且從中獲取DWARF信息。使用libelfin來完成這一步是非常簡單的,僅僅需要對調(diào)試器做如下的改變:
class?debugger?{public:????debugger?(std::string?prog_name,?pid_t?pid)?????????:?m_prog_name{std::move(prog_name)},?m_pid{pid}?{????????auto?fd?=?open(m_prog_name.c_str(),?O_RDONLY);????????m_elf?=?elf::elf{elf::create_mmap_loader(fd)};????????m_dwarf?=?dwarf::dwarf{dwarf::elf::create_loader(m_elf)};????}????//...private:????//...????dwarf::dwarf?m_dwarf;????elf::elf?m_elf;};
##?調(diào)試信息原語接下來我們可以實(shí)現(xiàn)根據(jù)RIP的值來檢索行條目和函數(shù)DIE。先從```get_function_from_pc```開始吧:```c++dwarf::die?debugger::get_function_from_pc(uint64_t?pc)?{????for?(auto?&cu?:?m_dwarf.compilation_units())?{????????if?(die_pc_range(cu.root()).contains(pc))?{????????????for?(const?auto&?die?:?cu.root())?{????????????????if?(die.tag?==?dwarf::DW_TAG::subprogram)?{????????????????????if?(die_pc_range(die).contains(pc))?{????????????????????????return?die;????????????????????}????????????????}????????????}????????}????}????throw?std::out_of_range{"Cannot?find?function"};}
這里我采取了一個(gè)比較笨拙的方法,只需遍歷編譯單元,直到知道到包含RIP的代碼,然后一直迭代,直到在子節(jié)點(diǎn)中找到相關(guān)函數(shù)(DW_TAG_subprogram)。正如在上篇提到的,你可以想成員函數(shù)一樣來處理這些,如果你想的話你還可以使用內(nèi)聯(lián)。 接下來是get_line_entry_from_pc:
dwarf::line_table::iterator?debugger::get_line_entry_from_pc(uint64_t?pc)?{????for?(auto?&cu?:?m_dwarf.compilation_units())?{????????if?(die_pc_range(cu.root()).contains(pc))?{????????????auto?=?cu.get_line_table();????????????auto?it?=?lt.find_address(pc);????????????if?(it?==?lt.end())?{????????????????throw?std::out_of_range{"Cannot?find?line?entry"};????????????}????????????else?{????????????????return?it;????????????}????????}????}????throw?std::out_of_range{"Cannot?find?line?entry"};}
同樣的,我們只需找到正確的便宜單元,然后請求行列表來獲取相關(guān)條目。
輸出源碼
當(dāng)命中斷點(diǎn)的時(shí)候或者在源碼上單步的時(shí)候,我們需要知道源代碼被執(zhí)行到哪里了。
void?debugger::print_source(const?std::string&?file_name,?unsigned?line,?unsigned?n_lines_context)?{????std::ifstream?file?{file_name};????//Work?out?a?window?around?the?desired?line????auto?start_line?=?line?<=?n_lines_context???1?:?line?-?n_lines_context;????auto?end_line?=?line?+?n_lines_context?+?(line??"?:?"??");????//Write?lines?up?until?end_line????while?(current_line?<=?end_line?&&?file.get(c))?{????????std::cout?<?"?:?"??");????????}????}????//Write?newline?and?make?sure?that?the?stream?is?flushed?properly????std::cout?<
現(xiàn)在,可以輸出源碼了,只需要將其掛載到我們的調(diào)試器中。當(dāng)調(diào)試器從斷點(diǎn)或者(實(shí)際上)但不中獲取信號的時(shí)候是顯示源碼的上好時(shí)機(jī)了。這樣做的話,調(diào)試器就需要一個(gè)更好的信號處理了。
更好的信號處理
我們希望能夠輸出什么樣的信號被發(fā)送給了進(jìn)程,同時(shí)亦希望知道該信號是如何被產(chǎn)生的。例如,我們想知道收到的SIGTRAP信號是由于命中斷點(diǎn)還是一個(gè)單步執(zhí)行完產(chǎn)生的,亦或者是由于新線程建立而產(chǎn)生的,等等。 幸運(yùn)的是,ptrace再一次支援了我們。ptrace有一個(gè)參數(shù)PTRACE_GETSIGINFO,該參數(shù)將會給出進(jìn)程之前發(fā)出的信號的相關(guān)信息。如下:
siginfo_t?debugger::get_signal_info()?{????siginfo_t?info;????ptrace(PTRACE_GETSIGINFO,?m_pid,?nullptr,?&info);????return?info;}
這里出現(xiàn)了一個(gè)siginfo_t的對象,它提供了如下的信息:
siginfo_t?{????int??????si_signo;?????/*?Signal?number?*/????int??????si_errno;?????/*?An?errno?value?*/????int??????si_code;??????/*?Signal?code?*/????int??????si_trapno;????/*?Trap?number?that?caused??????????????????????????????hardware-generated?signal??????????????????????????????(unused?on?most?architectures)?*/????pid_t????si_pid;???????/*?Sending?process?ID?*/????uid_t????si_uid;???????/*?Real?user?ID?of?sending?process?*/????int??????si_status;????/*?Exit?value?or?signal?*/????clock_t??si_utime;?????/*?User?time?consumed?*/????clock_t??si_stime;?????/*?System?time?consumed?*/????sigval_t?si_value;?????/*?Signal?value?*/????int??????si_int;???????/*?POSIX.1b?signal?*/????void????*si_ptr;???????/*?POSIX.1b?signal?*/????int??????si_overrun;???/*?Timer?overrun?count;??????????????????????????????POSIX.1b?timers?*/????int??????si_timerid;???/*?Timer?ID;?POSIX.1b?timers?*/????void????*si_addr;??????/*?Memory?location?which?caused?fault?*/????long?????si_band;??????/*?Band?event?(was?int?in??????????????????????????????glibc?2.3.2?and?earlier)?*/????int??????si_fd;????????/*?File?descriptor?*/????short????si_addr_lsb;??/*?Least?significant?bit?of?address??????????????????????????????(since?Linux?2.6.32)?*/????void????*si_lower;?????/*?Lower?bound?when?address?violation??????????????????????????????occurred?(since?Linux?3.19)?*/????void????*si_upper;?????/*?Upper?bound?when?address?violation??????????????????????????????occurred?(since?Linux?3.19)?*/????int??????si_pkey;??????/*?Protection?key?on?PTE?that?caused??????????????????????????????fault?(since?Linux?4.6)?*/????void????*si_call_addr;?/*?Address?of?system?call?instruction??????????????????????????????(since?Linux?3.5)?*/????int??????si_syscall;???/*?Number?of?attempted?system?call??????????????????????????????(since?Linux?3.5)?*/????unsigned?int?si_arch;??/*?Architecture?of?attempted?system?call??????????????????????????????(since?Linux?3.5)?*/}
我將使用si——signo來找出是哪一個(gè)信號被發(fā)送,然后使用si_code來獲取有關(guān)該信號的更多信息。放置該段代碼的最佳地方是在我們的wait_for_signal函數(shù)中:
void?debugger::wait_for_signal()?{????int?wait_status;????auto?options?=?0;????waitpid(m_pid,?&wait_status,?options);????auto?siginfo?=?get_signal_info();????switch?(siginfo.si_signo)?{????case?SIGTRAP:????????handle_sigtrap(siginfo);????????break;????case?SIGSEGV:????????std::cout?<"Yay,?segfault.?Reason:?"?<
現(xiàn)在處理SIGTRAP只需知道SI_KERNEL或者TRAP_BPKPT將會在斷點(diǎn)命中時(shí)被發(fā)送,TRAP_TRACE將會在單步完成的時(shí)候被發(fā)送:
void?debugger::handle_sigtrap(siginfo_t?info)?{????switch?(info.si_code)?{????//one?of?these?will?be?set?if?a?breakpoint?was?hit????case?SI_KERNEL:????case?TRAP_BRKPT:????{????????set_pc(get_pc()-1);?//put?the?pc?back?where?it?should?be????????std::cout?<"Hit?breakpoint?at?address?0x"?<file->path,?line_entry->line);????????return;????}????//this?will?be?set?if?the?signal?was?sent?by?single?stepping????case?TRAP_TRACE:????????return;????default:????????std::cout?<"Unknown?SIGTRAP?code?"?<
你可以處理一堆不同風(fēng)格的信號。詳情請參閱man sigaction。 由于我們現(xiàn)在在得到SIGTRAP時(shí)修正RIP,所以可以去掉step_over_breakpoint中的部分代碼:
void?debugger::step_over_breakpoint()?{????if?(m_breakpoints.count(get_pc()))?{????????auto&?bp?=?m_breakpoints[get_pc()];????????if?(bp.is_enabled())?{????????????bp.disable();????????????ptrace(PTRACE_SINGLESTEP,?m_pid,?nullptr,?nullptr);????????????wait_for_signal();????????????bp.enable();????????}????}}
測試
現(xiàn)在,你應(yīng)該可以在某些地址設(shè)置斷點(diǎn),運(yùn)行程序,查看鼠標(biāo)標(biāo)記的正在被執(zhí)行的代碼的源代碼了。
下一次我們將添加源碼級的斷點(diǎn)??梢栽诖颂帿@取源碼
?
評論