CSAPP:二进制炸弹实验

二进制炸弹是作为一个目标代码文件提供给学生们的程序,运行时,它提示用户输入6个不同的字符串。如果其中任何一个不正确,炸弹就会“爆炸”:打印出一条错误信息。学生通过反汇编和逆向工程来确定是哪六个字符串,从而解除他们各自炸弹的雷管。该实验教会学生理解汇编语言,并强制他们学习怎样使用调试器。

对这个实验慕名已久,做了一下不禁感叹:果然牛x,不愧是从美国进口的!

因为提供的二进制炸弹是根据特定的平台而生成的,所以必须在”官方“提供的服务器上进行拆弹,我们一般用putty登录。在这里,我没有研究怎么搞putty,而是直接找别人的炸弹研究一下,理解汇编指令即可,后面有空,在慢慢学习gdb调试!

首先来看phase1 的代码:

08048b90 : 8048b90: 55 push %ebp 8048b91: 89 e5 mov %esp,%ebp 8048b93: 83 ec 08 sub $0x8,%esp 8048b96: c7 44 24 04 c0 98 04 movl $0x80498c0,0x4(%esp) 8048b9d: 08 8048b9e: 8b 45 08 mov 0x8(%ebp),%eax 8048ba1: 89 04 24 mov %eax,(%esp) 8048ba4: e8 69 04 00 00 call 8049012 8048ba9: 85 c0 test %eax,%eax 8048bab: 74 05 je 8048bb2 8048bad: e8 7e 09 00 00 call 8049530 8048bb2: 89 ec mov %ebp,%esp 8048bb4: 5d pop %ebp 8048bb5: c3 ret

分析解答:

注意strings_not_equal 这个函数,从字面上可以猜想地理解为:把输入的字符串和内存某处字符串相比较,不相等的时候函数返回值的为1。再看接下来两句:

test %eax,%eax
je 8048bb2

test 是把两个操作数进行与运算,而通常这两个操作数是一样的,此操作的意义就在于影响标志位,当%eax 为0 时,零标志位置1,否则零标志为0。而从je指令可以知道当%eax 为0 时程序会跳过接下来一句对爆炸函数的引用,所以我们的目标就是要是%eax 即strings_not_equal 的返回值为0,即要是输入的字符串与内存某处的存放的字符串相等。于是现在的关键就是找出那个字符串是放在内存的哪个地方。其实非常明显,$0x80498c0 这个地址在程序里实在太显眼,用x/s 0x80498c0 命令一查,果然那里存放有一句话:"I am not part of the problem.I am a Republican."于是run ,输入,果然:Phase 1 defused. How about the next one?


然后来看phase2:

08048bb6 : 8048bb6: 55 push %ebp 8048bb7: 89 e5 mov %esp,%ebp 8048bb9: 53 push %ebx 8048bba: 83 ec 34 sub $0x34,%esp 8048bbd: 8d 45 d8 lea 0xffffffd8(%ebp),%eax 8048bc0: 89 44 24 04 mov %eax,0x4(%esp) 8048bc4: 8b 45 08 mov 0x8(%ebp),%eax 8048bc7: 89 04 24 mov %eax,(%esp) 8048bca: e8 d9 03 00 00 call 8048fa8 8048bcf: 83 7d d8 01 cmpl $0x1,0xffffffd8(%ebp) 8048bd3: 74 05 je 8048bda 8048bd5: e8 56 09 00 00 call 8049530 8048bda: bb 01 00 00 00 mov $0x1,%ebx 8048bdf: 8d 43 01 lea 0x1(%ebx),%eax 8048be2: 0f af 44 9d d4 imul 0xffffffd4(%ebp,%ebx,4),%eax 8048be7: 39 44 9d d8 cmp %eax,0xffffffd8(%ebp,%ebx,4) 8048beb: 74 05 je 8048bf2 8048bed: e8 3e 09 00 00 call 8049530 8048bf2: 43 inc %ebx 8048bf3: 83 fb 05 cmp $0x5,%ebx 8048bf6: 7e e7 jle 8048bdf 8048bf8: 83 c4 34 add $0x34,%esp 8048bfb: 5b pop %ebx 8048bfc: 5d pop %ebp 8048bfd: c3 ret

分析解答:

注意到read_six_numbers 这个函数,同样故名思义我先猜他为输入6 个数字。暂且不看read_six_numbers 函数的内容,先注意8048bcf 这句,cmpl 显然是要引起我高度注意的,因为比较的结果往往直接关系到爆不爆的问题。这条指令的内容很明确,就是看0xffffffd8(%ebp)处的数字是否为1,不是的话就会调用爆炸函数。于是我非常坚定地认为1 便是我该输入的第一个数字,而输入的数字是存放在0xffffffd8(%ebp)开始的地址处的。

接着看下去,lea 0x1(%ebx),%eax 这句将%eax 置为2。8048be2 这句中的0xffffffd4(%ebp,%ebx,4)这个存储器操作数稍作思考便可以知道其实就是0xffffffd8(%ebp),即是我们要输入的第一个数字,因为此时%ebx 为1。所以这句是将第一个数字乘以2 放入%eax。8048be7 又来cmp 了,很容易看出0xffffffd8(%ebp,%ebx,4)是我们输入第2 个数字的地方,还是因为此时%ebx 为1,所以这里告诉我们,要输入的第2 个数字就是第一个乘以2 的结果。

8048bf2 和8048bf3 两句告诉我们从%ebx 等于1 到5 分别进行以上两段的操作,即后面一个数字由前面一个数字乘以%eax 得到,而每次的乘数%eax 为%ebx 加1,即乘数分别为2,3,4,5,6,所以可以确定这六个数字分别为1,2,3,4,5,6 的阶乘,即1 2 6 24 120 720。于是输入,果然:That's number 2. Keep going!


来看第3 个bomb:

08048bfe : 8048bfe: 55 push %ebp 8048bff:89 e5 mov %esp,%ebp 8048c01: 83 ec 28 sub $0x28,%esp 8048c04: 89 5d fc mov %ebx,0xfffffffc(%ebp) 8048c07: 8d 45 f8 lea 0xfffffff8(%ebp),%eax 8048c0a: 89 44 24 10 mov %eax,0x10(%esp) 8048c0e: 8d 45 f7 lea 0xfffffff7(%ebp),%eax 8048c11: 89 44 24 0c mov %eax,0xc(%esp) 8048c15: 8d 45 f0 lea 0xfffffff0(%ebp),%eax 8048c18: 89 44 24 08 mov %eax,0x8(%esp) 8048c1c: c7 44 24 04 26 99 04 movl $0x8049926,0x4(%esp) 8048c23: 08 8048c24: 8b 45 08 mov 0x8(%ebp),%eax 8048c27: 89 04 24 mov %eax,(%esp) 8048c2a: e8 95 fc ff ff call 80488c4 8048c2f: 83 f8 02 cmp $0x2,%eax 8048c32: 7f 05 jg 8048c39 8048c34: e8 f7 08 00 00 call 8049530 8048c39: 83 7d f0 07 cmpl $0x7,0xfffffff0(%ebp) 8048c3d: 77 61 ja 8048ca0 8048c3f: 8b 45 f0 mov 0xfffffff0(%ebp),%eax 8048c42: ff 24 85 38 99 04 08 jmp *0x8049938(,%eax,4) 8048c49: b3 72 mov $0x72,%bl 8048c4b: 81 7d f8 2a 01 00 00 cmpl $0x12a,0xfffffff8(%ebp) 8048c52: eb 48 jmp 8048c9c 8048c54: b3 76 mov $0x76,%bl 8048c56: 81 7d f8 29 01 00 00 cmpl $0x129,0xfffffff8(%ebp) 8048c5d: eb 3d jmp 8048c9c 8048c5f: b3 65 mov $0x65,%bl 8048c61: 83 7d f8 52 cmpl $0x52,0xfffffff8(%ebp) 8048c65: eb 35 jmp 8048c9c 8048c67: b3 6e mov $0x6e,%bl 8048c69: 81 7d f8 cb 00 00 00 cmpl $0xcb,0xfffffff8(%ebp) 8048c70: eb 2a jmp 8048c9c 8048c72: b3 61 mov $0x61,%bl 8048c74: 81 7d f8 d8 01 00 00 cmpl $0x1d8,0xfffffff8(%ebp) 8048c7b: eb 1f jmp 8048c9c 8048c7d: b3 6f mov $0x6f,%bl 8048c7f: 81 7d f8 c8 03 00 00 cmpl $0x3c8,0xfffffff8(%ebp) 8048c86: eb 14 jmp 8048c9c 8048c88: b3 63 mov $0x63,%bl 8048c8a: 81 7d f8 69 02 00 00 cmpl $0x269,0xfffffff8(%ebp) 8048c91: eb 09 jmp 8048c9c 8048c93: b3 7a mov $0x7a,%bl 8048c95: 81 7d f8 77 02 00 00 cmpl $0x277,0xfffffff8(%ebp) 8048c9c: 74 09 je 8048ca7 8048c9e: eb 02 jmp 8048ca2 8048ca0: b3 70 mov $0x70,%bl 8048ca2: e8 89 08 00 00 call 8049530 8048ca7: 3a 5d f7 cmp 0xfffffff7(%ebp),%bl 8048caa: 74 05 je 8048cb1 8048cac: e8 7f 08 00 00 call 8049530 8048cb1: 8b 5d fc mov 0xfffffffc(%ebp),%ebx 8048cb4: 89 ec mov %ebp,%esp 8048cb6: 5d pop %ebp 8048cb7: c3 ret

分析解答:

Bomb3 的代码就比较长了,先抓住重点:jmp *0x8049938(,%eax,4)这句话让我想起了switch 语句。再翻翻书,发现这段代码就是一个跳转表结构。从语句上便可以看出备选的跳转地址存放在0x8049938 开始的地址处,通过%eax 的值来选择。通过打印0x8049938 处的16 进制数可以确认:
(gdb) print /x *0x8049938
$3 = 0x8048c49
(gdb) print /x *0x804993c
$4 = 0x8048c54
(gdb) print /x *0x8049940
$5 = 0x8048c5f
(gdb) print /x *0x8049944
$6 = 0x8048c67
(gdb) print /x *0x8049948
$7 = 0x8048c72
(gdb) print /x *0x804994c
$8 = 0x8048c7d
(gdb) print /x *0x8049950
$9 = 0x8048c88
(gdb) print /x *0x8049954
$10 = 0x8048c93

switch转换表是这样的:

0x08049938 0x08048c49 0x08048c54 0x08048c5f 0x08048c67 0x08048c72 0x08048c7d 0x08048c88 0x08048c93
可以看到这8 个16 进制数正好是程序中的8 个地址(都用黑体标出),对应于%eax为0 到7 时的跳转地址。由于

cmp $0x2,%eax
jg 8048c39


两句限定了%eax 大于2,所以我取%eax 为3,然后查表到8048c67 处。

mov$0x6e,%bl 和cmp 0xfffffff7(%ebp),%bl 告诉了我们0xfffffff7(%ebp)处应该输入的值,cmpl $0xcb,0xfffffff8(%ebp)与je 8048ca7 告诉了我们0xfffffff8(%ebp)处应该输入的值,然后我很自然的把0x6e 和0xcb 换算成了10 进制数110 和203,然后迫不及待地输入3 110 203,结果很失望的到了break point1。
这里我困惑了挺久,我还试了16 进制的输入,甚至怀疑自己整个的理解是否有问题。终于我发现了一个问题:第2 个“数字”的地址为0xfffffff7(%ebp)而第三个为0xfffffff8(%ebp),显然这是不合常理的,不可能只占一个字节。而只占一个字节的东西,我就只能想到字符了,但明明分析程序看到的是一个“数字”,要把数字与字符联系起来,就是ASCII 码了!查表,6e 果然对应着一个字母:n于是迫不及待地输入:3 n 203,果然Halfway there!
当然对应于不同的第一个数字,有不同的答案。

事后我还发现, 语句8048c1c: c7 44 24 04 26 99 04 movl$0x8049926,0x4(%esp)中的$0x8049926 处的字符串:

(gdb) x/s 0x8049926
0x8049926 <_IO_stdin_used+514>: "%d %c %d"


原来对输入的格式早有说明!

 

第4 个bomb:

08048cb8 : 8048cb8: 55 push %ebp 8048cb9: 89 e5 mov %esp,%ebp 8048cbb: 83 ec 0c sub $0xc,%esp 8048cbe: 89 5d f8 mov %ebx,0xfffffff8(%ebp) 8048cc1: 89 75 fc mov %esi,0xfffffffc(%ebp) 8048cc4: 8b 75 08 mov 0x8(%ebp),%esi 8048cc7: b8 01 00 00 00 mov $0x1,%eax 8048ccc: 83 fe 01 cmp $0x1,%esi 8048ccf:7e 1b jle 8048cec 8048cd1: 8d 46 ff lea 0xffffffff(%esi),%eax 8048cd4: 89 04 24 mov %eax,(%esp) 8048cd7: e8 dc ff ff ff call 8048cb8 8048cdc: 89 c3 mov %eax,%ebx 8048cde: 8d 46 fe lea 0xfffffffe(%esi),%eax 8048ce1: 89 04 24 mov %eax,(%esp) 8048ce4: e8 cf ff ff ff call 8048cb8 8048ce9: 8d 04 18 lea (%eax,%ebx,1),%eax 8048cec: 8b 5d f8 mov 0xfffffff8(%ebp),%ebx 8048cef:8b 75 fc mov 0xfffffffc(%ebp),%esi 8048cf2: 89 ec mov %ebp,%esp 8048cf4: 5d pop %ebp 8048cf5: c3 ret 08048cf6 : 8048cf6: 55 push %ebp 8048cf7: 89 e5 mov %esp,%ebp 8048cf9: 83 ec 18 sub $0x18,%esp 8048cfc:8d 45 fc lea 0xfffffffc(%ebp),%eax 8048cff:89 44 24 08 mov %eax,0x8(%esp) 8048d03: c7 44 24 04 2c 99 04 movl $0x804992c,0x4(%esp) 8048d0a: 08 8048d0b: 8b 45 08 mov 0x8(%ebp),%eax 8048d0e: 89 04 24 mov %eax,(%esp) 8048d11: e8 ae fb ff ff call 80488c4 8048d16: 83 f8 01 cmp $0x1,%eax 8048d19: 75 06 jne 8048d21 8048d1b: 83 7d fc 00 cmpl $0x0,0xfffffffc(%ebp) 8048d1f: 7f 05 jg 8048d26 8048d21: e8 0a 08 00 00 call 8049530 8048d26: 8b 45 fc mov 0xfffffffc(%ebp),%eax 8048d29: 89 04 24 mov %eax,(%esp) 8048d2c: e8 87 ff ff ff call 8048cb8 8048d31: 83 f8 59 cmp $0x59,%eax 8048d34: 74 05 je 8048d3b 8048d36: e8 f5 07 00 00 call 8049530 8048d3b: 89 ec mov %ebp,%esp 8048d3d: 5d pop %ebp 8048d3e: c3 ret

分析解答:

 

首先要研究下func4 的功能。
容易看出是一个递归函数。lea 0xffffffff(%esi),%eax 是得到%esi-1 的值然后调用func4,同样lea 0xfffffffe(%esi),%eax 是得到%esi-2 的值然后调用func4,lea (%eax,%ebx,1),%eax 即是将f(%esi-1)的返回值(在%ebx 里面)与f(%esi-2)的返回值相加放在%eax 中作为func4 的返回值,很明显这是一个斐波那契数列的函数。

 

细节研究可知:

func4(0) = 1,func4(1) = 1;

func4(2) = func4(1) + func4(0) = 2;

func4(3) = func4(2) + func4(1) = 3;

func4(4) = func4(3) + func4(2) = 5;

...

func4(10) = func4(9) + func4(8) = 89;


明白了func4 的意思,我直插phase4 的心脏:

cmp $0x59,%eax je 8048d3b
很清楚,当%eax 等于0x59 的时候就可以过关了。%eax 是什么?是call 8048cb8 后的返回值!那此次调用的函数参数是什么呢?就是我们输入的东西了。8048d11 和8048d16 两句也说明了输入的是一个数字。然后把斐波那契数列一排,0x59 对应的序号为10,输入,果然:So you got that one. Try this one.


看第5 个:

08048d3f : 8048d3f: 55 push %ebp 8048d40: 89 e5 mov %esp,%ebp 8048d42: 53 push %ebx 8048d43: 83 ec 24 sub $0x24,%esp 8048d46: 8b 5d 08 mov 0x8(%ebp),%ebx 8048d49: 89 1c 24 mov %ebx,(%esp) 8048d4c: e8 a8 02 00 00 call 8048ff9 8048d51: 83 f8 06 cmp $0x6,%eax 8048d54: 74 05 je 8048d5b 8048d56: e8 d5 07 00 00 call 8049530 8048d5b: ba 00 00 00 00 mov $0x0,%edx 8048d60: 0f be 04 1a movsbl (%edx,%ebx,1),%eax 8048d64: 83 e0 0f and $0xf,%eax 8048d67: 0f b6 80 c0 a5 04 08 movzbl 0x804a5c0(%eax),%eax 8048d6e: 88 44 2a e8 mov %al,0xffffffe8(%edx,%ebp,1) 8048d72: 42 inc %edx 8048d73: 83 fa 05 cmp $0x5,%edx 8048d76: 7e e8 jle 8048d60 8048d78: c6 45 ee 00 movb $0x0,0xffffffee(%ebp) 8048d7c: c7 44 24 04 2f 99 04 movl $0x804992f,0x4(%esp) 8048d83: 08 8048d84: 8d 45 e8 lea 0xffffffe8(%ebp),%eax 8048d87: 89 04 24 mov %eax,(%esp) 8048d8a: e8 83 02 00 00 call 8049012 8048d8f: 85 c0 test %eax,%eax 8048d91: 74 05 je 8048d98 8048d93: e8 98 07 00 00 call 8049530 8048d98: 83 c4 24 add $0x24,%esp 8048d9b: 5b pop %ebx 8048d9c: 5d pop %ebp 8048d9d: c3 ret

分析解答:

 

首先,call 8048ff9 和cmp $0x6,%eax 两句告诉我们要输入的是6 个字符。在指令movsbl (%edx,%ebx,1),%eax 中,%ebx 为6 个字符的起始地址,通过循环增加%edx 的值来依次将这6 个字符的ASCII 码传给%eax进行下一步操作。

 

说明一下movsbl (%edx,%ebx,1),%eax具体含义:我们知道,movsbl S ,D的意思是将S(不论寄存器还是内存地址)里面存放的值的一个最低位字节拷贝出来,设置其高24位为此字节最高有效位,然后传送到D中。简单描述就是传送符号扩展的字节。因此,此指令的含义为将%ebx+%edx里面的内容(其实应该为一个地址),把这个地址中的值得最低位字节符号扩展,然后传送到%eax中。

与movsbl相对的是movzbl,它是0扩展,其余与 movsbl 一样。


研究下这句:movzbl 0x804a5c0(%eax),%eax。

说明一下这个格式0x804a5c0(%eax),它的原型为Imm(Eb),操作为M[Imm+R[Eb]],表示为(基址+偏移量)寻址,它很容易让我们联想到数组的操作。
刚才已经说了%eax 是我们输入字符的ASCII 码,经过and $0xf,%eax 处理后也就是相应ASCII 码的低4 位。查看0x804a5c0 处存放的东西:
(gdb) x/s 0x804a5c0
0x804a5c0 : "isrveawhobpnutfgs/001"
0x804a5c0(%eax) 的寻址方式说明了这条指令是将字符串"isrveawhobpnutfgs/001"中的第%eax 个字符传送给%eax,%eax 起到了一个索引的作用。循环6 次以后,以输入的6 个字符的的ASCII 码低4 位为索引得到的字符串"isrveawhobpnutfgs/001"中的6 个字符,被装入0xffffffe8(%ebp)为起始地址的连续存储空间中。
接着可以看到8048d8a 处调用了strings_not_equal,8048d7c 处清楚地说明了比较的对象,查看0x804992f 处的字符串:
(gdb) x/s 0x804992f
0x804992f <_IO_stdin_used+523>: "giants"
说明我们只要使索引得到的字符串为"giants"就可以了!
反推回去,g、i、a、n、t、s 对应的索引值为15、0、5、11、13、1。所以只要使我们输入的6 个字符的ASCII 码低4 位依次为15 0 5 11 13 1 就可以了。
我取为o0ekma,于是:Good work! On to the next...

 

当然,这里的答案不是固定的,只要满足要求即可!

 

第六个:

08048d9e : 8048d9e: 55 push %ebp 8048d9f: 89 e5 mov %esp,%ebp 8048da1: 57 push %edi 8048da2: 56 push %esi 8048da3: 53 push %ebx 8048da4: 83 ec 6c sub $0x6c,%esp 8048da7: c7 45 a4 0c a6 04 08 movl $0x804a60c,0xffffffa4(%ebp) 8048dae: 8d 45 c8 lea 0xffffffc8(%ebp),%eax 8048db1: 89 44 24 04 mov %eax,0x4(%esp) 8048db5: 8b 45 08 mov 0x8(%ebp),%eax 8048db8: 89 04 24 mov %eax,(%esp) 8048dbb: e8 e8 01 00 00 call 8048fa8 8048dc0: bf 00 00 00 00 mov $0x0,%edi 8048dc5: 8b 44 bd c8 mov 0xffffffc8(%ebp,%edi,4),%eax 8048dc9: 48 dec %eax 8048dca: 83 f8 05 cmp $0x5,%eax 8048dcd: 76 05 jbe 8048dd4 8048dcf: e8 5c 07 00 00 call 8049530 8048dd4: 8d 5f 01 lea 0x1(%edi),%ebx 8048dd7: 83 fb 05 cmp $0x5,%ebx 8048dda: 7f 15 jg 8048df1 8048ddc: 8b 44 bd c8 mov 0xffffffc8(%ebp,%edi,4),%eax 8048de0: 3b 44 9d c8 cmp 0xffffffc8(%ebp,%ebx,4),%eax 8048de4: 75 05 jne 8048deb 8048de6: e8 45 07 00 00 call 8049530 8048deb: 43 inc %ebx 8048dec: 83 fb 05 cmp $0x5,%ebx 8048def: 7e eb jle 8048ddc 8048df1: 47 inc %edi 8048df2: 83 ff 05 cmp $0x5,%edi 8048df5: 7e ce jle 8048dc5 8048df7: bf 00 00 00 00 mov $0x0,%edi 8048dfc: 8b 75 a4 mov 0xffffffa4(%ebp),%esi 8048dff:bb 01 00 00 00 mov $0x1,%ebx 8048e04: 3b 5c bd c8 cmp 0xffffffc8(%ebp,%edi,4),%ebx 8048e08: 7d 0c jge 8048e16 8048e0a: 8b 44 bd c8 mov 0xffffffc8(%ebp,%edi,4),%eax 8048e0e: 8b 76 08 mov 0x8(%esi),%esi 8048e11: 43 inc %ebx 8048e12: 39 c3 cmp %eax,%ebx 8048e14: 7c f8 jl 8048e0e 8048e16: 89 74 bd a8 mov %esi,0xffffffa8(%ebp,%edi,4) 8048e1a: 47 inc %edi 8048e1b: 83 ff 05 cmp $0x5,%edi 8048e1e: 7e dc jle 8048dfc 8048e20: 8b 75 a8 mov 0xffffffa8(%ebp),%esi 8048e23: 89 75 a4 mov %esi,0xffffffa4(%ebp) 8048e26: bf 01 00 00 00 mov $0x1,%edi 8048e2b: 8b 44 bd a8 mov 0xffffffa8(%ebp,%edi,4),%eax 8048e2f: 89 46 08 mov %eax,0x8(%esi) 8048e32: 89 c6 mov %eax,%esi 8048e34: 47 inc %edi 8048e35: 83 ff 05 cmp $0x5,%edi 8048e38: 7e f1 jle 8048e2b 8048e3a: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax) 8048e41: 8b 75 a4 mov 0xffffffa4(%ebp),%esi 8048e44: bf 00 00 00 00 mov $0x0,%edi 8048e49: 8b 56 08 mov 0x8(%esi),%edx 8048e4c: 8b 06 mov (%esi),%eax 8048e4e: 3b 02 cmp (%edx),%eax 8048e50: 7d 05 jge 8048e57 8048e52: e8 d9 06 00 00 call 8049530 8048e57: 8b 76 08 mov 0x8(%esi),%esi 8048e5a: 47 inc %edi 8048e5b: 83 ff 04 cmp $0x4,%edi 8048e5e: 7e e9 jle 8048e49 8048e60: 83 c4 6c add $0x6c,%esp 8048e63: 5b pop %ebx 8048e64: 5e pop %esi 8048e65: 5f pop %edi 8048e66: 5d pop %ebp 8048e67: c3 ret

第六个炸弹代码太长,应该步步调试来获取答案,下面简述一下gdb调试

gdb调试:
作为调试,我觉得最重要的就是要搞清楚如何单步调试,接下来区分step、stepi和next、nexti
1,step和next是对c源程序进行调试,每步一行。而stepi和nexti主要对汇编指令进行调试,每步一个指令语句。
2,step和stepi是遇到调用函数则会进入调用函数里面单步执行,而next和nexti遇到调用函数则不进入,跳过继续执行本函数的下一行或下一指令语句。
3,对汇编进行调试时,若需要到达调试点有三种方式:step、next以及continue,以coninue使用最多。

GBD常用命令

1.启动GDB
你可以输入GDB来启动GDB程序。GDB程序有许多参数,在此没有必要详细介绍,但一个最为常用的还是要介绍的:如果你已经编译好一个程序,我们假设文件名为hello,你想用GDB调试它,可以输入gdb hello来启动GDB并载入你的程序。如果你仅仅启动了GDB,你必须在启动后,在GDB中再载入你的程序。
2.载入程序 === file
在GDB内,载入程序很简单,使用file命令。如file hello。当然,程序的路径名要正确。
退出GDB === quit
在GDB的命令方式下,输入quit,你就可以退出GDB。你也可以输入'C-d'来退出GDB。
3.运行程序 === run
当你在GDB中已将要调试的程序载入后,你可以用run命令来执行。如果你的程序需要参数,你可以在run指令后接着输入参数,就象你在SHELL下执行一个需要参数的命令一样。
4.查看程序信息 === info
info指令用来查看程序的信息,当你用help info查看帮助的话,info指令的参数足足占了两个屏幕,它的参数非常多,但大部分不常用。我用info指令最多的是用它来查看断点信息。
4.1查看断点信息
info br
br是断点break的缩写,记得GDB的补齐功能吧。用这条指令,你可以得到你所设置的所有断点的详细信息。包括断点号,类型,状态,内存地址,断点在源程序中的位置等。
4.2查看当前源程序
info source
4.3查看堆栈信息
info stack
用这条指令你可以看清楚程序的调用层次关系。
4.4查看当前的各寄存器值
info registers
5.列出源一段源程序 === list
5.1列出某个函数
list FUNCTION
5.2以当前源文件的某行为中间显示一段源程序
list LINENUM
5.3接着前一次继续显示
list
5.4显示前一次之前的源程序
list -
5.5显示另一个文件的一段程序
list FILENAME:FUNCTION 或 list FILENAME:LINENUM
6.设置断点 === break
现在我们将要介绍的也许是最常用和最重要的命令:设置断点。无论何时,只要你的程序已被载入,并且当前没有正在运行,你就能设置,修改,删除断点。设置断点的命令是break。有许多种设置断点的方法。如下:
6.1在函数入口设置断点
break FUNCTION
6.2在当前源文件的某一行上设置断点
break LINENUM
6.3在另一个源文件的某一行上设置断点
break FILENAME:LINENUM
6.4在某个地址上设置断点,当你调试的程序没有源程序是,这很有用
break *ADDRESS
除此之外,设置一个断点,让它只有在某些特定的条件成立时程序才会停下,我们可以称其为条件断点。这个功能很有用,尤其是当你要在一个程序会很多次执行到的地方设置断点时。如果没有这个功能,你必须有极大的耐心,加上大量的时间,一次一次让程序断下,检查一些值,接着再让程序继续执行。事实上,大部分的断下并不是我们所希望的,我们只希望在某些条件下让程序断下。这时,条件断点就可以大大提高你的效率,节省你的时间。条件断点的命令如下,在后面的例子中会有示例。
当你设置一个断点后,它的确省状态是有效。你可以用enable和disable指令来设置断点的状态为有效或禁止。例如,如果你想禁止2号断点,可以用下面的指令:
disable 2
相应的,如果想删除2号断点,可以有下面的指令:
delete 2
7.检查数据
最常用的检查数据的方法是使用print命令。
print exp
print指令打印exp表达式的值。却省情况下,表达式的值的打印格式依赖于它的数据类型。但你可以用一个参数/F来选择输出的打印格式。F是一个代表某种格式的字母,详细可参考输出格式一节。表达式可以是常量,变量,函数调用,条件表达式等。但不能打印宏定义的值。表达式exp中的变量必须是全局变量或当前堆栈区可见的变量。否则GDB会显示象下面的一条信息:
No symbol "varible" in current context
8.修改变量值
在调试程序时,你可能想改变一个变量的值,看看在这种情况下会发生什么。用set指令可以修改变量的值:
set varible=value
例如你想将一个变量tmp的值赋为10,
set tmp=10
9.检查内存值
检查内存值的指令是x,x是examine的意思。用法如下:
x /NFU ADDR
其中N代表重复数,F代表输出格式(见2.13),U代表每个数据单位的大小。U可以去如下值:
b :字节(byte)
h :双字节数值
w :四字节数值
g :八字节数值
因此,上面的指令可以这样解释:从ADDR地址开始,以F格式显示N个U数值。例如:
x/4ub 0x4000
会以无符号十进制整数格式(u)显示四个字节(b),0x4000,0x4001,0x4002,0x4003。
10.输出格式
缺省情况下,输出格式依赖于它的数据类型。但你可以改变输出格式。当你使用print命令时,可以用一个参数/F来选择输出的打印格式。F可以是以下的一些值:
'x' 16进制整数格式
'd' 有符号十进制整数格式
'u' 无符号十进制整数格式
'f' 浮点数格式
11.单步执行指令
单步执行指令有两个step和next。Step可以让你跟踪进入一个函数,而next指令则不会进入函数。
12.继续执行指令
当程序被断下后,你查看了所需的信息后,你会希望程序执行下去,输入 continue, 程序会继续执行下去。
13.帮助指令help
在GDB中,如果想知道一条指令的用法,最方便的方法是使用help。使用方法很简单,在help后跟上指令名。例如,想知道list指令用法,输入help list。

你可能感兴趣的:(Major)