使用GDB中修改特定寄存器值及其原理

November 20th, 2013 by JasonLe's Tech Leave a reply »

最近重新学习了一下gdb的使用,以前我只是熟悉特定的命令。现在配合听课和ppt。对于functions载入内存的过程有了一定的了解,结合我前面写的assemble笔记中那个linux中程序的栈帧分配图,会达到一个很好的效果。http://lizhaozhong.sinaapp.com/?p=458

首先我们知道64bit的机器,寄存器数量大大增加,%rsp是栈指针 %rip是顺序执行的指令指针,准确的来讲就是将要执行的下一条指令(instruction)。%rbp是frame的基地址。了解了这些就具备了我们要debug 程序的基础。
以下面程序为例:

#include<stdio.h>
#include<stdlib.h>
int foo1(void)
{
    int i_foo1=4,j_foo2=5;
    printf("foo1\n");
    return 1;
}
int foo2(void)
{
    printf("foo2\n");
    return 1;
}
int main(int argc ,char **argv)
{
    int i=0,j=1,k=2;
    foo1();
    //printf("Hello\n");
    return 0;
}

这个程序里面,既有main的全局变量,也有局部的变量。
编译的时候我们要加入一些option gcc -g3 -o test test.c 这样可以方便我们调试。
PS:在调试过程中,我们经常遇到segment fault或者是page fault 。如果我们要查看特定的错误,使用ulimit -u 20000之后,发生错误的时候,我们就可以在这个source code下面生成core.XXX的文件。使用 gdb sourcecode core.XXX就可以进入gdb查看最后出现错误的代码!

假如我们已经生成了一个调试版本的可执行二进制文件。

[root@localhost dslab]# gdb test1
GNU gdb (GDB) Fedora (7.5.1-38.fc18)
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/lzz/dslab/test1...done.
(gdb) b foo1
Breakpoint 1 at 0x400534: file test1.c, line 5.
(gdb) b foo2
Breakpoint 2 at 0x400557: file test1.c, line 11.
(gdb) b main
Breakpoint 3 at 0x400577: file test1.c, line 16.
(gdb) r
Starting program: /home/lzz/dslab/test1

Breakpoint 3, main (argc=1, argv=0x7fffffffe0e8) at test1.c:16
warning: Source file is more recent than executable.
16		int i=0,j=1,k=2;
Missing separate debuginfos, use: debuginfo-install glibc-2.16-34.fc18.x86_64
(gdb) disassemble
Dump of assembler code for function main:
   0x0000000000400568 <+0>:	push   %rbp
   0x0000000000400569 <+1>:	mov    %rsp,%rbp
   0x000000000040056c <+4>:	sub    $0x20,%rsp
   0x0000000000400570 <+8>:	mov    %edi,-0x14(%rbp)
   0x0000000000400573 <+11>:	mov    %rsi,-0x20(%rbp)
=> 0x0000000000400577 <+15>:	movl   $0x0,-0x4(%rbp)
   0x000000000040057e <+22>:	movl   $0x1,-0x8(%rbp)
   0x0000000000400585 <+29>:	movl   $0x2,-0xc(%rbp)
   0x000000000040058c <+36>:	callq  0x40052c
   0x0000000000400591 <+41>:	mov    $0x0,%eax
   0x0000000000400596 <+46>:	leaveq
   0x0000000000400597 <+47>:	retq
End of assembler dump.
(gdb)

首先我们要先明白几个命令b (break)就是在特定的funtion上加break断点。
然后我们使用 r (run)来运行程序。
使用disassemble 来查看当前的汇编代码。如果在后面加 funtion 显示的是funtion的汇编代码。
我们可以通过自己计算偏移量来修改寄存器的值,比如 0x0000000000400573 <+11>: mov %rsi,-0x20(%rbp)就是在%rbp的地址上减去-0x20个单位
有时候我们也要打印某些寄存器的制,可是进制可能不是我们想要的,这个时候,可以显示寄存器可以使用的格式
p/格式 变量
格式 说明
x 显示为十六进制
d 显示为十进制
u 显示为无符号十进制(unsigned)
o 显示为八进制

下面我们来修改某个寄存器的值。
set *addr value 其中*addr可以是地址的解引用或者是特定的某个寄存器的值。

下面我们来使用gdb脚本来强制让程序执行特定的路径。

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include
#define FILE "/home/lzz/dslab/exercise/file"
int main(int argc,char **argv)
{
    int fd = 0 ;
    fd = open(FILE,O_CREAT,440);
    if(-1 == fd)
    {
        printf("open file error!\n");
    }
    else
    {
        printf("open file success!\n");
    }
    return 0;
}

上面的程序正常运行的话会打印open file success的语句,我们把gdb中单步调式的语句写在一个shellscript里面,例如:

#!/bin.bash //filename : gdbscript.sh
file a.out //file后面要跟你调式的文件
break 11
run
set fd =-1
c
q

然后后执行 gdb -x gdbscript.sh .gdb就会自动化执行我们写在脚本里面的操作。
打印信息:

[lzz@localhost exercise]$ gdb -x gdbscript.sh
GNU gdb (GDB) Fedora (7.5.1-38.fc18)
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Breakpoint 1 at 0x400db0: file openfile.c, line 11.

Breakpoint 1, main (argc=1, argv=0x7fffffffe0b8) at openfile.c:11
11		if(-1 == fd)
open file error!
[Inferior 1 (process 7720) exited normally]

现在我们基本已经在pc上面完成了gdb基本调试,这为我们下一步remote debug做了充分的准备。

——————————————————————————————————————————————————————————

我们虽然掌握了GDB的用法,但是对于GDB的原理也有必要了解,尤其是对于内核开发人员!

在某个地址增加一个断点,实际上就是修改那个地址的代码,把原来的代码替换成INT 3指令,同时让gdb捕获这个signal并做相应的处理:包括执行被替换掉的指令,并跳转回来。因此,只要断点不被走到,那么断点就不会影响程序的运行效率;因为程序的其他地方都没改变,该怎么运行还是怎么运行。

gdb在遇到断点之后可以做很多事情包括:
停下来等用户处理

  • 自动继续
  • 自动执行一些gdb指令(用commands命令可以配置断点的自动执行)
  • 判断条件是否要停下来(这就是大名鼎鼎的条件断点)

在走到断点之后,不管是路过还是停下来,都会影响性能
因为通常到了断点就会通过INT3停下来。
只是条件断点有点特别,它分为软件和硬件两种。这是因为条件断点这个需求太普遍了,因此有人就想了一个办法从硬件支持一部分:在x86平台,某些条件断点可以不插入INT 3,而是插入一个其他指令,当程序走到这个地址的时候,并不是直接发出INT 3信号,而是先去比较一下特定寄存器和某个地址的内容,再决定要不要INT 3。
如果能采用硬件条件断点,会比软件条件断点的性能好很多。

注:

https://sourceware.org/gdb/wiki/InternalsManual

【1】陈浩专栏: “用GDB调试工具”

一、 http://blog.csdn.net/haoel/article/details/2879

二、http://blog.csdn.net/haoel/article/details/2880

三、http://blog.csdn.net/haoel/article/details/2881

四、http://blog.csdn.net/haoel/article/details/2882

五、http://blog.csdn.net/haoel/article/details/2883

六、http://blog.csdn.net/haoel/article/details/2884

七、http://blog.csdn.net/haoel/article/details/2885

【2】http://blog.csdn.net/zhujinghao09/article/details/8461543

【3】http://blog.csdn.net/ganggexiongqi/article/details/8846001

【4】http://blog.csdn.net/yukin_xue/article/details/7653482