linux调试工具gdb的演示分析

时间:2022-05-18 02:07:05
 
一)gdb的调试信息

1)gdb是一个命令行调试器,它可用于全面控制和检查运行中的程序.
2)所有程序都会对gdb发出的命令有所响应,但只有按照合适选项编译并连接的程序才能包括足够的调试信息.
3)一般我们在gcc编译时,加入-g参数,指定程序在编译的时候加入调试信息到目标文件中.

下面我们对gcc启用debug信息进行分析.首先我们来看一下在加入debug信息与不加入debug信息在生成目标二进制文件后有什么区别.

我们用下面这个有问题的小程序进行测试,源程序如下:
#include <stdio.h>
int wib(int no1, int no2)
{
        int result, diff;
        diff = no1 - no2;
        result = no1 / diff;
        return result;
}
int main(int argc, char *argv[])
{
        int value, div, result, i, total;
        value = 10;
        div = 6;
        total = 0;
        for(i = 0; i < 10; i++)
        {
                result = wib(value, div);
                total += result;
                div++;
                value--;
        }
        printf("%d wibed by %d equals %d\n", value, div, total);
        return 0;
}

我们用gcc对它进行编译,如下:

gcc test.c -o regular

我们再次对它编译,这里指定用-g参数:
gcc test.c -o gdber -g 

我们用readelf来观察他们的区别,如下:

readelf -a gdber

以下是截取内容
Section Headers:
..............
  [25] .debug_aranges    PROGBITS        00000000 000714 000020 00      0   0  1
  [26] .debug_pubnames   PROGBITS        00000000 000734 000023 00      0   0  1
  [27] .debug_info       PROGBITS        00000000 000757 0001e3 00      0   0  1
  [28] .debug_abbrev     PROGBITS        00000000 00093a 000077 00      0   0  1
  [29] .debug_line       PROGBITS        00000000 0009b1 000048 00      0   0  1
  [30] .debug_frame      PROGBITS        00000000 0009fc 000054 00      0   0  4
  [31] .debug_str        PROGBITS        00000000 000a50 00000d 00      0   0  1
  [32] .debug_loc        PROGBITS        00000000 000a5d 00006f 00      0   0  
..............
注:我们看到gdber的section头中有debug信息,而这是不加-g选项的编译出的程序所没有的.


下面我们来具体查看一下debug信息,这里我们在编译时加入-save-temps,这样我们可以看到汇编文件,如下:

gcc test.c -save-temps -o regular
mv test.s regular.s
gcc test.c -save-temps -o gdber -g
mv test.s gdber.s

我们用vim的-d选项对两个文件进行比较,发现使用-g选项的汇编程序中有大量的调试标记,而这是不加-g选项的汇编程序所没有的.
vim -d gdber.s regular.s

注:下面是wib函数的比较
wib:                                                            |  wib:
  .LFB2:                                                          |  ---------------------------------------------------------------
          .file 1 "test.c"                                        |  ---------------------------------------------------------------
          .loc 1 3 0                                              |  ---------------------------------------------------------------
          pushl   %ebp                                            |          pushl   %ebp
  .LCFI0:                                                         |  ---------------------------------------------------------------
          movl    %esp, %ebp                                      |          movl    %esp, %ebp
  .LCFI1:                                                         |  ---------------------------------------------------------------
          subl    $16, %esp                                       |          subl    $16, %esp
  .LCFI2:                                                         |  ---------------------------------------------------------------
          .loc 1 5 0                                              |  ---------------------------------------------------------------
          movl    12(%ebp), %edx                                  |          movl    12(%ebp), %edx
          movl    8(%ebp), %eax                                   |          movl    8(%ebp), %eax
          subl    %edx, %eax                                      |          subl    %edx, %eax
          movl    %eax, -4(%ebp)                                  |          movl    %eax, -4(%ebp)
          .loc 1 6 0                                              |  ---------------------------------------------------------------
          movl    8(%ebp), %edx                                   |          movl    8(%ebp), %edx
          movl    %edx, %eax                                      |          movl    %edx, %eax
          sarl    $31, %edx                                       |          sarl    $31, %edx
          idivl   -4(%ebp)                                        |          idivl   -4(%ebp)
          movl    %eax, -8(%ebp)                                  |          movl    %eax, -8(%ebp)
          .loc 1 7 0                                              |  ---------------------------------------------------------------
          movl    -8(%ebp), %eax                                  |          movl    -8(%ebp), %eax
          .loc 1 8 0                                              |  ---------------------------------------------------------------
          leave                                                   |          leave
          ret                                                     |          ret

同时,我们在readelf中看到的section信息也可以在gdber.s找到,如下:

.section        .debug_abbrev,"",@progbits
.Ldebug_abbrev0:
        .section        .debug_info,"",@progbits
.Ldebug_info0:
        .section        .debug_line,"",@progbits
.Ldebug_line0:

最后我们也可以用objdump来直接查看二进制程序(以汇编语言的格式),如下:

objdump -S regular> /tmp/regular
objdump -S gdber> /tmp/gdber 
注:-S可以以混合的模式输出汇编语言

我们比较一下加入调试信息的文件与没有加入调试信息的文件有什么区别.
vim -d /tmp/gdber /tmp/regular 

注:这里比较的依旧是wib函数,我们看到加入调试信息后,我们除了看到汇编代码外,我们也看到了我们的C代码,这是我们可以理解的变量和符号,这就是为什么可以用gdb来跟踪和打印相关语句和变量的原因了.

08048354 <wib>:                                                 |  08048354 <wib>:
  #include <stdio.h>                                              |  ---------------------------------------------------------------
  int wib(int no1, int no2)                                       |  ---------------------------------------------------------------
  {                                                               |  ---------------------------------------------------------------
   8048354:       55                      push   %ebp             |   8048354:       55                      push   %ebp
   8048355:       89 e5                   mov    %esp,%ebp        |   8048355:       89 e5                   mov    %esp,%ebp
   8048357:       83 ec 10                sub    $0x10,%esp       |   8048357:       83 ec 10                sub    $0x10,%esp
          int result, diff;                                       |  ---------------------------------------------------------------
          diff = no1 - no2;                                       |  ---------------------------------------------------------------
   804835a:       8b 55 0c                mov    0xc(%ebp),%edx   |   804835a:       8b 55 0c                mov    0xc(%ebp),%edx
   804835d:       8b 45 08                mov    0x8(%ebp),%eax   |   804835d:       8b 45 08                mov    0x8(%ebp),%eax
   8048360:       29 d0                   sub    %edx,%eax        |   8048360:       29 d0                   sub    %edx,%eax
   8048362:       89 45 fc                mov    %eax,0xfffffffc(%|   8048362:       89 45 fc                mov    %eax,0xfffffffc(
          result = no1 / diff;                                    |  ---------------------------------------------------------------
   8048365:       8b 55 08                mov    0x8(%ebp),%edx   |   8048365:       8b 55 08                mov    0x8(%ebp),%edx
   8048368:       89 d0                   mov    %edx,%eax        |   8048368:       89 d0                   mov    %edx,%eax
   804836a:       c1 fa 1f                sar    $0x1f,%edx       |   804836a:       c1 fa 1f                sar    $0x1f,%edx
   804836d:       f7 7d fc                idivl  0xfffffffc(%ebp) |   804836d:       f7 7d fc                idivl  0xfffffffc(%ebp)
   8048370:       89 45 f8                mov    %eax,0xfffffff8(%|   8048370:       89 45 f8                mov    %eax,0xfffffff8(
          return result;                                          |  ---------------------------------------------------------------
   8048373:       8b 45 f8                mov    0xfffffff8(%ebp),|   8048373:       8b 45 f8                mov    0xfffffff8(%ebp)
  }                                                               |  ---------------------------------------------------------------
   8048376:       c9                      leave                   |   8048376:       c9                      leave
   8048377:       c3                      ret                     |   8048377:       c3                      ret


最后我们来说一下gcc的调试有三个级别,即g1,g2,g3,如果不指定具体的调试级别,默认情况下是g2.
g1:该级别在目标代码中插入的信息最少,它没有可执行代码和源代码相关信息,也没有足够信息可以检查局部变量.
g2:这是默认级别,该级别包括g1的所有信息,而且还添加了从源代码行到可执行代码相关的必要信息,以及局部变量的名字和位置.
g3:该级别包括级别1和级别2的所有信息,此外还加入额外的信息,包括预处理定义.
另外如果在gcc编译程序时加入-O选项优化程序,我们很有可能将无法对程序进行调试,因为优化选项会合并很多堆栈操作和变量名的调整.


二)程序断点调试法

1)gdb加载动态库函数

我们先来调试一个讨厌的小程序,程序源码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void *nasty(char *buf, int setlen)
{
        memset(buf, 'a', setlen);
}

const int buflen = 16;

int main (int argc,char *argv[])
{
        char *buf = malloc(buflen);

        int len = buflen;
        if (argc > 1)
                len = atoi(argv[1]);
        nasty(buf,len);
        free(buf);

        printf("buflen=%d len=%d okay\n", buflen, len);
        return 0;
}


gcc -g  nasty.c -o nasty  
注:
程序会分配16个整型数据空间,不过它也会根据用户的输入参数来填充内存,如果输入参数超过16,那么将会导致内存溢出.
我们来执行一下这个程序,如下:
./nasty 
buflen=16 len=16 okay

./nasty 100
*** glibc detected *** ./nasty: free(): invalid next size (fast): 0x08b0b008 ***
======= Backtrace: =========
/lib/libc.so.6[0xbd3f7d]
/lib/libc.so.6(cfree+0x90)[0xbd75d0]
./nasty[0x804849d]
/lib/libc.so.6(__libc_start_main+0xdc)[0xb83dec]
./nasty[0x8048391]
======= Memory map: ========
0078c000-0078d000 r-xp 0078c000 00:00 0          [vdso]
00b51000-00b6a000 r-xp 00000000 08:01 3704501    /lib/ld-2.5.so
00b6a000-00b6b000 r-xp 00018000 08:01 3704501    /lib/ld-2.5.so
00b6b000-00b6c000 rwxp 00019000 08:01 3704501    /lib/ld-2.5.so
00b6e000-00ca5000 r-xp 00000000 08:01 3704502    /lib/libc-2.5.so
00ca5000-00ca7000 r-xp 00137000 08:01 3704502    /lib/libc-2.5.so
00ca7000-00ca8000 rwxp 00139000 08:01 3704502    /lib/libc-2.5.so
00ca8000-00cab000 rwxp 00ca8000 00:00 0 
00dab000-00db6000 r-xp 00000000 08:01 3704511    /lib/libgcc_s-4.1.1-20070105.so.1
00db6000-00db7000 rwxp 0000a000 08:01 3704511    /lib/libgcc_s-4.1.1-20070105.so.1
08048000-08049000 r-xp 00000000 08:01 327681     /root/nasty
08049000-0804a000 rw-p 00000000 08:01 327681     /root/nasty
08b0b000-08b2c000 rw-p 08b0b000 00:00 0 
b7e00000-b7e21000 rw-p b7e00000 00:00 0 
b7e21000-b7f00000 ---p b7e21000 00:00 0 
b7f2a000-b7f2b000 rw-p b7f2a000 00:00 0 
b7f3f000-b7f40000 rw-p b7f3f000 00:00 0 
bfa68000-bfa7d000 rw-p bfa68000 00:00 0          [stack]
Aborted

我们下面用gdb来调试一下这个程序,如下:
gdb ./nasty
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) b memset             //用break来设置断点为memset函数.
Function "memset" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (memset) pending.
(gdb) run 100              //用run来指定输入参数为100,这会导致内存溢出.
Starting program: /root/nasty 100
Breakpoint 2 at 0xb66520
Pending breakpoint "memset" resolved

Breakpoint 2, 0x00b66520 in memset () from /lib/ld-linux.so.2
(gdb) cont                 //用cont命令从断点处继续执行,因为这不是nasty函数中的memset调用.
Continuing.

Breakpoint 2, 0x00b66520 in memset () from /lib/ld-linux.so.2
(gdb) cont                 //用cont命令从断点处继续执行,因为这不是nasty函数中的memset调用.
Continuing.

Breakpoint 2, 0x00b66520 in memset () from /lib/ld-linux.so.2
(gdb) cont                 //用cont命令从断点处继续执行,因为这不是nasty函数中的memset调用.
Continuing.
Breakpoint 2 at 0xbdb560

Breakpoint 2, 0x00bdb560 in memset () from /lib/libc.so.6
(gdb) bt                   //用bt命令查看堆栈信息.
#0  0x00bdb560 in memset () from /lib/libc.so.6
#1  0x08048434 in nasty (buf=0x9dee008 "", setlen=100) at nasty.c:7
#2  0x08048492 in main (argc=2, argv=0xbf822a34) at nasty.c:19
(gdb) 

注:
当加载程序时,共享的标准库还没有加载,由于memset属于标准库,但是由于它还未加载,因此gdb会提示是否加载该库,同时在程序运行中,会对每个memset的调用都用断点中止. 


2)gdb的条件断点设定

我们也可以用条件断点来调试,如下:
 gdb ./nasty
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) b nasty if setlen > buflen   //当setlen大于buflen时,在nasty函数处暂停.
Breakpoint 1 at 0x804841a: file nasty.c, line 7.
(gdb) run 100                      //用run来指定输入参数为100,这会导致内存溢出.
Starting program: /root/nasty 100

Breakpoint 1, nasty (buf=0x9955008 "", setlen=100) at nasty.c:7
7               memset(buf, 'a', setlen);

可以在条件断*包含任意地址和条件,唯一的限制是使用的变量必须同断点的地址在同一区域,像下面的条件断点将不能执行:
(gdb) b nasty if len > buflen
No symbol "len" in current context.

注:因为len不是nasty函数的局部变量,而是main函数的局部变量.


3)关于Tab键的使用技巧

下面我们讨论gdb下Tab键的小技巧,源程序如下:

namespace inconvenient {
        void *annoyingFunctionName1(void *ptr){
                return ptr;
        };

        void *annoyingFunctionName2(void *ptr){
                return ptr;
        };

        void *annoyingFunctionName3(void *ptr){
                return ptr;
        };

        void *annoyingFunctionName3(int x){
                return (void *)x;
        };
};

using namespace inconvenient;

int main (int argc, char *argv[])
{
        annoyingFunctionName1(0);
        annoyingFunctionName2(0);
        annoyingFunctionName3(0);
        annoyingFunctionName3((int) 0);
}

g++ -g cppsym.c -o cppsym

注:
这是一个c++程序,由于C++具有命令空间,重载和模板的技术,因此很难用很少的符号来设置断点.幸运的是,gdb提供了一些捷径来使调试变得更加容易.
上面的程序因为函数名比较长,而且非常相似,所以调试志来尤其困难,首先程序将它们存在命名空间并重载它们,导致输入数量增大,而我们使用自动补全命令和符号,来减少输入数量.
由于上面程序的函数都在命名空间(namespace)中,所以只输入annoy<Tab>是没有用的.我们用info function annoy来查看命名空间中的函数,如下:
gdb ./cppsym 
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) info function annoy
All functions matching regular expression "annoy":

File cppsym.c:
void *inconvenient::annoyingFunctionName1(void*);
void *inconvenient::annoyingFunctionName2(void*);
void *inconvenient::annoyingFunctionName3(int);
void *inconvenient::annoyingFunctionName3(void*);

如果我们在这里想设断点为inconvenient::annoyingFunctionName3(int)要这么做:
(gdb) b 'inc<Tab>::<Tab>3(<Tab>
这样的tab键使用会快捷的找到对映的函数,上面的命用等同于下面直接的输入:
b 'inconvenient::annoyingFunctionName3(int)'
这里最后要说明的是一定要在函数前加引号,不然是无法来配匹那两个冒号的.


4)设置观察点

下面我们依然用第一个程序来设置观察点,源程序如下:
#include <stdio.h>
int wib(int no1, int no2)
{
        int result, diff;
        diff = no1 - no2;
        result = no1 / diff;
        return result;
}
int main(int argc, char *argv[])
{
        int value, div, result, i, total;
        value = 10;
        div = 6;
        total = 0;
        for(i = 0; i < 10; i++)
        {
                result = wib(value, div);
                total += result;
                div++;
                value--;
        }
        printf("%d wibed by %d equals %d\n", value, div, total);
        return 0;
}

注:
在main函数里,如果div==value,将会出现除数为零的情况,所以我们将观察点设为div==value

gdb ./test 
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) break main          /*在这里设置断点为main函数*/
Breakpoint 1 at 0x8048389: file test.c, line 12.
(gdb) run                 /*运行程序,此时为停止在断点1,即main函数入门处*/
Starting program: /root/test 
Breakpoint 1, main () at test.c:12
12              value = 10;
(gdb) watch div==value    /*设定观察点,当div==value时,停止程序的运行*/
Hardware watchpoint 2: div == value
(gdb) cont                /*继续运行程序*/
Continuing.
Hardware watchpoint 2: div == value

Old value = 0
New value = 1
main () at test.c:15
15              for(i = 0; i < 10; i++)
(gdb) info locals         /*查看当前的变量,我们看到value和div两个变量的值都是8*/
value = 8
div = 8
result = 4
i = 1
total = 6

在这里我们对本次调试做一下说明:
1)使用观察点(watch)的理由是因为观察点使用的是寄存器,速度远快于断点.
2)若要设定观察点,就必须加载相关的变量,所以这里我们设定断点为main函数,当程序执行到main函数时,会加载相关的调试信息到寄存器,这时才可以利用watch来设置观察点div==value.

我们也可以用info watch来查看相应的断点和观察点,这里也可以看到它的命中数,如下:
info watch
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048389 in main at test.c:12
        breakpoint already hit 1 time
2   hw watchpoint  keep y              div == value
        breakpoint already hit 1 time
        
        
三)检查和管理数据

在gdb中有丰富的指令来检测数据,print,x,whatis,info locals可以查看变量,显示输出等.
backtrace,up,down,frame可以用来查看堆栈.

1)print
print可以显示所有格式的数据,包括字符串和数组,print允许使用修饰语法去改变输出行为,修饰语法和print之间用/来分隔.
下面是print的简单应用:
gdb ./test
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) run
Starting program: /root/test 

Program received signal SIGFPE, Arithmetic exception.
0x0804836d in wib (no1=8, no2=8) at test.c:6
6               result = no1 / diff;
(gdb) p result            /*打印result变量*/
$1 = 4
(gdb) p no1
$2 = 8
(gdb) p no2
$3 = 8
(gdb) p diff             
$4 = 0
(gdb) p/x no1            /*以十六进制的方式打印变量no1*/
$5 = 0x8
(gdb) p/x no2
$6 = 0x8
(gdb) p/t no2            /*以二进制的方式打印变量no2*/
$7 = 1000

print也可以调用函数,如下:
(gdb) p getpid()         /*调用getpid()函数,输出当前进程的PID*/
$9 = 2857
(gdb) p kill(getpid(),0) /*调用kill()函数,发送信号0给当前进程*/
$10 = 0
(gdb) p kill(getpid(),9) /*调用kill()函数,发送信号9给当前进程,此时会杀死当前进程*/

Program terminated with signal SIGKILL, Killed.
The program no longer exists.
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on"
Evaluation of the expression containing the function (kill) will be abandoned.


2)x
x是examine的缩写,和print命令相似,但它只针对内存地址和一些原始数据.

下面是x的一些简单应用:
gdb ./test
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) run
Starting program: /root/test 

Program received signal SIGFPE, Arithmetic exception.
0x0804836d in wib (no1=8, no2=8) at test.c:6
6               result = no1 / diff;
(gdb) x result                  /*打印result,此处返回错误,提示不能打印内存地址*/
0x4:    Cannot access memory at address 0x4
(gdb) x &result                 /*打印result的值,此处返回的是result变量的值*/
0xbff62090:     0x00000004
(gdb) x &no1
0xbff620a0:     0x00000008
(gdb) x &diff
0xbff62094:     0x00000000

我们也可以用x指令的i格式在内存的任何地址分解机器代码,如下:
(gdb) x/10i main
0x8048378 <main>:       lea    0x4(%esp),%ecx
0x804837c <main+4>:     and    $0xfffffff0,%esp
0x804837f <main+7>:     pushl  0xfffffffc(%ecx)
0x8048382 <main+10>:    push   %ebp
0x8048383 <main+11>:    mov    %esp,%ebp
0x8048385 <main+13>:    push   %ecx
0x8048386 <main+14>:    sub    $0x34,%esp
0x8048389 <main+17>:    movl   $0xa,0xffffffe8(%ebp)
0x8048390 <main+24>:    movl   $0x6,0xffffffec(%ebp)
0x8048397 <main+31>:    movl   $0x0,0xfffffff8(%ebp)


3)whatis及locals info

whatis对于给定的符号,gdb会给出所有它知道的相关信息,如下:
(gdb) whatis result     /*打印出result变量的类型,这里的result为int型*/
type = int              


locals info用于显示当前堆栈页的所有本地变量,如下:
(gdb) info locals       /*打印本地变量*/
result = 4
diff = 0


4)backtrace与frame

backtrace用于显示当前调用的堆栈,包括本地变量.
frame可以替代up指令和down指令,frame允许转向具体指定的页,所有的页都用数字标记,这些数字被列在backtrace指令中.

gdb ./test
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) run
Starting program: /root/test 

Program received signal SIGFPE, Arithmetic exception.
0x0804836d in wib (no1=8, no2=8) at test.c:6
6               result = no1 / diff;
(gdb) bt                /*打印堆栈的信息*/
#0  0x0804836d in wib (no1=8, no2=8) at test.c:6
#1  0x080483b9 in main () at test.c:17
(gdb) frame 0           /*输出堆栈信息中的#0*/
#0  0x0804836d in wib (no1=8, no2=8) at test.c:6
6               result = no1 / diff;
(gdb) frame 1           /*输出堆栈信息中的#1*/
#1  0x080483b9 in main () at test.c:17
17                      result = wib(value, div);


5)从gdb中调用函数

之前我们用print指令来调用函数,这里我们也可以用call指令来调用函数.

gdb ./test
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) run
Starting program: /root/test 

Program received signal SIGFPE, Arithmetic exception.
0x0804836d in wib (no1=8, no2=8) at test.c:6
6               result = no1 / diff;
(gdb) call getpid()       /*调用getpid()函数,获取当前进程的PID,它默认将PID赋值给$1*/
$1 = 2930
(gdb) call kill($1,0)     /*调用kill()函数,发送信号0给当前进程*/
$2 = 0



四)使用gdb连接正在运行的进程

能够将调试器附带到运行程序上是非常有用的,例如,程序运行一段时间之后进行无响应的循环或者程序突然开始做一些不应该做的事情.
将调试器附带到运行程序上必需要满足两个先决条件,首先,必需按照-g选项的形式编译进程,第二,必需确定运行进程的进程号.
下面我们展示一下gdb的这种能力,测试程序如下:
/*looper.c*/ 
void goaround(int);

int main(int argc,char *argv[])
{
        printf("started\n");
        goaround(20);
        printf("done\n");
}
void goaround(int counter)
{
        int i = 0;

        while (i < counter){
                if (i++ == 17)
                        i = 10;
        }
}

编译:
gcc -g looper.c -o looper

运行looper程序:
./looper &
[2] 3031
started

注:这个程序永远无法跳出循环

我们用gdb加载这个程序,如下:
gdb looper 3031
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

Attaching to program: /root/looper, process 3031
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0x080483a9 in goaround (counter=20) at looper.c:14
14                      if (i++ == 17)

(gdb) display i
1: i = 18
(gdb) step
15                              i = 10;
1: i = 18
(gdb) step
13              while (i < counter){
1: i = 10
(gdb) step
14                      if (i++ == 17)
1: i = 10
(gdb) step
13              while (i < counter){
1: i = 11
(gdb) step
14                      if (i++ == 17)
1: i = 11

我们也可以在gdb中分离进程或加载进程,如下:
gdb 
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu".
(gdb) file looper   /*加载looper程序*/
Reading symbols from /root/looper...done.
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) attach 3041   /*加载进程(PID=3041)*/
Attaching to program: /root/looper, process 3041
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
goaround (counter=20) at looper.c:14
14                      if (i++ == 17)
(gdb) step
13              while (i < counter){
(gdb) step
14                      if (i++ == 17)
(gdb) step
13              while (i < counter){
(gdb) step
14                      if (i++ == 17)
(gdb) detach 3041   /*分离进程(PID=3041),让进程继续执行*/
Detaching from program: /root/looper, process 3041
ptrace: Input/output error.



五)调试内核文件

在Linux系统中,崩溃的程序可以激活操作系统的函数,将程序的内容完全复制到core文件中,此时core文件里面包括当时进程的部分虚拟内存,产生core文件往往是一个信号的结果.
调试时经常遇到的内核产生的信号是:
SIGSEGV---段违例,当一个进程尝试读或写一个无效的内存地址,当尝试往一个只读页里写东西或读取一个不允许读的页时产生该信号.
SIGFPE---浮点错误,奇怪的是,这个信号通常不是浮点函数产生的,然而当一个进程尝试用整数去除0时会在x86体系结构中看到SIGFPE.
SIGABRT---异常中断,在异常中断函数和声明函数中使用.
SIGILL---非法指令.在手工汇编过程中,如果试图从用户模式使用特权指令就容易出现这个信号.
SIGBUS---总线错误,不属于是硬件错误,这个信号可能是由于不能完成页的错误结果,比如在运行时超出交换空间.

我们用下面的程序进行调试,源程序如下:
/*falldown.c*/
char **nowhere;
void setbad();

int main (int argc, char *argv[])
{
        setbad();
        printf("%s\n",*nowhere);
}

void setbad()
{
        nowhere = 0;
        *nowhere = "This is a string\n";

编译:
gcc falldown.c -o falldown -g
注:本程序由于向内存地址0x0写数据,导致产生段违例,这时Linux系统会发送SIGSEGV信号给程序,并将当前的堆栈信息转储到core文件.

运行程序:
./falldown
Segmentation fault (core dumped)

查看core文件,此时产生了一个core文件
ls core.*
core.2650

我们用gdb调试这个程序,如下:
gdb falldown core.2650 
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".


warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./falldown'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048394 in setbad () at falldown.c:13
13            *nowhere = "This is a string\n";   /*在执行到这行时程序出现段非法,并收到信号*/
(gdb) print nowhere                              /*我们打印nowhere,发现它是地址0x0*/
$1 = (char **) 0x0
(gdb) bt                                         /*此处我们打印堆栈信息*/
#0  0x08048394 in setbad () at falldown.c:13
#1  0x0804836a in main () at falldown.c:6



六关于多线程的调试

当用gdb调试多线程的程序时,所有的线程都停止了,当单步执行代码时,gdb尝试停留在当前进程中,如果断点发生在不同的线程,promopt指令会转换线程的堆栈页面.

源程序如下:
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int GetCpuCount()
{
    return (int)sysconf(_SC_NPROCESSORS_ONLN);
}

void *thread_fun()
{
    int i;
    while(1)
    {
        i = 0;
    }

    return NULL;
}

int main()
{
    int cpu_num = 0;
    cpu_num  = GetCpuCount();
    printf("The number of cpu is %d\n", cpu_num);

    pthread_t t1;
    pthread_t t2;
    pthread_attr_t attr1;
    pthread_attr_t attr2;

    pthread_attr_init(&attr1);
    pthread_attr_init(&attr2);

    cpu_set_t cpu_info;
    CPU_ZERO(&cpu_info);
    CPU_SET(0, &cpu_info);
    if (0!=pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpu_info))
    {
        printf("set affinity failed");
        return;
    }

    CPU_ZERO(&cpu_info);
    CPU_SET(0, &cpu_info);
    if (0!=pthread_attr_setaffinity_np(&attr2, sizeof(cpu_set_t), &cpu_info))
    {
        printf("set affinity failed");
    }

    if (0!=pthread_create(&t1, &attr1, thread_fun, NULL))
    {
        printf("create thread 1 error\n");
        return;
    }

    if (0!=pthread_create(&t2, &attr2, thread_fun, NULL))
    {
        printf("create thread 2 error\n");
        return;
    }

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

编译:
gcc test.c -o test -pthread -g 

注:程序会派生两个新的线程,它们会陷入死循环.

运行该程序:
./test &
[1] 2719
The number of cpu is 1

我们用gdb来加载该进程,如下:
gdb test 2719
GNU gdb Red Hat Linux (6.5-16.el5rh)
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

Attaching to program: /root/test, process 2719
Reading symbols from /lib/libpthread.so.0...done.
[Thread debugging using libthread_db enabled]
[New Thread -1208904000 (LWP 2719)]
[New Thread -1219396720 (LWP 2721)]
[New Thread -1208906864 (LWP 2720)]
Loaded symbols for /lib/libpthread.so.0
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0x00600402 in __kernel_vsyscall ()
(gdb) info threads                 /*查看当前的线程信息*/
  3 Thread -1208906864 (LWP 2720)  thread_fun () at test.c:17
  2 Thread -1219396720 (LWP 2721)  thread_fun () at test.c:17
  1 Thread -1208904000 (LWP 2719)  0x00600402 in __kernel_vsyscall ()
(gdb) break thread_fun thread 3    /*设定断点在第3个线程的thread_fun函数*/
Breakpoint 1 at 0x804854e: file test.c, line 16.
(gdb) step                         /*单步执行,由于我们此时在线程1,也就是两个新线程的父进程*/
Single stepping until exit from function __kernel_vsyscall, 
which has no line number information.
[Switching to Thread -1208904000 (LWP 2719)]   /*注意我们看到此时它切换到了线程2*/
0x00ce2457 in pthread_join () from /lib/libpthread.so.0
(gdb) step                         /*我们再次单步执行,此时它切换到了线程3,由于我们在线程3设定了断点,以后的单步执行都在线程3上执行*/
Single stepping until exit from function pthread_join, 
which has no line number information.
[Switching to Thread -1208906864 (LWP 2720)]

Breakpoint 1, thread_fun () at test.c:16
16            i = 0;
(gdb) step
17        }
(gdb) step

Breakpoint 1, thread_fun () at test.c:16
16            i = 0;