【Linux C】GCC编译 && GDB调试 从入门到放弃 (gcc调试选项详解、gdb调试、条件断点、远程调试、脚本化调试)(一)

简介: 阅读本文可能需要一些基础,比如:C语言基础、Linux基础操作、vim、防火墙等。篇幅有限,本文讲的“比较浅显”。通过本文你将学会:gcc编译gdb调试

阅读本文可能需要一些基础,比如:C语言基础、Linux基础操作、vim、防火墙等。篇幅有限,本文讲的“比较浅显”。

通过本文你将学会:

  • gcc编译
  • gdb调试

一、使用GCC编译C程序

1.1 准备工作

1.2 编译源代码

1.3 gcc常用选项

1.31 只生成目标文件:-c

1.32 指定生成可执行文件名称:-o

1.33 代码优化:-O

1.34 显示警告信息:-Wall

1.35 将警告视为错误:-Werror

1.36 指定C语言标准:-std

1.37 添加包含文件目录:-I

1.38 库文件目录:-L

1.39 指定链接库:-l

◐生成调试信息:-g

1.4 大型项目

二、使用GDB调试

2.1 gdb调试完整过程

2.2 一些进阶用法

2.21 break与条件断点

2.22 运行时表达式计算

2.23 显示调试状态信息:info命令

2.24 追踪执行流程

2.25 观察点

2.26远程调试

(1)介绍

(2)实操

2.27 调试核心转储文件

2.28 GDB脚本化调试


一、使用GCC编译C程序

当谈到C语言编译器时,GNU Compiler Collection(GCC)是最常用和广泛支持的工具之一。GCC是一个强大的编译器套件,支持多种编程语言,包括C、C++、Objective-C、Fortran和Ada等。还支持交叉编译,即在一个平台下编译另一个平台上的程序(GO语言也可以)。本节将介绍GCC的基本用法和一些常见选项。

1.1 准备工作

(1)安装GCC:


要使用GCC,首先需要安装它。GCC通常在大多数Linux发行版中默认安装,可以通过在终端中运行gcc --version来检查GCC是否已安装。如果系统中未安装GCC,可以通过在终端中运行适当的包管理器命令(如apt、yum或brew)来安装它。

root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

(2)编写源代码:

在使用GCC之前需要编写C源代码文件。我已经创建了一个名为example.c的文件,其中包含以下代码:

#include<stdio.h>
int main()
{
        puts("Haha,I am cat"); 
         return 0;
}

1.2 编译源代码

代码编译完整过程:预处理->编译->汇编->链接

(1)编译源代码:

要使用GCC编译源代码,打开终端并导航到源代码所在的目录。然后使用以下命令编译代码(不进入目录,给出文件的完整路径也可以,不建议):

gcc -o output example.c

上述命令中,-o选项用于指定生成的可执行文件的名称,ouputput是输出文件的名称,example.c是输入源代码文件的名称。

这样写也可的:gcc example.c -o new,-o后面紧跟输出文件名就可以了。


(2)运行可执行文件:

在成功编译后,可以在终端中运行生成的可执行文件:

./new

将在终端中看到输出:Haha,I am cat


运行可执行文件,直接用它的名字就可以了,前面加上./是为了指明可执行文件的路径,即当前目录下面,你直接换成绝对路径也可以的。若果想要不加路径,只用名称来运行,就需将它的路径添加到环境变量了,因为你在命令行输入一个东西,他都会在环境变量中去寻找,没添加环境变量之前,系统根本不认识这个东西,所以,./可执行文件名 是常用的方式。

1.3 gcc常用选项

GCC的常见选项:

-c:只编译源代码,生成目标文件(xx.o)而不进行链接。

-E:只进行预处理,生成预处理后的源代码文件。

-O:优化生成的代码,可以使用-O1、-O2或-O3进行不同级别的优化(是大写字母O)。

-g:生成调试信息,以便进行源代码级调试。

-Wall:显示编译时的警告信息。

-std:指定所使用的C语言标准,如-std=c11。

-I:指定包含头文件的目录。

-L:指定链接库文件的目录。

-l:链接指定的库文件。

1.31 只生成目标文件:-c

这个选项告诉gcc只编译源文件,而不进行链接操作。它生成目标文件(通常是以.o为扩展名),可以在后续的链接阶段使用。


1.32 指定生成可执行文件名称:-o

使用这个选项指定生成的可执行文件的名称(Linux不看后缀)。例如,-o myprog将生成名为myprog的可执行文件。


注意不能和源代码名称相同,比如:

gcc -o hello.c hello.c

1.33 代码优化:-O

这个选项用于控制优化级别。可以使用不同的级别,如-O0(关闭优化)到-O3(最高优化级别)。更高的优化级别可能会增加编译时间,但可以生成更高效的代码。(字母O,markdown显示有问题)


与Visual中的debug和release相似,代码不是优化级别越高越好:


1.开发过程中不要优化,因为这使得编译时间可能很长,开发快结束时再说;

2.要调试时,不雅优化,因为代码可能会被改写,导致跟踪调试困难;

3.运行代码的机器资源有限时,可以不优化,优化是提高代码运行效率,但它可能曾加代码的体积。


优化前后对比示例:

编写一个浮点计算的程序:

#include<stdio.h>
int main(){
  double counter;
  double res;
  double tmp;
  for(counter=0;counter<2000.0*2000.0*2000.0/20.0+2023;counter+=(5-1)/4){
    tmp=counter/2023;
    res=counter;    
  }
  printf("res is: %f\n",res);
  return 0;
}

(1)不使用优化

使用time记录运行时间:

root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# time ./cau
res is: 400002022.000000
real    0m1.489s
user    0m1.488s
sys     0m0.000s

(2)使用-O2优化

root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# gcc -O2 -o cau complex.cau.c
root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# time ./cau
res is: 400002022.000000
real    0m0.597s
user    0m0.597s
sys     0m0.000s

可见,代码运行效率明显提升。

文件大小对比:

-rwxr-xr-x 1 root root 16712 May 21 18:34 cau  # 优化后
-rwxr-xr-x 1 root root 16704 May 21 18:38 cau  # 优化前

这个示例中,优化后的可执行文件的大小增加的比较少,因为程序本身就及其简单,但如果是一个项目,差距就可能很大了。

1.34 显示警告信息:-Wall

这个选项打开了gcc的警告功能,以便在编译过程中显示更多的警告信息。它可以帮助你发现潜在的问题或不规范的代码。

将上诉代码的main改为void型,开启-Wall选项:

root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# gcc -o cau complex.cau.c
root@CQUPTLEI:~/Linux_test/LinuxC_learn/gcc_learn# gcc  -Wall -o cau complex.cau.c
complex.cau.c:2:7: warning: return type of ‘main’ is not ‘int’ [-Wmain]
    2 | void  main(){
      |       ^~~~
complex.cau.c: In function ‘main’:
complex.cau.c:5:9: warning: variable ‘tmp’ set but not used [-Wunused-but-set-variable]
    5 |  double tmp;
      |         ^~~

警告信息(标注的有warining字样):


main函数返回类型不是int

变量定义了却没有使用

使用这个选项,只要程序没有错误,只有警告的话,只会显示警告信息,并且能够完成编译。上面编译后可以成功运行的。


1.35 将警告视为错误:-Werror

将所有警告视为错误。当使用此选项时,任何警告都将导致编译过程中止。


这个选项要和-Wall选项一起使用,否则无效。 一起使用时会将原来的warning信息变成error信息,并停止编译:

gcc  -Wall -Werror -o cau complex.cau.c

f283901ebb2f66f839da0265e1905c3c_88e9cbff7f7c4f96972c01d61e9c9309.png

警告严格来讲不是错误,却可能是一些潜在错误的栖身之所,你的程序很有可能因为忽略了某些警告而发生错误。

要写出健壮的代码,也要注意处理警告。

1.36 指定C语言标准:-std

用于指定编译时要使用的C或C++标准。例如,-std=c11表示使用C11标准进行编译。

查看gcc默认标准可以用:

gcc -dM -E - < /dev/null | grep __STDC_VERSION__

输出:#define __STDC_VERSION__ 201710L


该宏定义表示我的gcc默认c17标准。


现在的C语言标准有C89、C99、C11、C17和C2x。这些标准的主要区别在于它们引入了哪些新特性,以及它们对现有特性的修改和改进。例如,C99标准引入了一些新的数据类型,如long long int和_Bool,以及一些新的库函数,如snprintf()和vsnprintf()。C11标准引入了一些新的特性,如泛型选择表达式和多线程支持。

1.37 添加包含文件目录:-I

字母 i 的大写。


这个选项用于添加包含文件的目录。指定-I选项后,编译器将在指定的目录中查找头文件。


1.38 库文件目录:-L

用这个选项指定链接时要搜索库文件的目录。编译器将在指定的目录中查找库文件。


1.39 指定链接库:-l

通过这个选项指定要链接的库。例如,-lm表示链接数学库。


◐生成调试信息:-g

这个选项生成调试信息,使得在调试程序时可以进行源代码级别的调试。

1.4 大型项目

当一个项目有很多源程序、头文件、依赖库与、资源文件…时,其实就不建议在命令行使用gcc编译了,那将会是很长一段命令,都敲烦了,通常项目的编译通过Makefile来实现。

二、使用GDB调试

在图形化的IDE中进行调试是一件很简单的事情,在命令行,可以使用gdb调试,其功能也十分强大。


GDB(GNU Debugger)是一个功能强大的调试器,用于调试C、C++和其他编程语言的程序。它提供了一组丰富的功能,帮助开发者定位和修复程序中的错误。下面将详细介绍GDB的使用方法和一些常见的调试技巧。

gdb命令基本语法:

gdb  # 直接进入gdb调试环境
gbd programname  # 对programname进行调试

gdb参数:

-g:在可执行文件中包含调试信息,以便GDB能够进行源代码级别的调试。

-tui:以文本用户界面(TUI)模式启动GDB,该模式提供了源代码窗口和调试器命令窗口。

-b:指定调试器使用的调试文件格式,如ELF、COFF等。

-ex:在启动GDB后立即执行指定的命令。

-core <core文件>:指定要调试的核心转储文件。

-x <脚本文件>:从指定的文件中读取GDB命令,可以用于自动执行一系列的调试命令。

-args <可执行文件> <参数>:指定要调试的可执行文件及其命令行参数。

-p <进程ID>:连接到指定的正在运行的进程进行调试。

2.1 gdb调试完整过程

先走一个完整的流程。

1.编译源代码:

在开始调试之前,需要使用调试选项编译源代码。在使用GCC编译源代码时,添加-g选项,以生成包含调试信息的可执行文件。例如:

gcc -g -o exp example.c

2.启动GDB:

在终端中进入程序所在的目录,然后输入以下命令启动GDB:

gdb exp

这将启动GDB并将程序exp加载到调试环境中。


或者只输入gdb,先进入调试环境,然后使用:file exp,载入文件。


不管哪种方式,都会先输出一堆信息,gdb版本号之类的,进去后回车,就可以输入相关命令(q是退出),在以(gdb)开头的行,你可以执行各种命令:如设置断点、运行、调试等等。

c87676b194cca2d2d3cce25ec8bc499a_9dfb55c2f869456fa87326ccdc5b56ae.png

3.设置断点:

断点是指程序中的一个位置,当执行到该位置时,程序将停止执行,以便您可以检查程序的状态。您可以使用以下命令在特定的行号上设置断点(2.2节详细介绍):

break linenumber

例如:

bac765521267aa4225ef90567771e9b5_a727142603ed4bb792d1be8025ef8157.png

4.运行程序:

在设置断点后,可以使用以下命令运行程序:

run

程序将开始执行,直到遇到设置的断点或程序结束。

例如:

dbe779288c4d1ccfbea9e87dfe2e6eef_32704f23e1b14996a6ae4a5e2c12420f.png

5.调试命令:

在程序执行过程中,可以使用以下常用命令来调试程序:

run:从头运行程序(简写r)。

break:继续设置断点(简写b)。

next:执行下一行代码(简写n)。

step:进入函数调用,逐行执行函数内部的代码(简写s)。

print variable:打印变量variable的值(简写p)。

watch variable: 监视变量variable的值,当变量的值发生改变时,停止程序的执行(简写w)。

continue:继续执行程序直到下一个断点或程序结束(简写c)。

backtrace:显示当前函数调用的堆栈跟踪信息(简写bt)。

quit:退出GDB调试器(简写q)。

finish: 执行到当前函数返回为止(简写fin)

6.检查变量值:

在程序执行时,您可以使用print命令来检查变量的值。例如,要检查名为count的变量的值,可以输入print count。


7.分析堆栈:

使用backtrace命令可以查看当前函数调用的堆栈跟踪信息。这对于了解程序执行的控制流很有帮助。


8.内存调试:

GDB还提供了一些命令来检查和修改程序的内存状态。例如,watch命令可以设置内存访问断点,x命令可以以不同的格式显示内存内容。

2.2 一些进阶用法

上面的调试命令,多加练习才能熟练,这里分享一些高级调试方法。

2.21 break与条件断点

(1)基本用法:

break <location>:在指定的位置设置断点。位置可以是函数名、源文件名和行号的组合,也可以是函数内的具体行号。

break <line_number>:在指定的行号设置断点。

break <filename>:<line_number>:在指定的源文件和行号设置断点。

例:

31064de4b4da193982759b5814cf40d5_ff4e56b93ba64931af2e49b0b477c7dc.png

(2)断点类型:

break命令默认设置的是常规断点(regular breakpoint),即程序执行到该位置时停止。可以使用以下选项指定不同类型的断点:

break if <condition>:在满足特定条件时触发断点,条件断点。

break unless <condition>:在不满足特定条件时触发断点。

tbreak <location>:设置临时断点(temporary breakpoint),即断点只会在首次触发后被自动删除。

rbreak <regexp>:根据正则表达式匹配函数名来设置断点。

(3) 其他选项:

break命令还支持一些其他选项来提供更多的控制和灵活性:

-t:在设置断点时显示追踪(trace)信息。

-h:设置硬件断点(hardware breakpoint),如果硬件支持的话。

-a:设置断点时自动调整地址,以适应可执行文件的加载地址。

-p:指定断点命令,即在触发断点时执行指定的GDB命令。

-f:在设置断点时强制断点即使警告被设置为错误。

(4)断点的禁用、启用与删除


在GDB中,可以使用disable和enable命令来禁用和启用断点。这两个命令的语法如下:

disable [breakpoint-number]
enable [breakpoint-number]

其中,breakpoint-number表示断点编号。如果不指定断点编号,则禁用或启用所有断点。


例如,要禁用编号为1的断点,可以使用以下命令:

(gdb) disable 1

要启用编号为1的断点,可以使用以下命令:

(gdb) enable 1

可以使用delete命令来删除断点。该命令的语法如下:

delete [breakpoints num] [range...]

其中,num表示断点编号,range表示断点范围。如果不指定参数,则删除所有断点。


例如,要删除所有断点,可以使用以下命令:

(gdb) delete

(5)查看所有断点信息

info b

9cf21ea477ba1c15f69bed7bbbade1ce_178509c7812d42538db05f4fea2c0cf2.png

分别是:编号、类型、展示、启用状态、地址、在文件中的位置

(6)条件断点

条件断点是GDB中的一项强大功能,允许在满足特定条件时触发断点,以便在程序执行过程中更有针对性地进行调试。这里单独拿出来详细介绍:

1.设置条件断点:

使用break命令结合if选项可以设置条件断点。语法如下:

break <location> if <condition>

其中,<location>可以是函数名、源文件名和行号的组合,或者是函数内的具体行号。<condition>是一个表示条件的表达式,当满足该条件时触发断点。


2.条件表达式:

条件表达式可以是任何可以在编程语言中使用的合法表达式。例如,可以使用变量、函数调用、运算符和比较操作符来构建条件。一些示例:

i == 10:当变量i的值等于10时触发断点。

x > 0 && y < 5:当x大于0且y小于5时触发断点。

strcmp(str, "example") == 0:当字符串str与"example"相等时触发断点。


3.调试条件断点:

当条件断点触发时,程序会在设置断点的位置停止执行,以便进行调试。在断点停止时,可以使用GDB提供的其他调试命令来查看和修改变量的值,分析程序状态以及执行其他调试操作。


4.修改条件:

可以使用condition <breakpoint_number> <new_condition>命令来修改已设置的条件断点的条件。<breakpoint_number>是断点的编号,可以使用info breakpoints命令查看断点列表和编号。<new_condition>是新的条件表达式。

例: b max if a<b

c03e876359429ff33ea32123ed97c472_4f7e6190788a408e8476d173f9a712c4.png

上图,当max函数中的参数a<b时设置断点,而我传入的实参是a>b,所以程序不会停止。

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
24天前
|
Linux 编译器 C语言
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
|
3月前
|
NoSQL Linux C语言
Linux GDB 调试
Linux GDB 调试
59 10
|
2月前
|
Linux 编译器 C语言
Linux内核对GCC版本的检测
Linux内核对GCC版本的检测
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
|
3月前
|
NoSQL Linux 编译器
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
如何配置环境并使用QEMU虚拟机结合GDB进行Linux内核代码的断点调试,包括安装QEMU、交叉编译工具链,编译内核以及通过GDB远程连接进行调试的详细步骤。
121 0
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
|
3月前
|
NoSQL
技术分享:如何使用GDB调试不带调试信息的可执行程序
【8月更文挑战第27天】在软件开发和调试过程中,我们有时会遇到需要调试没有调试信息的可执行程序的情况。这可能是由于程序在编译时没有加入调试信息,或者调试信息被剥离了。然而,即使面对这样的挑战,GDB(GNU Debugger)仍然提供了一些方法和技术来帮助我们进行调试。以下将详细介绍如何使用GDB调试不带调试信息的可执行程序。
89 0
|
6月前
|
监控 网络协议 Java
Linux 网络编程从入门到进阶 学习指南
在上一篇文章中,我们探讨了 Linux 系统编程的诸多基础构件,包括文件操作、进程管理和线程同步等,接下来,我们将视野扩展到网络世界。在这个新篇章里,我们要让应用跳出单机限制,学会在网络上跨机器交流信息。
Linux 网络编程从入门到进阶 学习指南
|
存储 Linux C语言
Linux:入门学习知识及常见指令
Linux:入门学习知识及常见指令
|
6月前
|
存储 消息中间件 网络协议
Linux 系统编程从入门到进阶 学习指南
本文旨在为初学者提供一个清晰的 Linux 系统编程入门指南,带你步入 Linux 系统编程的世界,从基本概念到实用技能,一步步建立起您的知识体系。
Linux 系统编程从入门到进阶 学习指南
|
运维 Linux Shell
Linux权限维持入门学习(上)
Linux权限维持入门学习
137 0