Debugging

Debugging

The content of this file is based on “The Developer’s Guide to Debugging”, written by Thorsten Grotker, Ultich Holtmann, Holger Keding and Markus Wloka, and its simplified Chinese version. This material concentrates on basic concepts of debugging and usage of GDB.

I Commands of GDB

在使用GDB之前,需要先使用编译器标志-g来编译源程序。 例如:

gcc -g -o main main.c

I-1 常用命令

  • run,run命令将开始程序。
  • start,start命令将运行程序,知道main()的第一行,然后停止程序的执行。
  • pause,pause命令将中断一个正在运行的程序。
  • continue,continue命令使暂停的程序恢复执行。
  • step-into (step),作用是移动到下一个可执行的代码。如果当前行是一个函数调用,则调试器将进入函数,并停在函数体的第一行。
  • step-over (next),作用是在同一个调用栈层中移动到下一个可执行的代码行。
  • step-out (finish),作用是在栈中前进到下一层,并在调用函数的下一行停止。
  • list

I-2 栈跟踪

GDB的栈跟踪命令是backtrace(bt),可以使用命令up或down在栈中移动,来检查函数参数和局部变量的值。

I-3 断点

  • 行断点(line breakpoint) - 当到底源代码中的指定行时,暂停程序。

    break

  • 函数断点(function breakpoint) - 当到达指定函数的第一行时,暂停程序。

    break function

  • 条件断点(conditional breakpoint) - 如果特定条件保持为真,则暂停程序。

    condition bnum

  • 事件断点(event breakpoint) - 当发生特定事件时,使程序进入暂停模式。

    handle,signal

info breakpoints - 显示所有断点信息

I-4 检查数据

print - 将输出变量或表达式的当前值。
display - 在调试器每次暂停时,都计算并自动输出表达式的结果。

II Profile Memory Usage

II-1 内存问题

  • 内存泄露
  • 内存管理的错误使用
  • 缓冲区溢出
  • 未初始化的内存bug

可以使用valgrind来检测内存问题

valgrind --tool=memcheck --leak-check=yes ./a.out

II-2 剖析内存

可以使用Massif来剖析内训的使用。如果已用调试信息标志-g编译程序,则Massif将支持在统计输出中引用具体的行。

valgrind --tool=massif ./a.out

剖析数据将写入massif.out.

III Profile Programs

可以用基本事件测量工具time来测量时间,但可能会发生错误和误差,例如文件I/O、系统调用、低内存(交换/分页)、CPU时钟频率不稳定或其他进程的影响。

III-1 gprof

使用gprof需要以下3步:

  • 用-pg标志编译并链接程序。
  • 运行程序,剖析文件数据将被写入gmon.out文件中。
  • 运行gprof gmon.out命令生成剖析报告。

gprof的缺点:

  • 不剖析调用操作系统所花的时间,而且可能无法处理共享库。
  • 插入的剖析代码极大地降低了程序的运行速度,但其它工具(如Quantify或Valgrind)可能导致程序运行得更为缓慢。
  • 采样方法并不完全准确。

III-2 Callgrind

Callgrind是Valgrind调试和剖析工具中的一部分,实际上Valgrind包括

  • Memcheck: 内存检查器
  • Callgrind: 规则剖析工具
  • Cachegrind: 缓存剖析工具
  • Helgrind: 查找竞争条件工具
  • Massif: 内存剖析工具

使用Callgrind运行程序,剖析数据将被写入callgrind.out.文件中。

valgrind --tool=callgrind ./a.out
callgrind_annotate callgrind.out.10000

–tree=both选项将显示调用图,列出每个函数的调用者以及它调用的其它函数。
–auto=yes输出带注释的源文件。
可以用图形前段KCachegrind来查看callgrind.out.数据剖析文件,

kcachegrind callgrind.out.10000

IV Debugging Parellel Programs

GDB 线程相关命令:

  • info stack: 打印出当前线程栈的情况
  • frame : 跳转到栈中的某一帧
  • info threads: 打印当前进程中所有线程的信息
  • thread : 跳转到某一个线程

多线程分析工具Helgrind

valgrind --tool=helgrind ./a.out

V Other Debugging Skills

V-1 在C++函数、方法和操作符中设置断点

对于

break C::foo 

由于其未指定参数,GDB会匹配所有C::foo方法。可以用

ptype C

来查看class C中的所有信息。
info functions 将查找与匹配的所有全局函数和成员函数。

V-2 条件断点

本节中GDB的使用均针对以下代码(main.c)

    #include <stdio.h>
    #include <stdlib.h>

    int factorial(int n)
    {
        int result = 1;
        if (n == 0)
            return result;
        result = factorial(n - 1) * n;
        return result;
    }

    int main(int argc, char **argv)
    {
        int n, result;
        if (argc != 2) {
            fprintf(stderr, "usage: factorial n \n");
            return 1;
        }

        n = atoi(argv[1]);
        result = factorial(n);
        printf("factorial %d = %d\n", n, result);
        return 0;

    }

在GDB中,可以使用commands命令指定一个命令序列,每次到达一个特定断点时,都将执行此序列。

(gdb)break 6
(gdb)commands 1
>silent
>printf "n = %d\n", n
>continue
>end

silent - 不输出后续命令的执行结果。

GDB中可以使用condition命令创建一个条件断点,例如:

(gdb) break main.c:6
(gdb) condition 2 argc==1
(gdb) run

这段代码在main.c代码的第6行设置了一个条件断点。当条件(argc==1)满足时,程序会在此处停止。 GDB中创建条件断点的另一种方法是使用一个常规断点,然后附加一个包含条件语句的命令。

V-3 动态链接运行程序

本节内容针对以下代码(initial_delay.cpp)

    #include <unistd.h>

    static int delay_done = 0;
    static int ask_mice() {
        while(!delay_done)
            sleep(10);
        return 42;
    }
    static int pol = ask_mice();

    int main()
    {
        return 0;
    }

首先,编译源程序并运行生成的可执行文件

g++ -g initial_delay.cpp
./a.out &

查看改程序的PID,而后运行

sudo gdb
attach 3384
set var delay_done=1
continue

程序中变量delay_done的值将被改写为1,循环结束,程序将运行结束。

V-4 使用观察点

观察点或数据点的作用是在表达式值发生变化时停止程序的执行。在GDB中,watch命令的表达式可以是变量、内存地址或任意的复杂表达式。调试器将不间断地监视表达式,并在表达式值发生更改的语句停止程序的执行。例如,对求解factorial的程序,

gdb ./a.out
(gdb) start 10
(gdb) watch result
(gdb) cont

此时,程序会在result的值变化时暂停,并打印出result的old value和new value。

V-5 捕捉信号

调试信号问题,可以采用以下策略:

  • 提高可见性。令调试器在收到信号时输出一条消息,然后将此消息发送给程序。

    handle SIGUSR1 print nostop pass

  • 禁用干扰性信号。令调试器忽略所有进入的信号,不再调用信号处理程序。

    handle SIGUSR1 noprint nostop nopass

  • 引发bug。生成一个信号,并将其发送给程序。

    signal SIGUSR1

V-6 操纵正在运行的程序

本节针对以下代码(print_number.cpp):

    #include <iostream>

    int printNumber(int number)
    {
        std::cout << "number = " << number << std::endl;
        return number;
    }

    int main()
    {
        int ret = printNumber(10);
        std::cout << "return " << ret << std::endl;
        return 0;
    }

修改变量 - 可以通过set var命令来修改变量的值。

(gdb) break printNumber
(gdb) start
(gdb) print number
(gdb) set var number=100
(gdb) print number
(gdb) cont

在以上命令中,number的值从10修改为了1000。

调用函数 - 可以通过call命令来调用函数

(gdb) start
(gdb) printNumber(4)

修改函数返回值 - 可以用return命令重写函数的返回值

(gdb) start 
(gdb) break printNumber
(gdb) cont
(gdb) return 100
(gdb) cont

在以上命令中,原本printNumber函数的返回值应为10,但是通过return命令,返回值被修改为了100。

跳过或重复执行个别语句 - 可以用jump命令从当前的帧栈跳到任意一行

(gdb) start
(gdb) break printNumber
(gdb) cont
(gdb) jump 6
(gdb) cont

在以上命令中,原来应该执行的第5行被跳过,number的信息将不会被打印在屏幕上。
当程序运行到第6行后,也可以使用jump 5命令回到第5行,打印出number的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值