《Linux – Linux基础》第7章 编译调试工具

7.1 GCC

7.1.1简介

GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。 而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。

GCC官网

表1 GCC所支持的后缀解释
后缀名 所对应的语言 后缀名 所对应的语言
.c C语言程序 .s/.S 汇编语言原始程序
.C/.cc/.cxx C++原始程序 .h 预处理文件(头文件)
.m Object-C原始程序 .o 目标文件
.i 已经编译预处理的C原始程序 .a/.so 编译后的库文件
.ii 已经编译预处理的C++原始程序

7.1.2编译流程解析

GCC编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和链接(Linking)

下面就具体来查看一下GCC是如何完成四个步骤的。首先,有以下test.c源代码示例程序如下。

#include <stdio.h>
int main(void)
{
    printf("Hello World!\n");
    return 0;
}

这个程序,一步到位的编译指令是:

$gcc test.c -o test

7.1.2.1预处理

在该阶段,对应的头文件(#define)和宏定义(#define、#ifdef)进行处理。在上述代码的预处理过程中,编译器将上述代码中的stdio.h编译进来,并且用户可以使用gcc的选项”-E”进行查看,该选项的作用是让gcc在预处理结束后停止编译过程。

注意:gcc指令的一般格式为:gcc [ 选项] 要编译的文件 [ 选项] [ 目标文件]
其中,目标文件可缺省,gcc默认生成可执行的文件,命为:编译文件.out

$gcc -E test.c -o test.i 或 gcc -E test.c

【注1】在此处,选项”-o”是指目标文件,”.i”文件为已经过预处理的C原始程序。
【注2】gcc -E test.c -o test.i表示将预处理文件保存到test.i中,而gcc -E test.c 会在终端显示预处理后的代码。

以下列出了test.i文件的部分内容:

# 1 "test.c"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"

…

# 943 "/usr/include/stdio.h" 3 4

# 9 "test.c" 2

int main(void)
{
    printf("Hello World!\n");

    return 0;

}

由此可见,gcc确实进行了预处理,它把”stdio.h”的内容插入到test.i文件中。test.i文件中存放着test.c经预处理之后的代码。

gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将stdio.h 文件中的内容插入到test.c中了。

7.1.2.2编译为汇编代码(Compilation)

接下来进行的是编译阶段,在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项仅编译到汇编语言,不进行汇编和链接,生成汇编代码。对生成的test.i文件编译,生成汇编代码。

$gcc -S test.i -o test.s

gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。
以下是编译处理后的代码,有兴趣的可以一看。

.file   "test.c"
    .section    .rodata
.LC0:
    .string "Hello World!"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

7.1.2.3汇编(Assembly)

汇编阶段是把编译阶段生成的”.s”文件转成目标文件,读者在此可使用选项”-c”就可看到汇编代码已转化为”.o”的二进制目标代码了。对于上一小节中生成的汇编代码文件test.s,gcc汇编器负责将其编译为目标文件,如下:

$gcc -c test.s -o test.o

7.1.2.4链接(Linking)

在成功编译之后,就进入了链接阶段。在这里涉及到一个重要的概念:函数库。

读者可以重新查看这个小程序,在这个程序中并没有定义”printf”的函数实现,且在预编译中包含进的”stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现”printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf”了,而这也就是链接的作用。

函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。Gcc在编译时默认使用动态库。

完成了链接之后,gcc就可以生成可执行文件,gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。

对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test。

$gcc test.o -o test

在命令行窗口中,执行./test, 让它说HelloWorld吧!

7.1.3多个程序文件的编译

通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由test1.c和 test2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序test,可以使用下面这条命令:

$gcc test1.c test2.c -o test

如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

$gcc -c test1.c -o test1.o
$gcc -c test2.c -o test2.o
$gcc test1.o test2.o -o test

7.1.4检错

$gcc -pedantic illcode.c -o illcode

-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合 ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息。

$gcc -Wall illcode.c -o illcode

GCC给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:

$gcc -Werror test.c -o test

7.1.5库文件连接

开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助许多函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(so、或lib、dll)的集合。虽然Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下;Windows所使用的库文件主要放在Visual Stido的目录下的include和lib,以及系统文件夹下。但也有的时候,我们要用的库不再这些目录下,所以GCC在编译时必须用自己的办法来查找所需要的头文件和库文件。

例如我们的程序test.c是在linux上使用c连接mysql,这个时候我们需要去mysql官网下载MySQL Connectors的C库,下载下来解压之后,有一个include文件夹,里面包含mysql connectors的头文件,还有一个lib文件夹,里面包含二进制so文件libmysqlclient.so。

其中inclulde文件夹的路径是/usr/dev/mysql/include,lib文件夹是/usr/dev/mysql/lib。

7.1.5.1编译成可执行文件

首先我们要进行编译test.c为目标文件,这个时候需要执行

$gcc -c -I/usr/dev/mysql/include test.c -o test.o

7.1.5.2链接

最后我们把所有目标文件链接成可执行文件:

$gcc -L/usr/dev/mysql/lib -lmysqlclient test.o -o test

Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。

7.1.5.3强制链接时使用静态链接库

默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。

在/usr/dev/mysql/lib目录下有链接时所需要的库文件libmysqlclient.so和libmysqlclient.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:

$gcc -L/usr/dev/mysql/lib -static -lmysqlclient test.o -o test

【注1】静态库链接时搜索路径顺序:

1.ld会去找GCC命令中的参数-L
2.再找gcc的环境变量LIBRARY_PATH
3.再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的

【注2】动态链接时、执行时搜索路径顺序:

1.编译目标代码时指定的动态库搜索路径
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径
4.默认的动态库搜索路径/lib
5.默认的动态库搜索路径/usr/lib

【注3】有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

7.1.6编译选项分析

(1)总体选项

gcc有超过100个的可用选项,主要包括总体选项、告警和出错选项、优化选项和体系结构相关选项。以下对每一类中最常用的选项进行讲解。

表2编译选项分析
选项 含义
-c 仅执行编译操作,不进行连接操作
-S 将C代码转换为汇编代码
-E 仅执行编译预处理
-o 指定生成的输出文件的文件名
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 添加头文件(.h)搜索路径
-L dir 添加库文件搜索路径
-g 在可执行程序中包含标准调试信息

对于“-c”、“-E”、“-o”、“-S”选项在前一小节中已经讲解了其使用方法,在此主要讲解另外两个非常常用的库依赖选项“-I dir”和“-L dir”。

  • “-I dir”

正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,gcc就会到相应的位置查找对应的目录。

比如在“/root/workplace/gcc”下有两个文件:

/*hello.c*/
#include<my.h>

int main()
{
    printf(“Hello!!n”);

    return 0;
}
/*my.h*/
#include<stdio.h>

这样,就可在gcc命令行中加入“-I”选项:

$gcc hello.c -I /root/workplace/gcc/ -o hello

这样,gcc就能够执行出正确结果。

【注】在include语句中,“<>”表示在标准路径中搜索头文件,““””表示在本目录中搜索。故在上例中,可把hello1.c的“#include”改为“#include “my.h””,就不需要加上“-I”选项了。

  • “-L dir”

选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录。例如有程序hello_sq.c需要用到目录“/root/workplace/Gcc/lib”下的一个动态库libsunq.so,则只需键入如下命令即可:

$gcc hello_sq.c -L /root/workplace/gcc/lib –lsunq –o hello_sq

需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。

另外值得详细解释一下的是“-l”选项,它指示gcc去连接库文件libsunq.so。由于在Linux下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库文件名时可以省去lib三个字母。也就是说gcc在对”-lsunq”进行处理时,会自动去链接名为libsunq.so的文件。

(2)库选项

GCC库选项如下所示。

表3 gcc库选项分析
选项 含义
-static 进行静态编译,即链接静态库,禁止使用动态库
-shared 1.可以使用动态库文件2.进行动态编译,尽可能地动态链接库,只有当没有动态库时才会链接同名的静态库(默认选项,即可省略)
-L dir 在库路径的搜索路径列表中添加dir目录
-lname 链接库为libname.a(静态库)或者libname.so(动态库)的库文件。若两个库都存在,则根据编译方式(-static还是-shared)而进行链接
-fPIC(或-fpic) 生成使用相对地址的位置无关的目标代码(Position Independent Code)。然后使用gcc的-static选项从该PIC目标文件生成动态库文件

关于动态库和静态库会在后文讲解。

(3)告警和出错选项省略

告警和出错选项如下图所示:

表4告警和出错选项
选项 含义
-ansi 支持符合ANSI标准的C程序
-pedantic 允许发出ANSI C标准所列的全部警告信息
-pedantic-error 允许发出ANSI C标准所列的全部错误信息
-w 关闭所有告警
-Wall 允许发出gcc提供的所有有用的报警信息
-werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程

下面结合实例对这几个告警和出错选项进行简单的讲解。如有以下程序段:

#include<stdio.h>

void main()
{
    long long tmp = 1;

    printf("This is a bad code!n");

    return 0;
}

这是一个很糟糕的程序,读者可以考虑一下有哪些问题?

  • “-ansi”

该选项强制gcc生成标准语法所要求的告警信息,尽管这还并不能保证所有没有警告的程序都是符合ANSI C标准的。运行结果如下所示:

$gcc -ansi warning.c -o warning
warning.c: In function ‘main’:
warning.c:9:5: warning: ‘return’ with a value, in function returning void [enabled by default]
     return 0;
     ^

上文的警告标示,在无返回值的函数中,”return”没有返回值。

  • “-pedantic”

允许发出ANSI C标准所列的全部警告信息,同样也保证所有没有警告的程序都是符合ANSI C标准的。其运行结果如下所示:

$gcc -pedantic warning.c -o warning
warning.c:3:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
 void main()
      ^
warning.c: In function ‘main’:
warning.c:5:10: warning: ISO C90 does not support ‘long long’ [-Wlong-long]
     long long tmp = 1;
          ^
warning.c:9:5: warning: ‘return’ with a value, in function returning void [enabled by default]
     return 0;
     ^
  • “-Wall”

允许发出Gcc能够提供的所有有用的报警信息。该选项的运行结果如下所示:

$gcc -Wall warning.c -o warning
warning.c:3:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
 void main()
      ^
warning.c: In function ‘main’:
warning.c:9:5: warning: ‘return’ with a value, in function returning void [enabled by default]
     return 0;
     ^
warning.c:5:15: warning: unused variable ‘tmp’ [-Wunused-variable]
     long long tmp = 1;
               ^

使用“-Wall”选项找出了未使用的变量tmp,但它并没有找出无效数据类型的错误。

(4)优化选项
gcc可以对代码进行优化,它通过编译选项“-On”来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的Gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

不同的优化级别对应不同的优化处理工作。如使用优化选项“-O”主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项“-O3”则还包括循环展开和其他一些与处理器特性相关的优化工作。

虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。所以笔者建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

(5)体系结构相关选项
gcc的体系结构相关选项如下表所示。

表5 Gcc体系结构相关选项列表
选项 含义
-mcpu=type 针对不同的CPU使用相应的CPU指令。可选择的type有i386、i486、pentium及i686等
-mieee-fp 使用IEEE标准进行浮点数的比较
-mno-ieee-fp 不使用IEEE标准进行浮点数的比较
-msoft-float 输出包含浮点库调用的目标代码
-mshort 把int类型作为16位处理,相当于short int
-mrtd 强行将函数参数个数固定的函数用ret NUM返回,节省调用函数的一条指令

这些体系结构相关选项在嵌入式的设计中会有较多的应用,读者需根据不同体系结构将对应的选项进行组合处理。

7.2 GDB

GDB官网:http://www.gnu.org/software/gdb/

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具,GDB主要可帮助工程师完成下面4个方面的功能:

 启动程序,可以按照工程师自定义的要求随心所欲的运行程序。
 让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式。
 当程序被停住时,可以检查此时程序中所发生的事,并追索上文。
 动态地改变程序的执行环境。

我们直接来看实例,通过实际操作来学习GDB的操作。

/*test.c*/
#include <stdio.h>

int sum(int m);

int main()
{
    int i, n = 0;
    sum(50);

    for(i = 1; i<= 50; i++)
    {
        n += i;
    }

    printf("The sum of 1-50 is %d \n", n );

}

int sum(int m)
{
    int i, n = 0;

    for (i = 1; i <= m; i++)
    {
        n += i;
        printf("The sum of 1-m is %d\n", n);
    }

}

在保存退出后首先使用 gcc 对test.c 进行编译,注意一定要加上选项"-g",这样编译出的可执行代码中才包含调试信息,否则之后gdb 无法载入该可执行文件。

$ gcc -g test.c -o test

虽然这段程序没有错误,但调试完全正确的程序可以更加了解gdb 的使用流程。接下来就启动gdb 进行调试。注意,gdb 进行调试的是可执行文件,而不是如".c"的源代码,因此,需要先通过gcc 编译生成可执行文件才能用gdb 进行调试。

$ gdb test
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...done.
(gdb)

可以看出,在gdb 的启动画面中指出了gdb 的版本号、使用的库文件等信息,接下来就进入了由"(gdb)"开头的命令行界面了。

(1)查看文件

在 gdb 中键入"l"(list)就可以查看所载入的文件,如下所示。

(gdb) l

1   /*test.c*/
2   #include <stdio.h>
3   
4   int sum(int m);
5   
6   int main()
7   {
8       int i, n = 0;
9       sum(50);
10  

(gdb) l

11      for(i = 1; i<= 50; i++)
12      {
13          n += i;
14      }
15  
16      printf("The sum of 1-50 is %d \n", n );
17  
18  }
19  
20  int sum(int m)

(gdb) l

21  {
22      int i, n = 0;
23  
24      for (i = 1; i <= m; i++)
25      {
26          n += i;
27          printf("The sum of 1-m is %d\n", n);
28      }
29  
30  }

(gdb) l

Line number 31 out of range; test.c has 30 lines.

【注】在gdb的命令中都可以使用缩略形式的命令,如“I”代表“list”,“b”代表“breakpoint”,“p”代表“print”等,对着可以使用“help”查看。
可以看出,gdb 列出的源代码中明确地给出了对应的行号,这样就可以大大地方便代码的定位。

(2)设置断点

设置断点是调试程序中一个非常重要的手段,它可以使程序运行到一定位置时暂停。因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出代码的症结所在。
在 gdb 中设置断点非常简单,只需在"b"后加入对应的行号即可(这是常用的方式,另外还有其他方式设置断点),如下所示:

(gdb) b 11

Breakpoint 1 at 0x400587: file test.c, line 24.

要注意的是,在gdb 中利用行号设置断点是指代码运行到对应行之前将其停止,如上例中,代码运行到第24行之前暂停(并没有运行第24行)。

(3)查看断点情况
在设置完断点之后,用户可以键入"info b"来查看设置断点情况,在gdb 中可以设置多个断点。
(gdb) info b

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400587 in main at test.c:24

用户在断点键入"backrace"(只输入"bt"即可)可以查到调用函数(堆栈)的情况,这个功能在程序调试之中使用非常广泛,经常用于排除错误或者监视调用堆栈的情况。

(4)运行代码
接下来就可运行代码了,gdb 默认从首行开始运行代码,键入"r"(run)即可(若想从程序中指定行开始运行,可在r 后面加上行号)。
(gdb) r

Starting program: /mnt/hgfs/share/test 
Breakpoint 1, sum (m=50) at test.c:24
24      for (i = 1; i <= m; i++)

可以看到,程序运行到断点处就停止了。

(5)查看变量值
在程序停止运行之后,程序员所要做的工作是查看断点处的相关变量值。在 gdb 中键入"p"+变量值即可,如下所示:
(gdb) p n

$1 = 0

(gdb) p i

$2 = 0

在此处,程序是在断点设置的对应行之前停止的,那么在此时,并没有把"i"的数值赋为零,而只是一个随机的数字,随机为0而已。但变量"n"是在第8行赋值的,故在此时已经为零。

(6)观察变量
在某一处循环之后,程序员希望能观察某一个变量的变化情况,这是就需要输入命令“watch”来查看变量的值,如下所示。
(gdb) watch n

Hardware watchpoint 2: n

可以看到,GDB对n设置了观察点。

(7)单步运行
单步运行可以使用命令"n"(next)或"s"(step),它们之间的区别在于:若有函数调用的时候,"s"会进入该函数而"n"不会进入该函数。因此,"s"就类似于Uisual 等工具中的"step in","n"类似与Uisual等工具中的"step over"。它们的使用如下所示:

(gdb) n

26          n += i;

(gdb) c

Continuing.
Hardware watchpoint 2: n

Old value = 0
New value = 1
sum (m=50) at test.c:27
27          printf("The sum of 1-m is %d\n", n);

可以看到程序的但不运行结果,当n的值发生变化能自动显示n的值。

【注】我们在回复运行之后,但是还是会停在24行,这是一个循环的程序,每次执行完一遍循环后再循环没有结束前会一直停留在循环里,直道循环结束,再使用“c”才会仅需运行完剩余的代码。

(8)恢复程序运行
在查看完所需变量及堆栈情况后,就可以使用命令"c"(continue)恢复程序的正常运行了。这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。以下是之前使用"n"命令恢复后的执行结果:

(gdb) c

Continuing.
The sum of 1-50 is 1275 
[Inferior 1 (process 3658) exited with code 031]

可以看出,程序在运行完后,之后程序处于"停止状态"

(9)恢复程序运行
退出GDB只需要输入指令“q”即可。
(gdb) q

Related posts

Leave a Comment