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

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

(23)ptype 查看类型

查看一个变量的数据类型

(24)display 跟踪变化

查看某个变量或表达式的值,和 p 命令类似,但是 display 会一直跟踪这个变量或表达式值得变化,每执行一条语句都会打印一次变量或表达式的值。

display 也可以按格式打印,语法和 print 一样,请参照上表(print)。

display 跟踪得变量或表达式也会放入一张表中,使用 info 命令可以查看信息

同样,Num表示编号,Enb表示是否激活,Expression表示被跟踪的表达式。

(25)undisplay 取消跟踪

后面加 Num 编号,删除取消跟踪。其实也可以使用 del 删除。

(26)bt (backtrace)查看栈信息

在一个程序的执行过程中,如果遇到函数调用,会产生一系列一些与函数上下文相关的信息:比如函数调用的位置、函数参数、函数内部的临时变量等。这些信息会被存放在一块称为栈帧的内存空间中,并且每一个函数调用都对应一个栈帧(main 函数也有自己的栈帧,称为初始帧)。这些所有的栈帧都存放在内存中的栈区。通过命令 info frame 可以查看当前使用的栈帧所存储的信息,这里面包含了栈帧编号、栈帧地址、调用者、源码编程语言等信息。通过命令 frame numupdown 可以选的改变栈帧。

查看当前所有栈帧 bt

(27)x 查看内存

同样可以指定按什么格式查看。

(28)disas 反汇编

查看函数 print_array() 的反汇编代码,使用命令 q 退出。

(29)finish

跳出当前所在的函数。

(30)return

忽略后面的语句,立即返回,可以指定返回值 return -1

(31)call

调用某个函数,call func() 调用 func() 函数。

(32)edit

进入编辑模式

(33)search

search 搜索,reverse-search 反向搜索。

2. GDB跟踪可以正常编译运行的源文件

(1)调试非运行状态的可执行程序

这个很简单,我们前面介绍命令时,所举的例子,都是在这种情况下进行的。也就是对编译好的可执行文件进行调试。

进入gdb调试,然后用上面介绍的命令进行调试即可。

(2)调试一个正在运行的程序

有时候我们运行一个一直执行的程序时,希望能够调试这个程序。比如某个带有无限循环打印某些信息的程序。

我们可以这么做,首先编译生成可执行文件,然后在运行时加 & 让进程转为后台执行,或者通过 SecureCRT 克隆会话来新打开一个会话进行调试。

① 首先通过 ps 命令查看进程号,找到 loop 进程的进程信息

② 通过gdb的 -p 参数,指定进程进入调试

③ 正在运行的程序会暂停,可以正常调试了

3. GDB跟踪core(调试挂掉的程序)

(1)什么是 core dump 核心转储

core是指core memory,dump即堆放。core dump就是核心转储的意思。在Unix系统中,经常会将主内存 main memory 称为核心 core,而核心映像 core image 是指进程执行时的内存状态。当程序发生错误或者异常或者收到某些信号而终止执行的时候,操作系统会把核心映像写入一个文件(core 文件)来作为调试依据,这就是核心转储 core dump。

换句话说,当我们写的程序在运行时发生异常而退出的时候,由操作系统把程序当前的内存状况存储在一个core文件中,这就叫core dump。也就是说,所谓core dump核心转储,就是当我们写的程序当掉(异常退出)时,把程序当前的内存状况存储起来,以作为调试的参考的这么一种技术。

(2)产生 core dump 的原因

主要原因可以分为三大类:

① 访问越界

包括数组下标越界,C语言字符串无结束符引起的越界,使用非法指针(空指针NULL、野指针、未初始化的指针、越界指针)等。

② 多线程

多线程访问全局变量未加同步机制(锁机制等),或使用了线程不安全的函数。

③ 堆栈溢出

使用了太大的局部变量或无限嵌套、递归调用函数,可能会造成栈溢出。

(3)core 文件的相关配置与 shell 资源限制

我们先准备一个有问题的程序

编译并运行这个程序,程序发生 core dump,但是我们并没有找到 core 文件

这是因为,默认情况下 core 文件被 shell 限制大小为0了,所以我们看不到 core 文件,可以通过 ulimit 命令查看限制

实际上,ulimit 是 shell 的一个命令,通过这个命令可以查看 shell 对各种资源的限制,比如 -a 选项可以查看所有限制

第一条就是 core 文件的限制,大小被限制为0。我们可以去改变它的大小限制,最简单的方法就是改为无限制,无限制就相当于可以是任意大小。

ulimit -c unlimited

再次查看 shell 的限制就能看到,现在 core 的限制变为 unlimited 了

我们现在再一次运行刚才的 err 可执行文件,就可以看到生成了一个 core 文件

作为一个优秀的程序员,我们可能决定还不够好,这名字是啥呀 core.9546,怪怪的,我们希望他有一个符合我们心意的名字,这也可以实现,我们可以修改 core 的配置文件 /proc/sys/kernel/core_pattern ,那你改吧,你发现改完保存不了。

因为这个文件是不能写入的,我们可以借助重定向来修改这个文件

echo "core-%e-%t" > /proc/sys/kernel/core_pattern

关于里面的参数,列表如下

参数 含义
%p 添加 pid
%u 添加 uid
%g 添加 gid
%s 添加导致 core dump 的信号
%t 添加 core 生成的时间
%h 添加主机名
%e 添加命令名

注意,core 文件是执行可执行文件时,产生 core dump 后才会产生的一种文件,所以要先执行可执行文件,产生 core dump,这样才能得到 core 文件。

(4)通过core文件调试当掉的程序

使用 gdb 可执行文件名 core文件名 进入gdb调试

where 命令查看出错的位置

4. GDB调试多线程

(1)创建一个多线程测试文件

创建一个测试文件,代码如下,本人 Linux 专题系列有线程专题与进程专题,本文只做一个简单的线程创建。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void* thread1()
{
  printf("this is thread1...\n");
  for(;;)
  {
    sleep(1);
  }
}
void* thread2()
{
  printf("this is thread2...\n");
  for(;;)
  {
    sleep(1);
  }
}
int main(int argc, char* argv[])
{
  pthread_t tid1;
  pthread_t tid2;
  printf("this is main...");
  pthread_create(&tid1, NULL, thread1, NULL); /*创建线程1*/
  pthread_create(&tid2, NULL, thread2, NULL); /*创建线程2*/
  /*等待线程结束*/
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
  return 0;
}

(2)undefined reference to `pthread_create’ 错误

上面的文件创建好之后,如果直接编译,会报错undefined reference to `pthread_create’

这是因为,<pthread.h> 并非 Linux 系统的默认库,而是POSIX线程库。在Linux中将 <pthread.h> 作为一个库来使用的话,要加上 -l pthread 来显式链接该库。

这样编译就通过了。

(3)多线程调试

① 首先,运行 ttt 可执行文件,这里也会显示主进程 ID

② 然后用 SecureCRT 克隆会话或在 Linux 下直接打开一个新的终端,在另一个会话中查看进程 ID

查看主线程的线程树 pstree ,可以看到两个子线程的线程 ID

③ 查看线程栈信息,pstack

④ 进入 gdb 调试

查看线程

切换线程,根据 info 查看到的编号来切换,我们可以通过线程 ID 来判断是否切换

⑤ 打断点等等指令与之前讲的无异,这里讲一些用于线程的命令

(gdb)thread apply num n   让线程 num 继续执行,num 是线程的编号,用info查看
(gdb)set scheduler-locking on   只执行当前线程,输入 n 继续执行
(gdb)set scheduler-locking off  所有线程并发执行

总结

熟练掌握 gdb 调试是一个高水平程序员的基本技能,其实我们用习惯了 IDE 中的调试器之后,反而越来越忽视 gdb 这种命令行的调试。但是实际上,熟练掌握 gdb 会对调试程序本身产生更深刻的理解,可以大大提高程序调试水平。如果这篇文章大家觉得有帮助,可以关注我的 Linux 专栏,里面有更多 Linux 相关的优质文章。“纸上得来终觉浅,绝知此事要躬行”,学习 Linux 知识的同时,一定要动手练习,亲自去调试一些程序,只能理解这只指令是怎么执行的。

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
2月前
|
NoSQL Linux 程序员
Linux:gdb调试器的解析+使用(超详细版)
Linux:gdb调试器的解析+使用(超详细版)
85 1
|
5月前
|
NoSQL Linux 程序员
Linux | 调试器GDB的详细教程【纯命令行调试】-1
Linux | 调试器GDB的详细教程【纯命令行调试】
273 0
|
3天前
|
NoSQL Linux C语言
【Linux】Linux调试器-gdb使用
【Linux】Linux调试器-gdb使用
7 0
|
17天前
|
NoSQL IDE Linux
Linux的学习之路:8、Linux调试器-gdb使用
Linux的学习之路:8、Linux调试器-gdb使用
21 0
|
NoSQL Linux 编译器
【Linux】——调试器-gdb的使用
【Linux】——调试器-gdb的使用
|
3月前
|
NoSQL Linux C语言
【Linux】Linux调试器-gdb使用
【Linux】Linux调试器-gdb使用
【Linux】Linux调试器-gdb使用
|
3月前
|
NoSQL Linux 编译器
【Linux工具篇】调试器gdb
【Linux工具篇】调试器gdb
27 0
|
4月前
|
NoSQL 编译器 Linux
Linux——编译器gcc/g++、调试器gdb以及自动化构建工具makefile&&make详解
Linux——编译器gcc/g++、调试器gdb以及自动化构建工具makefile&&make详解
|
4月前
|
NoSQL Linux C语言
调试器gdb
调试器gdb
48 0
|
5月前
|
监控 NoSQL Linux
3.6、linux调试器:gdb
3.6、linux调试器:gdb
36 0