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

目录

[一、gdb/strace 跟踪程序系统调用](#一、gdb/strace 跟踪程序系统调用)

二、实现原理

三、代码实现

四、总结


(代码: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单步调试

一、gdb/strace 跟踪程序系统调用

gdb、strace都能够跟踪被调试程序在执行过程中的所有系统调用,其底层依赖的就是ptrace(PTRACE_SYSCALL) 的能力。当使用 PTRACE_SYSCALL 时,被跟踪的进程会在每次系统调用的开始或结束时被暂停挂起,并通知父进程(gdb、strace)。这使得开发者能够详细地监控和分析程序的系统调用行为,对于调试和性能分析非常有用。

下图是strace跟踪ls命令执行期间,用到的所有系统调用信息:

二、实现原理

上图是gdb跟踪被调试程序执行过程中所有系统调用的原理:

1)gdb通过ptrace(PTRACE_SYSCALL),为被调试程序的task置位TIF_SYSCALL_TRACE,然后返回;

2)被调试程序执行系统调用陷入内核;

3)在系统调用的入口,调用tracehook_report_syscall,判断当前task是否置位TIF_SYSCALL_TRACE;

4)若置位,则将PTRACE_EVENTMSG_SYSCALL_ENTRY记录到被调试程序task的ptrace_message中;

5)随后给父进程gdb发送SIGCHLD信号,并唤醒gdb的wait操作,同时设置父进程gdb wait操作的status值 ( (SIGTRAP | 0x80) << 8) | 0X70;其中,0x80代表被调试程序触发了syscall!

6)被调试程序将自己挂起;

7)gdb被唤醒后,检查wait的status返回值内容,发现置位了0x80,说明被调试程序执行了syscall;

8)gdb通过ptrace(PTRACE_GETEVENTMSG) 获取被调试任务内核中的task->ptrace_message内容,来判断当前被调试程序是刚进入syscall、还是已经执行完毕syscall;

9)gdb唤醒被调试程序继续运行,被调试程序被调度运行后,调用invoke_syscall执行真正的系统调用任务;

10)当invoke_syscall执行完毕后,会再次调用tracehook_report_syscall,将自身挂起并通知gdb(这个流程与上述3~6步一致),唯一的区别是:此时设置到被调试任务task->ptrace_message中的字段是PTRACE_EVENTMSG_SYSCALL_EXIT;

11)gdb被唤醒后,判断出被调试程序是因为syscall挂起的,通过ptrace(PTRACE_GETEVENTMSG)可以获取到被调试程序执行完毕系统调用的信息;

三、代码实现

1、gdb、strace 通过 ptrace(PTRACE_SYSCALL) 为被调试程序置位标志

cpp 复制代码
ptrace_request
	case PTRACE_SYSCALL:
	return ptrace_resume(child, request, data) {
		if (request == PTRACE_SYSCALL)
			set_task_syscall_work(child, SYSCALL_TRACE) {
				set_ti_thread_flag(task_thread_info(t), TIF_SYSCALL_TRACE)
			}
	}

2、被调试程序进入系统调用前夕,将自己暂停下来并通知gdb

cpp 复制代码
el0t_64_sync_handler {
	el0_svc
		do_el0_svc
			el0_svc_common {
				unsigned long flags = current_thread_info()->flags
				
				if (has_syscall_work(flags)) {
					syscall_trace_enter {
						if (flags & (_TIF_SYSCALL_EMU | _TIF_SYSCALL_TRACE))
							tracehook_report_syscall(struct pt_regs *regs        = regs,
						                             enum ptrace_syscall_dir dir = PTRACE_SYSCALL_ENTER) {
								regno = (is_compat_task() ? 12 : 7)
								saved_reg = regs->regs[regno]
								regs->regs[regno] = dir
								
								if (dir == PTRACE_SYSCALL_ENTER) {
									tracehook_report_syscall_entry(regs){
										ptrace_report_syscall(regs, unsigned long message = PTRACE_EVENTMSG_SYSCALL_ENTRY) {
/* 保存syscall entry/exit event */			current->ptrace_message = message		// 保存 PTRACE_EVENTMSG_SYSCALL_ENTRY 到ptrace_message, 之后gdb会调用ptrace来获取该信息
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>			ptrace_notify(int exit_code = SIGTRAP | 0x80) {
												ptrace_do_notify(int signr = SIGTRAP, int exit_code = exit_code, int why = CLD_TRAPPED) {
													ptrace_stop(exit_code, why, int clear_code = 1, &info) {	// 通知tracer, 并将自己挂起
														current->last_siginfo = info
														current->exit_code = exit_code
														
														do_notify_parent_cldstop(current, true, why = CLD_TRAPPED)
															info.si_signo  = SIGCHLD
															info.si_code   = why			// A.K.A: CLD_TRAPPED
															info.si_status = tsk->exit_code & 0x7f
															
															__group_send_sig_info(SIGCHLD, &info, parent)
																send_signal(sig, info, p, PIDTYPE_TGID)
																	__send_signal(sig, info, t, type, force)

															__wake_up_parent
														freezable_schedule
														current->last_siginfo = NULL	// after wake up by gdb, clear last_siginfo
													}
												}
											}
											
											if (current->exit_code) {
												send_sig(current->exit_code, current, 1)
												current->exit_code = 0
											}
											
											current->ptrace_message = 0
											return fatal_signal_pending(current)
										}//ptrace_report_syscall
									}//tracehook_report_syscall_entry
								}
								regs->regs[regno] = saved_reg
							}
						return regs->syscallno
					}//syscall_trace_enter
				}
				invoke_syscall
				...
			}
}

3、被调试程序系统调用执行完毕后,将自己暂停下来并通知gdb

cpp 复制代码
el0t_64_sync_handler {
	el0_svc
		do_el0_svc
			el0_svc_common {
				unsigned long flags = current_thread_info()->flags
				
				if (has_syscall_work(flags)) {
					syscall_trace_enter
				}
				
				invoke_syscall
				
				syscall_trace_exit {
					unsigned long flags = READ_ONCE(current_thread_info()->flags)
					
					if (flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP))
						tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT) {
							tracehook_report_syscall_exit(regs, step = 0) {
								ptrace_report_syscall(regs, unsigned long message = PTRACE_EVENTMSG_SYSCALL_EXIT) {
									current->ptrace_message = message
									ptrace_notify(exit_code = SIGTRAP | 0x80)
										ptrace_do_notify(SIGTRAP, exit_code, why = CLD_TRAPPED) {
											kernel_siginfo_t info
											info.si_signo = signr
											info.si_code  = exit_code  // A.K.A: SIGTRAP | 0x80
											
											ptrace_stop(exit_code, why, 1, &info) {
												current->last_siginfo = info
												current->exit_code = exit_code

												do_notify_parent_cldstop(current, true, why) {
													info.si_signo  = SIGCHLD
													info.si_code   = why			// A.K.A: CLD_TRAPPED
													info.si_status = tsk->exit_code & 0x7f
													
													__group_send_sig_info(SIGCHLD, &info, parent)
														send_signal(sig, info, p, PIDTYPE_TGID)
															__send_signal(sig, info, t, type, force)
												}
												
												current->last_siginfo = NULL	// after wake up by gdb, clear last_siginfo
											}
										}//ptrace_do_notify
								}//ptrace_report_syscall
							}//tracehook_report_syscall_exit
						}//tracehook_report_syscall	
				}//syscall_trace_exit
			}//el0_svc_common
}

四、总结

gdb、strace监控被调试程序的系统调用,主要是依赖系统调用的路径上,根据被调试程序是否置位TIF_SYSCALL_TRACE,通过tracehook_report_syscall将自身暂停,并记录相应的信息(PTRACE_EVENTMSG_SYSCALL_ENTRY、PTRACE_EVENTMSG_SYSCALL_EXIT)到current->ptrace_message中供后续gdb、strace通过ptrace(PTRACE_GETEVENTMSG)获取,最后通知gdb。

相关推荐
斗转星移343 分钟前
Ubuntu20.04 中使用vscode中编辑查看PlantUML
linux·vscode·uml·plantuml
sukida1002 小时前
BIOS主板(非UEFI)安装fedora42的方法
linux·windows·fedora
●^●2 小时前
Linux 权限修改详解:chmod 命令与权限数字的秘密
linux
唯独失去了从容2 小时前
WebRTC服务器Coturn服务器中的通信协议
运维·服务器·webrtc
光而不耀@lgy3 小时前
C++初登门槛
linux·开发语言·网络·c++·后端
偶尔微微一笑3 小时前
AI网络渗透kali应用(gptshell)
linux·人工智能·python·自然语言处理·编辑器
Run1.4 小时前
深入解析 Linux 中动静态库的加载机制:从原理到实践
linux·运维·服务器
The Mr.Nobody4 小时前
STM32MPU开发之旅:从零开始构建嵌入式Linux镜像
linux·stm32·嵌入式硬件
老兵发新帖4 小时前
Ubuntu 上安装 Conda
linux·ubuntu·conda
秋秋秋秋秋雨4 小时前
linux安装单节点Elasticsearch(es),安装可视化工具kibana
linux·elasticsearch·jenkins