Linux C调试入门

1.自定义输出
  想必大家都有利用输出函数如printf来帮助我们调试程序的经历,这是一种比较原始的程序调试辅助方法,在Linux下也可以为我们所用。不过这种方法有一个明显的缺点,就是在调试完后我们必须注释或删除掉这些辅助代码。Linux C提供了-DDEBUG这个编译标记来定义DEBUG这个符号,借助于该符号,我们可以在应用程序中添加额外代码并根据需要决定执行与否。
  如:
  #include<stdio.h>
  //*******dtest.c*******

  int main()
  {
    #ifdef DEBUG
      printf("Debug output....../n");
    #endif

    printf("Main function ended!/n";
  }

  运行:
  $ cc -o dtest dtest.c
  $ ./dtest
  Main function ended!
  $ rm dtest
  $ cc -o dtest  -DDEBUG dtest.c
  $ ./dtest
   Debug output......
   Main function ended!

  通过以上示例,你应该明白了-DDEBUG标记的用法了吧,呵呵~~~也许你会想,如果我有好几段调试代码,而我希望根据需要每次选择相应的一段来运行,这是否能实现呢?不怕做不到,就怕想不到。事实上我们可以方便的实现这种构想,如下:
  #define BASIC_DEBUG 1
  #define EXTRA_DEBUG 2
  #define SUPER_DEBUG 4

  //code 1
  # if (DEBUG & BASIC_DEBUG )
    printf。。。
  #endif

  //code 2
   # if (DEBUG & EXTRA_DEBUG )
    printf。。。
  #endif
 
  //code 3
   # if (DEBUG & SUPER_DEBUG )
    printf。。。
  #endif

  上述代码中我们自定义了几个宏变量,通过"&“来决定代码段的执行与否。我们知道”&“为按位取余运算符,所以当DEBUG的值为1时,只有code 1会执行,当DEBUG的值为2时,只有code 2会执行,而当DEBUG的值为3时,code 1和code 2都会执行,依次类推。。。可是我们如何根据需要设置DEBUG的值呢?这还得看-DDEBUG标记,在命令提示符下直接给-DDEBUG赋值就OK了,如cc -o dtest 
-DDEBUG=5  dtest.c

 

2 调试工具(gdb)
  前面介绍的调试方法只是一种帮助我们调试的辅助方法,作为专业的程序员,我们应该熟练掌握功能更加全面的调试工具。下面就详细介绍在Linux下比较常用的gdb工具,它是GNU所提供的。

示例程序:
#include<stdio.h>
//*******ArraySort.c*******

/* 1 */ typedef struct {
/* 2 */      char *data;
/* 3 */       int key;
/* 4 */ } item;
/* 5 */
/* 6 */  item array[] = {
/* 7 */       {“bill”, 3},
/* 8 */       {“neil”, 4},
/* 9 */       {“john”, 2},
/* 10 */      {“rick”, 5},
/* 11 */      {“alex”, 1},
/* 12 */  };
/* 13 */
/* 14 */  sort(a,n)
/* 15 */   item *a;
/* 16 */  {
/* 17 */      int i = 0, j = 0;
/* 18 */       int s = 1;
/* 19 */
/* 20 */      for(; i < n && s != 0; i++) {
/* 21 */          s = 0;
/* 22 */          for(j = 0; j < n; j++) {
/* 23 */          if(a[j].key > a[j+1].key) {
/* 24 */              item t = a[j];
/* 25 */              a[j] = a[j+1];
/* 26 */              a[j+1] = t;
/* 27 */              s++;
/* 28 */          }
/* 29 */        }
/* 30 */       n--;
/* 31 */     }
/* 32 */  }
/* 33 */
/* 34 */  main()
/* 35 */  {
/* 36 */      int i;
/* 37 */       sort(array,5);
/* 38 */      for(i = 0; i < 5; i++)
/* 39 */      printf(“array[%d] = {%s, %d}/n”,
/* 40 */           i, array[i].data, array[i].key);
/* 41 */   }

  下面我将借助于该程序来逐步讲解gdb的使用方法。
a. 进入调试模式
    $ cc -g  -o ASort  ArraySort.c
    $ gdb ASort
    输入以上命令后就进入调试模式了,此时命令提示符变为”(gdb)",可以输入help命令查看相应的命令。有一点需要说明的是,-g参数可以帮助输出调试信息。
    (gdb) help
    List of classes of commands:

    aliases — Aliases of other commands
    breakpoints — Making program stop at certain points
    data — Examining data
    files — Specifying and examining files
    internals — Maintenance commands
    obscure — Obscure features
    running — Running the program
    stack — Examining the stack
    status — Status inquiries
    support — Support facilities
    tracepoints — Tracing of program execution without stopping the program
    user-defined — User-defined commands
    Type “help” followed by a class name for a list of commands in that class.
    Type “help” followed by command name for full documentation.
    Command name abbreviations are allowed if unambiguous.
    (gdb)

    除了上述命令,我还想补充的是kill命令可以终止正在调试的程序,而quit命令将终止gdb的使用。

b. 运行程序
    在调试模式下通过run命令来运行程序,如果程序需要参数,你可以像在普通模式下运行程序那样在run命令后添加参数。在本程序中,直接输入run命令即可:
   (gdb) run
   结果:
        Starting program: /home/ubuntu/ASort

        Program received signal SIGSEGV, Segmentation fault.
        0x080484ae in sort (a=0x804a040, n=5) at ArraySort.c:28
        28 /* 23 */   if(a[j].key > a[j+1].key) {
        (gdb)
    程序因出现错误而终止执行,我们可以用backtrace(可简写为bt)或where命令来查看退栈里的情况,如:
    (gdb) bt
    #0 0x080484ae  in  sort  (a=0x804a040, n=5) at ArraySort.c:28
    #1 0x0804863a  in  main () at ArraySort.c:47

c. 检查变量
    当程序出现问题的时候我们会想到去查看变量的值进而推断问题所在,在调试模式下print命令可以帮助我们输出变量或者表达式的值。
    前面讲到程序在if(a[j].key > a[j+1].key) 处终止执行,于是我们想到查看变量j的当前数值:
    (gdb) print j
    $1 = 4
   gdb把输出变量保存在伪变量中,这里j 的值为4且保存在变量$1中。从结果我们可以看出,程序是在尝试执行if(a[4].key > a[4+1].key)的时候终止执行的,由此我们不难看出,我们定义的数组的长度为5,即数组下标为0-4,而此处用到了下标为5的数组元素,显然越界。至此我们已经明白了问题所在,一个解决方法为将第二个for循环改为:for(j = 0; j < n-1; j++) {
    再此,我还要探讨一下print的用法,利用print命令我们可以查看变量,数组或者指针元素的值,再举一个例子:
    (gdb) print a[j]
    $2 = {data="rick",'/0'<repats 4091 times>, key=5}
    (gdb) print $
    $3 = {data="rick",'/0'<repats 4091 times>, key=5}
    变量$通常保存着上一个伪变量的值,而$$则保存着当前倒数第二个伪变量的值。如果希望输出数组中连续几个元素的值,可用@<number>命令,如:
    (gdb) print array[0]@5
    此外,我们可以利用list命令将当前位置的程序输出,再次输入list命令将继续输出后续部分。

d. 设置断点
    我们可以通过设置断点来暂停程序的运行,下面我们先来看看gdb中与设置断点相关的命令
    (gdb) help breakpoint
    Making program stop at certain points.

    List of commands:

    awatch — Set a watchpoint for an expression
    break — Set breakpoint at specified line or function
    catch — Set catchpoints to catch events
    clear — Clear breakpoint at specified line or function
    commands — Set commands to be executed when a breakpoint is hit
    condition — Specify breakpoint number N to break only if COND is true
    delete — Delete some breakpoints or auto-display expressions
    disable — Disable some breakpoints
    enable — Enable some breakpoints
    hbreak — Set a hardware assisted breakpoint
    ignore — Set ignore-count of breakpoint number N to COUNT
    rbreak — Set a breakpoint for all functions matching REGEXP
    rwatch — Set a read watchpoint for an expression
    tbreak — Set a temporary breakpoint
    tcatch — Set temporary catchpoints to catch events
    thbreak — Set a temporary hardware assisted breakpoint
    watch — Set a watchpoint for an expression

    Type “help” followed by command name for full documentation.
    Command name abbreviations are allowed if unambiguous.
   
    可用break linenumber命令给指定行设置断点,continue(cont)命令将继续执行断点后面的程序。前面提到可以用print
array@<number>命令输出数组中连续几个元素的值,事实上我们还可以利用display array@<number>命令在程序每次运行到该断点的时候自动输出数组的内容,此外,我们还可以通过commands命令让程序在执行到断点的时候自动输出数组的内容但不暂停程序的执行,如:
    (gdb) display array[0]@5
    (gdb) commands
    >cont
    >end
    设置了断点就需要清楚断点,gdb给我们提供了disable命令和delete命令来禁止或者清除断点,我们可以先通过infor命令查看程序已定义的断点,如:info  break(display命令类似),gdb就会列出已定义的断点及其序号,然后就可以利用该序号执行disable命令或delete命令,如disable  1将会禁止第一个断点的执行
    前面的实例程序还有一个问题,就是第30行“n--”这句,实际上该行是不需要的,它的存在反而导致比较次数不够而无法得到正确的结果,一种简单的解决方法为将该行直接删除,此处我们介绍利用commands命令来修改变量的值,如下:
    <gdb> commands 2
    > set variable n=n+1
    >cont
    >end
    (gdb) run

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值