zoukankan      html  css  js  c++  java
  • 调试利器GDB(下)

    本节我们研究gdb更深层的用法:

    数据断点:

    可以根据变量的值来监视变量。

    数据断点本质是硬件断点,数量有限。

    watch var_name告诉gdb我们关注var_name这个变量,如果它的值被改变了,程序运行就停止。

    数据断点常常配合gdb里面的内存查看命令使用。

    x是检查内存数据的命令。

    上图中的意思是打印从0x804a024地址开始连续4个字节的数据,呈现格式以16进制打印。x代表16进制。

    上图中/a代表打印地址。

    变量断点和内存查看实验:

    watch.c程序如下:

     1 #include <stdio.h>
     2 #include <pthread.h>
     3 #include <unistd.h>
     4 
     5 int g_var = 0;
     6 
     7 void* thread_func(void* args)
     8 {
     9     sleep(5);
    10     
    11     g_var = 1;
    12 }
    13 
    14 int main()
    15 {
    16     int i = 0;
    17     pthread_t tid = 0;
    18     
    19     pthread_create(&tid, NULL, thread_func, NULL);
    20     
    21     for(i=0; i<10; i++)
    22     {
    23         printf("g_var = %d
    ", g_var);
    24         
    25         sleep(1);
    26     }
    27 }

     先编译运行一下:

    子线程中将g_var的值改为1,影响到了主线程的工作,我们认为这是一个错误的程序,进行debug调试。

    开始调试:

     使用watch监视g_var:

    continue继续执行程序:

    当g_var被改变的时候,程序停下了。图中提示g_var的值被改写了,是被thread_func函数中的第12行改写的(gdb表达的意思是程序执行到第12行时,g_var的值被改写了,意味着是第11行改写的)。

    查看内存的值:

    上图也验证了当前系统是小端系统。

    继续执行:

    本次调试,我们确定了是什么地方修改了g_var的值。

     函数调用相关:

    为什么要打印函数调用栈呢?举个例子,当我们看一份开源代码的时候,我们想快速弄清楚函数的调用关系,这时可以在感兴趣的函数上打上断点,当程序断在这里时,可以用backtrace查看函数调用栈。

    frame N可以跳转到某一个函数的上下文当中。切换过去之后可以查看一些函数参数信息等。

    info fream可以查看栈帧信息。

    每一次函数调用的活动记录就存在栈上,这就是栈帧。

     实验:

    frame.c程序如下:

     1 #include <stdio.h>
     2 
     3 
     4 int sum(int n)
     5 {
     6     int ret = 0;
     7     
     8     if( n > 0 )
     9     {
    10         ret = n + sum(n-1);
    11     }
    12     
    13     return ret;
    14 }
    15 
    16 
    17 int main()
    18 {
    19     int s = 0;
    20     
    21     s = sum(10);
    22     
    23     printf("sum = %d
    ", s);
    24     
    25     return 0;
    26 }

    先编译运行一下程序:

     启动gdb:

     打一个条件断点:

     继续执行:

    程序在sum函数的第6行停下了。

    backtrace打印了函数的调用信息。

    main -> sum(10) -> sum(9) .... -> sum(0)

    程序在sum(0)时停了下来。

    next继续执行:

    函数停在return ret的时候,我们通过info args打印当前栈帧这个函数参数的值,可以看到为0。

    切换函数栈帧:

    我们切换到7号栈帧上。

    打印信息告诉我们,目前函数调用上下文里面语句停留在ret = n  + sum(n-1);这一行。

    info args打印参数信息,info locals打印局部变量的值:

    将栈帧切换回0号栈帧,并打印寄存器:

    当前的sp的值是0xbfffee80。

    打印当前栈帧的详细信息:

    我们关注Previous frame's sp is 0xbfffeeb0这个信息。

    这就是上一个栈帧sp的值。

    调用这个函数之前,ebp的值被保存在了0xbfffeea8地址处,eip保存在了0xbfffeeac地址处。

    打印函数调用之前ebp的值:

     继续验证上面的打印是否正确:

    执行next,sum(0)返回,这就到了sum(1)的地方:

    可以看到ebp的值和上一次的打印是一样的。esp的值也完全能对应上。

    调试技巧:

    whatis和ptype是动态查看符号的方法。

    上图中在gdb中直接编译程序。

    编译后用file载入运行。

    符号查看:

    从上图中可知,func是一个函数类型,具体为int(),返回值为int,没有参数。

    实验:

    tricks.c如下:

     1 #include <stdio.h>
     2 
     3 int g_var = 1;
     4 
     5 struct ST
     6 {
     7     int i;
     8     int j;
     9 };
    10 
    11 int func()
    12 {
    13     struct ST st[5] = {0};
    14     int i = 0;
    15     
    16     for(i=0; i<5; i++)
    17     {
    18         st[i].i = i;
    19         st[i].j = i * i;
    20     }
    21     
    22     for(i=0; i<5; i++)
    23     {
    24         printf("st[%d].i = %d
    ", i, st[i].i);
    25         printf("st[%d].j = %d
    ", i, st[i].j);
    26     }
    27 }
    28 
    29 int main()
    30 {
    31     static c_var = 2;
    32     
    33     func();
    34     
    35     return 0;
    36 }

    开始调试:

    打印结果如下:

    list是列出源文件的某一行的内容,但是相邻的几行也会打印出来。

    设置显示的行数重新打印:

    继续执行:

     使用display使得每次执行到这个断点的时候都自动打印。

    最后程序正常退出:

    我们再次直接run,上次设置的东西还都存在:

     undisplay可以取消之前的自动打印。

    使用符号查看:

     ptype是打印详细的类型定义。

    查看全局的变量符号:

    info functions可以得到以下信息:

    小结:

  • 相关阅读:
    Luogu-P2295 MICE
    Luogu-P2627 修剪草坪
    Loj-10176-最大连续和
    Luogu-P1886 滑动窗口
    Luogu-P3807 【模板】卢卡斯定理
    Luogu-P1879 [USACO06NOV]玉米田Corn Fields
    Luogu-P1896 [SCOI2005]互不侵犯
    Loj-SGU 223-国王
    Luogu-P2657 [SCOI2009]windy数
    素数
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9821349.html
Copyright © 2011-2022 走看看