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\n",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

Linux下gdb调试(tui)_第1张图片

 

如果正在使用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\n",i,j);
    swap(&i,&j);
    printf("i:%d,j:%d\n",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安静地执行调用函数的每一行,不向我们展示细节。

Linux下gdb调试(tui)_第2张图片Linux下gdb调试(tui)_第3张图片

 

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恢复执行,直到恰好在函数返回之后为止。

Linux下gdb调试(tui)_第4张图片

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

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

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

6.4 使用until恢复程序执行

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

Linux下gdb调试(tui)_第5张图片

 

 

当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可以包含如下形式,但是必须是布尔值:

Linux下gdb调试(tui)_第6张图片Linux下gdb调试(tui)_第7张图片

 

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

(gdb) cond 3 i==3

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

(gdb) cond 3

8 断点命令列表

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

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

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

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

例如:

fibonacci.c

#include<stdio.h>
int fibonacci(int n);

int main(void)
{
    printf("Fibonacci(3) is %d\n",fibonacci(3));

    return 0;
}

int fibonacci(int n)
{
    if(n<=0||n==1)
        return 1;
    else
        return fibonacci(n-1)+fibonacci(n-2);
}

gdb调试:

Linux下gdb调试(tui)_第9张图片Linux下gdb调试(tui)_第10张图片Linux下gdb调试(tui)_第11张图片

 

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

Linux下gdb调试(tui)_第12张图片Linux下gdb调试(tui)_第13张图片

 

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

Linux下gdb调试(tui)_第14张图片

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

Linux下gdb调试(tui)_第15张图片Linux下gdb调试(tui)_第16张图片

 

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.\n",i);

    i=5;
    printf("i is %d.\n",i);

    return 0;
}

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

Linux下gdb调试(tui)_第17张图片

既然i已经在作用域中了,现在设置监视点并通知GDB继续执行程序。Linux下gdb调试(tui)_第18张图片Linux下gdb调试(tui)_第19张图片

 

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;
}

然后执行:

Linux下gdb调试(tui)_第20张图片

 

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

*pointer@number_of_elements

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

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

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

 

 

 

 

 

 

 

你可能感兴趣的:(linux)