gdb和gdbserver源碼架構分析


大致瀏覽了下GDB源碼,分析記錄如下:

 

1.       GDBGCC等其他GNU工具以前,構成了程序開發調試不可缺少的一環.

2.       GDBSERVER源碼架構如下:

a)         Gdbserver代碼簡單,本身支持serialtcp連接

b)         源碼位於 gdb/gdbserver

c)         Server.c文件是入口文件

d)         編譯配置

                         i.              Makefile.in中,gdbserver依賴OBSOBS依賴DEPFILESDEPFILES=@GDBSERVER_DEPFILES@

                       ii.              GDBSERVER_DEPFILESconfigure傳遞的,configure.ac中,GDBSERVER_DEPFILES=”$srv_regobj $srv_tgt ….”

                      iii.              $srv_regobjconfigure.srv中根據—target選項生成,configure.srv被主configure包含,所以最終結果是不同的target,不同的文件列表

                      iv.              對於arm linux來說,configure.srv中定義如下:

srv_regobj="reg-arm.o arm-with-iwmmxt.o"

                            srv_regobj="${srv_regobj} arm-with-vfpv2.o"

                            srv_regobj="${srv_regobj} arm-with-vfpv3.o"

                            srv_regobj="${srv_regobj} arm-with-neon.o"

                            srv_tgtobj="linux-low.o linux-osdata.o linux-arm-low.o linux-procfs.o"

                       v.              reg-arm.c是生成的,linux-low.c是原有的

                      vi.              initialize_low接口在linux-low.c

e)         代碼流程:

                         i.              重要變量

1.         the_target全局默認的調試目標

2.         the_low_target 底層目標

                       ii.              initialize_low接口被調用,底層初始化

1.         調用set_target_ops賦值the_target

2.         linux_target_opslinux上目標的操作集合定義

                      iii.              remote_prepare接口准備遠程連接,也就是gdb這一端

                      iv.              之后是事件循環

1.         remote_open (port);打開遠程

a)         區分tcp或者serial即可

b)         add_file_handler (listen_desc, handle_accept_event, NULL);這句,設置handle_accept_event接口來處理tcp或者serial的事件

2.         start_event_loop ();

a)         處在等事件處理事件的循環里

f)          調試目標的啟動

                         i.              調試目標有兩種

1.         可執行文件

2.         attach到現有進程

                       ii.              可執行文件在處理輸入參數時候,直接執行的

1.         start_inferior (program_argv);

                      iii.              attach是在處理輸入參數時候,直接attach

1.         attach_inferior (pid)

g)         處理遠程交互過程

                         i.              handle_accept_event接口處理遠程包

                       ii.              handle_serial_event處理連接后的事件

                      iii.              handle_serial_event主要調用process_serial_event接口實現處理原承包

                      iv.              process_serial_event過程如下

1.         解析命令

a)         g G p P m M z Z d D等命令,詳見GDB Remote Serial Protocol - RSP

2.         處理命令

a)         the_target的各種接口被調用,處理命令

3.         填充own_buf

a)         g包為例

                                                                   i.              g包意思是要獲取所有寄存器值

                                                                 ii.              the_low_targetarch_setup接口對於arm-linuxarm_arch_setup接口,會在適當的實際被調用,會調用init_registers_arm初始化寄存器,init_registers_arm(arm.c – 編譯時候生成的,里邊的寄存器列表是根據gdb/regformats目錄下的reg-arm.dat生成的)接口.

                                                                iii.              init_registers_arm接口設置寄存器cache,寄存器數量等.

                                                                iv.              處理g包的時候,

1.         調用fetch_traceframe_registers獲取寄存器

2.         registers_to_string轉換成串

                                                                 v.              這里看到gdbserver回復的寄存器數量是根據目標架構的不同,arm_linux_init_hwbp_cap接口獲取的設置到arm_linux_hwbp_cap變量里,不同的寄存器數量,具體是在arm_arch_setup接口中,調用arm_linux_init_hwbp_cap獲取能力,根據能力注冊不同的寄存器組.

4.         最后,調用putpkt (own_buf);發送返回包

 

3.       GDB源碼架構如下:

a)         名詞解釋

                         i.              Interpreter – 解釋,執行器,指的是consoletuimiUI,即用戶接口,包括:

1.         #define INTERP_CONSOLE                "console"

2.         #define INTERP_MI1             "mi1"

3.         #define INTERP_MI2             "mi2"

4.         #define INTERP_MI3             "mi3"

5.         #define INTERP_MI          "mi"

6.         #define INTERP_TUI                  "tui"

7.         #define INTERP_INSIGHT                  "insight"

                       ii.              Command loop (event loop) – 命令(事件)循環,實際任何程序都是無限循環,循環的過程中,處理外接的事件輸入,產生輸出,GDB也是基於這樣的架

事件包括很多類型:輸入,網絡等

                      iii.              Target – 調試的目標,本地文件,進程,網絡,串口等,調試目標是真正的讀取數據,寫入數據的接口.

b)         關鍵變量

                         i.              current_target – 當前目標

                       ii.              current_interpreter – 當前執行器

                      iii.              inf – info 命令

                      iv.              cli – command line interface

                       v.              tui – text user interface

                      vi.              insight – 是一個圖形前端

c)         架構

                         i.              編譯時候,根據configure --target參數,確定了目標架構需要編譯的.c,根據參數生成init.c,主要是里邊的initialize_all_files接口,只包含了配置需要的初始化函數,初始化函數就是類似: _initialize_arm_tdep_initialize_xxxx這樣的接口,這樣的接口都會被配置生成到init.c.

                       ii.              實際編譯出來后,自然是針對目標架構調試的gdb

                      iii.              initialize_all_files接口中,會調用針對目標架構的初始化接口,如_initialize_arm_tdep,這樣的接口會初始化目標架構,包括寄存器列表,特殊的命令等.

                      iv.              初始化另一個比較重要的事情是選擇InterpreterInterpreter有特有的事件循環,這樣無論是GUI還是Console,都可以根據需要處理用戶交互了.

                       v.              初始化完成后,進入事件(命令)循環,處理事件和命令,直到退出.

                      vi.              GDB新版本中,加入了事件監聽機制

d)         基本執行流程

                         i.              main (gdb.c) -> 程序入口

                       ii.              gdb_main (main.c) -> gdb入口

                      iii.              captured_main (main.c) -> gdb入口

                      iv.              gdb_init(top.c)  -> 總初始化入口,很重要,很多重點信息都在這

                       v.              captured_command_loop(main.c) -> 命令循環入口

                      vi.              current_interp_command_loop(interps.c) -> 命令循環,調用到此處后,不再返回

                    vii.              current_interp_command_loop接口根據不同的Interpreter,進入不同的循環里.

e)         初始化和init.c

                         i.              gdb-7.2/gdb/Makefile.in中把init.c作為一個總目標的依賴,目標的生成過程就是,建立initialize_all_files函數,添加一系列的_initialize_xxxx函數,這也是_initialize_xxx函數看不到調用的原因.

                       ii.              initialize_all_files函數被gdb_init調用,初始化目標.

                      iii.              命令注冊

                      iv.              gdb實際上是基於命令循環的,在用戶的命令作用下,執行相應的動作

                       v.              命令有兩個部分:

1.         公共的命令

a)         gdb_init -> init_cli_cmds 初始化了一些命令,其他命令可自行查找

2.         目標特有的命令

a)         _initialize_arm_tdep接口內部注冊此類命令

f)          命令添加和分類

                         i.              分類,如下:

 no_class = -1, class_run = 0, class_vars, class_stack, class_files,

 class_support, class_info, class_breakpoint, class_trace,

 class_alias, class_bookmark, class_obscure, class_maintenance,

 class_pseudo, class_tui, class_user, class_xdb,

                       ii.              接口

1.         add_cmd, - xxx

2.         add_com, - xxx

3.         add_prefix_cmd, zzz xxx yyy

4.         add_info, - info xxx

5.         add_setshow_integer_cmd – set xxx

g)         命令執行

                         i.              Interpreterresume接口,調用gdb_setup_readline初始化了call_readline input_handler = command_line_handler;這兩個變量。最后調用 add_file_handler (input_fd, stdin_event_handler, 0);,注冊了標准輸入事件的回調是stdin_event_handler

                       ii.              主事件循環中,如果發生了輸入事件,stdin_event_handler接口被調用,進一步調用call_readlinecall_readline調用input_handler接口來處理,這里的input_handler就是command_line_handler

                      iii.              command_line_handler 調用command_handlercommand_handler調用execute_command執行命令.

                      iv.              命令回調的參數

 

h)         目標CPU注冊

                         i.              比較重要的就是目標cpu的注冊

                       ii.              initialize_all_files->_initialize_arm_tdep類接口,初始化了目標

                      iii.              _initialize_arm_tdep為例:

1.         調用gdbarch_register注冊架構

2.         增加一些特殊命令

3.         重要的接口是:arm_gdbarch_init,這個接口真正初始化了arm架構

4.         arm_gdbarch_init這個接口,不會直接調用,會根據目標文件的格式來啟用執行的.

i)           目標架構初始化

                         i.              file命令

1.         add_cmd ("file", class_files, file_command….

2.         file_command接口處理file命令

3.         file_command -> exec_file_command -> exec_file_attach

4.         exec_file_attach重要的部分是,調用set_gdbarch_from_file (exec_bfd);調用gdbarch_find_by_info初始化了目標架構.

                       ii.              targetopen操作,會引起架構初始化

j)           調試GDB

                         i.              可以通過pc上的gdb調試編譯出來的gdb

                       ii.              通過設置gdbarch_debug變量,可以打開gdb本身的調試信息,實際就是打開GDBARCH_DEBUG宏,輸出信息相當多.

k)         Target概述

                         i.              分類

1.         gdb下輸入help target

    target async -- Use a remote computer via a serial line

target child -- Win32 child process (started by the "run" command)

target core -- Use a core file as a target

target exec -- Use an executable file as a target

target extended-async -- Use a remote computer via a serial line

target extended-remote -- Use a remote computer via a serial line

target remote -- Use a remote computer via a serial line

                       ii.              啟用

1.         通過顯示的執行 target xxx yyy

a)         xxx 是上邊列出的名字,yyy是參數

b)         命令后,target ops相應的open接口會被打開

c)         所有的初始化都在init.c

d)         target_fetch_registers獲取寄存器

e)         target打開后,被調試文件應該被載入,進一步根據文件格式初始化了目標架構

                      iii.              默認target

1.         gdb_init -> initialize_targets -> push_target (&dummy_target);設置了dummy_target為默認,名字是”None”

2.         我們直接用gdb hello,之后直接run,這時候有個默認的目標

3.         注意到initialize_all_files接口,后側調用的_initialize_exec接口,初始化了exec目標,過程中會調用add_target增加target

4.         初始化完后,加載可執行文件的時候,exec_file_attach 調用 add_target_sections, add_target_sections會調用          push_target (&exec_ops);,把exec設置成默認目標的.

l)           寄存器的顯示和獲取

                         i.              顯示

1.         Info registers info all-registers 可以顯示寄存器

2.         registers_info接口用來處理”info registers”命令, add_info ("registers", nofp_registers_info, _("\

3.         registers_info調用gdbarch_print_registers_info調用gdbarch->print_registers_infogdbarch->print_registers_info實際上是初始化架構的時候賦值的,一般都是用的默認值default_print_registers_info,是在gdbarch_alloc接口中賦值的,還有很多架構的其他默認值.

4.         default_print_registers_info實際是從緩存里那結果,也就是這個寄存器的結果是別的地方拿回來的.

                       ii.              獲取

1.         遠程的情況

a)         所有的遠程的情況都可以在remote.c找到答案

b)         _initialize_remote->init_remote_ops接口初始化了remote_ops變量,接着調用add_target 這個接口比較特殊,也比較重要,調用 add_cmd (t->to_shortname, no_class, t->to_open, t->to_doc, &targetlist); t->to_shortname是在init_remote_ops里賦值成” remote”的,也就是”target remote xxx”的來歷,同時t->to_open正是remote_open接口,就是說命令執行的時候,remote_open被調用.

c)         remote_open調用remote_open_1remote_open_1調用reopen_exec_file重開可執行文件,也意味着重新獲取架構arch.調用reread_symbols重新獲取符號表.

d)         remote_open_1接下來調用remote_serial_open打開遠程連接,

e)         remote_fetch_registers接口負責獲取遠程寄存器,會被target_fetch_registers接口間接調用到.

f)          target_fetch_registers接口是target的獲取寄存器的接口,regcache_raw_read接口調用了target_fetch_registers

g)         一定是通過某種方式:調用到了target_fetch_registers,獲取一個寄存器,並且緩存起來.

h)         target_preopen接口會被remote_open_1調用,target_preopen迭代了所有的觀察者: iterate_over_inferiors (dispose_inferior, NULL);使用dispose_inferior接口分發,調用到switch_to_threadswitch_to_thread調用regcache_read_pc讀出pc寄存器的值.

2.         本地的情況

a)         類似遠程情況,由當前的targettarget_fetch_register接口獲取

b)         File或者exec-file命令加載文件后,exec變成默認目標

c)         運行起來后,child變成默認目標

d)         所以這時需要關注child目標的獲取寄存器接口

e)         child 目標在i386-linux-nat.c中,_initialize_i386_linux_nat接口注冊

f)          i386_linux_fetch_inferior_registers接口作為child目標的獲取寄存器接口

g)         i386_linux_fetch_inferior_registers接口通過ptrace調用,獲取了寄存器的值

m)       調試功能實現

                         i.              Linux系統上

1.         基本都是通過ptrace系統調用實現的,無論是本地的child目標,還是gdbserver,都是通過ptrace來控制子進程完成的

2.         ptrace包括了讀寫寄存器,讀寫內存

3.         單步需要硬件支持,軟件做的話,要分析出下一條指令的准確地址,然后替換

4.         斷點功能是通過讀寫內存,修改對應指令即可實現

                       ii.              Win32系統上

1.         Winapi有很多這樣的接口可以實現調試功能,讀寫子進程內存,讀寫寄存器等

2.         單步一樣需要硬件支持

3.         斷點也是通過讀寫內存,修改對應指令實現的

n)         gdbgdbserver的交互

                         i.              一般情況下,gdbgdbserver

1.         使用tcp連接

2.         targetremote

3.         Interpreter任選

4.         架構從被調試的elf文件中獲取

5.         使用gdb remote serial protocol – GDB RSP

a)         簡單的字符串命令

                       ii.              gdbserver,功能需要如下,也就是需要能夠響應的命令如下

1.         讀寫寄存器 - 必須

2.         讀寫內存 - 必須

3.         軟硬件斷點 - 可選

4.         中斷執行 - 可選

5.         單步執行 - 可選

                      iii.              RSP – 讀寫寄存器

1.         remote_fetch_registers接口負責獲取remote寄存器

a)         兩種方法獲取

                                                                   i.              fetch_registers_using_g – “g”命令

                                                                 ii.              fetch_register_using_p – “p”命令

                                                                iii.              regcache_raw_supply負責填充返回的結果到cache里,這樣后邊就可以使用了

b)         寄存器的順序和數量

                                                                   i.              gdbarch_num_regs接口用戶獲取目標架構的寄存器數量

                                                                 ii.              default_print_registers_info接口是默認的打印寄存器的接口,這里邊設計到了緩存的reg值,reg名字,以及順序

1.         fputs_filtered (gdbarch_register_name (gdbarch, i), file);是在打印名字,其中i是寄存器索引

2.         arm架構對應的gdbarch_register_name調用接口是arm_register_name(arm-tdep.c)

a)         arm_register_names[i]被返回

b)         arm_register_names定義為特定值,這個順序可作為gdbserver返回g命令時候的順序.

c)         寄存器映射

1.         arm_gdbarch_init接口非常重要,初始化了很多架構相關的數據和接口

2.         本地的寄存器和remote有個對應關系

3.         arm_gdbarch_init中有一部分是初始化架構描述,這部分描述了寄存器,具體見 if (tdesc_has_registers (tdesc))下部分代碼,remote目標初始化調用init_remote_state接口,初始化了寄存器映射關系,之后調用      tdesc_use_registers (gdbarch, tdesc, tdesc_data)

4.         tdesc_use_registers 設置了寄存器相關的名字,類型,映射關系的回調,set_gdbarch_remote_register_number (gdbarch,tdesc_remote_register_number);tdesc_remote_register_number接口,

5.         init_remote_state接口,負責初始化remote狀態,適當的時機被調用

6.         init_remote_state接口初始化了g包的大小,調用map_regcache_remote_table接口初始化了寄存器順序和映射關系,這個接口很重要.

7.         過濾的結果是,那些不支持的寄存器不會在遠程package的數據包里.

8.         process_g_packet接口處理了g數據包返回的寄存器結果包,包括了解析包的過程.

d)         g包的大小

                                                                   i.              init_remote_state接口中,分配了rsa – Remote_arch_state,並且初始化了rsa->sizeof_g_package,調用的是map_regcache_remote_table

                                                                 ii.              map_regcache_remote_table接口完成了計算.

1.         首先獲取架構寄存器總個數,架構初始化時候固定的

2.         遍歷所有寄存器,如果寄存器大小描述不是0,則通過接口gdbarch_remote_register_number獲取遠程寄存器pnum,也就是g包里的偏移.寄存器大小通過register_size獲取,register_size接口直接使用的descr->sizeof_register[regnum]descr->sizeof_register[regnum]是在初始化regcache描述的時候賦值的,接口是init_regcache_descrinit_regcache_descr接口中,通過descr->sizeof_register[i] = TYPE_LENGTH(descr->register_type[i])descr->register_type[i]在稍微上邊一點賦值,具體等於descr->register_type[i]=gdbarch_register_type(gdbarch,i)gdbarch_register_type接口,gdbarch_register_type最終映射到arm_register_type接口,arm_register_type根據架構支持特性,根據寄存器所在偏移,返回寄存器類型.此處需要注意:arm-tdep.h中定義了寄存器的偏移,如ARM_PC_REGNUM = 15arm-tdep.c中定義了寄存器的列表,arm_register_names,此處判斷如果不在arm_register_names范圍內,就會返回bulltin_int0類型,實際這個類型大小size0,額外的寄存器會通過xml文件的形式傳遞過來.xml描述由遠端傳遞過來,如arm-with-vfpv2.xml文件,存在於gdb/features目錄下,gdbserver端對應的變量是gdbserver_xmltarget.如果此時gdbserver提供的g包很大,而不在pcg包大小范圍內,就會出現”g package too long”這樣的提示了.就是說,最好打開gdbxml支持,才能不會出現類似的問題了,支持xml方法是

                                                                                                             i.              Sudo apt-get install expat libexpat1-dev

                                                                                                           ii.              ./configure –enable-werror=no –target=arm-linux-gnueabi  --with-expat=yes --with-expat-prefix =/uar/local

                                                                                                          iii.              上面那樣做,不起作用,需要手動修改configure,直接修改成不需要檢查,同時設置好expat.so路徑即可

                                                                                                          iv.              Expat庫,自己下載編譯安裝,會安裝到/usr/local

                      iv.              RSP - 讀寫內存

1.         類似讀寫寄存器,命令不同而已

                       v.              RSP – 軟硬件斷點

                      vi.              RSP – 中斷執行

                    vii.              RSP – 單步執行

1.         以上都是發命令和參數的過程

o)         gdb的編譯

                         i.              官網獲取源碼,解壓

                       ii.              針對arm,但是在pc上運行

1.         ./configure --target=arm-linux-gnueabi --enable-werror=no

2.         make –j5

                      iii.              針對arm,在arm上運行

1.         ./configure --target=arm-linux-gnueabi –host=arm-linux-gnueabi –enable-werror=no

2.         這時候需要termcap庫,下載編譯即可

3.         make –j5

 

4.       嵌入式gdbserver實現思路

a)         gdb保持gnu工具里的不變

b)         沒有標准操作系統(linux,win32,unix),就沒法運行標准的gdbserver,所以要在pc上實現一個gdbserver,之后在板子端實現gdbstubgdbservergdb通信,gdbserver通過板子與pc的連接口與gdbstub通信。

c)         gdbserver這塊還是主要實現gdb發過來的命令,比較重要的是g獲取全部寄存器和m讀內存,gdb根據寄存器值,配合內存讀寫命令,就能得到幾乎所有的內容了

                         i.              g命令要注意寄存器順序,數量,大小

                       ii.              寄存器順序,數量,大小和架構相關,同時順序參照gdb定義,當然自然情況是按照編號排序(r0,r1,…,r15之類).

                      iii.              因為嵌入式gdbserver無法獲取調試文件或進程的架構,所以需要在啟動gdbserver的時候傳遞參數,這時候已經知道需要調試的目標的信息了.

d)         gdbservergdbstub通信,根據板子不同而不同


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM