* [[https://www.amazon.com/How-Debuggers-Work-Algorithms-Architecture/dp/0471149667|How Debuggers Work: Algorithms, Data Structures, and Architecture]] * [[http://stackoverflow.com/questions/216819/how-does-a-debugger-work|How does a debugger work?]] * [[http://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1/|How debuggers work: Part 1 – Basics]] * Linux 提供系統調用 [[http://man7.org/linux/man-pages/man2/ptrace.2.html|ptrace]] 和 [[http://www.secretmango.com/jimb/Whitepapers/ptrace/ptrace.html|/proc]] 檔案系統供除錯器使用。 * [[http://blog.linux.org.tw/~jserv/archives/002027.html|以 ptrace 系統呼叫來追蹤/修改行程]] * [[http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints/|How debuggers work: Part 2 – Breakpoints]] * 硬體提供 trap 指令供 debugger 實現斷點。針對 CISC 架構,trap 指令長度為硬體指令中最短者。 * [[wp>INT (x86 instruction)|INT 3]] * [[wp>Interrupts in 65xx processors|BRK]] * [[http://eli.thegreenplace.net/2011/02/07/how-debuggers-work-part-3-debugging-information/|How debuggers work: Part 3 – Debugging information]] * 編譯器透過 [[wp>stabs|STABS]] 或 [[wp>DWARF]] 資訊提供 debugger 代碼資訊。 * 部分編碼後的代碼資訊需要透過虛擬機重建資訊。 * [[http://wiki.dwarfstd.org/?title=DWARF_FAQ#What_is_a_state_machine_which_is_used_to_decode_the_byte_stream_of_line_and_file_debug_information.3F|What is a state machine which is used to decode the byte stream of line and file debug information?]] * [[http://www.codeproject.com/Articles/43682/Writing-a-basic-Windows-debugger|Writing a basic Windows debugger]] * 描述除錯器基本程式邏輯。 * Also, no other method can terminate the process (Task Manager, Process Explorer, Kill utility...). Attempting to kill the process from these utilities will, however, schedule the terminating process. Thus, the debugger would receive EXIT_PROCESS_DEBUG_EVENT as the next event! * 對目標進程下 kill 指令並不會立刻生效,控制權會先交還給除錯器,由除錯器處理 EXIT_PROCESS_DEBUG_EVENT 例外。 * When a process is being debugged, the debugger always receives the exception before the debuggee gets it. You must have seen "First-chance exception at 0x00412882 in SomeModule:..." while debugging your Visual C++ module. This is referred to as First Chance Exception. * [[http://blogs.msdn.com/b/davidklinems/archive/2005/07/12/438061.aspx|What is a First Chance Exception?]] * 除錯器會攔截到目標進程的所有例外。因此當目標進程觸發例外時,Windows 會將該例外丟給除錯器,此即 first chance exception。除錯器可以選擇處理或是不處理 first chance exception。如果除錯器選擇不處理該例外,則轉交由目標進程處理。如果目標進程仍然不處理該例外,則除錯器將會再次看到該例外,此即 second chance exception。First chance exception 是否代表問題發生,端視目標進程是否正確處理該例外; 若 second chance exception 發生,一般代表問題發生,因為在沒有除錯器的情況下,該例外會導致程序崩潰。 * EXCEPTION_BREAKPOINT 和 EXCEPTION_SINGLE_STEP 是除錯器關注的焦點。 * [[http://www.codeproject.com/Articles/132742/Writing-Windows-Debugger-Part|Writing Windows Debugger - Part 2]] * 處理斷點。 * 顯示暫存器、調用棧和源代碼。除錯器和 GUI 交互。 ====== 硬體支援 ====== 處理器一般提供以下功能供上層軟體除錯: * A special instruction that halts execution. * 提供 trap 指令供除錯器插入斷點。硬體必須提供此基本功能。 * A special mode of the processor to execute a single instruction. * 提供單步模式供除錯器設置單步執行。 * Page protection mechanisms. * 如果無硬體支援 data breakpoint,需提供頁機制,透過頁錯誤達成該功能。 * Exception or fault detection mechanisms. * 提供例外處理機制。 * In some case, special debug registers. * 提供硬體暫存器供除錯器使用。 ====== 作業系統 ====== Linux 提供 ptrace,Windows 提供 [[http://msdn.microsoft.com/en-us/library/ms809754.aspx|Win32 Debug API]]。 ====== 除錯資訊 ====== 針對匯編語言,除錯訊息應該要能帶入以下資訊。 * 源代碼資訊 * 目錄、檔名、巨集名稱... * 符號資訊 * 全域符號、本地符號 * 匯編中的符號一般沒有型別資訊 * 控制流資訊 * 指令對映 * 段資訊 * 巨集資訊 ===== STABS ===== * [[wp>stabs]] * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Flow.html|1.1 Overview of Debugging Information Flow]] * 編譯器在編譯源代碼時,會將除錯訊息輸出至匯編,再經由匯編器和鏈結器輸出至最終執行檔。 * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Stabs-Format.html|1.2 Overview of Stab Format]] * 編譯器以匯編指示 .stab 輸出除錯訊息輸出至匯編。匯編指示基本有底下三種: * .stabs: string * .stabs "string",type,other,desc,value * .stabn: number * .stabn type,other,desc,value * .stabd: dot * .stabd type,other,desc * 除錯 symbol 有底下基本 type ([[https://sourceware.org/gdb/current/onlinedocs/stabs/Stab-Types.html|Appendix A Table of Stab Types]]): * [[https://www.sourceware.org/gdb/onlinedocs/stabs.html#Source-Files|N_SO]] * Before any other stabs occur, there must be a stab specifying the source file. This information is contained in a symbol of stab type N_SO; the string field contains the name of the file. The value of the symbol is the start address of the portion of the text section corresponding to that file. * [[https://www.sourceware.org/gdb/onlinedocs/stabs.html#Line-Numbers|N_SLINE]] * An N_SLINE symbol represents the start of a source line. The desc field contains the line number and the value contains the code address for the start of that source line. * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Block-Structure.html|N_LBRAC, N_RBRAC]] * The N_LBRAC and N_RBRAC stabs that describe the block scope of a procedure are located after the N_FUN stab that represents the procedure itself. * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Include-Files.html|N_SOL, N_BINCL, N_EINCL]] * [[https://sourceware.org/gdb/current/onlinedocs/stabs/String-Field.html|1.3 The String Field]] * 描述上述 "string" 欄位格式。 * "name:symbol-descriptor type-information" * .stabs "int:t(0,1)=r(0,1);-2147483648;2147483647;",128,0,0,0 * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Traditional-Integer-Types.html|5.1.1.1 Traditional Integer Types]] * int: [[https://sourceware.org/gdb/current/onlinedocs/stabs/Nested-Symbols.html|name]] * t(0,1)=r(0,1): [[https://sourceware.org/gdb/current/onlinedocs/stabs/Symbol-Descriptors.html|symbol-descriptor]] * ;-2147483648;2147483647; * int 代表的數值範圍。 * 鏈結器將目的檔中所有的 .stab 和 .stabstr 段合併成一份 .stab 和 .stabstr 段。 * [[https://sourceware.org/gdb/current/onlinedocs/stabs/Stab-Sections.html|Appendix F Using Stabs in Their Own Sections]] * 匯編器將除錯訊息放在底下兩個段中: * .stab * 匯編中每一個除錯訊息皆以一個固定大小的資料結構存放在 .stab 段。 * .stabstr * 除錯訊息的 string 欄位內容皆存放在 .stabstr 段。 * [[http://www.sourceware.org/gdb/onlinedocs/stabs.html|The "stabs" representation of debugging information]] * [[http://www.bagualu.net/wordpress/archives/2349|gdb debug 信息 stabs 格式]] $ cat hello.c main() { printf("Hello world"); } $ gcc -gstabs -S hello.c .file "hello.c" .stabs "hello.c",100,0,2,.Ltext0 .text .Ltext0: .stabs "gcc2_compiled.",60,0,0,0 .stabs "int:t(0,1)=r(0,1);-2147483648;2147483647;",128,0,0,0 $ objdump --stabs hello a.out: file format elf32-i386 Contents of .stab section: Symnum n_type n_othr n_desc n_value n_strx String -1 HdrSym 0 118 00000f24 1 0 SO 0 2 0804841d 1 hello.c 1 OPT 0 0 00000000 9 gcc2_compiled. 2 LSYM 0 0 00000000 24 int:t(0,1)=r(0,1);-2147483648;2147483647; ===== DWARF ===== * [[wp>DWARF]] * [[http://www.dwarfstd.org/doc/Debugging%20using%20DWARF-2012.pdf|Introduction to the DWARF Debugging Format]] * 除錯資訊是以樹節點 (DIE, Debugging Information Entry) 構成的一個樹狀結構,以 prefix order 儲存成 table 放在段中。 * DIE 帶有 tag 和數個 attribute。 * CU (compilation unit) DIE 是根節點,有底下屬性: * DW_AT_low_pc * DW_AT_high_pc * [DW_AT_low_pc, DW_AT_high_pc) 為 CU 的範圍。 * Line number table * 存放代碼和對應二進制位址關係 $ cat hello.c int main() { int i = 0; return 0; } $ gcc -g hello.c # 觀察 DWAFT 資訊。 $ objdump --dwarf=info a.out a.out: file format elf32-i386 Contents of the .debug_info section: Compilation Unit @ offset 0x0: Length: 0x4f (32-bit) Version: 4 Abbrev Offset: 0x0 Pointer Size: 4 <0>: Abbrev Number: 1 (DW_TAG_compile_unit) DW_AT_producer : (indirect string, offset: 0x22): GNU C 4.8.2 -mtune=generic -march=i686 -g -fstack-protector <10> DW_AT_language : 1 (ANSI C) <11> DW_AT_name : (indirect string, offset: 0x0): hello.c <15> DW_AT_comp_dir : (indirect string, offset: 0x8): /home/chenwj/project/misc <19> DW_AT_low_pc : 0x80483ed <1d> DW_AT_high_pc : 0x14 <21> DW_AT_stmt_list : 0x0 <1><25>: Abbrev Number: 2 (DW_TAG_subprogram) <26> DW_AT_external : 1 <26> DW_AT_name : (indirect string, offset: 0x5e): main <2a> DW_AT_decl_file : 1 <2b> DW_AT_decl_line : 1 <2c> DW_AT_type : <0x4b> <30> DW_AT_low_pc : 0x80483ed <34> DW_AT_high_pc : 0x14 <38> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa) <3a> DW_AT_GNU_all_call_sites: 1 <3a> DW_AT_sibling : <0x4b> <2><3e>: Abbrev Number: 3 (DW_TAG_variable) <3f> DW_AT_name : i <41> DW_AT_decl_file : 1 <42> DW_AT_decl_line : 2 <43> DW_AT_type : <0x4b> <47> DW_AT_location : 2 byte block: 91 74 (DW_OP_fbreg: -12) <2><4a>: Abbrev Number: 0 <1><4b>: Abbrev Number: 4 (DW_TAG_base_type) <4c> DW_AT_byte_size : 4 <4d> DW_AT_encoding : 5 (signed) <4e> DW_AT_name : int <1><52>: Abbrev Number: 0 * [[http://www.ibm.com/developerworks/library/os-debugging/|Debugging formats DWARF and STAB]] * [[https://blogs.oracle.com/quenelle/entry/stabs_versus_dwarf|Stabs versus dwarf]] ==== CIE & FDE ==== 目的是要從當前棧回復調用棧的狀態。掌握此原則之後,較好理解底下的內容。 * [[https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html|Chapter 8. Exception Frames]] * [[http://ucla.jamesyxu.com/?p=231|Decoding .debug_frame information from DWARF-2]] * 先閱讀 [[http://dwarfstd.org/doc/dwarf-2.0.0.pdf|DWARF Debugging Information Format]] 第 6.4 節 Call Frame Information。 * CFI (Call Frame Information) 的目的是重建 callee-save 暫存器於一個調用棧的值,其方式是透過底下的表加以重建。LOC 基本可以想成是當前棧的 PC 值。CFA 是計算開棧前,棧指針的位址 (即一個調用棧的頂部)。RN 代表該虛擬暫存器在一個調用棧的值如何復現,基本上透過 CFA 作為基地址加以計算。 LOC CFA R0 R1 ... RN L0 L1 // 執行完 L0 之後的狀態 ... LN * CFA 一行,是計算指向 caller 棧的 CFA。RN 基本上是以 CFA 作為基地址加以計算。 * CIE (Common Information Entry): 用來建立上表中的第一列。 * FDE (Frame Description Entry): 描述如何從表中的第一列,依序產生其它列。 * [[wp>LEB128|LEB128 (Little Endian Base 128)]]: 一種壓縮方式,可以以少量 byte 表示任意整數。Call Frame Instructions 的操作數基本以 LEB128 表示。 {{https://image.slidesharecdn.com/hes2011-joakley-sbratus-exploiting-the-hard-working-dwarf-110415112206-phpapp01/95/hes2011-james-oakley-and-sergey-bratusexploitingthehardworkingdwarf-24-728.jpg?500}} * 在一個編譯單元中,每一個函式對應一個 FDE。FDE 中相同的部分,抽取出來作為 CIE。 * CIE 中的 return_address_register 代表哪一個虛擬暫存器存放返回地址。 * CIE 中的 initial_instructions 用來表示如何重建上述表的第一行。 * FDE 中的 initial location 和 address range 決定此 FDE 對應到哪一個函式。 * FDE 中的 augmentation 代表平台或語言相關訊息。 * FDE 中的 instructions 用來表示如何重建上述表中,除第一行外的其它行。 底下是 [[http://dwarfstd.org/doc/dwarf-2.0.0.pdf|DWARF Debugging Information Format]] 的 Appendix 5。 * 執行 foo 前五條指令之後,棧的佈局。 high -------------- <---- old R7 (CFA) | R1 | -------------- 8 | old R6 | -------------- 4 | R4 | -------------- <---- new R7/R6 low * new R6 為棧底。new R7 繼續增長。 * foo 代碼。 ;; start prologue foo sub R7, R7, ; Allocate frame foo+4 store R1, R7, (-4) ; Save the return address foo+8 store R6, R7, (-8) ; Save R6 foo+12 add R6, R7, 0 ; R6 is now the Frame ptr foo+16 store R4, R6, (-12) ; Save a preserve reg. ;; This subroutine does not change R5 ... ;; Start epilogue (R7 has been returned to entry value) foo+64 load R4, R6, (-12) ; Restore R4 foo+68 load R6, R7, (-8) ; Restore R6 foo+72 load R1, R7, (-4) ; Restore return address foo+76 add R7, R7, ; Deallocate frame foo+80 jump R ; Return foo+84 * 用於復原 foo caller 棧狀態的表。 Loc CFA R0 R1 R2 R3 R4 R5 R6 R7 R8 foo [R7]+0 s u u u s s s s r1 # 尚未執行第一條指令。caller 調用 foo 時,R8 (PC) 被存在 R1。 foo+4 [R7]+fsize s u u u s s s s r1 # 執行完 foo 指令。CFA 為當前 R7 + fsize。 foo+8 [R7]+fsize s u u u s s s s c4 # 執行完 foo+4 指令。R1 存在 CFA - 4。 foo+12 [R7]+fsize s u u u s s c8 s c4 # 執行完 foo+8 指令。R6 存在 CFA - 8。 foo+16 [R6]+fsize s u u u s s c8 s c4 # 執行完 foo+12 指令。當前棧底由 R7 改為 R6。CFA 為當前 R6 + fsize。 foo+20 [R6]+fsize s u u u c12 s c8 s c4 # 執行完 foo+16 指令。R4 存在 CFA - 12。 foo+64 [R6]+fsize s u u u c12 s c8 s c4 # 此時棧頂 R7 已回復到 R6 所在位置。 foo+68 [R6]+fsize s u u u s s c8 s c4 # 執行完 foo+64 指令。R4 回復至調用前的狀態。 foo+72 [R7]+fsize s u u u s s s s c4 # 執行完 foo+68 指令。R6 回復至 old R6。當前棧底由 R6 改為 R7。 foo+76 [R7]+fsize s u u u s s s s r1 # 執行完 foo+72 指令。R1 回復至調用前的狀態。 foo+80 [R7]+0 s u u u s s s s r1 # 執行完 foo+74 指令。CFA 為當前 R7。 * CIE 用於復原表中的第一行。 cie+13 DW_CFA_def_cfa (7, 0) ; CFA = [R7]+0 cie+16 DW_CFA_same_value (0) ; R0 not modified (=0) cie+18 DW_CFA_undefined (1) ; R1 scratch cie+20 DW_CFA_undefined (2) ; R2 scratch cie+22 DW_CFA_undefined (3) ; R3 scratch cie+24 DW_CFA_same_value (4) ; R4 preserve cie+26 DW_CFA_same_value (5) ; R5 preserve cie+28 DW_CFA_same_value (6) ; R6 preserve cie+30 DW_CFA_same_value (7) ; R7 preserve cie+32 DW_CFA_register (8, 1) ; R8 is in R1 * FDE 用於復原表中剩餘的其它行。 fde+16 DW_CFA_advance_loc(1) ; foo+4 fde+17 DW_CFA_def_cfa_offset(/4) ; CFA = [R7] + /4 (4) fde+19 DW_CFA_advance_loc(1) ; foo+8 fde+20 DW_CFA_offset(8,1) ; R8 = CFA - 1 (4) fde+22 DW_CFA_advance_loc(1) ; foo+12 fde+23 DW_CFA_offset(6,2) ; R6 = CFA - 2 (4) fde+25 DW_CFA_advance_loc(1) ; foo+16 fde+26 DW_CFA_def_cfa_register(6) ; CFA = [R6] + /4 (4) fde+28 DW_CFA_advance_loc(1) ; foo+20 fde+29 DW_CFA_offset(4,3) ; R4 = CFA - 3 (4) fde+31 DW_CFA_advance_loc(11) ; foo+64 fde+32 DW_CFA_restore(4) ; R4 回復至調用前的狀態。 fde+33 DW_CFA_advance_loc(1) ; foo+68 fde+34 DW_CFA_restore(6) ; R6 回復至調用前的狀態。 fde+35 DW_CFA_def_cfa_register(7) ; R6 回復至 old R6。當前棧底由 R6 改為 R7。 fde+37 DW_CFA_advance_loc(1) ; foo+68 fde+38 DW_CFA_restore(8) ; R8 回復至調用前的狀態。 fde+39 DW_CFA_advance_loc(1) ; foo+72 fde+40 DW_CFA_def_cfa_offset(0) ; CFA = [R7]+0 * [[https://sourceware.org/binutils/docs/as/CFI-directives.html|7.10 CFI directives]] * [[https://stackoverflow.com/questions/2529185/what-are-cfi-directives-in-gnu-assembler-gas-used-for|What are CFI directives in Gnu Assembler (GAS) used for?]] * GAS 的 CFI 指示符 ([[https://sourceware.org/binutils/docs/as/CFI-directives.html|7.10 CFI directives]]) 基本對映 [[http://dwarfstd.org/doc/dwarf-2.0.0.pdf|6.4.2 Call Frame Instructions]],用來生成 debugger 和 stack unwinding 所需的 ''.debug_frame'' 和 ''.eh_frame'',後者用於支持 C++ 的例外處理。 * [[https://stackoverflow.com/questions/7534420/gas-explanation-of-cfi-def-cfa-offset|GAS: Explanation of .cfi_def_cfa_offset]] * ''cfi_def_cfa_offset'' 對應的是 [[http://dwarfstd.org/doc/dwarf-2.0.0.pdf|6.4.2 Call Frame Instructions]] 中的 ''DW_CFA_def_cfa''。 * 最重要的指示符有底下幾條: * ''.cfi_def_cfa register, offset'' * CFA 由指示符定義的 register 和 offset 相加而得到。 * ''.cfi_def_cfa_register register'' * 更新計算 CFA 所用的 register。 * ''.cfi_def_cfa_offset offset'' * 更新計算 CFA 所用的 offset。 * ''.cfi_offset register, offset'' * register 由 CFA 和 offset 相加而得到。 $ gcc -g test.c -S -o test.s main: .LFB0: .file 1 "test.c" .loc 1 1 0 .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 ; (a) .cfi_offset 6, -16 ; (b) movq %rsp, %rbp .cfi_def_cfa_register 6 ; (c) .loc 1 1 0 movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ; (d) ret .cfi_endproc .LFE0: .size main, .-main $ gcc -g test.c $ objdump -d --start-address=0x5fa --stop-address=0x605 a.out 00000000000005fa
: 5fa: 55 push %rbp 5fb: 48 89 e5 mov %rsp,%rbp 5fe: b8 00 00 00 00 mov $0x0,%eax 603: 5d pop %rbp 604: c3 retq $ objdump --dwarf=frames a.out 00000030 0000000000000014 00000000 CIE Version: 1 Augmentation: "zR" Code alignment factor: 1 Data alignment factor: -8 Return address column: 16 Augmentation data: 1b DW_CFA_def_cfa: r7 (rsp) ofs 8 DW_CFA_offset: r16 (rip) at cfa-8 DW_CFA_nop DW_CFA_nop 00000088 000000000000001c 0000005c FDE cie=00000030 pc=00000000000005fa..0000000000000605 DW_CFA_advance_loc: 1 to 00000000000005fb DW_CFA_def_cfa_offset: 16 (a) DW_CFA_offset: r6 (rbp) at cfa-16 (b) DW_CFA_advance_loc: 3 to 00000000000005fe DW_CFA_def_cfa_register: r6 (rbp) (c) DW_CFA_advance_loc: 6 to 0000000000000604 DW_CFA_def_cfa: r7 (rsp) ofs 8 (d) DW_CFA_nop DW_CFA_nop DW_CFA_nop ====== 除錯器 ====== 以下描述除錯器內部資料結構。 * 斷點可以分為邏輯斷點 (logical breakpoints) 和物理斷點 (physical breakpoints)。 * 邏輯斷點: 對映到使用者設置的斷點,通常是針對特定代碼行數。計算條件斷點的成立與否,通常是於邏輯斷點進行判斷。 * 物理斷點: 實際需要寫入斷點指令的位址。 * 邏輯斷點與物理斷點是多對一映射。 ====== 術語 ====== * [[wp>Breakpoint]] * [[http://stackoverflow.com/questions/8878716/what-is-the-difference-between-hardware-and-software-breakpoints|What is the difference between hardware and software breakpoints?]] * hardware breakpoint * 硬體暫存器提供的中斷,如 [[wp>x86 debug register]]。將欲設置斷點的位址寫入硬體暫存器,之後對該位址的讀/寫/執行,皆會觸發中斷。 * the main strength of hardware breakpoints is that you can use them to halt on non-execution accesses to memory locations. * software breakpoint * 硬體提供陷阱指令供除錯器插入中斷,如 [[wp>INT (x86 instruction)|INT 3]]。 * instruction breakpoint * conditional breakpoint, data breakpoint, watchpoint * 當條件成立,或是變數值改變,觸發斷點。 * 一般透過 hardware breakpoint 實現。若硬體不支援,則採 page protection 機制達成。 * [[wp>Program animation|Single step]] * [[http://neilscomputerblog.blogspot.tw/2012/10/single-step-debugging-explained.html|Single Step Debugging Explained]] * 單步可以透過狀態暫存器的號誌位實現,也可以透過斷點實現。 * step into, step over, step out * [[wp>Stack trace]] ====== 外部連結 ====== * [[https://sourceware.org/gdb/onlinedocs/stabs/|STABS]] * [[http://www.dwarfstd.org/|The DWARF Debugging Standard]] * [[http://wiki.dwarfstd.org/index.php?title=Libdwarf_And_Dwarfdump#Do_I_need_to_use_it.3F|libdwarf]]