Linux gdb调式的原理

文章目录

  • 一、原理分析
  • 二、dmoe测试
    • 2.1 hello.s
    • 2.2 demo演示
  • 参考资料

一、原理分析

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/user.h>int main(int argc, char ** argv)
{pid_t childPid;int status;childPid = fork();if (childPid == 0) {//设置子进程进入跟踪模式ptrace(PTRACE_TRACEME, 0, 0, 0);//待调试的程序,进入调试模式状态execvp(argv[1], argv + 1);return 0;} else {//阻塞在这里--等待子进程停止waitpid(childPid, 0, 0);//子进程进入停止状态,父进程开始调试程序//调用 ptrace 系统调用来调式程序//这里只进行一次ptrace 调用//获取子进程执行第一个系统调用的系统调用号  即exec系统调用号//execve系统调用号是59struct user_regs_struct regs;ptrace(PTRACE_GETREGS, childPid, 0, &regs);long syscallNumber = regs.orig_rax;printf("syscallNumber = %d\n", syscallNumber);ptrace(PTRACE_DETACH, childPid, 0, 0);}return 0;
}

这个例子没有实际意义,这里只是用来说明gdb调试进程的原理:

# ./a.out ./hello
syscallNumber = 59
Hello, World!

子进程调用 ptrace(PTRACE_TRACEME, 0, 0, 0),进入被跟踪模式,PTRACE_TRACEME该值仅tracee使用,指示此进程将由其父进程跟踪。

父进程是tracer,子进程是tracee.

接下来让我们分析其源码:

SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,unsigned long, data)
{struct task_struct *child;long ret;if (request == PTRACE_TRACEME) {ret = ptrace_traceme();}......}
/*** ptrace_traceme  --  helper for PTRACE_TRACEME** Performs checks and sets PT_PTRACED.* Should be used by all ptrace implementations for PTRACE_TRACEME.*/
static int ptrace_traceme(void)
{int ret = -EPERM;write_lock_irq(&tasklist_lock);/* Are we already being traced? */if (!current->ptrace) {ret = security_ptrace_traceme(current->parent);/** Check PF_EXITING to ensure ->real_parent has not passed* exit_ptrace(). Otherwise we don't report the error but* pretend ->real_parent untraces us right after return.*/if (!ret && !(current->real_parent->flags & PF_EXITING)) {current->ptrace = PT_PTRACED;__ptrace_link(current, current->real_parent);}}write_unlock_irq(&tasklist_lock);return ret;
}

这里子进程设置自己的 ptrace 字段的为PT_PTRACED:

/** Ptrace flags** The owner ship rules for task->ptrace which holds the ptrace* flags is simple.  When a task is running it owns it's task->ptrace* flags.  When the a task is stopped the ptracer owns task->ptrace.*/#define PT_PTRACED	0x00000001
struct task_struct {unsigned int ptrace;
}
current->ptrace = PT_PTRACED;

接下来子进程执行exec系列的函数调用来执行程序,exec系统调用请请参考:
Linux 进程启动 execve 系统调用内核源码解析

这里只介绍和调试相关的部分:

SYSCALL_DEFINE3(execve,const char __user *, filename,const char __user *const __user *, argv,const char __user *const __user *, envp)
{struct filename *path = getname(filename);int error = PTR_ERR(path);if (!IS_ERR(path)) {error = do_execve(path->name, argv, envp);putname(path);}return error;
}
do_execve()-->do_execve_common()-->search_binary_handler()-->ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
int search_binary_handler(struct linux_binprm *bprm)
{//获取父进程的pidpid_t old_vpid;rcu_read_lock();old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));rcu_read_unlock();//检查是否处于PT_PTRACED,处于PT_PTRACED状态ptrace_event(PTRACE_EVENT_EXEC, old_vpid);}
/*** ptrace_event - possibly stop for a ptrace event notification* @event:	%PTRACE_EVENT_* value to report* @message:	value for %PTRACE_GETEVENTMSG to return** Check whether @event is enabled and, if so, report @event and @message* to the ptrace parent.** Called without locks.*/
static inline void ptrace_event(int event, unsigned long message)
{if (unlikely(ptrace_event_enabled(current, event))) {current->ptrace_message = message;ptrace_notify((event << 8) | SIGTRAP);} else if (event == PTRACE_EVENT_EXEC) {/* legacy EXEC report via SIGTRAP */if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)send_sig(SIGTRAP, current, 0);}
}

当一个进程执行 exec 系统调用的时候,会调用ptrace_event函数判断是否处于自己是否PT_PTRACED状态,即调试状态,如果处于PT_PTRACED状态,则给自己发送SIGTRAP信号,当一个进程收到SIGTRAP信号时,就会调用do_signal()内核函数对该信号进行处理:

当一个进程收到SIGTRAP信号后,如果当前进程处于被跟踪状态,即:

struct task_struct {unsigned int ptrace;
}current->ptrace && signr != SIGKILL

(1)设置其进程状态为TASK_TRACED:

struct task_struct {volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
}
#define set_current_state(state_value)		\set_mb(current->state, (state_value))set_current_state(TASK_TRACED);

(2)给父进程即调试器发送SIGCHLD信号:

do_notify_parent_cldstop()
static void do_notify_parent_cldstop(struct task_struct *tsk,bool for_ptracer, int why)
{__group_send_sig_info(SIGCHLD, &info, parent);/** Even if SIGCHLD is not generated, we must wake up wait4 calls.*/__wake_up_parent(tsk, parent);	
}				 

父进程收到SIGCHLD信号就知道子进程处于停止和被跟踪状态了,这样父进程就可以对子进程进行各种调试操作了。

SIGCHLD是一个由子进程发送给父进程的信号,用于通知父进程子进程的状态变化。具体来说,当一个子进程终止或停止时,它会向父进程发送SIGCHLD信号。

这里是指一个子进程停止时会向父进程发送SIGCHLD信号。

一个子进程发送SIGCHLD信号给父进程后,然后调用__wake_up_parent 唤醒父进程,在开头的测试例程中我们可以看到父进程 调用了waitpid函数,处于阻塞状态,因此这里唤醒父进程,这样父进程就可以开始调试子进程了。

void __wake_up_parent(struct task_struct *p, struct task_struct *parent)
{__wake_up_sync_key(&parent->signal->wait_chldexit,TASK_INTERRUPTIBLE, 1, p);
}

__wake_up_sync_key 函数用于唤醒等待队列中的进程。具体来说,通过调用该函数,父进程的等待队列被唤醒,以便其可以继续执行。

/*** __wake_up_sync_key - wake up threads blocked on a waitqueue.* @q: the waitqueue* @mode: which threads* @nr_exclusive: how many wake-one or wake-many threads to wake up* @key: opaque value to be passed to wakeup targets** The sync wakeup differs that the waker knows that it will schedule* away soon, so while the target thread will be woken up, it will not* be migrated to another CPU - ie. the two threads are 'synchronized'* with each other. This can prevent needless bouncing between CPUs.** On UP it can prevent extra preemption.** It may be assumed that this function implies a write memory barrier before* changing the task state if and only if any tasks are woken up.*/
void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{unsigned long flags;int wake_flags = WF_SYNC;......spin_lock_irqsave(&q->lock, flags);__wake_up_common(q, mode, nr_exclusive, wake_flags, key);spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL_GPL(__wake_up_sync_key);

__wake_up_sync_key 函数用于唤醒在等待队列上被阻塞的线程。

(3)发出调度请求,主动让出CPU:

freezable_schedule();

二、dmoe测试

2.1 hello.s

.section .data
message:.string "Hello, World!\n"
len = . - message.section .text
.globl _start
_start:# 调用 write() 函数输出 "Hello, World!"mov $1, %rax            # 系统调用号为 1 表示 write()mov $1, %rdi            # 文件描述符为 1 表示标准输出lea message(%rip), %rsi # 输出的字符串地址mov $len, %rdx          # 输出的字符串长度syscall                 # 调用系统调用# 调用 exit() 函数退出程序mov $60, %rax           # 系统调用号为 60 表示 exit()xor %rdi, %rdi          # 返回值为 0syscall                 # 调用系统调用

可以看到一共有八条指令。
这段汇编代码是在标准输出上输出 “Hello, World!”,然后退出程序:

as -o hello.o hello.s
ld -o hello hello.o
# ./hello
Hello, World!

2.2 demo演示

/* Code sample: using ptrace for simple tracing of a child process.
**
** Note: this was originally developed for a 32-bit x86 Linux system; some
** changes may be required to port to x86-64.
**
** Eli Bendersky (https://eli.thegreenplace.net)
** This code is in the public domain.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <unistd.h>
#include <errno.h>/* Print a message to stdout, prefixed by the process ID
*/
void procmsg(const char* format, ...)
{va_list ap;fprintf(stdout, "[%d] ", getpid());va_start(ap, format);vfprintf(stdout, format, ap);va_end(ap);
}void run_target(const char* programname)
{procmsg("target started. will run '%s'\n", programname);/* Allow tracing of this process */if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {perror("ptrace");return;}/* Replace this process's image with the given program */execl(programname, programname, 0);
}void run_debugger(pid_t child_pid)
{int wait_status;unsigned icounter = 0;procmsg("debugger started\n");/* Wait for child to stop on its first instruction */wait(&wait_status);while (WIFSTOPPED(wait_status)) {icounter++;struct user_regs_struct regs;ptrace(PTRACE_GETREGS, child_pid, 0, &regs);unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.rip, 0);procmsg("icounter = %u.  rip = 0x%08x.  instr = 0x%08x\n",icounter, regs.rip, instr);/* Make the child execute another instruction */if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) {perror("ptrace");return;}/* Wait for child to stop on its next instruction */wait(&wait_status);}procmsg("the child executed %u instructions\n", icounter);
}int main(int argc, char** argv)
{pid_t child_pid;if (argc < 2) {fprintf(stderr, "Expected a program name as argument\n");return -1;}child_pid = fork();if (child_pid == 0)run_target(argv[1]);else if (child_pid > 0)run_debugger(child_pid);else {perror("fork");return -1;}return 0;
}

在这里插入图片描述

参考资料

https://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1/

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

M2DGR数据集各相机话题名与外参名的对应关系

M2DGR数据集除了视觉惯性器件、天向相机&#xff0c;还有6个安装在同一平面、参数一致的鱼眼相机。 本文对这6个相机的安装位置、外参、topic话题进行区分。 安装图&#xff1a; 6个鱼眼相机 fish-eye camera装载在同一层。 外参情况 fish-eye camera在calibration_results…

解决Jackson解析JSON时出现的Illegal Character错误

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Vue2项目练手——通用后台管理项目第五节

Vue2项目练手——通用后台管理项目 首页组件布局面包屑&tag面包屑使用组件使用vuex存储面包屑数据src/store/tab.jssrc/components/CommonAside.vuesrc/components/CommonHeader.vue tag使用组件文件目录CommonTag.vueMain.vuetabs.js 用户管理页新增功能使用的组件页面布局…

第49节:cesium 倾斜模型osgb转3dtiles,并加载(含源码+视频)

结果示例: 完整步骤: 1、启动并登陆cesiumlab 2、准备OSGB模型数据(含下载地址) 链接:https://pan.quark.cn/s/46ac7b0b2bed 提取码:TvWL3、倾斜模型切片 选择倾斜模型data文件夹 空间参考、零点坐标 默认 强制双面关闭、无光照 打开

Debian12搭建Nextcloud最新版并frp到二级域名

起因&#xff1a;因为台风的原因&#xff0c;要居家办公&#xff0c;但正值公司业务最要紧的时刻&#xff0c;所以需要搭建远程共享&#xff0c;结果发现基于原有的经验&#xff0c;已经难以适应版本更新带来的问题&#xff0c;所以就解决方法&#xff0c;进行了一次重新总结&a…

【狂神】Spring5笔记(10-19)

又是美好而努力的一天呀~ __ /|* * * * * * / * * * / * * * * / * * * * * * * happy valentines day * * * * …

stable diffusion实践操作-提示词插件安装与使用

本文专门开一节写提示词相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 正文 1、提示词插件安装 1.1、 安装 1.2 加载【应用更改并重载前端】 1.3 界面展示 1.3.-4 使用 里面有个收藏列表&#xff0c;可以收藏以前的所有提示…

gin框架

【狂神说】Gin框架一小时上手 | 快速转型GoWeb开发 | Go语言零基础教程_哔哩哔哩_bilibili 1.介绍 2.简单程序 1&#xff09;gin.GET/POST/PUT/DELETE函数 Go Gin 简明教程 | 快速入门 | 极客兔兔 (geektutu.com) 我的理解是&#xff1a;这类函数就像是在监听接口一样&…

Docker环境搭建Prometheus实验环境

环境&#xff1a; OS&#xff1a;Centos7 Docker: 20.10.9 - Community Centos部署Docker 【Kubernetes】Centos中安装Docker和Minikube_云服务器安装docker和minikube_DivingKitten的博客-CSDN博客 一、拉取Prometheus镜像 ## 拉取镜像 docker pull prom/prometheus ## 启动p…

【MySQL系列】索引的学习及理解

「前言」文章内容大致是MySQL索引的学习。 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 一、索引概念二、从硬件角度理解2.1 磁盘2.2 结论 三、从软件角度理解四、共识五、索引的理解5.1 一个现象和结论5.2 对Page进行建模5.3 索引可以采用的数据结构5.…

同创永益入选首批“金融数字韧性与混沌工程实践试点机构”

8月16日下午&#xff0c;由北京国家金融科技认证中心、北京国家金融标准化研究院联合主办的“传递信任 服务发展”金融科技标准认证生态大会在太原成功举办。中国金融电子化集团有限公司党委书记、董事长周逢民&#xff0c;中国科学院院士冯登国&#xff0c;中国工商银行首席技…

STM32 RTC实验

RTC时钟简介 STM32F103的实时时钟&#xff08;RTC&#xff09;是一个独立的定时器。 STM32的RTC模块拥有一组连续计数的计数器&#xff0c;在相对应的软件配置下&#xff0c;可提供时钟日历的功能。 修改计数器的值可以重新设置系统的当前时间和日期。 RTC模块和时钟配置系统…

DEAP库文档教程三-----创建类型

本节将继续展示如何通过creator创建类型以及如何使用toolbox如何对复杂问题进行初始化。 Particle的初始化--粒子初始化 一个Particle是另一个特殊类型的个体&#xff0c;这是因为通常情况下它有一个速度&#xff0c;并且有一个最优的位置需要去记忆。这种类型个体的创建与通…

Redis 7 第六讲 主从模式(replica)

🌹🌹🌹 此篇开始进入高级篇范围(❤艸`❤) 理论 即主从复制,master以写为主,Slave以读为主。当master数据变化的时候,自动将新的数据异步同步到其它slave数据库。 使用场景 读写分离 容灾备份数据备份水平扩容主从架构 演示案例 注:masterauth、replicaof主…

JDBC连接数据库

目录 一.什么是JDBC 二.JDBC的实现步骤 三.简单使用JDBC 一.什么是JDBC JDBC是Java数据库连接&#xff0c;是java中提供数据库访问的Java API,它为关系型数据库的提供了统一访问规范。 二.JDBC的实现步骤 1.创建数据库连接 这里有两种方式: DataSource创建&#xff0c;提…

【Python从入门到进阶】34、selenium基本概念及安装流程

接上篇《33、使用bs4获取星巴克产品信息》 上一篇我们介绍了如何使用bs4来解析星巴克网站&#xff0c;获取其产品信息。本篇我们来了解selenium技术的基础。 一、什么是selenium&#xff1f; Selenium是一种用于自动化Web浏览器操作的开源工具。它提供了一组API&#xff08;应…

go锁-互斥锁

go锁-互斥锁 sema初始值是0&#xff0c;waitershift等待协程的数量 正常枷锁&#xff1a; 尝试CAS直接加锁&#xff0c;通过原子包给lockerd 为枷锁 若无法直接获取&#xff0c;进行多次自旋尝试&#xff0c;未获取到的锁的g &#xff0c;多次执行空语句&#xff0c;多次尝试…

如何为 Flutter 应用程序创建环境变量

我们为什么需要环境变量&#xff1f; 主要用于存储高级机密数据&#xff0c;如果泄露可能会危及您产品的安全性。这些变量本地存储在每个用户的本地系统中&#xff0c;不应该签入存储库。每个用户都有这些变量的副本。 配置 在根项目中创建一个名为 .env 的文件夹&#xff08…

【UE 材质】模型部分透明

材质节点如下&#xff0c;这里简单解释一下。首先通过“Mask”节点将"Texture Coordinate" 节点中的“G”通道分离出来&#xff0c;然后通过“if”节点进行判断&#xff0c;当值小于0.5时为透明&#xff0c;当颜色不小于5时为不透明。可以通过一个参数来控制模型透明…

四、MySQL(表操作)如何添加字段?修改表?删除字段?修改表名?删除表?格式化某张表?

1、添加字段 &#xff08;1&#xff09;基础语法&#xff1a; alter table 表名 add 字段名 类型名(长度) [comment注释] [约束]; &#xff08;2&#xff09;示例&#xff1a;添加nickname这个字段 2、修改表 修改表中某个字段的【数据类型】/【数据类型&字段名】 &…