Linux多线程调试没那么难,别只会printf!

嵌入式大杂烩 2023-10-21 20:26

多线程调试的困惑

不少人在调试多线程程序时,遇到过各种各样看似奇怪的问题。比如,本来很容易复现的问题,调试时却莫名其妙地消失了;或者,本来程序可以正常运行的,调试时却总是异常退出。

究其原因,是因为程序的正常执行逻辑在调试的过程中受到了干扰。比如,调试时我们可能需要经常设置断点,查看变量、内存、寄存器等状态信息,这可能会导致多个线程间的时序逻辑受到干扰,改变了程序的正常执行逻辑。

有人可能会说,我不用调试器,就用printf。然而,即便是最简单的printf,也可能会对程序逻辑产生很大的影响。不过这不是本文重点,不再展开,后续会有专门文章来分析,感兴趣的可以右上角关注一下!

GDB作为调试神器,为我们提供了很多专门针对多线程调试的工具和方法,本文会以实例的形式,逐一详细讲解!

示例代码

本文以下面这个程序为例,详细介绍GDB调试多线程代码的方法:

多线程调试基础命令

GDB提供了多个针对多线程程序的调试命令。

查看线程状态信息

info threads 命令可以查询被调试程序中的线程的信息。

使用GDB调试程序时,每个线程至少有三个ID:

  • • Pthread库为线程分配的pthread ID,也就是用pthread_self()返回的ID

  • • Linux kernel为线程分配的thread ID,也就是gettid()返回的ID

  • • GDB为线程分配的ID。执行GDB调试命令时要指定的线程ID,如无特殊说明,都是指的这个ID

如:

info threads显示,当前共有三个线程。以主线程(第一行)为例:1是GDB为主线程分配的ID,接下来0x7ffff7da3740是pthread库为主线程分配的pthread ID,后面括号中的355298是kernel为主线程分配的thread ID,再后面是该线程被中断时,代码正在执行的代码位置。

可用通过ps命令或者proc文件系统查看确认:

root@ubuntu:~# ps -eT | grep test
 355298  355298 pts/9    00:00:02 test
 355298  355301 pts/9    00:00:02 test
 355298  355302 pts/9    00:00:02 test
root@ubuntu:~# 
root@ubuntu:~# ls /proc/355298/task/
355298  355301  355302
root@ubuntu:~# 

线程1前面有个星号*,表示当前线程。默认情况下,执行的GDB命令是针对当前线程。比如此时执行bt(backtrace)命令,获取的是线程1的调用栈:

切换当前线程

thread 命令可以切换当前线程,如thread 2把线程2切换为当前线程:

此时用info threads查看,星号*已经切到了线程2的前面。

针对指定线程执行命令

thread apply [thread-id-list | all] command 可以针对指定线程执行命令

如:

  • • thread apply all bt:打印所有线程的调用栈信息

  • • thread apply 3 bt:打印线程3的调用栈信息

  • • thread apply 2-3 bt:打印线程2和线程3的调用栈信息

针对特定线程设置断点

在设置断点时,我们可以选择让断点对所有的线程生效,或只对特定线程生效。命令格式为:

break   # 在设置断点,并对所有线程生效
break  thread  # 在设置断点,仅对指定的线程生效
break  thread  if  # 在设置条件断点断点,仅对指定的线程生效

比如 break do_stuff thread 2, 这个命令在do_stuff()入口设置断点,只有thread 2调用这个函数是才会触发断点,其他thread调用这个函数不会触发断点。

GDB中的其他所有类型的断点,也支持对特定线程设置断点,如tbreak、watch等。

断点相关的东西,相对比较简单,不再单独演示。

控制线程创建和退出信息

  • set print thread-events on/off 设置是否打印线程创建和退出信息,如上面例子中的:

[New Thread 0x7ffff7da2700 (LWP 355301)]
[New Thread 0x7ffff75a1700 (LWP 355302)]
  • • 命令缩写

有时候使用thread apply [thread-id-list | all] command会稍显繁琐,GDB贴心的为我们提供了几种命令缩写:

taas command 相当于 thread apply all -s command

tfaas command 相当于 thread apply all -s -- frame apply all -s command 

这个命令非常有用。比如,有时我们只记得一个变量或参数的名字,却忘了或不知道它是在哪个具体的函数中,就可以用这个命令:tfaas p var_name,这个命令会搜索所有线程的调用栈,找到名字为var_name的变量,并打印它的值,如:

(gdb) tfaas p x1

Thread 2 (Thread 0x7ffff7da2700 (LWP 370514) "test"):
#0  thread_1 (arg=0x0) at test.c:10
$5 = 432504243
(gdb) tfaas p y2

Thread 3 (Thread 0x7ffff75a1700 (LWP 370515) "test"):
#0  thread_2 (arg=0x0) at test.c:18
$6 = 428735002
(gdb)

了解控制程序执行的两种模式

为了更好地调试多线程程序,GDB提供了两种模式来控制程序的执行:

  • • All-Stop Mode:在该模式下,不管因为什么原因,一个线程被中断执行,其他所有的线程都会同时被中断执行。

  • • Non-Stop Mode:在该模式下,一个线程被中断执行,不会影响其他线程的正常执行。

接下来,我们分别详细讲解一下这两种模式的区别和用法。

All-Stop mode

在GDB中调试多线程程序,默认处于All-Stop Mode,即只要有一个线程被中断执行,其他所有的线程都会被中断执行。

比如,程序在运行时,按Ctrl+C,所有的线程都会被中断执行,同样的,当恢复程序执行时,所有的线程都会同时恢复执行。比如执行continue、step、next等命令。

也就是说,在All-Stop模式下,只要有一个线程由于触发断点等原因停止执行,整个进程都会被停下来,这在一定程度上可以保证所有线程状态的同步,保持数据的一致性。比如,在查看某个可能会被多个线程修改的数据时,不需要担心在查看的同时被其他线程修改掉。

但是,这也给程序调试带来了一些困难,比如,无法100%精确地进行单步调试。有时你会发现,在执行step命令之后,程序却停在了另外一个线程中。

在线程2和线程3都会调用do_stuff(),在do_stuff()设置断点后,线程2先触发断点停在第4行,同时当前线程切换为线程2,然后执行step命令单步执行一条语句,理论上讲,线程2执行一条语句应该停在第5行代码,然而实际上却是执行完step命令后,线程3触发了断点,而此时线程3并未按照step命令的预期执行一行代码,而是仍然停留在第4行。再次单步执行,线程2却再次触发了断点,说明线程2执行了不止一条语句(思考题:这是为什么呢?)!

为了解决这个问题,GDB提供了set scheduler-locking mode命令,锁定当前线程。(思考题:针对这个特定案例,还有其他方法吗?)

线程锁定模式

set scheduler-locking mode

mode 可能是:

  • off:不锁定任何线程,当恢复程序执行时,所有线程都可以自由执行

  • on:锁定当前线程,执行continue、step、next、finish等命令时,只有当前线程恢复执行,其余线程仍然处于中断状态

  • step:当单步执行时,和on一样,其余情况和off一样。简单来说,就是在这个锁定模式下执行单步操作,只有当前线程会执行,其他线程仍然处于中断状态。而执行其他命令时,如continue、finish等,和off一样,即所有线程都可以自由执行。

  • replay:在反向调试时和on一样,其余情况下,和off一样。后续会有专门文章详细介绍反向调试。

默认是replay模式,可以通过show scheduler-locking查看当前锁定模式。

我们演示一下:

简单解释一下:

  • • 先在do_stuff()设置断点,并执行continue恢复程序执行。随后,线程2触发断点,停在第4行代代码。

  • • 此时程序的状态是:线程2由于触发断点,停在第4行;由于在All-Stop模式,所以线程3也被中断,恰巧也停在第4行

  • • 然后执行set scheduler-locking on 锁定当前线程2。

  • • 此时单步执行,线程2停在第5行,而线程3仍然停在第4行。再执行continue,线程2会再次触发断点,此时发现线程3仍然停在第4行。

可见,执行set scheduler-locking on开启锁定模式之后,无论用什么命令恢复程序执行,只有当前线程2会执行,其余线程一直处于中断状态。

不过,同时中断所有的线程,可能会导致有些程序无法正常运行。比如,有些程序需要有专门的线程实时处理外部的消息或事件等,如果把所有的线程都中断的话,会由于外部事件得不到及时响应而导致程序异常终止。

这时,就需要用到Non-Stop模式

Non-Stop Mode

在Non-Stop模式下,一个线程被中断执行,并不会影响到其他线程。比如,一个线程触发断点,只有这一个线程会被中断执行,其余线程不受影响继续执行。同样的,在程序运行时,执行Ctrl+C,也只会中断一个线程。

要开启Non-Stop模式,需要在程序开始运行前,执行下面两个命令:

set pagination off
set non-stop on

可以通过下面这个命令关闭Non-Stop模式,即进入All-Stop模式

set non-stop off

查看Non-Stop模式是否开启:

show non-stop

实例演示

重新在GDB中加载测试程序,在开始运行之前,执行set non-stop on命令开启Non-Stop模式:

执行run(r)命令开始运行程序,然后Ctrl+C中断执行,并用info threads命令查看所有线程状态:

可见,在Non-Stop模式下,Ctrl+C只中断了线程1,线程2和3并未受到影响,仍处于运行状态。因此,此时无法通过bt命令获取线程2和线程3的调用栈:

interrupt -a命令可以中断所有线程的执行,有时候这是非常必要的:

continue -a命令可以恢复所有线程的执行

后台执行

我们知道,在Shell中执行程序时,后面加一个&符号,可以把程序放在后台执行。在GDB中你同样可以在命令后面加一个&符号,这样就能把命令放在后台执行。

之所以提供后台执行的模式,其实就是让GDB始终可以接收用户输入,即便被调试程序正常运行时,仍然可以在GDB中执行命令。这在Non-Stop模式下,有时会非常有用。

前面讲过,Non-Stop模式下,按Ctrl+C只会中断那个接收到SIGINT信号的线程,其他线程不会受到影响继续运行。但是,有时候,为了尽量保证线程状态的同步,我们可能想要让所有的线程同时被中断或同时恢复运行,这种情况下,就可以通过后台执行命令的方式来实现。

并不是所有的命令都支持后台执行,目前支持后台执行的命令有:continue、run、attach、step、stepi、next、nexti、finish、until

系统调用的处理

我们知道,在执行系统调用的时候,如read(), write(), select()等,如果收到信号,这些系统调用会异常返回,errno会被赋值为EINTR。

一般来说,程序中需要判断系统调用的返回值和errno来对这种情况进行适当的处理,比如重试一次。不过,有些实现不太规范的程序可能不会对这种情况进行判断和处理。

因此在用GDB调试这类程序的时候,可能会发现程序的执行逻辑发生了很大的变化。这是因为,使用GDB进行调试时,我们经常需要中断一个或多个线程的执行来观察当前程序的运行状态,如变量、内存数据、寄存器等,而这有可能会导致一些系统调用异常返回,从而给人产生某种错觉,似乎程序正常执行时的逻辑和在GDB中执行时的逻辑有很大的差异。

不过,这也算是提前暴露了程序中隐藏的问题,从这个角度讲,也是一件好事!

常用命令小结

- info threads:查看线程状态信息
- thread :切换当前线程
- thread apply [thread-id-list | all] :针对指定线程执行命令
- break thread if :设置条件断点,仅对指定线程生效
- set scheduler-locking on/off/step/replay:控制是否锁定当前线程,如果当前程序被锁定,恢复程序运行时,只有当前线程可以运行
- set non-stop on:开启Non-Stop模式,该模式下,一个线程被中断执行,不会影响其他线程
- set non-stop off:关闭Non-Stop模式,即进入All-Stop模式。该模式下,一个线程被中断执行,其他所有线程都会被中断
- interrut -a:中断所有线程执行
- command &:后台执行

本文是程序调试系列专题的第七篇。本系列专题旨在介绍一些高阶调试技巧、调试器的工作原理以及常见问题的定位方法和思路等内容。

往期推荐

《嵌入式Linux驱动大全》

如何高效阅读嵌入式项目代码?

推荐一个好用的嵌入式静态代码扫描工具!


在公众号聊天界面回复1024,可获取嵌入式资源

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论 (0)
  •   定制软件开发公司推荐清单   在企业数字化转型加速的2025年,定制软件开发需求愈发多元复杂。不同行业、技术偏好与服务模式的企业,对开发公司的要求大相径庭。以下从技术赛道、服务模式及行业场景出发,为您提供适配的定制软件开发公司推荐及选择建议。   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转
    华盛恒辉l58ll334744 2025-05-12 15:55 332浏览
  • 在印度与巴基斯坦的军事对峙情境下,歼10C的出色表现如同一颗投入平静湖面的巨石,激起层层涟漪,深刻印证了“质量大于数量”这一铁律。军事领域,技术优势就是决定胜负的关键钥匙。歼10C凭借先进的航电系统、强大的武器挂载能力以及卓越的机动性能,在战场上大放异彩。它能够精准捕捉目标,迅速发动攻击,以一敌多却毫不逊色。与之形成鲜明对比的是,单纯依靠数量堆砌的军事力量,在面对先进技术装备时,往往显得力不从心。这一现象绝非局限于军事范畴,在当今社会的各个领域,“质量大于数量”都已成为不可逆转的趋势。在科技行业
    curton 2025-05-11 19:09 259浏览
  • 在 AI 浪潮席卷下,厨电行业正经历着深刻变革。AWE 2025期间,万得厨对外首次发布了wan AiOS 1.0组织体超智能系统——通过AI技术能够帮助全球家庭实现从健康检测、膳食推荐,到食材即时配送,再到一步烹饪、营养总结的个性化健康膳食管理。这一创新之举并非偶然的个案,而是整个厨电行业大步迈向智能化、数字化转型浪潮的一个关键注脚,折射出全行业对 AI 赋能的热切渴求。前有标兵后有追兵,万得厨面临着高昂的研发成本与技术迭代压力,稍有懈怠便可能被后来者赶
    用户1742991715177 2025-05-11 22:44 177浏览
  • 感谢面包板论坛组织的本次测评活动,本次测评的对象是STM32WL Nucleo-64板 (NUCLEO-WL55JC) ,该测试板专为LoRa™应用原型构建,基于STM32WL系列sub-GHz无线微控制器。其性能、功耗及特性组合经过精心挑选,支持通过Arduino® Uno V3连接,并利用ST morpho接头扩展STM32WL Nucleo功能,便于访问多种专用屏蔽。STM32WL Nucleo-64板集成STLINK-V3E调试器与编程器,无需额外探测器。该板配备全面的STM
    无言的朝圣 2025-05-13 09:47 149浏览
  • ‌磁光克尔效应(Magneto-Optic Kerr Effect, MOKE)‌ 是指当线偏振光入射到磁性材料表面并反射后,其偏振状态(偏振面旋转角度和椭偏率)因材料的磁化强度或方向发生改变的现象。具体表现为:1、‌偏振面旋转‌:反射光的偏振方向相对于入射光发生偏转(克尔旋转角 θK)。2、‌椭偏率变化‌:反射光由线偏振变为椭圆偏振(克尔椭偏率 εK)。这一效应直接关联材料的磁化状态,是表征磁性材料(如铁磁体、反铁磁体)磁学性质的重要非接触式光学探测手段,广泛用于
    锦正茂科技 2025-05-12 11:02 297浏览
  •   舰艇电磁兼容分析与整改系统平台解析   北京华盛恒辉舰艇电磁兼容分析与整改系统平台是保障海军装备作战效能的关键技术,旨在确保舰艇电子设备在复杂电磁环境中协同运行。本文从架构、技术、流程、价值及趋势五个维度展开解析。   应用案例   目前,已有多个舰艇电磁兼容分析与整改系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润舰艇电磁兼容分析与整改系统。这些成功案例为舰艇电磁兼容分析与整改系统的推广和应用提供了有力支持。   一、系统架构:模块化智能体系   电磁环境建模:基
    华盛恒辉l58ll334744 2025-05-14 11:22 45浏览
  •   基于 2025 年行业权威性与时效性,以下梳理国内知名软件定制开发企业,涵盖综合型、垂直领域及特色技术服务商:   华盛恒辉科技有限公司:是一家专注于高端软件定制开发服务和高端建设的服务机构,致力于为企业提供全面、系统的开发制作方案。在部队政企开发、建设到运营推广领域拥有丰富经验,在教育,工业,医疗,APP,管理,商城,人工智能,部队软件、工业软件、数字化转型、新能源软件、光伏软件、汽车软件,ERP,系统二次开发,CRM等领域有很多成功案例。   五木恒润科技有限公司:是一家专业的部队信
    华盛恒辉l58ll334744 2025-05-12 16:13 250浏览
  • 【拆解】+CamFi卡菲单反无线传输器拆解 对于单反爱好者,想要通过远程控制自拍怎么办呢。一个远程连接,远程控制相机拍摄的工具再合适不过了。今天给大伙介绍的是CamFi卡菲单反无线传输器。 CamFi 是专为数码单反相机打造的无线传输控制器,自带的 WiFi 功能(无需手机流量),不但可通过手机、平板、电脑等设备远程连接操作单反相机进行拍摄,而且还可实时传输相机拍摄的照片到 iPad 和电视等大屏设备进行查看和分享。 CamFi 支持大部分佳能和尼康单反相机,内置可充电锂离子电池,无需相机供电。
    zhusx123 2025-05-11 14:14 408浏览
  •   电磁数据管理系统深度解析   北京华盛恒辉电磁数据管理系统作为专业的数据处理平台,旨在提升电磁数据的处理效率、安全性与可靠性。以下从功能架构、核心特性、应用场景及技术实现展开分析:   应用案例   目前,已有多个电磁数据管理系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁数据管理系统。这些成功案例为电磁数据管理系统的推广和应用提供了有力支持。   一、核心功能模块   数据采集与接入:实时接收天线、频谱仪等设备数据,兼容多协议接口,确保数据采集的全面性与实时性
    华盛恒辉l58ll334744 2025-05-13 10:59 270浏览
  • 在全球供应链紧张和国产替代需求推动下,国产存储芯片产业快速发展,形成设计到封测一体化的完整生态。北京君正、兆易创新、紫光国芯、东芯股份、普冉股份和佰维存储等六大上市公司在NOR/NAND Flash、DRAM、嵌入式存储等领域布局各具特色,推动国产替代提速。贞光科技代理的品牌紫光国芯,专注DRAM技术,覆盖嵌入式存储与模组解决方案,为多领域客户提供高可靠性产品。随着AI、5G等新兴应用兴起,国产存储厂商有望迎来新一轮增长。存储芯片分类与应用易失性与非易失性存储芯片易失性存储芯片(Volatile
    贞光科技 2025-05-12 16:05 200浏览
  • 在当下竞争激烈的 AI 赛道,企业高层的变动往往牵一发而动全身,零一万物近来就深陷这样的动荡漩涡。近日,零一万物联合创始人、技术副总裁戴宗宏离职创业的消息不胫而走。这位在大模型基础设施领域造诣颇深的专家,此前在华为云、阿里达摩院积累了深厚经验,在零一万物时更是带领团队短期内完成了千卡 GPU 集群等关键设施搭建,其离去无疑是重大损失。而这并非个例,自 2024 年下半年以来,李先刚、黄文灏、潘欣、曹大鹏等一众联创和早期核心成员纷纷出走。
    用户1742991715177 2025-05-13 21:24 108浏览
  • 文/Leon编辑/cc孙聪颖‍2025年1月至今,AI领域最出圈的除了DeepSeek,就是号称首个“通用AI Agent”(智能体)的Manus了,其邀请码一度被炒到8万元。很快,通用Agent就成为互联网大厂、AI独角兽们的新方向,迅速地“卷”了起来。国外市场,Open AI、Claude、微软等迅速推出Agent产品或构建平台,国内企业也在4月迅速跟进。4月,字节跳动、阿里巴巴、百度纷纷入局通用Agent市场,主打复杂的多任务、工作流功能,并对个人用户免费。腾讯则迅速更新腾讯元器的API接
    华尔街科技眼 2025-05-12 22:29 148浏览
  •   电磁数据展示系统平台解析   北京华盛恒辉电磁数据展示系统平台是实现电磁数据高效展示、分析与管理的综合性软件体系,以下从核心功能、技术特性、应用场景及发展趋势展开解读:   应用案例   目前,已有多个电磁数据展示系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁数据展示系统。这些成功案例为电磁数据展示系统的推广和应用提供了有力支持。   一、核心功能模块   数据采集与预处理   智能分析处理   集成频谱分析、时频变换等信号处理算法,自动提取时域频域特征;
    华盛恒辉l58ll334744 2025-05-13 10:20 359浏览
我要评论
0
1
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦