当前位置: 首页 > article >正文

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

目录

一、gdb attach 调试程序

二、PTARCE_ATTACH 实现原理

三、gdb attach多线程程序

四、代码实现

五、总结


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

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

相关链接:

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

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

一、gdb attach 调试程序

        上篇文章中,介绍了使用gdb加载并调试APP的方式,不过在实际应用场景下,很多程序是已经在系统中处于运行状态的。此时,若需要对正在运行的程序进行调试的话,就需要采用gdb attach的方式。

        通过 ptrace(PTRACE_ATTACH) 系统调用, 允许 gdb 附加到一个正在运行的进程上进行调试,这种功能特别适用于调试已经在运行的程序,例如在生产环境中对服务进行问题排查。

使用实例如下:

1)获取被调试程序pid

2)gdb attach 指定pid,并附加到被调试程序

        需要注意的是:使用 PTRACE_ATTACH 时可能遇到权限问题,例如:一个非root用户无法附加到一个以root用户运行的进程上。这种情况下,需要使用root用户权限来执行gdb attach 命令。

二、PTARCE_ATTACH 实现原理

        可以看到,gdb attach正在运行的程序进行调试的实现原理,就是依靠的ptrace系统调用,并且以PTRACE_ATTACH为参数。其实现原理如下图所示:

        1)gdb调用ptrace(PTRACE_ATTACH)系统调用陷入内核;

        2)根据被调试程序的pid,找到内核中该程序的task_struct对象,并为其置位PT_PTRACED,代表该进程处于被跟踪状态

        3)将gdb设置为被调试程序的养父;

        4)在内核中给被调试程序发送一个SIGSTOP信号,然后gdb的ptrace系统调用返回;

        5)被调试程序接收到信号,在内核态返回用户态前夕,执行信号处理流程;

        6)被调试程序发现自身处于PTRACED状态,于是调用ptrace_signal函数,给养父进程(gdb)发送SIGCHLD信号,并唤醒养父进程的wait系统调用;

        7)最后被调试程序将自己挂起,等待后续养父gdb将自己唤醒;

三、gdb attach多线程程序

        大部分情况下,被调试程序是一个多线程进程,那么gdb attach调试该进程时,需要针对每个线程都调用ptrace(PTRACE_ATTACH)系统调用,用于在内核中给每个被调试线程置位PT_PTRACED标志。

        上述过程的实现原理:gdb会通过遍历proc文件系统中pid对应进程中的所有线程,并针对每个线程都调用一次ptrace(PTRACE_ATTACH),大致代码如下:

linux_nat_target::attach {
	pid_t   pid = parse_pid_to_attach (args)
	ptid_t ptid = ptid_t (inferior_ptid.pid (), inferior_ptid.pid ())
	
	/* Add the initial process as the first LWP to the list. */
	lp = add_initial_lwp (ptid)
	
	/* We must attach to every LWP. If /proc is mounted, use that to find them now. */
	linux_proc_attach_tgid_threads(pid_t pid = lp->ptid.pid (), linux_proc_attach_lwp_func attach_lwp = attach_proc_task_lwp_callback) {
		xsnprintf (pathname, sizeof (pathname), "/proc/%ld/task", (long) pid)
		dir = opendir (pathname)
		for (iterations = 0; iterations < 2; iterations++) {
			while ((dp = readdir (dir)) != NULL)
				attach_lwp(ptid)
				A.K.A
				attach_proc_task_lwp_callback {
					lp = find_lwp_pid (ptid)
					int lwpid = ptid.lwp ()
					
					ptrace(PTRACE_ATTACH, lwpid, 0, 0) {	// <<<<<<< 对每个thread都调用ptrace(PTRACE_ATTACH)
						/* 1) notify PM to ptrace_link */
						/* 2) trap into kernel and send SIGSTOP to taregt task */
						child = pid_to_task(pid)
						ptrace_attach(ktask_t *task = child, long request) {
							task->restore_state_flags = task->state_flags
							task->state_flags         = K_TASK_INTERRUPTIBLE
							
							### ptrace_attach就是给当前任务发送SIGSTOP信号
							if (request != PTRACE_SEIZE) {
								task->ptrace = PT_PTRACED
								_tkill(task->pid, _SIGSTOP)
							}
						}
					}
					
					lp = add_lwp (ptid)
					add_thread (linux_target, lp->ptid)
				}
		}
		closedir (dir)
	}
}

四、代码实现

1、PTRACE_ATTACH实现原理

SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
		        unsigned long, data)
{
	struct task_struct *child
	
	child = find_get_task_by_vpid(pid)
	
	if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
		ptrace_attach(struct task_struct *task = child, request, addr, data) {
			flags = PT_PTRACED
			
			task_lock(task)
			retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS)
			task_unlock(task)
			
			write_lock_irq(&tasklist_lock)
			
			/* 1) 将被调试任务的task置上PT_PTRACED标志, 代表该任务处于被跟踪状态 */
			task->ptrace = flags
			
			/* 2) 设置gdb作为被调试程序的养父 */
			ptrace_link(struct task_struct *child = task, struct task_struct *new_parent = current) {
				__ptrace_link(child, new_parent, const struct cred *ptracer_cred = current_cred()) {
					list_add(&child->ptrace_entry, &new_parent->ptraced)
					child->parent = new_parent					 // <<<<<<<<<
					child->ptracer_cred = get_cred(ptracer_cred)
				}
			}
			
			/* 3) 发送SIGSTOP信号给被调试任务, 让其响应信号停下来并通知养父gdb */
			if (!seize)
				send_sig_info(SIGSTOP, SEND_SIG_PRIV, task)
			
			spin_lock(&task->sighand->siglock)
			if (task_is_stopped(task) && task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) {
				task->jobctl &= ~JOBCTL_STOPPED
				signal_wake_up_state(task, __TASK_STOPPED)
			}
			spin_unlock(&task->sighand->siglock)
			
			write_unlock_irq(&tasklist_lock)
			
			return retval
		}
		
		goto out_put_task_struct
	}
	
	...
	
out_put_task_struct:
	put_task_struct(child)
	
	return ret
}

2、被调试程序处理SIGSTOP并将自己挂起

exit_to_user_mode {
	prepare_exit_to_user_mode
		local_daif_mask
		do_notify_resume {
			if (thread_flags & _TIF_NEED_RESCHED)
				schedule()
			if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
				do_signal {
					get_signal
						ptrace_signal    /* Detail see below */
				}
		}
}


ptrace_signal(int signr, kernel_siginfo_t *info) {
	ptrace_stop(int exit_code          = signr,
				int why                = CLD_TRAPPED,
				int clear_code         = 0,
				kernel_siginfo_t *info = info) {
		set_special_state(TASK_TRACED)									// (1) set current task state to TRACED
			current->__state = TASK_TRACED
			A.K.A
			current->__state = TASK_WAKEKILL | __TASK_TRACED
			
		spin_unlock_irq(&current->sighand->siglock)
		
		read_lock(&tasklist_lock)
		
		if (likely(current->ptrace)) {									/* Tracer is alive */
			do_notify_parent_cldstop(struct task_struct *tsk =current,
									 bool for_ptracer = true, why = CLD_TRAPPED) {				
				struct kernel_siginfo info
				if (for_ptracer)
					parent = tsk->parent								// here parent is tracer
				
				info.si_signo  = SIGCHLD								// send SIGCHLD signal
				info.si_code   = why		// A.K.A CLD_TRAPPED
				info.si_status = tsk->exit_code & 0x7f
				
				struct sighand_struct *sighand = parent->sighand
				spin_lock_irqsave(&sighand->siglock, flags)
				
				if (sighand->action[SIGCHLD-1].sa.sa_handler != SIG_IGN) {
					__group_send_sig_info(SIGCHLD, &info, parent)		// (2) notify tracer about every stop of tracee
						send_signal
				}
				
				/* Even if SIGCHLD is not generated, we must wake up wait4 calls. */
				__wake_up_parent(struct task_struct *p = tsk,			// (3) wake up parent if it's waiting
				                 struct task_struct *parent = parent) {
					__wake_up_sync_key(struct wait_queue_head *wq_head = &parent->signal->wait_chldexit,
					                   unsigned int mode = TASK_INTERRUPTIBLE,
									   void *key = p)
						__wake_up_common_lock(wq_head, mode, 1, WF_SYNC, key)
							__wake_up_common
				}
				
				spin_unlock_irqrestore(&sighand->siglock, flags)
				
			}
			/* Don't want to allow preemption here, because sys_ptrace() needs this task to be inactive. */
			preempt_disable()
			read_unlock(&tasklist_lock)
			preempt_enable_no_resched()
			freezable_schedule()
				schedule												// (4) tracee will be scheduled out
		} else {														/* Tracer is gone */
			__set_current_state(TASK_RUNNING)
			read_unlock(&tasklist_lock)
		}
		
		spin_lock_irq(&current->sighand->siglock)
		
		recalc_sigpending_tsk(current) {								/* Queued signals ignored us while we were stopped for tracing. */
			if (PENDING(&t->pending, &t->blocked),
				PENDING(&t->signal->shared_pending, &t->blocked)) {
				set_tsk_thread_flag(t, TIF_SIGPENDING)
			}
		}
	}
}

五、总结

        PTRACE_ATTACH 的主要作用就是给被调试任务的内核task_struct对象置位PT_PTRACED标志。这样一来,后续该任务在内核中执行一些指定行为(接受信号、系统调用、处理断点异常等)时,都会判断自己处于被跟踪状态后,通知养父进程gdb,并将自己挂起。

        不论是TRACEME的方式,还是ATTACH的方式,最终目的都是将被调试任务内核task对象置位PT_PTRACED标志,并建立gdb与被调试程序之间养父的关联。

        至此,gdb 跟踪被调试程序的两种方式(TRACEME、ATTACH)的实现原理就都清楚了,我们知道GDB是如何跟被调试程序之间建立关联的。后续就可以继续讲述gdb是如何给被调试程序打软断点、硬断点、观察点,如何跟踪程序系统调用,如何跟踪程序fork、exec,如何进行单步调试等一系列底层实现原理了。


http://www.kler.cn/a/600014.html

相关文章:

  • Edge浏览器如何默认启动某个工作区 / 为工作区添加快捷方式
  • docker 容器 php环境中安装gd 、mysql 等扩展
  • 数据库原理及应用mysql版陈业斌实验一
  • 【Docker系列二】 Docker 镜像
  • Spring-Mybatis框架常见面试题
  • Java面试第十三山!《设计模式》
  • 快速部署Samba共享服务器作为k8s后端存储
  • Android adb调试应用程序
  • 解锁应急管理新境界:AR眼镜与指挥平台的完美融合
  • 常见框架漏洞:Thinkphp(TP)篇
  • 【Git流程最佳实践】 开发较大功能时应使用project branch
  • WordPress二次开发中常用到的一些变量和函数
  • [C++面试] 你了解transform吗?
  • 嵌入式驱动开发方向的基础强化学习计划
  • fuctioncall使用经验
  • QA:备份产品的存储架构采用集中式和分布式的优劣?
  • ai-by-hand-excel: 用 Excel 手搓各种 AI 算法和模型
  • 大型语言模型(LLM)推理框架的全面分析与选型指南(2025年版)
  • 【深度学习】Self-Attention机制详解:Transformer的核心引擎
  • LeetCode 942 增减字符串匹配