zoukankan      html  css  js  c++  java
  • Linux下gdb调试(tui)

    1 处于TUI模式的GDB

    为了以TUI模式运行GDB,可以在调用GDB时在命令行上指定-tui选项,或者处于非TUI模式时在GDB中使用Ctrl+X+A组合键。如果当前处于TUI模式,后一种命令方式就会使你离开TUI模式。

    在TUI模式中,GDB窗口划分为两个子窗口——一个用于输入GDB命令,而另一个用于查看源代码。

    例如:

    源代码为ins.c

    #include <stdio.h>
    
    int x[10],
            y[10], 
            num_inputs,                                     
            num_y = 0;
    
    
    void get_args(int ac,char **av){
            int i;
            num_inputs = ac - 1;
            for(i = 0;i < num_inputs;++i)
                    x[i] = atoi(av[i + 1]);
    }
    
    void scoot_over(int jj){
            int k;
            for(k = num_y;k > jj;--k)
                    y[k] = y[k - 1];
    }
    
    void insert(int new_y){
            int j;
            //
            if(num_y==0){
                    y[0] = new_y;
                    return;
            }
    
            for(j = 0;j < num_y;++j){
                    if(new_y < y[j]){
                            scoot_over(j);
                            y[j] = new_y;
                            return;
                    }
            }
            y[num_y]=new_y;
    }
    
    void process_data(){
            for(num_y = 0;num_y < num_inputs;++num_y)
                    insert(x[num_y]);
    }
    
    void print_results(){
            int i;
            for(i = 0;i < num_inputs;++i)
                    printf("%d
    ",y[i]);
    }
    
    int main(int argc,char **argv){
            get_args(argc,argv);
            process_data();
            print_results();
    }

    编译后:

    gcc -g3 -Wall -o insert_sort ins.c

    注意,在GCC中可以用-g选项让编译器将符号表(即对应于程序的变量和代码行的内存地址列表)保存在生成的可执行文件(这里是insert_sort)中。这是一个绝对必要的步骤,这样才能在调试会话过程中引用源代码中的变量名和行号。

    使用GDB调试insert_sort

    如果正在使用GDB但没有使用TUI模式,则位于下方的子窗口确切地显示你将看到的内容。此处,该子窗口显示如下内容。

    1)发出一条break命令,在当前源文件第12行处设置断点。

    2)执行run命令运行程序,并且向该程序传递命令行参数12、5、6.在此之后,调试器在指定的断点处停止执行。GDB会提醒用户断点位于ins.c的第12行,并且通知该源代码行的机器代码驻留在内存地址0xbffff484中。

    3)发出next命令,步进到下一个代码行,即第13行。

    2 主要的调试操作

    退出GDB:quit或者Ctrl+d

    执行程序:run

    2.1 单步调试源代码

    安排程序的执行在某个地方暂停,以便检查变量的值,从而得到关于程序错误所在位置的线索。

    • 断点

    调试工具会在指定断点处暂停程序的执行。在GDB中是通过break命令及其行号完成的。

    普通断点和条件断点

    (gdb) break 30

    Breakpoint 1 at 0x80483fc: file  ins.c,line 30.

    (gdb) condition 1 num_y==1

    第一个命令在第30行放置一个断点。这里的第二个命令condition 1 num_y==1使得该断点称为有条件的断点:只有当满足条件num_y==1时,GDB才会暂停程序的执行。

    注意,与接受行号(或函数名)的break命令不同,condition接受断点号。总是可以用命令info  break来查询要查找的断点的编号。

    用break if可以将break和condition命令组合成一个步骤,如下所示:

    (gdb) break 30 if num_y==1

    • 单步调试

    前面提到过,在GDB中next命令会让GDB执行下一行,然后暂停。step命令的作用与此类型,只是函数调用时step命令会进入函数,而next导致程序执行的暂停出现在下次调用函数时。

    • 恢复操作

    在GDB中,continue命令通知调试器恢复执行并继续,直到遇到断点为止。

    • 临时断点

    在GDB中,tbreak命令与break相似,但是这一命令设置的断点的有效期限只到首次到达指定行时为止。

    2.2 检查变量

    (gdb) print j

    $1=1

    对GDB的这一查询的输出表明j的值为1.$1标签意味着这是你要求GDB输出的第一个值。($1、$2、$3等表示的值统称为调试会话的值历史。)

    2.3 在GDB中设置监视点以应对变量值的改变

    监视点结合了断点和变量检查的概念。最基本形式的监视点通知调试器,每当指定变量的值发生变化时都暂停程序的执行

    (gdb) watch z

    当运行程序时,每当z的值发生变化,GDB都会暂停执行。

    更好的方法是,可以基于条件表达式来设置监视点。例如,查找执行期间z 的值大于28的第一个位置

    (gdb) watch(z>28)

    2.4 上下移动调用栈

    在函数调用期间,与调用关联的运行时信息存储在称为栈帧的内存区域中。帧中包含函数的局部变量的值、其形参,以及调用该函数的记录。每次发生函数调用时,都会创建一个新帧,并将其推导一个系统维护的栈上;栈最上方的帧表示当前正在执行的函数,当函数退出时,这个帧被弹出栈,并且被释放。

    在GDB中可用用如下命令查看以前的帧:

    (gdb) frame 1

    当执行GDB的frame命令时,当前正在执行的函数的帧被编号为0,其父帧(即该函数的调用者的栈帧)被编号为1,父帧的父帧被编号为2,以此类推。GDB的up命令将你带到调用栈中的下一个父帧(例如,从帧0到帧1),down则引向相反方向。

    显示整个栈:backtrace

    浏览以前的GDB命令:上一个Ctrl+P、下一个Ctrl+N

    3 联机帮助

    在GDB中,可以通过help命令访问文档。例如:

    (gdb) help breakpoints

    4 启动文件的使用

    在重新编译代码时,最好不要退出GDB。这样,你的断点和建立的其他各种动作都会保留。要是退出GDB,就不得不再次重复键入这些内存。

    然而,在完成调试前可能需要退出GDB。如果你要离开一段时间,而且不能保持登录在计算机中,则需要退出GDB。为了不丢失它们,可以将断点和设置的其他命令放在一个GDB启动文件中,然后每次启动GDB时都会自动加载它们。

    GDB的启动文件默认名为.gdbinit。

    在调用GDB时可以指定启动文件。例如,

    $ gdb -command=z x

    表示要在可执行文件x上运行GDB,首先要从文件z中读取命令。

    5 gdb暂停机制

    有3种方式可以通知GDB暂停程序的执行。

    1)断点:通知GDB在程序中的特定位置暂停执行。

    2)监视点:通知GDB当特定内存位置的值发生变化时暂停执行

    3)捕获点:通知GDB当特定事件发生时暂停执行。

    GDB中使用delete命令删除断点:

    (gdb) help delete

    5.1 断点概述

    GDB中关于断点“位置”的含义非常灵活,它可以指各种源代码行、代码地址、源代码文件中的行号或者函数的入口等。

    例如:

    break 35

    这里指GDB执行到第34行,但是第35行还没有执行。断点显示的是将要执行的代码行。

    5.2 跟踪断点

    程序员创建的每个断点(包括断点、监视点和捕获点)都被标识为从1开始的唯一整数标识符。这个标识符用来执行该断点上的各种操作。

    5.2.1 GDB中的断点列表

    当创建断点时,GDB会告知你分配给该断点的编号。例如,

    (gdb) break main
    Breakpoint 1 at 0x8048569: file ins.c, line 52.

    被分配的编号是1.如果忘记了分配给哪个断点的编号是什么可以使用info  breakpoints命令来提示。

    (gdb) info breakpoints
    Num Type Disp Enb Address What
    1 breakpoint keep y 0x08048569 in main at ins.c:52
    2 breakpoint keep y 0x0804847e in insert at ins.c:25
    3 breakpoint keep y 0x08048491 in insert at ins.c:30

    如果想要删除断点,可以通过delete命令以及断点标识符,例如

    (gdb) delete 1 2 3

    5.3 设置断点

    5.3.1 在GDB中设置断点

    1)break function

    在函数function()的入口处设置断点。

    (gdb) break main

    在main函数的入口处设置断点

    2)break line_number

    在当前活动源代码文件的line_number处设置断点。

    (gdb) break 35

    在当前显示的源文件的35行设置了一个断点。

    3)break filename:line_number

    在源代码文件filename的line_number处设置断点。如果filename不在当前工作目录中,则可以给出相对路径名或者完全路径名来帮助GDB查找该文件,例如:

    (gdb) break source/bed.c:35

    4)break filename:function

    在文件filename中的函数function()的入口处设置断点。重载函数或者使用同名静态函数的程序可能需要使用这种形式,例如:

    (gdb) break bed.c:parseArguments

    正如我们看到的,当设置一个断点时,该断点的有效性会持续到删除、禁用或者退出GDB时。然而,临时断点时首次到达后就会被自动删除的断点。临时断点使用tbreak命令设置。

    C++允许重载函数,使用break function会在所有具有相同名称的函数上设置断点。如果要在函数的某个特定实例上设置断点,需要没有歧义,则使用源文件中的行号。

    GDB实际设置断点的位置可能和我们请求将断点放置的位置不同。
    比如下列代码:
    int main(void)  
    {  
        int i;  
        i = 3;  
      
       return 0;  
    }  

    如果我们尝试在函数main入口处设置断点,断点实际会被设置在第4行。因为GDB会认为第三行的机器码对我们的调试目的来说没有用处。

    5.4 多文件中的断点设置

    例如:

    main.c

    #include<stdio.h>
    void swap(int *a,int *b);
    
    int main()
    {
        int i=3;
        int j=5;
        printf("i:%d,j:%d
    ",i,j);
        swap(&i,&j);
        printf("i:%d,j:%d
    ",i,j);
    
        return 0;
    }

    swap.c

    void swap(int *a,int *b)
    {
        int c=*a;
        *a=*b;
        *b=c;
    }

    在main上设置断点:

    (gdb) break main
    Breakpoint 1 at 0x80483cd: file main.c, line 6.

    在swap上设置断点的方法:

    (gdb) break swapper.c:1
    Breakpoint 2 at 0x804843a: file swapper.c, line 1.
    (gdb) break swapper.c:swap
    Note: breakpoint 2 also set at pc 0x804843a.
    Breakpoint 3 at 0x804843a: file swapper.c, line 3.
    (gdb) break swap
    Note: breakpoints 2 and 3 also set at pc 0x804843a.
    Breakpoint 4 at 0x804843a: file swapper.c, line 3.

    每个GDB都有一个焦点,可以将它看作当前“活动”文件。这意味着除非对命令做了限定,否则都是在具有GDB的焦点的文件上执行命令。默认情况下,具有GDB的初始焦点的文件是包含main()函数的文件,但是当发生如下任一动作时,焦点会转移到不同的文件上。

    1)向不同的源文件应用list命令

    2)进入位于不同的源代码文件中的代码

    3)当在不同的源代码文件中执行代码时GDB遇到断点

    例如:

    (gdb) break 6
    Note: breakpoint 1 also set at pc 0x80483cd.
    Breakpoint 5 at 0x80483cd: file main.c, line 6.

    当前焦点是main.c,所以在main.c中设置。

    (gdb) list swap
    (gdb) break 6
    Breakpoint 6 at 0x8048454: file swapper.c, line 6.

    现在的焦点是swapper.c。

    (gdb) info breakpoints
    Num Type Disp Enb Address What
    1 breakpoint keep y 0x080483cd in main at main.c:6
    2 breakpoint keep y 0x0804843a in swap at swapper.c:1
    3 breakpoint keep y 0x0804843a in swap at swapper.c:3
    4 breakpoint keep y 0x0804843a in swap at swapper.c:3
    5 breakpoint keep y 0x080483cd in main at main.c:6
    6 breakpoint keep y 0x08048454 in swap at swapper.c:6

    5.5 断点的持久性

    如果在修改和重新编译代码时没有退出GDB,那么在下次执行GDB的run命令时,GDB会感知到代码已修改,并自动重新加载新版本。

    5.6 删除和禁用断点

    在调试会话期间,有时会发现有的断点不再使用。如果确认不再需要断点,可以删除它。也许你不想删除它,而是打算将它虚置起来,这称为禁用断点。如果以后再次需要,可以重新启用断点。

    2.6.1 在GDB中删除断点

    如果确认不再需要当前断点,那么可以删除该断点。

    delete命令用来基于标识符删除断点,clear命令使用和创建断点的语法删除相同。

    1)delete breakpointer_list

    删除断点使用数值标识符。断点可以是一个数字,比如delete 2 删除第2个断点;也可以是数字列表,不然delete 2 4 删除第二个和第四个断点。

    2)delete

    删除所有断点。

    3)clear

    清除GDB将执行的下一个指令处的断点。这种方法适用于要删除GDB已经到达的断点额情况。

    4)clear function、clear filename:function、clear line_number和clear filename:line_number

    2.6.2 在GDB中禁用断点

    每个断点都可以禁用和启用。只有遇到启用的断点时,才会暂停程序的执行;它会忽略禁用的断点。

    为什么要禁用断点呢?在调试会话期间,会遇到大量断点。对于经常重复的循环结构或函数,这种情况使得调试极不方便。如果要保留断点以便以后使用,暂时又不希望GDB停止执行,可以禁用它们,在以后需要时再启用。

    使用disable breakpoint-list命令禁用断点,使用enable breakpoint-list命令启用断点。

    例如,

    (gdb) disable 3

    将禁用第三个断点

    (gdb) enable 1 5

    将启用第一个和第五个断点。

    不带任何参数地执行disable命令将禁用所有现有断点。类似的,不带任何参数的enable命令将启用所有断点。

    还有一个enable once命令,在得到下次引起GDB暂停执行后被禁用。语法为:

    enable once breakpoint-list

    例如,enable once 3 会使得断点3 在下次导致GDB停止程序的执行后被禁用。这个命令与tbreak命令非常类似,但是当遇到断点时,它是禁用断点,而不是删除断点。

    2.6.3 浏览断点属性

    info breakpoints命令(简写 i b)来获得设置的所有断点的清单,以及它们的属性。

    例如:

    (gdb) info breakpoints
    Num Type Disp Enb Address What
    1 breakpoint keep y 0x080483cd in main at main.c:6
    2 breakpoint keep y 0x0804843a in swap at swapper.c:1
    3 breakpoint keep y 0x0804843a in swap at swapper.c:3
    4 breakpoint keep y 0x0804843a in swap at swapper.c:3
    5 breakpoint keep y 0x080483cd in main at main.c:6
    6 breakpoint keep y 0x08048454 in swap at swapper.c:6

    7 hw watchpoint keep y                         counter

    让我们分析info breakpoints的这一输出:

    1)标识符(num):断点的唯一标识符

    2)类型(type):这个字段指出该断点是断点、监视点还是捕获点

    3)部署(disp):每个断点都有一个部署,指示断点下次引起GDB暂停程序的执行后该断点上会发生什么事情。

    保持(keep),下次到达断点后不改变断点

    删除(del),下次到达断点后删除断点,临时断点(tbreak设置)

    禁用(dis),下次到达后会禁用断点,使用enable once命令设置的

    4)启用状态(enb):这个字段说明断点当前是启用还是禁用的

    5)地址(Address):这是内存中设置断点的位置。

    6)位置(what):what字段显示了断点所在的位置的行号和文件名

    6 恢复执行

    恢复执行的方法有3类。第一类是使用step和next“单步”调试程序,仅执行代码的下一行然后再次暂停。第二类由使用continue组成,使GDB无条件地恢复程序的执行,直到遇到另一个断点或程序结束。最后一类方法涉及条件:用finish或until命令恢复。在这种情况下,GDB会恢复执行;程序继续运行直到遇到某个预先确定的条件(比如,到达函数的末尾),到达另一个断点,或者程序完成。

    6.1 使用step和next单步调试

    一旦GDB在断点处停止,可以使用next(简写n)和step(简写s)命令来单步调试代码。

    这两个命令的不同之处在于它们如何处理函数调用:next执行函数,不会在其中暂停,然后在调用之后的第一条语句处暂停。而step在函数中的第一个语句处暂停。step命令会进入调用的函数,这称为单步进入函数。而next永远不会离开main()。这是两个命令的主要区别。next将函数调用看做一行代码,并在一个操作中执行整个函数,这称为单步越过函数。

    然而,似乎next越过调用的函数主体,但是它并未真的单步“越过”任何内容。GDB安静地执行调用函数的每一行,不向我们展示细节。

    6.2 使用continue恢复程序执行

    第二种恢复执行的方法是使用continue命令,简写为c。这个命令使GDB恢复程序的执行,直到触发断点或者程序结束。

    continue命令可以接受一个可选的整数参数n。这个数字要求GDB忽略下面n个断点。例如,continue 3让GDB恢复程序执行,并忽略接下来的3个断点。

    6.3 使用finish恢复程序执行

    一旦触发了断点,就使用next和step命令逐行执行程序。有时这是一个痛苦的过程。

    有时使用step进入的调用的函数,查看了几个变量的信息,如果没有兴趣单步调试其余部分,想返回到单步进入被调用函数之前GDB所在的调用函数。然而,如果要做的只是跳过函数的其余部分,那么再设置一个无关断点并使用continue似乎比较浪费。这是可以使用finish命令。

    finish命令(简写为fin)指示GDB恢复执行,直到恰好在当前栈帧完成之后为止。也就是说,这意味着如果你在一个不是main()的函数中,finish命令会导致GDB恢复执行,直到恰好在函数返回之后为止。

    虽然可以键入next 3 而不是finish,但是后者更容易。

    finish的另一个常见用途是当不小心单步进入原本希望单步越过的函数时(换言之,当需要使用next时使用了step)。在这种情况下,使用finish可以讲你正好放回到使用next会位于的位置。

    如果在一个递归函数中,finish只会将你带到递归的上一层。

    6.4 使用until恢复程序执行

    finish命令在不进一步在函数中暂停(除了中间断点)的情况想完成当前函数的执行。类似地,until命令(简写为u)通常用来在不进一步在循环中暂停(除了循环中的中间断点)的情况下完成正在执行的循环。

    当i很大是,使用next需要多次。而使用until会执行循环的其余部分,让GDB在循环后面的第一行代码处暂停。当然,如果GDB在离开循环前遇到一个断点,它就会在那里暂停。

    7 条件断点

    只要启用了断点,调试器就总是在该断点处停止。然而,有时有必要告诉调试器只有当符合某种添条件时才在断点处停止。

    7.1 设置条件断点

    break break-args if (condition)

    其中brea-args是可以传递给break以指定断点位置的任何参数。括着condition的圆括号是可选的。

    例如:

    break main if argc>1

    例如,在循环中,满足一定次数之后发生中断:

    break if (i==7000) 

    条件中断中的condition可以包含如下形式,但是必须是布尔值:

    可以对正常断点设置条件以将它转变为条件断点。例如,如果设置了断点3为无条件断点,但是希望添加添加i==3,只有键入:

    (gdb) cond 3 i==3

    如果以后要删除条件,但是保持该断点,只要键入:

    (gdb) cond 3

    8 断点命令列表

    当GDB遇到断点时,几乎总是要查看某个变量。如果反复遇到同一个断点,将反复查看相同的变量。让GDB在每次到达某个断点时自动执行一组命令,从而自动完成这一过程。

    事实上,使用“断点命令列表”就可以做这件事。

    使用commands命令设置命令列表。

    其中breakpoint-number是要将命令添加到其上的断点的标识符,commands是用行分隔的任何有效GDB命令列表。逐条输入命令,然后键入end表示输入命令完毕。从那以后,每当GDB在这个断点处中断时,它都会执行输入的任何命令。

    例如:

    fibonacci.c

    #include<stdio.h>
    int fibonacci(int n);
    
    int main(void)
    {
        printf("Fibonacci(3) is %d
    ",fibonacci(3));
    
        return 0;
    }
    
    int fibonacci(int n)
    {
        if(n<=0||n==1)
            return 1;
        else
            return fibonacci(n-1)+fibonacci(n-2);
    }

    gdb调试:

    如果觉得输出太冗长了,可以使用silent命令使GDB更安静地触发断点。

    现在输出结果不错,但是每次要键入continue,可以修改如下:

    也可以使用define定义宏来代替:

    9 监视点

    监视点是一种特殊类型的断点,它类似于正常断点,是要求GDB暂停程序执行的指令。监视点是指示GDB每当某个表达式改变了值就暂停执行的指令。

    (gdb) watch i

    它会使得每当i改变值时GDB就暂停。

    9.1 设置监视点

    当变量var存在且在作用域中时,可以通过使用如下命令来设置监视点

    watch  var

    该命令会导致每当var改变值时GDB都中断。

    例如:

    #include<stdio.h>
    int i=0;
    
    int main()
    {
        i=3;
        printf("i is %d.
    ",i);
    
        i=5;
        printf("i is %d.
    ",i);
    
        return 0;
    }

    我们每当i大于4时得到通知。因此在main()的入口处放一个断点,以便让i在作用域中,并设置一个监视点以指出i何时大于4.不能在i上设置监视点,因为在程序运行之前,i不存在。因此必须现在main()上设置断点,然后在i上设置监视点

    既然i已经在作用域中了,现在设置监视点并通知GDB继续执行程序。

    10 显示数值中的值

    比如声明数组:

    int x[25];

    方法是通过键入:

    (gdb) p x

    但是,如果是动态创建的数组会是什么样呢?比如:

    int *x

    ...

    x=(int *)malloc(25*sizeof(int));

    如果要在GDB中输出数组,就不能输入:

    (gdb) p x

    可以简单打印数组地址。或者键入:

    (gdb) p *x

    这样只会输出数组的一个元素——x[0]。仍然可以像在命令 p x[5]中那样输入单个元素,但是不能简单地在x上使用print命令输出整个数组。

    1)在GDB的解决方案

    在GDB中,可以通过创建一个人工数组来解决这个问题。如下:

    #include<stdio.h>
    #include<stdlib.h>
    int *x;
    void main()
    {
        x=(int*)malloc(25*sizeof(int));
        x[3]=12;
    }

    然后执行:

    我们可以看到,一般形式为:

    *pointer@number_of_elements

    GDB还允许在适当的时候使用类型强制转换,比如:

    (gdb) p (int [25])*x

    $2={0,0,0,12,0 <repeats 21 times>}

     

     

  • 相关阅读:
    BUAA OO 2019 第三单元作业总结
    OpenJML入门
    BUAA OO 2019 第二单元作业总结
    BUAA OO 2019 第一单元作业总结
    Spring MVC原理
    Spring AOP原理
    Spring DI原理
    Spring IOC原理分析
    观察者模式
    装饰模式
  • 原文地址:https://www.cnblogs.com/wuchanming/p/4494141.html
Copyright © 2011-2022 走看看