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

news/2025/3/15 20:49:07/

目录

一、基础介绍

二、PTRACE_TRACE 实现原理

三、代码实现

四、总结


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

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

一、基础介绍

        GDB(GNU Debugger)是 Linux 系统中功能强大的调试工具,用于调试 C、C++ 等编程语言编写的程序。GDB 支持两种主要的调试方式:"gdb主动加载被调试程序""gdb通过 attach 调试正在运行的程序"。这两种方式各有特点,适用于不同的调试场景。

        1)在 主动加载被调试程序 的方式中:GDB 通过 fork 和 exec 系统调用启动目标程序,并使用 ptrace 对其进行控制。这种方式适用于从头开始调试程序,开发者可以在程序启动时设置断点、单步执行或观察变量的初始状态。

        2)在 通过 attach 调试正在运行的程序 的方式中:GDB 通过 ptrace 附加到目标进程的 PID,直接接管其执行流程。这种方式适用于调试已经运行的程序,尤其是当程序出现崩溃、死锁或性能问题时,开发者可以实时分析程序的状态、调用栈和内存信息。

        两种调试方式的对比:

二、PTRACE_TRACE 实现原理

        以上就是 gdb 加载“被调试程序”启动阶段时的完整实现流程:

        1)gdb 调用 fork 创建子进程,用作后续的被调试程序,fork执行完毕后,gdb就调用wait系统调用等待在子进程上;

        2)子进程调用ptrace系统调用,请求类型为PTRACE_TRACEME,在内核中给子进程的task_struct对象置上PT_PTRACED标志;

        3)子进程调用exec,加载被调试程序的ELF文件;

        4)内核中调用 load_elf_binary 完成ELF加载工作;

        5)在内核的 exec 末期,发现自身置位了PT_PTRACED标志,于是调用ptrace_notify,给子进程自身发送一个SIGTRAP信号用于后续将子进程挂起

        6)子进程exec系统调用执行完毕后,在返回用户态前夕检查信号,发现自身有一个SIGTRAP信号需要处理,于是走信号处理流程

        7)子进程在内核的信号处理流程中,发送SIGCHLD信号给父进程gdb,并唤醒父进程gdb,同时将自身挂起;

        8)gdb被唤醒后,控制权交给用户,用户可以对被调试程序进行一系列操作(如:打断点、观察点等);

        9)用户操作完毕后,输入 continue指令,让目标程序继续运行。该指令实际会调用ptrace(PTRACE_CONT) 系统调用,在内核中该系统调用会将子进程唤醒;

        10)子进程被唤醒后,重新返回到用户态,开始执行第一条指令!

三、代码实现

1、gdb 加载 被调试程序

start_command {run_command_1 {run_target->create_inferior (exec_file, current_inferior ()->args (),current_inferior ()->environment.envp (), from_tty){fork_inferior(exec_file, allargs, env, void (*traceme_fun) () = gnu_ptrace_me, NULL, NULL, NULL, NULL) {pid = fork ()if (pid == 0) {					/* 子进程(被调试程序) */(*traceme_fun) ()A.K.Agnu_ptrace_me {ptrace (PTRACE_TRACEME)}execvp (argv[0], &argv[0])	// 加载被调试程序ELF}return pid	/* 父进程(GDB): 返回子进程pid */}...}}
}

2、PTRACE_TRACEME 内核实现

SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,unsigned long, data)
{if (request == PTRACE_TRACEME) {ptrace_traceme() {write_lock_irq(&tasklist_lock)if (!current->ptrace) {ret = security_ptrace_traceme(struct task_struct *parent = current->parent) {return cap_ptrace_traceme(parent)}if (!ret && !(current->real_parent->flags & PF_EXITING)) {current->ptrace = PT_PTRACED				// <<<<<<  子进程标记自己处于“PTRACED状态”  <<<<<<ptrace_link(current, current->real_parent)}}write_unlock_irq(&tasklist_lock)}}...
}

3、PTRACE_CONT 内核实现

ptrace_request(struct task_struct *child, long request, unsigned long addr, unsigned long data) {switch (request) {case PTRACE_CONT:return ptrace_resume(child, request, data) {... 	/* PTRACE跟踪syscall、单步调试等处理 */spin_lock_irq(&child->sighand->siglock)child->exit_code = datachild->jobctl &= ~JOBCTL_TRACEDwake_up_state(child, __TASK_TRACED) {return try_to_wake_up(p, state, 0)		// <<<<< 尝试唤醒被调试程序 <<<<<}spin_unlock_irq(&child->sighand->siglock)}}
}

4、内核 exec执行完毕后,返回用户态前夕,发送SIGCHLD给父进程gdb

// gdb通过fork创建出来的子进程, 调用exec加载被调试程序, 并给自己发送SIGTRAP信号
SYSCALL_DEFINE3(execve,const char __user *, filename,const char __user *const __user *, argv,const char __user *const __user *, envp)
{do_execvedo_execveat_commonbprm_execveexec_binprm {search_binary_handler {list_for_each_entry(fmt, &formats, lh) {retval = fmt->load_binary(bprm)		// <<<<< 加载ELF程序 主体函数 <<<<<}}### 给自身发送SIGTRAP信号, 在exec系统调用执行完毕返回用户态前夕, 处理该信号ptrace_event(PTRACE_EVENT_EXEC, old_vpid) {if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)send_sig(SIGTRAP, current, 0)}}
}// 子进程exec执行完毕返回用户态前夕, 处理自身的SIGTRAP信号, 发送信号给GDB, 并将其唤醒, 随后自身挂起
exit_to_user_mode(regs) {prepare_exit_to_user_modelocal_daif_maskdo_notify_resume {if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))do_signal {get_signalptrace_signalptrace_stop(exit_code = signo, why = CLD_TRAPPED, 0, info)	/* Stop tracee itself, and notify parent tracer */{current->last_siginfo = infocurrent->exit_code = exit_codedo_notify_parent_cldstop(current, true, why) {info.si_signo  = SIGCHLDinfo.si_code   = whyinfo.si_status = tsk->exit_code & 0x7fsend_signal_locked(SIGCHLD, &info, parent, PIDTYPE_TGID)	// <<<<<< 发送信号给父进程GDB__wake_up_parent(tsk, parent)		// <<<<<< 唤醒父进程GDB}schedule()		// <<<<<< 被调试程序自身挂起}}}
}

四、总结

        gdb加载 “被调试程序” 进行调试的模式,主要依赖 PTRACE_TRACEME请求类型的ptrace系统调用,给子进程置上ptraced标记,后续子进程调用exec加载被调试程序ELF时给自己发送一个SIGTRAP信号,最后exec系统调用执行完毕并返回用户态前夕,在信号处理流程中,将自身挂起并唤醒GDB,让用户可以接管GDB串口,对被调试程序进行一系列调试操作。


http://www.ppmy.cn/news/1579398.html

相关文章

C#类型转换大总结

在 C# 中,类型转换是将数据从一种类型转换为另一种类型的过程,常见的转换方式包括隐式转换、显式转换、方法转换(如 Convert 类或 Parse/TryParse)以及自定义转换操作符。以下是详细的分类和示例: 隐式转换(Implicit Conversion) 无需显式声明,编译器自动完成,通常发生…

2025-3-12 leetcode刷题情况(贪心算法--区间问题)

一、452.用最少数量的箭引爆气球 1.题目描述 2.代码 3.思路 使用 Arrays.sort 方法对 points 数组按照气球的起始坐标进行排序。这里使用 Integer.compare(a[0], b[0]) 作为比较器&#xff0c;确保气球按起始坐标从小到大排列。将箭的数量 count 初始化为 1&#xff0c;因为至…

AWS Bedrock 正式接入 DeepSeek-R1 模型:安全托管的生成式 AI 解决方案

亚马逊云科技&#xff08;AWS&#xff09;于 2024 年 1 月 30 日 宣布&#xff0c;DeepSeek-R1 模型 正式通过 Amazon Bedrock 平台提供服务&#xff0c;用户可通过 Bedrock Marketplace 或自定义模型导入功能使用该模型。 DeepSeek-R1&#xff0c;其安全防护机制与全面的 AI 部…

wps word 正文部分段前段后间距调整无用

用了网上的方法&#xff0c;对我来说没用&#xff1a; https://zhidao.baidu.com/question/1894016349633589548.html 操作&#xff1a; 选中相关内容&#xff0c;菜单栏-开始 格式改为正文 调整段前段后间距

⭐LeetCode(数学分类) 48. 旋转图像——优美的数学法转圈(原地修改)⭐

⭐LeetCode(数学分类) 48. 旋转图像——优美的数学法转圈(原地修改)⭐ 示例 1&#xff1a; 输入&#xff1a;root [5,3,6,2,4,null,8,1,null,null,null,7,9] 输出&#xff1a;[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9] 示例 2&#xff1a; 输入&#xff1…

抽象工厂模式的C++实现示例

核心思想 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种方式&#xff0c;可以创建一系列相关或依赖的对象&#xff0c;而无需指定它们的具体类。抽象工厂模式的核心思想是&#xff1a; 抽象工厂接口&#xff1a…

快速集成1688商品API:10分钟实现跨境选品数据自动化

要快速集成 1688 商品 API 以实现跨境选品数据自动化&#xff0c;可参考以下步骤&#xff1a; 注册并申请 API 权限&#xff1a;注册账号创建应用并申请所需的 API 权限&#xff0c;如商品搜索、筛选、获取详情等相关权限。获取 API Key 和 Secret&#xff1a;在应用管理页面获…

LINUX 指令大全

Linux服务器上有许多常用的命令&#xff0c;可以帮助你管理文件、目录、进程、网络和系统配置等。以下是一些常用的Linux命令&#xff1a; 文件和目录管理 ls&#xff1a;列出当前目录中的文件和子目录 bash lspwd&#xff1a;显示当前工作目录的路径 bash pwdcd&#xff1a;切…