第09课:GDB 实用调试技巧(下)
本节课的核心内容:
- 多线程下禁止线程切换
- 条件断点
- 使用 GDB 调试多进程程序
多线程下禁止线程切换
假设现在有 5 个线程,除了主线程,工作线程都是下面这样的一个函数:
void thread_proc(void* arg)
{
//代码行1
//代码行2
//代码行3
//代码行4
//代码行5
//代码行6
//代码行7
//代码行8
//代码行9
//代码行10
//代码行11
//代码行12
//代码行13
//代码行14
//代码行15
}
为了能说清楚这个问题,我们把四个工作线程分别叫做 A、B、C、D。
假设 GDB 当前正在处于线程 A 的代码行 3 处,此时输入 next 命令,我们期望的是调试器跳到代码行 4 处;或者使用“u 代码行10”,那么我们期望输入 u 命令后调试器可以跳转到代码行 10 处。
但是在实际情况下,GDB 可能会跳转到代码行 1 或者代码行 2 处,甚至代码行 13、代码行 14 这样的地方也是有可能的,这不是调试器 bug,这是多线程程序的特点,当我们从代码行 4 处让程序 continue 时,线程 A 虽然会继续往下执行,但是如果此时系统的线程调度将 CPU 时间片切换到线程 B、C 或者 D 呢?那么程序最终停下来的时候,处于代码行 1 或者代码行 2 或者其他地方就不奇怪了,而此时打印相关的变量值,可能就不是我们需要的线程 A 的相关值。
为了解决调试多线程程序时出现的这种问题,GDB 提供了一个在调试时将程序执行流锁定在当前调试线程的命令:set scheduler-locking on。当然也可以关闭这一选项,使用 set scheduler-locking off。除了 on/off 这两个值选项,还有一个不太常用的值叫 step,这里就不介绍了。
条件断点
在实际调试中,我们一般会用到三种断点:普通断点、条件断点和硬件断点。
硬件断点又叫数据断点,这样的断点其实就是前面课程中介绍的用 watch 命令添加的部分断点(为什么是部分而不是全部,前面介绍原因了,watch 添加的断点有部分是通过软中断实现的,不属于硬件断点)。硬件断点的触发时机是监视的内存地址或者变量值发生变化。
普通断点就是除去条件断点和硬件断点以外的断点。
下面重点来介绍一下条件断点,所谓条件断点,就是满足某个条件才会触发的断点,这里先举一个直观的例子:
void do_something_func(int i)
{
i ++;
i = 100 * i;
}
int main()
{
for(int i = 0; i < 10000; ++i)
{
do_something_func(i);
}
return 0;
}
在上述代码中,假如我们希望当变量 i=5000 时,进入 do_something_func() 函数追踪一下这个函数的执行细节。此时可以修改代码增加一个 i=5000 的 if 条件,然后重新编译链接调试,这样显然比较麻烦,尤其是对于一些大型项目,每次重新编译链接都需要花一定的时间,而且调试完了还得把程序修改回来。
有了条件断点就不需要这么麻烦了,添加条件断点的命令是 break [lineNo] if [condition],其中 lineNo 是程序触发断点后需要停下的位置,condition 是断点触发的条件。这里可以写成 break 11 if i==5000,其中,11 就是调用 do_something_fun() 函数所在的行号。当然这里的行号必须是合理行号,如果行号非法或者行号位置不合理也不会触发这个断点。
(gdb) break 11 if i==5000
Breakpoint 2 at 0x400514: file test1.c, line 10.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/testgdb/test1
Breakpoint 1, main () at test1.c:9
9 for(int i = 0; i < 10000; ++i)
(gdb) c
Continuing.
Breakpoint 2, main () at test1.c:11
11 do_something_func(i);
(gdb) p i
$1 = 5000
把 i 打印出来,GDB 确实是在 i=5000 时停下来了。
添加条件断点还有一个方法就是先添加一个普通断点,然后使用“condition 断点编号断点触发条件”这样的方式来添加。添加一下上述断点:
(gdb) b 11
Breakpoint 1 at 0x400514: file test1.c, line 11.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400514 in main at test1.c:11
(gdb) condition 1 i==5000
(gdb) r
Starting program: /root/testgdb/test1
y
Breakpoint 1, main () at test1.c:11
11 do_something_func(i);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64
(gdb) p i
$1 = 5000
(gdb)
同样的规则,如果断点编号不存在,也无法添加成功,GDB 会提示断点不存在:
(gdb) condition 2 i==5000
No breakpoint number 2.
使用 GDB 调试多进程程序
这里说的多进程程序指的是一个进程使用 Linux 系统调用 fork() 函数产生的子进程,没有相互关联的进程就是普通的 GDB 调试,不必刻意讨论。
在实际的应用中,如有这样一类程序,如 Nginx,对于客户端的连接是采用多进程模型,当 Nginx 接受客户端连接后,创建一个新的进程来处理这一路连接上的信息来往,新产生的进程与原进程互为父子关系,那么如何用 GDB 调试这样的父子进程呢?一般有两种方法:
- 用 GDB 先调试父进程,等子进程 fork 出来后,使用 gdb attach 到子进程上去,当然这需要重新开启一个 session 窗口用于调试,gdb attach 的用法在前面已经介绍过了;
- GDB 调试器提供了一个选项叫 follow-fork,可以使用 show follow-fork mode 查看当前值,也可以通过 set follow-fork mode 来设置是当一个进程 fork 出新的子进程时,GDB 是继续调试父进程还是子进程(取值是 child),默认是父进程( 取值是 parent)。
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork child
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "child".
(gdb)
建议读者自己写个程序,然后调用 fork() 函数去实践一下,若要想阅读和调试 Apache HTTP Server 或者 Nginx 这样的程序,这个技能是必须要掌握的。
第09课:GDB 实用调试技巧(下)的更多相关文章
- 第08课:GDB 实用调试技巧( 上)
本节课的核心内容: 将 print 打印结果显示完整 让被 GDB 调试的程序接收信号 函数明明存在,添加断点时却无效 将 print 打印结果显示完整 当使用 print 命令打印一个字符串或者字符 ...
- Chrome 实用调试技巧
Chrome 实用调试技巧 2016-07-23 如今Chrome浏览器无疑是最受前端青睐的工具,原因除了界面简洁.大量的应用插件,良好的代码规范支持.强大的V8解释器之外,还因为Chrome开发者工 ...
- gdb命令调试技巧
gdb命令调试技巧 一.信息显示1.显示gdb版本 (gdb) show version2.显示gdb版权 (gdb) show version or show warranty3.启动时不显示提示信 ...
- Chrome实用调试技巧
如今Chrome浏览器无疑是最受前端青睐的工具,原因除了界面简洁.大量的应用插件,良好的代码规范支持.强大的V8解释器之外,还因为Chrome开发者工具提供了大量的便捷功能,方便我们前端调试代码,我们 ...
- Android Studio 实用调试技巧
Android Studio 是个发工具,其自身带调式环境是很强大的,我们要摆脱只会使用Log打印日志的低效的方法,掌握高级调试技巧对每个Android开发者都是很必要的,废话少说,直入正题 调试方式 ...
- VS2010中的调试技巧
作者: scottgu 这是我的博客中关于VS 2010和.NET 4发布系列的第二十六篇文章. 今天的博文将介绍Visual Studio中的一些实用调试技巧.这是受我朋友Scott Cate (他 ...
- Javascript调试技巧整理
整理一下网上看到的实用调试技巧! 1. 不要使用alert 首先,alert只能打印出字符串,如果打印的对象不是String,则会调用toString()方法将该对象转成字符串(比如转成[object ...
- 第七课 GDB调试 (下)
1序言: 通过前面一节第六课 GDB调试 (下)文章,可以掌握理解了gdb调试:怎么启动.运行,打断点.查看变量.甚至改变变量等的知识,今天来大概讲解下调试bug的类型. 2知识点: 2.1 就像之前 ...
- 【转贴】gdb中的信号(signal)相关调试技巧
一篇不错的帖子,讲的是gdb中的信号(signal)相关调试技巧 转自Magic C++论坛 http://www.magicunix.com/index_ch.html http://www.m ...
随机推荐
- noip模拟测试10
T1 这道题在考场上想到了二维前缀和,就是自己算前缀和的方式有点麻烦,导致花的时间较长,但还是成功搞了出来. 因为暴力计算的话需要不停枚举左上角和右下角的 i ,j, 时间复杂度为 n^4 ,我当时就 ...
- 洛谷P2858题解
这是一道裸的第二类区间DP(由已知区间向外扩展)题. 首先定义 \(f_{i,j}\) 为最后 \(j-i+1\) 个数取 \([i,j]\) 这个区间时,\([i,j]\) 这个区间可以产生的最大价 ...
- 5年Android开发诉苦:47天21家面试,半年空档期觉得整个人生都被毁了
近日,我在逛某社交论坛时,发现一位做了五年的Android开发将自己这段时间的所有面试经历发表了出来,根据网友自己提供的信息显示,主要面试的地点都在北京,上海等地. 微软和亚马逊刚面试完一面,都是以算 ...
- Docker部署Zookeeper部署集群实践(2)
注:本文使用docker for windows模拟构建zookeeper集群,在linux系统下,可使用同样的docker命令构建 参考url:https://github.com/31z4/zoo ...
- JavaWeb学习笔记(四)
本文内容 1. 会话技术 1. Cookie 2. Session 2. JSP:入门学习 会话技术 1. 会话:一次会话中包含多次请求和响应. * 一次会话:浏览器第一次给服务器资源发送请求,会话建 ...
- COM笔记-COM库函数
COM在OLE32.DLL和 OLE32.LIB定义了一些常用的函数.在使用这些函数前要先调用CoInitialize来初始化COM库.当进程不再需要使用COM库函数时要调用CoUninitializ ...
- C# lock的语法糖原理--《.net core 底层入门》之自旋锁,互斥锁,混合锁,读写锁
在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施 原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连 ...
- c++本地动态连接库代码
c++本地动态连接库代码 1 #pragma once 2 #include "stdafx.h" 3 4 #ifdef PERSON_EXPORTS 5 #define PERS ...
- 【转】Mysql中事务ACID实现原理
转自:https://www.cnblogs.com/rjzheng/p/10841031.html 作者:孤独烟 引言 照例,我们先来一个场景~ 面试官:"知道事务的四大特性么?" ...
- Contos 7.x 中Docker安装以及使用
Docker是什么? Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中, 然 ...