linux ptrace 图文详解(八) gdb跟踪被调试程序的子线程、子进程

news2025/5/10 7:10:25

目录

一、gdb跟踪被调试程序的fork、pthread_create操作

二、实现原理

三、代码实现

四、总结


(代码:linux 6.3.1,架构:arm64)

One look is worth a thousand words.  —— Tess Flanders

相关链接:

linux ptrace 图文详解(一)基础介绍

linux ptrace 图文详解(二) PTRACE_TRACEME 跟踪程序

linux ptrace 图文详解(三) PTRACE_ATTACH 跟踪程序

linux ptrace 图文详解(四) gdb设置软断点

linux ptrace 图文详解(五) gdb设置硬断点、观察点

linux ptrace 图文详解(六) gdb单步调试

linux ptrace 图文详解(七) gdb、strace跟踪系统调用

在开始之前,我们先思考几个问题:

1)gdb调试一个程序,该程序所创建的子进程、子线程,是否同样会被gdb跟踪?

2)若gdb可以跟踪被调试程序的子进程、子线程,这是如何实现的?gdb如何获取到子进程、子线程的信息?

3)gdb一定要跟踪被调试程序的子线程、子进程么?

一、gdb跟踪被调试程序的fork、pthread_create操作

        GDB 从 版本 7.0 开始支持用户显式控制是否监控被调试程序创建的子进程和子线程。以下是一些gdb控制调试子进程、子线程的一些常见指令:

        1)gdb控制子进程调试

                set follow-fork-mode <mode>

                mode选项:

                - parent(默认):仅调试父进程,子进程继续运行不受监控

                - child:切换到调试子进程,父进程继续运行

                set detach-on-fork <on/off>

                - on:分离非调试的进程(若选择跟踪子进程时,父进程会被分离)

                - off:同时调试父子进程(需配合inferior命令切换)

        2)gdb控制子线程调试

                set scheduler-locking <mode>

                mode选项:

                - off(默认):所有线程自由运行;

                - on:仅当前调试的线程运行,其他线程暂停;

                - setp:单步执行时仅当前线程运行(避免其他线程干扰);

        使用示例:

# 启动gdb设置调试模式
gdb ./test
(gdb) set follow-fork-mode child  # 跟踪子进程
(gdb) set detach-on-fork off      # 不分离父进程
(gdb) break child_function        # 在子进程函数设断点
(gdb) run

# 切换进程
(gdb) info inferiors
  Num  Description       Executable
  1    process 1234      ./test (父进程)
  2    process 1235      ./test (子进程)
(gdb) inferior 2  # 切换到子进程

二、实现原理

gdb跟踪被调试程序创建的子进程/子线程的原理如下:

        1)被调试程序(父进程)调用fork、pthread_create后,陷入内核的系统调用都是kernel_clone;

        2)为 子进程/子线程 创建对应的task_struct对象,并且dup相应的内核对象;

        3)子进程/子线程 的task_struct对象,继承父进程的ptrace字段(该字段的用途:控制当前进程的哪些行为需要被gdb接管,该字段通常是由gdb通过ptrace进行设置的);

        4)父进程 给 子进程/子线程 的task对象添加一个SIGSTOP信号,目的是让子进程/子线程被调度运行时将自己挂起;

        5)父进程通过wake_up_new_task 将 子进程/子线程 添加到就绪队列等待被调度运行;

        6)子进程/子线程被调度运行时,返回用户态前夕,检查是否存在pending信号,发现有SIGSTOP,于是将自己挂起,并通知gdb;

        7)父进程 将 子进程/子线程 的pid,记录到自己的ptrace_message字段中(后续gdb会来拿这个信息);

        8)父进程发现自己的task->ptrace字段包含了 PTRACE_EVENT_CLONE、PTRACE_EVENT_FORK,代表当前任务的fork、pthread_create动作需要通知gdb,于是调用ptrace_stop,通知父进程并将自己挂起;

        9)gdb被唤醒后,通过wait的status参数得知被调试程序执行了fork、pthread_create动作;

        10)gdb通过ptrace(PTRACE_GETEVENTMSG),陷入内核获取记录在父进程task->ptrace_message中的信息,该信息就是父进程创建的 子进程/子线程 的 pid;

        12)gdb 为 被调试程序创建的 子进程/子线程 创建对应的数据结构,记录在gdb中,至此gdb侧就掌握了被调试程序的所有子线程、子进程相关信息;

        13)最后,gdb通过ptrace(PTRACE_CONT)将被调试程序(父进程)、及其创建的 子进程/子线程 唤醒继续运行;

        14)父进程 以及其创建的 子进程/子线程 被调度继续运行;

三、代码实现

1、gdb为被调试程序设置对应的ptrace字段(告知子进程其哪些动作需要被gdb拦截)

// Case1
linux_process_target::post_create_inferior
	linux_enable_event_reporting
		supported_ptrace_options = (PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK
									| PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC)
		options &= supported_ptrace_options
		ptrace(PTRACE_SETOPTIONS, pid, ..., options)
            flags = task->ptrace
    		flags &= ~(PTRACE_O_MASK << PT_OPT_FLAG_SHIFT)
    		flags |= (options << PT_OPT_FLAG_SHIFT)
    		task->ptrace = flags

// Case2
linux_enable_event_reporting (pid_t pid, int options) {
	if (supported_ptrace_options == -1) {
		linux_check_ptrace_features() {
			supported_ptrace_options = (PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK
										| PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACEEXEC)
			...
		}
	}
	
	/* We always want clone events. */
	options |= PTRACE_O_TRACECLONE
	
	/* Filter out unsupported options. */
	options &= supported_ptrace_options
	
	ptrace(PTRACE_SETOPTIONS, pid, (PTRACE_TYPE_ARG3) 0, (PTRACE_TYPE_ARG4) (uintptr_t) options) {
		sys_ptrace(request, pid, addr, data) {
			ptrace_request(struct task_struct *child, long request, unsigned long addr, unsigned long data) {
				switch (request)
					case PTRACE_SETOPTIONS:
					ptrace_setoptions(child, data) {
						flags = child->ptrace
						flags &= ~(PTRACE_O_MASK << PT_OPT_FLAG_SHIFT)
						flags |= (data << PT_OPT_FLAG_SHIFT)			// 即: flag |= data << 3
						child->ptrace = flags							// <<<<<< 将 PTRACE_O_TRACEFORK 等event, 设置给目标任务task_struct->ptrace字段
					}
			}
		}
	}
}

2、父进程执行fork/pthread_create,在内核中的kernel_clone实现

SYSCALL_DEFINE0(fork) {
	kernel_clone(struct kernel_clone_args *args)
		int trace = 0
		
		u64 clone_flags = args->flags
		
		/* Determine whether and which event to report to ptracer. */
		if (!(clone_flags & CLONE_UNTRACED)) {
			if (clone_flags & CLONE_VFORK)
				trace = PTRACE_EVENT_VFORK
			else if (args->exit_signal != SIGCHLD)				// pthread_create
				trace = PTRACE_EVENT_CLONE
			else
				trace = PTRACE_EVENT_FORK
			
			/******** 判断current是否包含对应trace event *********/
			ret = ptrace_event_enabled(struct task_struct *task = current, int event = trace) {
				return task->ptrace & PT_EVENT_FLAG(event)
			}
			if (likely(!ret))	
				trace = 0
		}
		
		struct task_struct *p = copy_process(NULL, trace, NUMA_NO_NODE, args) {
			p = dup_task_struct(current, node)												// 为新创建的任务生成一个新的task_struct
			
			ptrace_init_task(p, bool ptrace = (clone_flags & CLONE_PTRACE) || trace)		// initialize ptrace state for a new child
				if (unlikely(ptrace) && current->ptrace)
					child->ptrace = current->ptrace				/* Set ptrace event to new child task's ptrace, which will be checked later */
					__ptrace_link(child, current->parent, current->ptracer_cred)
					
					sigaddset(&child->pending.signal, SIGSTOP)								// Add SIGSTOP to new task's pending.signal
		}
		wake_up_new_task(p)																	// new task will stop when go back to user space a.k.a do_signal
		
		if (unlikely(trace)) {	/* forking complete and child started to run, tell ptracer */
			ptrace_event_pid(event = trace, pid) {
				message = pid_nr_ns(pid, ns)			/* message: new task's pid. 保存在父进程的task_struct->ptrace_message中 */
				ptrace_event(event, message)
					if (unlikely(ptrace_event_enabled(current, event)))
						current->ptrace_message = message
						ptrace_notify(exit_code = (event << 8) | SIGTRAP)					// event: PTRACE_EVENT_FORK / PTRACE_EVENT_CLONE / PTRACE_EVENT_VFORK
							ptrace_do_notify(SIGTRAP, exit_code, CLD_TRAPPED)
								ptrace_stop(exit_code, why, 1, &info)
			}
		}
}

3、gdb收集被调试程序fork/pthread_create创建的子进程/子线程信息

linux_process_target::wait_for_event_filtered {	// 3) *** gdb获取子线程/子进程 pid信息 ***
	/* Always pull all events out of the kernel */
	while (event_child == NULL) {
		ret = my_waitpid(wstatp)
		if (ret > 0) {
			linux_process_target::filter_event(int lwpid = ret, int wstat = *wstatp) {
				
				ret = linux_is_extended_waitstatus (wstat)							// 该函数用于check wait返回后的入参,是否有event事件, 即: PTRACE_EVENT_FORK、PTRACE_EVENT_CLONE 这类事件
					return (linux_ptrace_get_extended_event (wstat) != 0)
						return (wstat >> 16)
						
				if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP && ret) {
					linux_process_target::handle_extended_wait {
						int event = linux_ptrace_get_extended_event (wstat)
							return (wstat >> 16)
						if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK) || (event == PTRACE_EVENT_CLONE)) {
							/* Get the pid of the new lwp. */
							ptrace (PTRACE_GETEVENTMSG, lwpid_of (event_thr), (PTRACE_TYPE_ARG3) 0, &new_pid)
								*(unsigned long *)data = child->ptrace_message
								A.K.A
								*new_pid = child->ptrace_message				// 获取新的task的pid
							
							/* If we haven't already seen the new PID stop, wait for it now.  */
							if (!pull_pid_from_list (&stopped_pids, new_pid, &status)) {
								ret = my_waitpid (new_pid, &status, __WALL)
							}
						}
						
						if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK) {
							ptid = ptid_t (new_pid, new_pid)
							/* Add the new process to the tables and clone the breakpoint lists of the parent. */
							child_proc = add_linux_process (new_pid, 0)
							child_lwp = add_lwp (ptid)
							child_lwp->stopped = 1
							clone_all_breakpoints (child_thr, event_thr)
							...
						}
					}
				}
			}
			/* Retry until nothing comes out of waitpid. */
			continue
		}
		
		/* Now that we've pulled all events out of the kernel, resume
		 * LWPs that don't have an interesting event to report.  */
		for_each_thread ([this] (thread_info *thread) {
			linux_process_target::resume_stopped_resumed_lwps(thread)
				resume_one_lwp {
					resume_one_lwp_throw
						/* 将fork/pthread_create的调用者及新创建的任务重新唤醒 */
						ptrace (PTRACE_CONT, lwpid_of (thread),...)
		}
		

		}
	}
}

四、总结

        被调试程序tracee创建新进程/线程时, 会给新任务添加SIGSTOP信号,同时将新创建子任务的pid记录到被调试任务的task->ptrace_message(用于后续gdb通过ptrace GETEVENTMSG来获取新创建任务的pid),然后通过ptrace_stop将自己及新创建的子任务都挂起并通知gdb, 待gdb收集好相关信息后, 会将被调试程序及其创建的子任务再次唤醒。

        至于被调试程序如何知道自己执行fork、pthread_create的时候需要通知gdb,是由gdb通过ptrace(PTRACE_SETOPTIONS)进行设置的。由gdb控制,是否需要跟踪被调试程序创建的子进程、子线程。

        最后,回答下文章开头的几个问题:

        1)被调试程序通过fork/pthread_create创建的子进程/子线程,同样会被gdb跟踪,不过gdb可以控制是否需要跟踪,也可以设置为不跟踪;

        2)在kernel_clone中,会将被调试程序以及其创建的子进程/子线程通过不同方式将其挂起,然后通知gdb;

        3)gdb通过ptrace(PTRACE_GETEVENTMSG)获取子进程/子线程的pid信息;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2372112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游戏:用python写梦幻西游脚本(谢苏)

《梦幻西游》是一款受欢迎的网络游戏&#xff0c;许多玩家希望通过脚本来增强游戏体验&#xff0c;比如自动打怪、自动治疗等。本文将为您展示一个用Python编写简单《梦幻西游》自动打怪脚本的方案。 需求分析 1.1 具体问题 在《梦幻西游》中&#xff0c;玩家需要频繁与怪物进行…

Spring Boot 3.x集成SaToken使用swagger3+knife4j 4.X生成接口文档

说一说Spring Boot 3.X集成SaToken使用swagger3并使用第三方的knife4j踩过的坑&#xff0c;废话不多说直接上正题&#xff0c;SaToken的我就不贴了 第一步当然是要先导入相关的依赖&#xff0c;包括swagger和knife4j&#xff0c;如下 <dependency><groupId>com.gi…

用Python监控金价并实现自动提醒!附完整源码

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【星海网址导航】&#x1f4bb;香港大宽带-4H4G 20M只要36/月&#x1f449; 点此查看详情 在日常投资中&#xff0c;很多朋友喜欢在一些平台买点黄金&#xff0c;低买高卖赚点小差价。但黄金价格实时波动频繁&#xf…

ChatTempMail - AI驱动的免费临时邮箱服务

在当今数字世界中&#xff0c;保护在线隐私的需求日益增长。ChatTempMail应运而生&#xff0c;作为一款融合人工智能技术的新一代临时邮箱服务&#xff0c;它不仅提供传统临时邮箱的基本功能&#xff0c;还通过AI技术大幅提升了用户体验。 核心功能与特性 1. AI驱动的智能邮件…

掌握单元测试:提升软件质量的关键步骤

介绍 测试&#xff1a;是一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。 阶段划分&#xff1a;单元测试、集成测试、系统测试、验收测试。 测试方法&#xff1a;白盒测试、黑盒测试及灰盒测试。 单元测试&#xff1a;就是针对最小的功能单元&#xff08;方法&…

YOLOv1模型架构、损失值、NMS极大值抑制

文章目录 前言一、YOLO系列v11、核心思想2、流程解析 二、损失函数1、位置误差2、置信度误差3、类别概率损失 三、NMS&#xff08;非极大值抑制&#xff09;总结YOLOv1的优缺点 前言 YOLOv1&#xff08;You Only Look Once: Unified, Real-Time Object Detection&#xff09;由…

【论文阅读】——Articulate AnyMesh: Open-Vocabulary 3D Articulated Objects Modeling

文章目录 摘要一、介绍二、相关工作2.1. 铰接对象建模2.2. 部件感知3D生成 三、方法3.1. 概述3.2. 通过VLM助手进行可移动部件分割3.3. 通过几何感知视觉提示的发音估计3.4. 通过随机关节状态进行细化 四、实验4.1. 定量实验发音估计设置: 4.2. 应用程序 五、结论六、思考 摘要…

HarmonyOS基本的应用的配置

鸿蒙HarmonyOS组建页面 1、创建ets文件并配置2、修改main_pages.json文件3、修改EntryAbility.ets文件&#xff08;启动时加载的页面&#xff09; 1、创建ets文件并配置 Index.ets是创建项目自动构建生成的&#xff0c;我们可以将其删除掉&#xff0c;并重新在page文件夹下创建…

【redis】集群模式

Redis Cluster是Redis官方推出的分布式解决方案&#xff0c;旨在通过数据分片、高可用和动态扩展能力满足大规模数据存储与高并发访问的需求。其核心机制基于虚拟槽分区&#xff0c;将16384个哈希槽均匀分配给集群中的主节点&#xff0c;每个键通过CRC16哈希算法映射到特定槽位…

DeepSeek实战--微调

1.为什么是微调 &#xff1f; 微调LLM&#xff08;Fine-tuning Large Language Models&#xff09; 是指基于预训练好的大型语言模型&#xff08;如GPT、LLaMA、PaLM等&#xff09;&#xff0c;通过特定领域或任务的数据进一步训练&#xff0c;使其适应具体需求的过程。它是将…

移动端前端开发中常用的css

在开发移动端项目的时候&#xff0c;很多样式都是相同的&#xff0c;比如说图标大小&#xff0c;头像大小&#xff0c;页面底部保存(添加按钮&#xff09;&#xff0c;项目主体颜色等等&#xff0c;对于这些在项目中常用到的&#xff0c;通常都会写在公共样式中&#xff08;pub…

Linux安装Weblogic 教程

前言 WebLogic 是一个由 Oracle 提供的企业级应用服务器&#xff0c;广泛用于部署和管理 Java EE&#xff08;Enterprise Edition&#xff09;应用程序。它支持多种服务&#xff0c;包括 Web 服务、企业信息系统、消息驱动的应用等。它是一个强大的应用服务器&#xff0c;旨在…

flutter 的热更新方案shorebird

Flutter 热修复&#xff08;Shorebird&#xff09;_flutter shorebird-CSDN博客 Preview Locally | ShorebirdLearn how to preview an existing release of your application.https://docs.shorebird.dev/code-push/preview/ 控制台&#xff1a; Shorebird Console 文档&…

创建型模式:抽象工厂(Abstract Factory)模式

一、概念与核心思想​ 抽象工厂(Abstract Factory)模式是创建型设计模式的重要成员,它提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。该模式将对象的创建逻辑封装在抽象工厂及其具体实现类中,客户端通过抽象工厂接口获取所需的对象族,实现对象创…

PDF文档解析新突破:图表识别、公式还原、手写字体处理,让AI真正读懂复杂文档!

要想LLM大模型性能更佳&#xff0c;我们需要喂给模型看得懂的高质量数据。那有没有一种方法&#xff0c;能让我们把各种文档“读懂”&#xff0c;再喂给大模型使用呢&#xff1f; 如果你用传统OCR工具直接从PDF中提取文本&#xff0c;结果往往是乱序、缺失、格式错乱。因为实际…

Redis 主从复制集群搭建教程

目录 为什么要搭建 Redis 主从复制集群&#xff1f;搭建 Redis 主从复制集群前提条件步骤一&#xff1a;创建 Docker 网络步骤二&#xff1a;启动 Redis 主节点步骤三&#xff1a;启动 Redis 从节点步骤四&#xff1a;验证复制状态步骤五&#xff1a;使用 Python 连接 Redis 集…

共模电感在开关电源交流侧的应用原理与原因

在开关电源的设计中&#xff0c;共模电感是一个关键的电子元件&#xff0c;它常被连接在开关电源的交流一侧。然而&#xff0c;很多人虽然对共模电感并不陌生&#xff0c;但对于它为何要接在交流一侧&#xff0c;可能并没有深入理解。接下来&#xff0c;我们将详细探讨共模电感…

MySQL——七、索引

优势&#xff1a;极高查询效率&#xff1b;极高排序效率 劣势&#xff1a;占用磁盘空间&#xff1b;降低更新表的速度&#xff08;可忽略&#xff0c;磁盘相对便宜&#xff1b;增删改比例较小&#xff09; 索引结构 MYSQL的索引是在存储引擎层实现的&#xff0c;不同的存储引…

HTML应用指南:利用POST请求获取全国德邦快递服务网点位置信息

德邦快递作为中国领先的综合性物流服务提供商,自1996年成立以来,始终致力于为客户提供高效、安全的大件快递及其他物流解决方案。德邦快递凭借其强大的直营模式、“最后一公里”的优质服务以及对科技的持续投入,在竞争激烈的物流市场中占据了重要位置。特别是在大件快递领域…

高级可视化图表分析实践——以《大侠立志传》武器系统为例

高级可视化图表分析实践——以《大侠立志传》武器系统为例 引言武器类型分布矩形树图结论 不同品质/类别武器的攻击力分布情况蜂群图分析结论 武器来源桑基图分析结论 武器附加属性词云图分析结论 不同品级武器装备熟练度要求/特质要求离散热力图结论品质与熟练度的正相关性品质…