gdb基本命令及处理框架

gdb基本命令及处理框架  

2011-09-03 21:52:51|  分类: gdb源代码分析 |字号 订阅

一、调试gdb

如果使用gdb的调试,会发现gdb在配置的时候(执行./configure)的时候并没有提供禁止优化的编译选项,也就是-O2的禁止。虽然一些资料上介绍可以通过 --disable-optimization来禁止优化,但是gdb的配置暂时还是不支持这个选项的。

解决的办法其实也很简单,那就是首先执行configure来生成默认的Makefile,这个默认的Makefile中是开启了优化的构建,此时就手动完成优化的禁止,使用sed可以非常简单的实现

sed -i -e "s/-O2//g" Makefile

这里不要在-O2的前后加上空格,以为通常的这个CFLAGS定义为CFLAGS=“-g -O2”也就是之后是没有空格的。这样做得另一个好处就是对于CXXFLAGS的修改也可以同时完成。

二、gdb常用选项

1、file命令

在有些时候启动gdb之后,我们才想调试一个文件;或者我们并不想调试一个函数,而只是想查看一下里面的一些函数。此时程序还没有启动,也没有指定调试文件,此时就可以通过file来指定当前使用的文件,这个文件里一般会有调试符号,此时就可以先设置断点,然后执行run来真正开始函数运行

2、show version

gdb不同时期的版本提供的命令集可能都不相同,某些命令可能只有在比较高的版本中才可以使用,所以此时就需要确定gdb的版本号,此时就可以使用这个命令来显示gdb的版本号。

3、set debug xxx 命令簇

这些是gdb内置的一些调试接口,我们可以通过这些命令来开启gdb的内置调试命令,从而看到系统的更多信息。例如可以通过

set debug frame 1

开启堆栈回溯功能,通过

set debug target 1

来显示对目标的特殊操作指令及序列。

4、命令脚本

按照约定,gdb在启动的时候会首先执行一些配置文件中的命令,从而完成一些用户定制的特殊命令,这些命令一般是对于某种常用调试的必须动作,从而避免重复的输入。这种脚本的默认名称就是.gdbinit,所以这些命令可以设置好之后供重复使用。例如,在使用qemu进行调试的时候,可能需要执行gdb的target 命令,或者一些定制的命令,比如打开调试的set debug target 1之类的命令,这些都可以放在当前工作目录下一个文件中。.gdbinit可以有多个,首先是--with-system-gdbinit制定的位置下的文件,然后是Home文件夹下,然后是当前工作路径下,所以我们一般是设置在当前工作路径,这样更容易定制。

另一种是在某种常见下才使用的命令。此时gdb也支持使用source来执行一个命令脚本。这个命令也是和BASH的执行脚本命令是一致的。顺便也可以说明为什么我们在调试器里使用list而不是source来查看源代码的原因了

注意:在默认的Linux GUI环境下,.gdbinit由于是以点开始的,也就是隐藏文件,所以可能不会显示出来,但是它的确是存在的。

5、set substitute-path from to

这个和BASH中的rename是相同的命令格式。这个命令对于大规模团队协助开发也是有用的。对于同一套代码,可能不同人放置的位置并不相同,但是版本上真正使用的是版本机发布的版本,此时版本机存放的位置一般是和开发者的位置不同的,此时就需要进行调试。这个比directory的功能要强大,因为gdb如果读取的源文件是一个相对路径,它一般只会在在当前目录下进行去掉所有路径之后的文件名匹配尝试,所以directory一般是不满足的。使用这个substitute-path对于保存了完整目录树的结构调试是最好的选择。debug信息中保存的文件路径即位gcc接受的文件名,这个文件名可以是一个绝对路径或者相对路径,无论如何,调试信息不会自行发挥这个信息,只是照原样保存到调试信息中

6、h命令

这个可以列出一些我们关心的特殊命令,注意,所有命令的帮助都是以h开头,而不是命令的那种以help结束的风格。gdb对自己的命令进行了分类,不同的命令可以通过h看到一个大概的分类。

三、gdb框架

1、gdb的激励

gdb的激励主要来自两个方面,一个是用户输入的调试命令,另一个是被调试的任务发过来的状态,例如debugee收到信号,创建成功等时间,后者在gdb中成为inferior时间,相对于用户输入信息。

虽然gdb可以收到两种激励,但是gdb不能通过select来同时等待两种激励,因为子进程状态的改变是通过信号+waitXXX函数来实现的,而对于标准输入则是通过select来等待的,而且此时不能将子进程状态改变放入一个文件描述符中。也就是说,debugger不同同时等待 标准用户输入和子进程状态 两个时间

但是话又说回来,子进程状态的改变会把debugger从select中唤醒,从而解除select阻塞。这也就是子进程被调试时需要同步唤醒和发信号两个动作的原因吧。

2、gdb命令的分类

gdb对所有的内部命令都进行了分类,对于gdb中的命令,一般是通过add_com的格式添加,对于不同的命令,它们有两个参数对于它们分类是很重要的。一个是这个命令所属的类型command_class,另一个是这个命令将会被放在哪一个命令链表中。其中的class主要是为了在help系统中使用,而cmd_list_element链表则决定了这个命令的实质性内容。例如,把一个命令放在setlist链表中,那么执行set的时候就会从这个里面搜索所有的实现,从而完成第二部的路由。

3、断点的设置及真正生效

断点的设置和生效并不是同步的。当debugger获得控制权的时候,用户可以通过breakpoint设置很多的断点,但是调试器并不是把在断点设置之后就马上生效,而是只有当用户输入了run continue之类的命令之后,调试器才会进行真正的断点插入操作,也就是一个lazy思想的应用。这个具体的动作在insert_breakpoints函数中完成,如果是查看gdb的源代码,对刚开始在breakpoint中没有找到具体设置接口,就是这个原因

4、debugger内存和寄存器的读出操作

linux_proc_xfer_partial    linux下的内存读出操作,遗憾的是这个函数的实现是只能通过/proc/PID/mem执行的操作,优点是速度很快,缺点是它只能执行读出操作,而不能进行写入操作。

inf_ptrace_xfer_partial   Linux下的内存写入操作,由于上面说到的proc只能进行读出操作,所以这里的写入操作还是老老实实的使用ptrace来进行写操作。ptrace (PT_WRITE_I, pid,   (PTRACE_TYPE_ARG3)(uintptr_t)ounded_offset,   buffer.word);

fetch_regs           位于i386-linux-nat.c文件中,是一个寄存器的读取操作。

store_regs           和fetch_regs相同位置,是寄存器的读取操作。

5、子进程状态的同步等待

Breakpoint 4, 0x008acb60 in waitpid () from /lib/libpthread.so.0
(top-gdb) bt
#0  0x008acb60 in waitpid () from /lib/libpthread.so.0
During symbol reading, incomplete CFI data; unspecified registers (e.g., eax) at 0x8acb6b.
#1  0x08092072 in my_waitpid (pid=-1, status=0xbfffebfc, flags=-2147483647)
    at ../../gdb-7.2/gdb/linux-nat.c:423
#2  0x08098093 in linux_nat_wait_1 (ops=0x84467e0, ptid=...,
    ourstatus=0xbfffed6c, target_options=0)
    at ../../gdb-7.2/gdb/linux-nat.c:3353
#3  0x08098e44 in linux_nat_wait (ops=0x84467e0, ptid=...,
    ourstatus=0xbfffed6c, target_options=0)
    at ../../gdb-7.2/gdb/linux-nat.c:3704
#4  0x0818aeb2 in target_wait (ptid=..., status=0xbfffed6c, options=0)
    at ../../gdb-7.2/gdb/target.c:2219
During symbol reading, DW_AT_name missing from DW_TAG_base_type.
#5  0x08156d86 in wait_for_inferior (treat_exec_as_sigtrap=0)
    at ../../gdb-7.2/gdb/infrun.c:2529
#6  0x0815634f in proceed (addr=4294967295, siggnal=TARGET_SIGNAL_DEFAULT,
    step=0) at ../../gdb-7.2/gdb/infrun.c:2064
#7  0x0814feba in continue_1 (all_threads=0) at ../../gdb-7.2/gdb/infcmd.c:689
#8  0x081500f0 in continue_command (args=0x0, from_tty=1)
    at ../../gdb-7.2/gdb/infcmd.c:781
#9  0x080c3e3f in do_cfunc (c=0x8461ff0, args=0x0, from_tty=1)
    at ../../gdb-7.2/gdb/cli/cli-decode.c:67
#10 0x080c64ed in cmd_func (cmd=0x8461ff0, args=0x0, from_tty=1)
    at ../../gdb-7.2/gdb/cli/cli-decode.c:1771
#11 0x08056e75 in execute_command (p=0x84329d1 "", from_tty=1)
---Type <return> to continue, or q <return> to quit---
    at ../../gdb-7.2/gdb/top.c:422
#12 0x0816e9b5 in command_handler (command=0x84329d0 "c")
    at ../../gdb-7.2/gdb/event-top.c:498
#13 0x0816ef19 in command_line_handler (rl=0x8538c30 "\250wT\b\270wT\b")
    at ../../gdb-7.2/gdb/event-top.c:702
#14 0x08259bc2 in rl_callback_read_char ()
    at ../../gdb-7.2/readline/callback.c:205
#15 0x0816e177 in rl_callback_read_char_wrapper (client_data=0x0)
    at ../../gdb-7.2/gdb/event-top.c:178
#16 0x0816e8ad in stdin_event_handler (error=0, client_data=0x0)
    at ../../gdb-7.2/gdb/event-top.c:433
#17 0x0816d640 in handle_file_event (data=...)
    at ../../gdb-7.2/gdb/event-loop.c:817
#18 0x0816ce84 in process_event () at ../../gdb-7.2/gdb/event-loop.c:399
#19 0x0816cf48 in gdb_do_one_event (data=0x0)
    at ../../gdb-7.2/gdb/event-loop.c:464
#20 0x08167dda in catch_errors (func=0x816ce92 <gdb_do_one_event>,
    func_args=0x0, errstring=0x832ae1b "", mask=6)
    at ../../gdb-7.2/gdb/exceptions.c:518
#21 0x080d9140 in tui_command_loop (data=0x0)
    at ../../gdb-7.2/gdb/tui/tui-interp.c:171
#22 0x081684ac in current_interp_command_loop ()
    at ../../gdb-7.2/gdb/interps.c:291
---Type <return> to continue, or q <return> to quit---
#23 0x0804e12e in captured_command_loop (data=0x0)
    at ../../gdb-7.2/gdb/main.c:227
#24 0x08167dda in catch_errors (func=0x804e123 <captured_command_loop>,
    func_args=0x0, errstring=0x830c143 "", mask=6)
    at ../../gdb-7.2/gdb/exceptions.c:518
#25 0x0804f003 in captured_main (data=0xbffff300)
    at ../../gdb-7.2/gdb/main.c:910
#26 0x08167dda in catch_errors (func=0x804e164 <captured_main>,
    func_args=0xbffff300, errstring=0x830c143 "", mask=6)
    at ../../gdb-7.2/gdb/exceptions.c:518
#27 0x0804f039 in gdb_main (args=0xbffff300) at ../../gdb-7.2/gdb/main.c:919
#28 0x0804deb3 in main (argc=1, argv=0xbffff3c4) at ../../gdb-7.2/gdb/gdb.c:34
(top-gdb)

然后就是子进程状态的处理,在linux_wait_for_event、linux_wait_for_event_1、handle_extended_wait中都对子进程的退出状态进行了判断。

四、各个模块初始化函数的调用

从整个gdb的代码中可以看到,其中没有对_initialize_breakpoint函数的调用,所以有人问到整个问题,所以就看了看,知道这些代码里没有的问题一般是在Makefile中倒腾的东西,所以就在Makefile中搜索一下就看到了。这个Makefile的脚本还是比较复杂的,所以我就这里分析一下。

gdb-7.2\gdb\Makefile.in

INIT_FILES = $(COMMON_OBS) $(TSOBS) $(CONFIG_SRCS)
init.c: $(INIT_FILES)
 @echo Making init.c 打印提示,也就是这个init.c是动态生成的一个文件,其中有代码
 @rm -f init.c-tmp init.l-tmp
 @touch init.c-tmp
 @echo gdbtypes > init.l-tmp首先向其中输入一个 gdbtypes 字符串
 @-LANG=C ; export LANG ; \
 LC_ALL=C ; export LC_ALL ; \
 echo $(INIT_FILES) | \ 将INIT_FILES中所有的变量都打印到标准输出中,变量中包含了很多的东西,例如可以看到有version.o auxv.o 也有cli/cli-dump.c等,也就是有XX.o也有 yyy/zzz.c等形式的内容
 tr ' ' '\012' | \ 由于这里写的变量时 xxx.o  yyy.c之类的形式,但是这些所有的符号都是使用空格分开的,但是sed只识别以行为单位的单词,所以这里要把这个所有的空格替换为换行符,以便于sed处理
 sed \
     -e '/^gdbtypes.[co]$$/d' \这里删除gdbtypes.o这个单词,这里是一个删除错误,接下来的d都表示是删除
     -e '/^init.[co]$$/d' \
     -e '/xdr_ld.[co]$$/d' \
     -e '/xdr_ptrace.[co]$$/d' \
     -e '/xdr_rdb.[co]$$/d' \
     -e '/udr.[co]$$/d' \
     -e '/udip2soc.[co]$$/d' \
     -e '/udi2go32.[co]$$/d' \
     -e '/version.[co]$$/d' \
     -e '/^[a-z0-9A-Z_]*_[SU].[co]$$/d' \
     -e '/[a-z0-9A-Z_]*-exp.tab.[co]$$/d' \
     -e 's/\.[co]$$/.c/' \这里将所有的.o后缀都转换为.c格式
     -e 's,signals\.c,common/signals\.c,' \
     -e 's|^\([^  /][^     ]*\)|$(srcdir)/\1|g' | \这里把所有的源文件添加生成绝对路径,前面已经转换为.c文件
 while read f; do \
     sed -n -e 's/^_initialize_\([a-z_0-9A-Z]*\).*/\1/p' $$f 2>/dev/null; \从所有的文件里搜索以_initialize_开头的标示符,注意,这里是从文件中搜索的,而不是从单词中搜索,这个操作之后就是打印出这个_initialize_之后的单词,例如breakpoint.c中的_initialize_breakpoint经过这个处理就只变成breakpoint这一个单词
 done | \
 while read f; do \
     case " $$fs " in \这里是一个删除重复的办法,因为如果这个单词已经在fs变量中,那么会满足第一个case,本次循环不做任何动作,否则添加到fs变量
         *" $$f "* ) ;; \
         * ) echo $$f ; fs="$$fs $$f";; \
            esac; \
 done >> init.l-tmp所有的单词打印到init.l-tmp文件中,里面只有xxx的形式,没有_initialize_前缀。这里单独提取之后用来生成C语言识别的函数调用
 @echo '/* Do not modify this file.  */' >>init.c-tmp
 @echo '/* It is created automatically by the Makefile.  */'>>init.c-tmp
 @echo '#include "defs.h"      /* For initialize_file_ftype.  */' >>init.c-tmp
 @echo '#include "call-cmds.h" /* For initialize_all_files.  */' >>init.c-tmp
 @sed -e 's/\(.*\)/extern initialize_file_ftype _initialize_\1;/' <init.l-tmp >>init.c-tmp
 @echo 'void' >>init.c-tmp
 @echo 'initialize_all_files (void)' >>init.c-tmp生成函数原型initialize_all_files的定义
 @echo '{' >>init.c-tmp
 @sed -e 's/\(.*\)/  _initialize_\1 ();/' <init.l-tmp >>init.c-tmp将init.l-tmp文件中的所有单词都加上一个_initialize_前缀,加上一个括弧和分号,从而形成一个初始化函数调用
 @echo '}' >>init.c-tmp
 @rm init.l-tmp
 @mv init.c-tmp init.c

.PRECIOUS: init.c

五、DWARF格式对于debug信息的支持

1、总体思想:

由于一般的大型软件中包含了非常多的调试信息,所以如何将调试信息在dwarf中进行压缩是一个符号系统的重要问题。这种压缩体现在各个方面,例如,其中使用的证书压缩方法,就是一个数是通过特殊的字节压缩方式而不是通常意义上的ASCII内码;大量使用“状态机”增量表示状态的变化,例如,当行号和汇编指令的对应关系转换,栈帧中寄存器和变量的变化都是用的增量迭代的表示方法。

2、.debug_info和.debug_abbrev

这两个节是天生在一起的两个节,它们是一个“实例和类型”的关系,也就是info节中的内容是abbrev节中的一个结构的实例。在abbrev节中声明了很多中不同的Dwarf类型组合(我们可以想象为C语言中的结构声明,而这些类型都是DWARF格式约定好的类型),然后在info节的每一项都声明自己使用的是abbrev节中的那个类型,也就是说明自己是那个结构的实例。这样两者结合就可以得到系统中的所有类型声明信息

现在依然是一个例子实例来说明这个问题

Contents of the .debug_info section:

  Compilation Unit @ offset 0x0:
   Length:        0x88 (32-bit)
   Version:       3
   Abbrev Offset: 0
   Pointer Size:
  4 一个标准节头信息
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit) 这里声明了自己是abbrev节中第一个类型的一个实例,我们可以交叉到abbrev节的第一个类型声明看一下其中关于这个结构类型的声明,通过这个类型的声明,我们可以知道这里存放的字节流如何划分,它们的长度,代表的意义等逻辑信息
    < c>   DW_AT_producer    : (indirect string, offset: 0x5c): GNU C 4.4.2 20091027 (Red Hat 4.4.2-7) 
    <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/tsecer/gdb7.2/Src/Obj/gdb 如果编译时使用的是绝对路径,那么这个dir项就不存在,如果是相对路径,那么这个编译路径是存在的
    <19>   DW_AT_low_pc      : 0x80483b4    代码段的起始地址
    <1d>   DW_AT_high_pc     : 0x80483d0    代码段的结束地址
    <21>   DW_AT_stmt_list   : 0x0 
 <1><25>: Abbrev Number: 2 (DW_TAG_base_type)
    <26>   DW_AT_byte_size   : 4 
    <27>   DW_AT_encoding    : 7 (unsigned)
    <28>   DW_AT_name        : (indirect string, offset: 0x3c): unsigned int 
 <1><2c>: Abbrev Number: 2 (DW_TAG_base_type) 可以看到此处有很多的都是基本类型,也就是我们通常所说的内置类型,例如char int short 等结构,这些是编译器内置识别的一些结构,它们使用的格式都是相同的,都是abbrev节中的第二项
    <2d>   DW_AT_byte_size   : 1 
    <2e>   DW_AT_encoding    : 8 (unsigned char)
    <2f>   DW_AT_name        : (indirect string, offset: 0x49): unsigned char 
 <1><33>: Abbrev Number: 2 (DW_TAG_base_type)
    <34>   DW_AT_byte_size   : 2 
    <35>   DW_AT_encoding    : 7 (unsigned)
    <36>   DW_AT_name        : (indirect string, offset: 0x83): short unsigned int 
 <1><3a>: Abbrev Number: 2 (DW_TAG_base_type)
    <3b>   DW_AT_byte_size   : 4 
    <3c>   DW_AT_encoding    : 7 (unsigned)
    <3d>   DW_AT_name        : (indirect string, offset: 0x37): long unsigned int 
 <1><41>: Abbrev Number: 2 (DW_TAG_base_type)
    <42>   DW_AT_byte_size   : 1 
    <43>   DW_AT_encoding    : 6 (signed char)
    <44>   DW_AT_name        : (indirect string, offset: 0x4b): signed char 
 <1><48>: Abbrev Number: 2 (DW_TAG_base_type)
    <49>   DW_AT_byte_size   : 2 
    <4a>   DW_AT_encoding    : 5 (signed)
    <4b>   DW_AT_name        : (indirect string, offset: 0x28): short int 
 <1><4f>: Abbrev Number: 3 (DW_TAG_base_type)
    <50>   DW_AT_byte_size   : 4 
    <51>   DW_AT_encoding    : 5 (signed)
    <52>   DW_AT_name        : int 
 <1><56>: Abbrev Number: 2 (DW_TAG_base_type)
    <57>   DW_AT_byte_size   : 8 
    <58>   DW_AT_encoding    : 5 (signed)
    <59>   DW_AT_name        : (indirect string, offset: 0x96): long long int 
 <1><5d>: Abbrev Number: 2 (DW_TAG_base_type)
    <5e>   DW_AT_byte_size   : 8 
    <5f>   DW_AT_encoding    : 7 (unsigned)
    <60>   DW_AT_name        : (indirect string, offset: 0x32): long long unsigned int 
 <1><64>: Abbrev Number: 2 (DW_TAG_base_type)
    <65>   DW_AT_byte_size   : 4 
    <66>   DW_AT_encoding    : 5 (signed)
    <67>   DW_AT_name        : (indirect string, offset: 0x9b): long int 
 <1><6b>: Abbrev Number: 4 (DW_TAG_base_type)
    <6c>   DW_AT_byte_size   : 4 
    <6d>   DW_AT_encoding    : 7 (unsigned)
 <1><6e>: Abbrev Number: 2 (DW_TAG_base_type)
    <6f>   DW_AT_byte_size   : 1 
    <70>   DW_AT_encoding    : 6 (signed char)
    <71>   DW_AT_name        : (indirect string, offset: 0x52): char 
 <1><75>: Abbrev Number: 5 (DW_TAG_subprogram) 这个是前面说的第一个项的子结构,因为它的前面有一个<1>,就表示他是第一项的一个子项,同样前面的所有的项都是第一项的子项。表示子项的方式就是在abbrev中会说明它是否有子项。例如,abbrev的第一项的就有“has children”标志,所以接下来的所有项都是它的子项,直到遇到一个00项为止。以此类推,从而形成一个树形结构
    <76>   DW_AT_external    : 1 
    <77>   DW_AT_name        : (indirect string, offset: 0x57): main 
    <7b>   DW_AT_decl_file   : 1 
    <7c>   DW_AT_decl_line   : 2 
    <7d>   DW_AT_type        : <0x4f> 
    <81>   DW_AT_low_pc      : 0x80483b4 
    <85>   DW_AT_high_pc     : 0x80483d0 
    <89>   DW_AT_frame_base  : 1 byte block: 9c  (DW_OP_call_frame_cfa)

Contents of the .debug_abbrev section:

  Number TAG
   1      DW_TAG_compile_unit    [has children]
    DW_AT_producer     DW_FORM_strp
    DW_AT_language     DW_FORM_data1
    DW_AT_name         DW_FORM_strp
    DW_AT_comp_dir     DW_FORM_strp
    DW_AT_low_pc       DW_FORM_addr
    DW_AT_high_pc      DW_FORM_addr
    DW_AT_stmt_list    DW_FORM_data4
   2      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
    DW_AT_name         DW_FORM_strp
   3      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
    DW_AT_name         DW_FORM_string
   4      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
   5      DW_TAG_subprogram    [no children]
    DW_AT_external     DW_FORM_flag
    DW_AT_name         DW_FORM_strp
    DW_AT_decl_file    DW_FORM_data1
    DW_AT_decl_line    DW_FORM_data1
    DW_AT_type         DW_FORM_ref4
    DW_AT_low_pc       DW_FORM_addr
    DW_AT_high_pc      DW_FORM_addr
    DW_AT_frame_base   DW_FORM_block1
3、.debug_frame格式

该节主要是为了表示函数栈帧的关系。也就是当执行一个函数的时候,这个函数中的各个寄存器的存放位置及变化情况,栈帧的计算方法的变化情况等。这些是编译器最为清楚的布局,所以最好让便一起来完成这个操作,从而不必通过指令码来进行猜测。该节的开始可以有若干个CIE(Common Information Entry),顾名思义,这些项主要是用来保存一些通用的信息,这些信息可以被接下来的FDE(Frame Description Entry)引用,从而提高存储的压缩效率。

Contents of the .debug_frame section:

00000000 00000010 ffffffff CIE
  Version:               1
  Augmentation:          ""
  Code alignment factor: 1
  Data alignment factor: -4
  Return address column: 8

  DW_CFA_def_cfa: r4 (esp) ofs 4 这里定义了CFA(Canonical Frame Address)的计算方法,也就是计算出当前的堆栈基准位置的计算方法,之后的大部分变量都将会通过这个CFA偏移来表示。这个指令有两个操作数,一个是寄存器,一个是偏移量,还有一种表示就是直接通过寄存器定义,DW_CFA_DEF_CFA_REGISTER,这种定义CFA的时候只需要一个操作数,就是寄存器编号
  DW_CFA_offset: r8 (eip) at cfa-4 这里定义一个寄存器的存放位置或者说计算方法,就是上面定义的CFA偏移4个字节。表示EIP寄存器在cfa-4的位置存放
  DW_CFA_nop
  DW_CFA_nop

00000014 0000001c 00000000 FDE cie=00000000 pc=080483b4..080483d0
  DW_CFA_advance_loc: 1 to 080483b5
  DW_CFA_def_cfa_offset: 8 表示当前的CFA值向下偏移8个字节,但是CFA使用的寄存器中的值并没有变化
  DW_CFA_advance_loc: 2 to 080483b7
  DW_CFA_offset: r5 (ebp) at cfa-8
  DW_CFA_def_cfa_register: r5 (ebp)
  DW_CFA_advance_loc: 24 to 080483cf
  DW_CFA_restore: r5 (ebp)
  DW_CFA_def_cfa: r4 (esp) ofs 4 这些DW_CFA_XXX的意义以及它们使用的操作数都已经在DWARF格式中规定,大家不要猜测,也不要自己发挥
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop
下面是汇编代码

080483b4 <main>:
 80483b4: 55                    push   %ebp
 80483b5: 89 e5                 mov    %esp,%ebp
 80483b7: 83 e4 f0              and    $0xfffffff0,%esp
 80483ba: 83 ec 10              sub    $0x10,%esp
 80483bd: c7 04 24 94 84 04 08  movl   $0x8048494,(%esp)
 80483c4: e8 27 ff ff ff        call   80482f0 <puts@plt>
 80483c9: b8 00 00 00 00        mov    $0x0,%eax
 80483ce: c9                    leave 
 80483cf: c3                    ret   
可以看到,在80483b5指令之后,堆栈的栈顶指针向下偏移了8个字节(call自动push的EIP加上开始的时候push的EBP),但是EBP的值尚未变化

4、.debug_line

这里包含的是行号和机器指令之间的映射关系,这也是实现源代码级调试的重要依据。用户在源代码中设置断点,我们就必须能够通过这个“源文件+行号”的信息来在制定的程序中打断点。它同样使用了一个状态机增量表示这个变化,并且在每个项中同样是通过《行号增加+地址增加》的格式来表示它们映射关系的变化。这里比较复杂一些,所以说的可能多一些。

它基本的思想就是通过一个字节来表示一个<行号变化,地质变化>pair关系,也就是DWARF中说明的special Opcode,这些special Opcode只有一个字节;这个字节中同时还要去掉两外两种操作类型:standard opcode和extended opcode,

standard 是DWARF自己保留的若干个操作符,它们的意义、格式、操作数的个数及大小都是规定好的。但是它们的个数可能之后还会增加,所以每个.debug_line节中都要说明自己使用了多少个标准操作符,这个就是节中的op_base的意义,也就是说,这个节之下的所有操作符都是标准操作符。这样一个低版本的调试器如果不识别某个格式也不会出问题。

extended 类型是以0开始的一个字节,它接下来是一个字节表示自己的长度,这样扩展性更强。具体的意义则由扩展类型确定,当然,dwarf也保留了标准的扩展操作符。

special 类型就是一个字节中剩下的数值了。它是一个和line_base和line_range相关的变量。它的根本目的是为了用一个字节表示大部分<行号偏移、指令偏移对>。它使用的方法就是使用不同的基数来表示,其中商为一个行号偏移,而余数为一个指令偏移。从而对一个字节的地址空间进行划分

Raw dump of debug contents of section .debug_line:

  Offset:                      0x0
  Length:                      52
  DWARF Version:               2
  Prologue Length:             30
  Minimum Instruction Length:  1
  Initial value of 'is_stmt':  1
  Line Base:                   -5
  Line Range:                  14
  Opcode Base:                 13

 Opcodes:
  Opcode 1 has 0 args
  Opcode 2 has 1 args
  Opcode 3 has 1 args
  Opcode 4 has 1 args
  Opcode 5 has 1 args
  Opcode 6 has 0 args
  Opcode 7 has 0 args
  Opcode 8 has 0 args
  Opcode 9 has 1 args
  Opcode 10 has 0 args
  Opcode 11 has 0 args
  Opcode 12 has 1 args

 The Directory Table is empty.

 The File Name Table:
  Entry Dir Time Size Name
  1 0 0 0 Hello.c

 Line Number Statements:
  Extended opcode 2: set Address to 0x80483b4
  Special opcode 7: advance Address by 0 to 0x80483b4 and Line by 2 to 3
  Special opcode 132: advance Address by 9 to 0x80483bd and Line by 1 to 4
  Special opcode 174: advance Address by 12 to 0x80483c9 and Line by 1 to 5
  Special opcode 76: advance Address by 5 to 0x80483ce and Line by 1 to 6
  Advance PC by 2 to 0x80483d0
  Extended opcode 1: End of Sequence

上面对应的line number statements对应的内存值

               00 05  02 B4 83 04 08 14 91 BB 
59 02 02 00 01 01 00 00                     

00 05  02 B4 83 04 08 这里最开始的00 05说明是一个扩展命令,并且接下来的总长度为5个字节,2表示扩展操作码为2,也就是定义基准地址的命令。接下来四个字节为操作数,表示基地址为xxxxxx

0x14  大于op_base 13,所以为特殊节,20-13 = 7   7 % 14 = 0 7 + (-5) =2,对应这里显示的地址增加0,行号增加2的意义,其它一次类推

5、.debug_str

这个我们最不陌生,就是C语言中的字符串组,它们以零结束,放在一个单独的节是为了提高存储效率。因为字符串的长度是任意的,所以在考虑节对其的时候比较有意义。这里只是列出一个其内容,大家一看便知

Contents of the .debug_str section:

  0x00000000 48656c6c 6f2e6300 2f686f6d 652f7473 Hello.c./home/ts
  0x00000010 65636572 2f676462 372e322f 5372632f ecer/gdb7.2/Src/
  0x00000020 4f626a2f 67646200 73686f72 7420696e Obj/gdb.short in
  0x00000030 74006c6f 6e67206c 6f6e6720 756e7369 t.long long unsi
  0x00000040 676e6564 20696e74 00756e73 69676e65 gned int.unsigne
  0x00000050 64206368 6172006d 61696e00 474e5520 d char.main.GNU
  0x00000060 4320342e 342e3220 32303039 31303237 C 4.4.2 20091027
  0x00000070 20285265 64204861 7420342e 342e322d  (Red Hat 4.4.2-
  0x00000080 37290073 686f7274 20756e73 69676e65 7).short unsigne
  0x00000090 6420696e 74006c6f 6e67206c 6f6e6720 d int.long long
  0x000000a0 696e7400      

http://tsecer.blog.163.com/blog/static/150181720118395251117/
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值