【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(一)

简介: 【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解

一、什么是GDB

1. 为什么要有GDB

我们在开发程序的过程中,应该很少会有一次就编译通过的吧,有时候即便是写了短短几十行的代码,都难免会有一些小的疏忽,更何况是几千上万甚至更大的代码,反正我在开发中几乎每次写完程序都会经过反复的调试,键盘的F11键经常会坏掉。在程序中,出现的错误主要分为 2大 类,即语法错误和逻辑错误:

  • 语法错误,顾名思义就是不符合编程语言语法的错误,这类错误一般都可以由编译器诊断出来,GCC编译器的编译阶段会进行语法检查(这方面内容我在GCC编译器那篇文章中已经详细介绍过了);
  • 逻辑错误,这部分错误是指我们在程序设计的逻辑上的错误,程序编译通过,但是执行结果并不符合我们的预期,这类错误就没有办法依靠GCC编译器去检查了,需要我们自己调试分析;

程序出现语法错误,可以依靠GCC检查出来,而逻辑错误就要我们今天的主角GDB登场解决了。所谓调试(Debug),就是单步执行代码,或通过断点让程序执行到某个位置,以此来逐步锁定程序出现问题的范围。在单步调试的过程中,我们可以监控程序执行的每一个行为,包括变量值的变化、函数的调用、内存中数据的变化、线程的调度等等,以此来修复BUG或者优化代码。

我们在Windows下开发最常用的Visual Studio,它自带的调试器是Remote Debugger,调试器与整个IDE无缝衔接,使用非常方便。在Linux下C/C++必备的调试器就是GDB了,下面讲解如何查看GDB版本及安装GDB。

2. 下载安装GDB

(1)查看GDB版本

gdb -v
gdb --version

如果你的执行结果如下,说明已经安装好了gdb,版本号如下,一般我们装好Linux后可以通过这个命令来测试是否已经安装gdb调试器。

如果你的运行结果显示 not found ,说明未安装gdb调试器,安装gdb的方法主要有两个,下面一节介绍安装方法。

bash: gdb: command not found

(2)安装GDB调试器

安装gdb主要有两种方法:

① 直接安装。通常我们安装好Linux之后,操作系统内会附带有gdb的安装包,我们可以直接使用操作系统内已有的gdb安装包,使用包管理器进行安装。这种方法简单有效,只需要一条命令就可以安装成功(以CentOS为例)

yum -y install gdb

安装好后,可以通过 gdb -v 查看版本,一般来说通过这种方式安装的gdb都不是最新版本,并且无法自己选择版本。

② 通过源码安装。源码安装是指首先去网上下载源码压缩包,然后在本地解压安装,我们可以选择自己需要的版本进行安装,可以直接点击源码包的链接gdb源码去下载。

里面有很多版本和格式,我们可以选择一个自己需要的版本 .tar.gz 格式下载,下载后进行下面的操作,比如说我下载最新的版本gdb-11.2.tar.gz。(通过源码安装文件以及 tar 压缩包管理命令可以查看我的文章《【Linux王者之路基础篇:基本命令与基础知识】Linux常用shell命令(及相关知识)详解与用法演示》第六章节《六、压缩文件管理相关命令》有详细介绍)

  • 解压文件

找到下载好的压缩包并解压

tar -zxvf gdb-11.2.tar.gz

如果你是在Windows下下载好的压缩包,要传到Linux下,可以借助SecureCRT的rz命令,教程请见《【Linux开发环境搭建:工具篇】SecureCRT工具连接虚拟机、rz/sz传输、中文乱码问题解决》

我下载的太慢了,半小时才下载三分之一,所以后面就只说命令了。

  • 解压后进入解压出来的目录
  • 运行 configure 文件配置环境,这时候会创建一个Makefile文件
  • make 编译源码文件
  • 安装 make install
  • gdb -v 查看
tar -zxvf gdb-11.2.tar.gz
cd gdb-11.2
./configure
make
make install
gdb -v

(3)卸载GDB

gdb调试器的卸载命令

yum remove gdb

二、GDB的启动与调试程序的上下文设置

1. 准备知识

(1)测试程序中的main函数参数解析argc与argv[]

首先我们创建一个C文件gdb_test.c,以用于后面举例使用,程序如下

#include <stdio.h> 
#include <stdlib.h> 
struct st 
{ 
  int a; 
  int b; 
}; 
void print_array(char* array, int len) 
{ 
  int i = 0; 
  for(i = 0; i < len; i++) 
  { 
    printf("array[%d]: %c\n", i, array[i]); 
  } 
} 
int main(int argc, char* argv[])
{
  struct st st_temp;
  int i = 0;
  char array[5];
  st_temp.a = 10;
  st_temp.b = 11;
  for(i = 0; i < 5; i++)
  {
    array[i] = i + '0';
  }   
  print_array(array, 5);
  for(i = 0; i < argc; i++)
  {
    printf("hello...argv[%d]: %s\n", i, argv[i]); 
  }
  return 0;
}

在这个测试程序中,main函数貌似有点不同寻常啊

int main(int argc, char* argv[])

多了两个东西,argc和argv,其实在main函数中本就应该有这两个参数,只不过在我们平常的大部分学习中,都弱化了这两个参数的作用,估计大部分人在学习编程时都从来没有写过这两个参数。第一个参数argc用来统计程序运行时传递给main函数的命令行参数的个数,这个不需要我们设置;argv是一个字符串数组,用来存放我们传入的参数,其中argv[0]默认就是程序运行的路径名。说起来不好理解,我们举个例子,就用上面给出的gdb_test.c文件,我们编译好运行一下,并传递参数

gcc gdb_test.c -o g3
./g3 111111

首先可以看到argc的值是2,argv的第一个参数是 ./g3 表示当前目录,第二个参数是我们传入的111111。如果我们不传任何参数,argc就是1,argv只有一个字符串就是当前路径。

(2)gcc编译时 -g 选项帮我们做了什么?

gdb主要的作用是跟踪程序的执行过程,所以要想用gdb调试程序,首先要把源程序编译为可执行文件。但是,我们正常使用gcc命令编译出来的可执行文件是无法通过gdb调试的,因为这样编译出来的可执行文件缺少gdb调试所需要的调试信息(比如每一行代码的行号、包含程序中所有符号的符号表等信息)。要想生成带有gdb调试信息的可执行文件,就要在gcc编译的时候添加==-g== 选项。

你可能通过尝试后会说,不加gcc的 -g 选项也能进入gdb调试,确实是这样,但是进入gdb并不代表就可以调试,比如下面

我们不加 -g 编译一个源文件,并启动gdb

进入gdb后我们发现,使用 r 命令执行可以,但是通过 list 查看源代码却不行。这是因为,我们不加 -g 编译出来的可执行文件不包含行号和符号表等调试所需要的信息,所以你想查看源码、添加断点都是无法实现的。而这就是 -g 选项的作用,我们可以对比一下加与不加 -g 选项生成的可执行文件大小

能够看得出,加了 -g 选项后编译出来的可执行文件占据了更多个空间,这是因为,它包含了调试信息。

有时候,我们在编译时会组合 -g-O 来使用,通常用 -Og 来实现在保证快速编译和更好的调试前提下,进行一定的优化。

(3)启动GDB与指定目标调试程序的方式

启动gdb调试器分为四种情况:

① 调试非运行状态且编译通过可运行的可执行文件

gdb exe(可执行文件名)
gdb ./exe(可执行文件名)

② 调试正在运行的可执行文件

gdb -p pid(进程号)

③ 调试core

gdb exe(可执行文件名) core.19761(core文件名)
gdb ./exe(可执行文件名) core.19761(core文件名)

上面这三种情况会在后面对应的章节详细介绍。

④ 假如直接使用 gdb 命令进入gdb调试器,gdb自己是无法确定要调试哪个可执行文件的,即使当前目录只有一个可执行文件也无法自动识别,这时我们可以手动指定目标调试文件。

提示信息中已经告诉我们使用哪个命令来指定待调试程序了,那就是 file 命令,使用方法是 file 直接加可执行文件所在目录以及可执行文件名,如果可执行文件就在gdb当前工作目录下,可以不加目录,这样我们就可以使用gdb调试 file 命令指定的可执行文件了

不管哪种情况,我们进入gdb时,总会打印一堆声明

要想去掉这些声明,可以在gdb后面加 –silent-q–quiet 选项。

只要最下面有一个 (gdb) 就说明进入成功。

2. 程序上下文

(1)gdb工作目录

默认情况下,GDB调试器会把启动时所在的目录作为工作目录,但有时候我们可能需要根据情况去改变gdb的工作目录,查看gdb当前工作目录和改变工作目录的命令和 shell 下一样。

① 查看当前gdb工作目录

pwd 命令可以查看当前gdb工作目录

② 改变gdb工作目录

使用shell下的 cd 命令,可以改变gdb工作目录,用法与shell下一样

另外提示一下,gdb调试时,也可以使用 tab 键命令补全、上下键查看历史命令等。

(2)程序运行参数

传递运行参数的方式有三种:

① 启动gdb时指定(exe表示可执行文件名,paras表示参数)

gdb --args exe paras

我们用前面的gdb_test.c编译为g3,并传入参数111111111

② set命令

gdb调试器启动后,在运行过程中,可以借助 set 命令指定目标调试程序启动所需要的运行参数

set paras

我们在函数print_array()处设置一个断点,并执行到断点处,然后把函数参数len设置为2,也就是只打印两个数据(array总共5个数据,可以看前面的图中打印结果)

可以看到 set 在运行的过程中改变了参数len的值。

③ 运行时指定

gdb调试器启动后,在运行时可以通过runstart 来指定参数

run paras
start paras

(3)查看及修改运行环境

① 查看程序的运行路径

show paths

② 设置程序的运行路径

path /xxx/xxx/

③ 查看环境变量

show environment

④ 设置环境变量

set environment PARA=para

(4)输入输出重定向

① 输入输出重定向

默认情况下,程序中的输出都是打印在终端上的,通过重定向可以把结果打印到指定位置。比如,我们可以把程序中的打印结果都打印到某个文件中

可以看到,运行程序后,屏幕上没有任何输出,我们退出gdb查看1.txt文件

程序运行结果都被打印到了该文件中。

② 选择终端

使用终端tty1,命令如下

tty /dev/tty1


相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
6月前
|
NoSQL Linux 程序员
Linux:gdb调试器的解析+使用(超详细版)
Linux:gdb调试器的解析+使用(超详细版)
253 1
|
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远程连接进行调试的详细步骤。
106 0
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
|
3月前
|
NoSQL
技术分享:如何使用GDB调试不带调试信息的可执行程序
【8月更文挑战第27天】在软件开发和调试过程中,我们有时会遇到需要调试没有调试信息的可执行程序的情况。这可能是由于程序在编译时没有加入调试信息,或者调试信息被剥离了。然而,即使面对这样的挑战,GDB(GNU Debugger)仍然提供了一些方法和技术来帮助我们进行调试。以下将详细介绍如何使用GDB调试不带调试信息的可执行程序。
73 0
|
5月前
|
NoSQL Linux C语言
GDB:强大的GNU调试器
GDB:强大的GNU调试器
|
5月前
|
NoSQL 编译器 Linux
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
61 0
|
6月前
|
NoSQL Java Unix
Linux:调试器 - gdb
Linux:调试器 - gdb
63 1
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试正在运行的程序
Linux C/C++ gdb调试正在运行的程序
|
6月前
|
NoSQL Linux 程序员
Linux调试器--gdb的介绍以及使用
Linux调试器--gdb的介绍以及使用
|
6月前
|
NoSQL Linux C语言
【Linux】Linux调试器-gdb使用
【Linux】Linux调试器-gdb使用
39 0