首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
C语言中如何实现数据帧封装与解析
2
【熟视C语言】如何快速的了解一个库函数(C语言讲解,以string.h中的部分库函数为例)
3
C语言代码封装MQTT协议报文,了解MQTT协议通信过程
4
NV12数据格式转H265编码格式实现过程
5
基于Modbus协议实现Openplc与Kingview的仿真通讯与模拟测试
6
onvif协议最新版本_接口协议测试工具
7
linux后台开发常用调试工具
8
C/C++开发人员要了解的几大著名C/C++开源库[通俗易懂]
9
适用于嵌入式环境的加速计算库
10
Linux下WebRTC框架Janus编译过程
11
探索嵌入式应用框架(EAF)
12
[C&C++]联合体union的特征及用其进行传输
13
联合体和结构体一起解析数据
14
国标GB28181协议客户端开发(四)实时视频数据传输
15
6.1 C/C++ 封装字符串操作
17
C语言进阶——自定义类型
18
干货 | 结构体、联合体嵌套使用的一些实用操作
19
C语言的面向对象编程
20
QT应用编程: 编写低功耗BLE蓝牙调试助手(Android系统APP)
21
设计模式之接口隔离原则C++实现
22
嵌入式软件开发的框架思维
23
通过面向对象设计串口协议
24
QT应用编程: 开发串口调试助手
25
一种高效的串口自定义16进制通信协议的嵌入式应用开发解决方案
26
嵌入式中状态机的几种骚操作
27
【干货】用FreeRTOS搭建Event-Driven应用框架
28
嵌入式开发基础之任务管理(线程管理)
29
SIP菜鸟如何学SIP
30
Linux下使用libuvc读取控制USB免驱摄像头
31
Linux 使用strace命令查找进程卡死原因
32
84-OOP之组合
33
如何调试多线程程序
34
GDB多线程调试分析
35
GDB多线程多进程调试
36
一个简单实用的线程基类
37
OpenThread是世界上最舒心的跨平台多线程并发库
38
OpenMiniServer是一个超迷你、 超易用的C++高并发跨平台服务器框架
39
OpenSocket是跨全平台的高性能高并发网络库
40
一个C++多线程TCP服务Demo
41
一文搞懂网络库的分层设计!
42
实现一个接收多路RTP流,输出一路RTMP流的简单MCU
43
谈谈嵌入式应用软件人机界面开发的菜单框架编写
44
union 的概念及在嵌入式编程中的应用
45
让终端支持https,移植OpenSSL和libcurl到嵌入式linux,遇到的问题总结
46
日常工作中的设计:解耦和封装
47
一种简易的嵌入式设备系统日志记录方法
48
PLC和计算机通信的数据采集方法和传输监控的实现(1)
49
C++随笔(五)三种实现串口通信的方式
50
开源一个自己写过的MQTT 客户端调试工具

GDB多线程调试分析

0x00: 在Linux系统上Gdb提供了一组多线程调试命令,如表所示:

多线程调试的主要任务是准确及时地捕捉被调试程序线程状态的变化的事件,并且GDB针对根据捕捉到的事件做出相应的操作,其实最终的结果就是维护一根叫thread list的链表。上面的调试命令都是基于thread list链表来实现的,后面会有讲到。

0x01:Gdb在linux平台多线程调试实现主要依赖下面三个文件

thread.c:文件它的任务非常简单,就是多线程调试命令子集的实现,比如info threads。 当用户在gdb命令行敲入多线程调试命令子集中的命令时,就会调用thread.c中对应的函数。Thread.c中的实现都是基于thread list的。而gdb对于thread list的维护工作主要在另外两个文件中实现。 Linux-nat.c:它实现了通常的调试功能,比如读写寄存器、读写内存、resume程序、wait程序、attach、detach等功能。更重要的是,在linux-nat.c中会维护一个lwp_list链表,表示当前进程所有的内核线程。

linux-thread-db.c:基于thread_db库的一组功能函数,用在多线程调试环境下的函数,比如to_get_thread_local_address。这些函数的任务就是获取用户态线程的产生、消亡事件,双及获取用户态线程相关的数据。Linux-thread-db.c获取用户线程的发生的事件和获取的信息、结合linux-nat.c中维护的lwp_list内核线程链表中提供的信息,以此维护一个完整的thread_list,该链表存放线程所有的信息。

0x02:Gdb功能函数分层:

Gdb中的功能函数使用struct target_ops数据结构来组织。不同功能的函数集抽象成不同的target_ops。比如用于处理coredump文件的”core” target_ops,而linux-nat.c中实现的linux应用程序本地调试功能也抽象成一个ops”child” target_ops,linux-thread-db.c中实现的基于libpthread库的调试功能抽象成”multi-thread”target_ops。 整个linux多线程应用程序本地调试的结构框架如下:

从上图可以看到当调试linux多线程程序时,就会使用thread_db_ops中的相应的函数。那么问题来了,对于resume和wait这些Linux_ops中也实现的函数,会调用哪个呢?Gdb中实现了很多的target_ops,有功能相近也有完全不同功能的,比如linux_ops和file_ops。那么对于功能相近的target_ops怎样使用呢?功能不同的target_ops之间又有怎样的关系呢?这些问题gdb分层机制能解释。 Gdb中把target_ops分为了7层,每一层负责不同的功能。如图所示:

0x03:GDB调试多线程

调试进程建立具体的流程下图所示:

在创建好被调试进程之后,gdb通过ptrace(PTRACE_SETOPTIONS)设置PTRACE_O_TRACECLONE,设置过后,当被调试进程创建线程的时候,就会给自己发送一个SIGTRAP信号,让被调试进程进入stop状态,使得gdb能够捕捉到这些事件,获取tid添加到lwp_list中后,gdb会让程序继续运行,直到被调试程序发生一些需要通知gdb用户的事件,比如触发了用户设置的断点,下面是流程图

Lwp_list链表 被调试进程创建线程最终是通过clone()系统调用实现的。要捕捉子线程的创建和死亡事件,这个捕捉事件由ptrace提供的机制实现。具体机制如下图所示。

Thread_list链表: Thread_list是struct thread_info类型的一个链表,记录的是被调试进程的所有线程的信息,里面包含线程用户态和内核态的一些信息。线程用户态信息的捕获基于libthread_db库,该库提供了一组调试接口。这么一组libpthread_db调试接口在gdb中使用struct thread_db_info进行管理,该数据主要结构的具体信息如下表:

在被调试进程加载libpthread库时,会为该进程创建这么一个struct thread_db_info记录该进程要使用到的libthread_db提供的调试接口。其中比较重要的是: td_create_bp_addr和td_death_bp_addr。这两个地址是对应libpthread库中的某个位置。当调用libpthread库创建线程或者线程死亡时,一定会分别调用这么两个addr处的代码。Gdb通过在这两个位置设置断点来捕获libpthread库的线程创建和死亡事件,断点的类型为bp_thread_event. 被调试程序创建子进程或者子进程死亡,会执行到libpthread库的td_create_bp_addr或td_death_bp_addr地址处,触发断点。线程进入stop状态 gdb 通过waitpid()监测到被调试进程的状态改变,分析子进程发生的事件,判断为bp_thread_event的断点触发。如果是create,获取新创建线程struct thread_info的相关的信息,并且加入到thread_list中;如果是death,从thread_list中删除该线程。

0x04:总结

GDB确定我们调试的程序是否为多线程, 通过判断被调试程序是否加载libpthread库来判断的。(捕捉装载libpthread库这个事件)也就是一旦被调试程序装在libpthread库,( Observer观察者模式)我们就应做初始化多线程调试功能。

下一篇
举报
领券