av手机免费在线观看,国产女人在线视频,国产xxxx免费,捆绑调教一二三区,97影院最新理论片,色之久久综合,国产精品日韩欧美一区二区三区

C語言

C語言調(diào)試器是如何工作的

時(shí)間:2025-01-23 06:21:19 C語言 我要投稿
  • 相關(guān)推薦

C語言調(diào)試器是如何工作的

  當(dāng)你用GDB 的時(shí)候,可以看到它完全控制了應(yīng)用程序進(jìn)程。當(dāng)你在程序運(yùn)行的時(shí)候用 Ctrl + C,程序的運(yùn)行就能夠終止,而GDB能展示它的當(dāng)前地址、堆棧跟蹤信息之類的內(nèi)容。你知道C語言調(diào)試器是如何工作的嗎?下面是小編為大家?guī)淼年P(guān)于C語言調(diào)試器是如何工作的的知識(shí),歡迎閱讀。

  但是它們怎么不工作呢?

  開始,讓我們先研究它怎樣才會(huì)不工作。它不能通過閱讀和分析程序的二進(jìn)制信息來模擬程序的運(yùn)行。它其實(shí)能做,而那應(yīng)該能起作用(Valgrind 內(nèi)存調(diào)試器就是這樣工作的),但是這樣的話會(huì)很慢。Valgrind會(huì)讓程序慢1000倍,但是GDB不會(huì)。它的工作機(jī)制與Qemu虛擬機(jī)一樣。

  所以到底是怎么回事?黑魔法?……不,如果那樣的話就太簡單了。

  另一種猜想?……?破解!是的,這里正是這樣的。操作系統(tǒng)內(nèi)核也提供了一些幫助。

  首先,關(guān)于Linux的進(jìn)程機(jī)制需要了解一件事:父進(jìn)程可以獲得子進(jìn)程的附加信息,也能夠ptrace它們。并且你可以猜到的是,調(diào)試器是被調(diào)試的進(jìn)程的父進(jìn)程(或者它會(huì)變成父進(jìn)程,在Linux中進(jìn)程可以將一個(gè)進(jìn)程變?yōu)樽约鹤舆M(jìn)程:-))

  Linux Ptrace API

  Linux Ptrace API 允許一個(gè)(調(diào)試器)進(jìn)程來獲取低等級的其他(被調(diào)試的)進(jìn)程的信息。特別的,這個(gè)調(diào)試器可以:

  讀寫被調(diào)試進(jìn)程的內(nèi)存 :PTRACE_PEEKTEXT、PTRACE_PEEKUSER、PTRACE_POKE……

  讀寫被調(diào)試進(jìn)程的CPU寄存器 PTRACE_GETREGSET、PTRACE_SETREGS

  因系統(tǒng)活動(dòng)而被提醒:PTRACE_O_TRACEEXEC, PTRACE_O_TRACECLONE, PTRACE_O_EXITKILL, PTRACE_SYSCALL(你可以通過這些標(biāo)識(shí)區(qū)分exec syscall、clone、exit以及其他系統(tǒng)調(diào)用)

  控制它的執(zhí)行:PTRACE_SINGLESTEP、PTRACE_KILL、PTRACE_INTERRUPT、PTRACE_CONT (注意,CPU在這里是單步執(zhí)行)

  修改它的信號處理:PTRACE_GETSIGINFO、PTRACE_SETSIGINFO

  Ptrace是如何實(shí)現(xiàn)的?

  Ptrace的實(shí)現(xiàn)不在本文討論的范圍內(nèi),所以我不想進(jìn)一步討論,只是簡單地解釋它是如何工作的(我不是內(nèi)核專家,如果我說錯(cuò)了請一定指出來,并原諒我過分簡化:-))

  Ptrace 是Linux內(nèi)核的一部分,所以它能夠獲取進(jìn)程所有內(nèi)核級信息:

  讀寫數(shù)據(jù)?Linux有copy_to/from_user。

  獲取CPU寄存器?用copy_regset_to/from_user很輕松(這里沒有什么復(fù)雜的,因?yàn)镃PU寄存器在進(jìn)程未被調(diào)度時(shí)保存在Linux的struct task_struct *調(diào)度結(jié)構(gòu)中)。

  修改信號處理?更新域last_siginfo

  單步執(zhí)行?在處理器出發(fā)執(zhí)行前,設(shè)置進(jìn)程task結(jié)構(gòu)的right flag(ARM、x86)

  Ptrace是在很多計(jì)劃的操作中被Hooked(搜索 ptrace_event函數(shù)),所以它可以在被詢問時(shí)(PTRACE_O_TRACEEXEC選項(xiàng)和與它相關(guān)的),向調(diào)試器發(fā)出一個(gè)SIGTRAP信號。

  沒有Ptrace的系統(tǒng)會(huì)怎么樣呢?

  這個(gè)解釋超出了特定的Linux本地調(diào)試,但是對于大部分其他環(huán)境是合理的。要了解GDB在不同目標(biāo)平臺(tái)請求的內(nèi)容,你可以看一下它在目標(biāo)棧里面的操作。

  在這個(gè)目標(biāo)接口里,你可以看到所有C調(diào)試需要的高級操作:

  struct target_ops

  {

  struct target_ops *beneath;

  /* To the target under this one. */

  const char *to_shortname;

  /* Name this target type */

  const char *to_longname;

  /* Name for printing */

  const char *to_doc;

  /* Documentation. Does not include trailing

  newline, and starts with a one-line descrip-

  tion (probably similar to to_longname). */

  void (*to_attach) (struct target_ops *ops, const char *, int);

  void (*to_fetch_registers) (struct target_ops *, struct regcache *, int);

  void (*to_store_registers) (struct target_ops *, struct regcache *, int);

  int (*to__breakpoint) (struct target_ops *, struct gdbarch *,

  struct bp_target_info *);

  int (*to__watchpoint) (struct target_ops *,

  CORE_ADDR, int, int, struct expression *);

  }

  普通的GDB調(diào)用這些函數(shù),然后目標(biāo)相關(guān)的組件再實(shí)現(xiàn)它們。(概念上)這是一個(gè)棧,或者一個(gè)金字塔:棧頂?shù)氖欠浅Mㄓ玫,比如?/p>

  系統(tǒng)特定的Linux

  本地或遠(yuǎn)程調(diào)試

  調(diào)試方式特定的(ptrace、ttrace)

  指令集特定的(Linux ARM、Linux x86)

  那個(gè)遠(yuǎn)程目標(biāo)很有趣,因?yàn)樗ㄟ^一個(gè)連接協(xié)議(TCP/IP、串行端口)把兩臺(tái)“電腦”間的執(zhí)行棧分離開來。

  那個(gè)遠(yuǎn)程的部分可以是運(yùn)行在另一臺(tái)Linux機(jī)器上的gdbserver。但是它也可以是一個(gè)硬件調(diào)試端口的界面(JTAG) 或者一個(gè)虛擬的機(jī)器管理程序(比如 Qemu),并能夠代替內(nèi)核和ptrace的功能。那個(gè)遠(yuǎn)程根調(diào)試器會(huì)查詢管理程序的結(jié)構(gòu),或者直接地查詢處理器硬件寄存器來代替對OS內(nèi)核結(jié)構(gòu)的查詢。

  想要深層次學(xué)習(xí)這個(gè)遠(yuǎn)程協(xié)議,Embecosm 寫了一篇一個(gè)關(guān)于不同信息的詳細(xì)指南。Gdbserver的事件處理循環(huán)在這,而也可以在這里找到Qemu gdb-server stub 。

  總結(jié)一下

  我們能看到ptrace的API提供了這里所有底層機(jī)制被要求實(shí)現(xiàn)的調(diào)試器:

  獲取exec系統(tǒng)調(diào)用并從調(diào)用的地方阻止它執(zhí)行

  查詢CPU的寄存器來獲得處理器當(dāng)前指令以及棧的地址

  獲取clone或fork事件來檢測新線程

  查看并改變數(shù)據(jù)地址讀取并改變內(nèi)存的變量

  但是這就是一個(gè)調(diào)試器的全部工作嗎?不,這只是那些非常低級的部分……它還會(huì)處理符號。這是,鏈接源程序和二進(jìn)制文件。被忽視可能也是最重要的的一件事:斷點(diǎn)!我會(huì)首先解釋一下斷點(diǎn)是如何工作的,因?yàn)檫@部分內(nèi)容非常有趣且需要技巧,然后回到符號處理。

  斷點(diǎn)不是Ptrace API的一部分

  就像我們之前看到的那樣,斷點(diǎn)不是ptrace API的一部分。但是我們可以改動(dòng)內(nèi)存并獲取被調(diào)試的程序信號。你看不到其中的相關(guān)之處?這是因?yàn)閿帱c(diǎn)的實(shí)現(xiàn)比較需要技巧并且還要一點(diǎn)hack!讓我們來檢驗(yàn)一下如何在一個(gè)指定的地址設(shè)置一個(gè)斷點(diǎn)。

  1、這個(gè)調(diào)試器讀取(ptrace追蹤)存在地址里的二進(jìn)制指令,并保存在它自己的數(shù)據(jù)結(jié)構(gòu)中。

  2、它在這個(gè)位置寫入一個(gè)不合法的指令。不管這個(gè)指令是啥,只要它是不合法的。

  3、當(dāng)被調(diào)試的程序運(yùn)行到這個(gè)不合法的指令時(shí)(或者更準(zhǔn)確地說,處理器將內(nèi)存中的內(nèi)容設(shè)置好時(shí))它不會(huì)繼續(xù)運(yùn)行(因?yàn)樗遣缓戏ǖ?。

  4、在現(xiàn)代多任務(wù)系統(tǒng)中,一個(gè)不合法的指令不會(huì)使整個(gè)系統(tǒng)崩潰掉,但是會(huì)通過引發(fā)一個(gè)中斷(或錯(cuò)誤)把控制權(quán)交回給系統(tǒng)內(nèi)核。

  5、這個(gè)中斷被Linux翻譯成一個(gè)SIGTRAP信號,然后被發(fā)送到處理器……或者發(fā)給它的父進(jìn)程,就像調(diào)試器希望的那樣。

  6、調(diào)試器獲得信號并查看被調(diào)試的程序指令指針的值(換言之,是陷入 trap發(fā)生的地方)。如果這個(gè)IP地址是在斷點(diǎn)列表中,那么就是一個(gè)調(diào)試器的斷點(diǎn)(否則就是一個(gè)進(jìn)程中的錯(cuò)誤,只需要傳過信號并讓它崩潰)。

  7、現(xiàn)在,那個(gè)被調(diào)試的程序已經(jīng)停在了斷點(diǎn),調(diào)試器可以讓用戶來做任何他/她想要做的事,等待時(shí)機(jī)合適繼續(xù)執(zhí)行。

  8、為了要繼續(xù)執(zhí)行,這個(gè)調(diào)試器需要 1、寫入正確的指令來回到被調(diào)試的程序的內(nèi)存; 2、單步執(zhí)行(繼續(xù)執(zhí)行單個(gè)CPU指令,伴隨著ptrace 單步執(zhí)行); 3、把非法指令寫回去(使得這個(gè)執(zhí)行過程下一次可以再次停止) ;4、讓這個(gè)執(zhí)行正常運(yùn)行

  很整潔,是不是?作為一個(gè)旁觀的評論,你可以注意到,如果不是所有線程同時(shí)停止的話這個(gè)算法是不會(huì)工作的(因?yàn)檫\(yùn)行的線程可能會(huì)在合法的指令出現(xiàn)時(shí)傳出斷點(diǎn))。我不會(huì)詳細(xì)討論GDB是如何解決這個(gè)問題的,但在這篇論文里已經(jīng)說得很詳細(xì)了:使用GDB不間斷調(diào)試多線程程序。簡要地說,他們把指令寫到內(nèi)存中的其他地方,然后把那個(gè)指令的指針指向那個(gè)地址并單步執(zhí)行處理器。但是問題在于一些指令是和地址相關(guān)的,比如跳轉(zhuǎn)和條件跳轉(zhuǎn)……

  處理符號和調(diào)試信息

  現(xiàn)在,讓我們回到信號和調(diào)試信息處理。我沒有詳細(xì)地學(xué)習(xí)這部分,所以只是大體地說一說。

  首先,我們是否可以不使用調(diào)試信息和信號地址來調(diào)試呢?答案是可以。因?yàn)檎缥覀兛吹竭^的那樣,所有的低級指令是對CPU寄存器和內(nèi)存地址來操作的,不是源程序?qū)用娴男畔。因此,這個(gè)到源程序的鏈接只是為了方便用戶。沒有調(diào)試信息的時(shí)候,你看程序的方式就像是處理器(和內(nèi)核)看到的一樣:二進(jìn)制(匯編)指令和內(nèi)存字節(jié)。GDB不需要進(jìn)一步的信息來把二進(jìn)制信息翻譯成CPU指令:

  (gdb) x/10x $pc # heXadecimal representation

  0x402c60: 0x56415741 0x54415541 0x55f48949 0x4853fd89

  0x402c70: 0x03a8ec81 0x8b480000 0x8b48643e 0x00282504

  0x402c80: 0x89480000 0x03982484

  (gdb) x/10i $pc # Instruction representation

  => 0x402c60: push %r15

  0x402c62: push %r14

  0x402c64: push %r13

  0x402c66: push %r12

  0x402c68: mov %rsi,%r12

  0x402c6b: push %rbp

  0x402c6c: mov %edi,%ebp

  0x402c6e: push %rbx

  0x402c6f: sub $0x3a8,%rsp

  0x402c76: mov (%rsi),%rdi

  現(xiàn)在,如果我們加上調(diào)試信息,GDB能夠把符號名稱和地址配對:

  (gdb) $pc

  $1 = (void (*)()) 0x402c60

  你可以通過 nm -a $file 來獲取ELF二進(jìn)制的符號列表:

  nm -a /usr/lib/debug/usr/bin/ls.debug | grep " main"

  0000000000402c60 T main

  GDB還會(huì)能夠展示堆棧跟蹤信息(稍后會(huì)詳細(xì)說),但是只有感興趣的那部分:

  (gdb) where

  #0 write ()

  #1 0x0000003d492769e3 in _IO_new_file_write ()

  #2 0x0000003d49277e4c in new_do_write ()

  #3 _IO_new_do_write ()

  #4 0x0000003d49278223 in _IO_new_file_overflow ()

  #5 0x00000000004085bb in print_current_files ()

  #6 0x000000000040431b in main ()

  我們現(xiàn)在有了PC地址和相應(yīng)的函數(shù),就是這樣。在一個(gè)函數(shù)中,你將需要對著匯編來調(diào)試!

  現(xiàn)在讓我們加入調(diào)試信息:就是DWARF規(guī)范下的gcc -g選項(xiàng)。我不是特別熟悉這個(gè)規(guī)范,但我知道它提供的:

  地址到代碼行和行到地址的配對

  數(shù)據(jù)類型的定義,包括typedef和structure

  本地變量和函數(shù)參數(shù)以及它們的類型

  $ dwarfdump /usr/lib/debug/usr/bin/ls.debug | grep 402ce4

  0x00402ce4 [1289, 0] NS

  $ addr2line -e /usr/lib/debug/usr/bin/ls.debug 0x00402ce4

  /usr/src/debug/coreutils-8.21/src/ls.c:1289

  試一試dwarfdump來查看二進(jìn)制文件里嵌入的信息。addr2line也能用到這些信息:

  很多源代碼層的調(diào)試命令會(huì)依賴于這些信息,比如next命令,這會(huì)在下一行的地址設(shè)置一個(gè)斷點(diǎn),那個(gè)print命令會(huì)依賴于變量的類型來輸出(char、int、float,而不是二進(jìn)制或十六進(jìn)制)。

  最后總結(jié)

  我們已經(jīng)見過調(diào)試器內(nèi)部的好多方面了,所以我只會(huì)最后說幾點(diǎn):

  這個(gè)堆棧跟蹤信息也是通過當(dāng)前的幀是向上“解開(unwinded)”的($sp和$bp/#fp),每個(gè)堆棧幀處理一次。函數(shù)的名稱和參數(shù)以及本地變量名可以在調(diào)試信息中找到。

  監(jiān)視點(diǎn)(<code>watchpoints)是通過處理器的幫助(如果有)實(shí)現(xiàn)的:在寄存器里標(biāo)記哪些地址應(yīng)該被監(jiān)控,然后它會(huì)在那內(nèi)存被讀寫的時(shí)候引發(fā)一個(gè)異常。如果不支持這項(xiàng)功能,或者你請求的斷點(diǎn)超過了處理器所支持的……那么調(diào)試器就會(huì)回到“手動(dòng)”監(jiān)視:一個(gè)指令一個(gè)指令地執(zhí)行這個(gè)程序,并檢查是否當(dāng)前的操作到達(dá)了一個(gè)監(jiān)視點(diǎn)的地址。是的,這很慢!

  反向調(diào)試也可以這樣進(jìn)行,記錄每個(gè)操作的效果,并反向執(zhí)行。

  條件斷點(diǎn)是正常的斷點(diǎn),除非在內(nèi)部,調(diào)試器在將控制權(quán)交給用戶前檢查當(dāng)前的情況。如果當(dāng)前的情況不滿足,程序?qū)?huì)默默地繼續(xù)運(yùn)行。

  還可以玩gdb gdb,或者更好的(好多了)gdb --pid $(pid of gdb),因?yàn)榘褍蓚(gè)調(diào)試器放到同一個(gè)終端里是瘋狂的:-)。還可以調(diào)試系統(tǒng):

  qemu-system-i386 -gdb tcp::1234

  gdb --pid $(pidof qemu-system-i386)

  gdb /boot/vmlinuz --exec "target remote localhost:1234"


【C語言調(diào)試器是如何工作的】相關(guān)文章:

如何學(xué)習(xí)c語言10-21

C語言是如何調(diào)用硬件的10-01

C語言如何輸入語句10-28

新手如何學(xué)習(xí)C語言09-29

如何搭建C語言環(huán)境10-27

如何學(xué)習(xí)C語言編程10-28

C語言EOF如何使用08-29

如何理解C語言指針05-19

c語言如何控制硬件09-14