Linux gdb调式的原理

news2025/1/12 13:29:26

文章目录

  • 一、原理分析
  • 二、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系统调用号是59
        struct 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)
{
	//获取父进程的pid
	pid_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          # 返回值为 0
	syscall                 # 调用系统调用

可以看到一共有八条指令。
这段汇编代码是在标准输出上输出 “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.coloradmin.cn/o/961513.html

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

相关文章

使用VBA快速比对数据

实例需求&#xff1a;第一行是全系列数据集合&#xff0c;现在需要对比第一行数据&#xff0c;查找第2行数据中缺失的数字&#xff0c;保存在第3行中。 具备VBA初步使用经验的同学&#xff0c;都可以使用双重循环实现这个需求&#xff0c;这里给大家提供另一种实现思路&#x…

写的一款简易的热点词汇记录工具

项目需要对用户提交的附件、文章、搜索框内容等做热词分析。如下图&#xff1a; 公司有大数据团队。本着不麻烦别人就不麻烦别人的原则&#xff0c;写了一款简易的记录工具&#xff0c;原理也简单&#xff0c;手工在业务插入锚点&#xff0c;用分词器分好词&#xff0c;排掉字…

阿晨的运维笔记 | CentOS部署Docker

使用yum安装 # step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 …

使用VBA快速关闭应用程序进程

使用VBA进行开发时&#xff0c;如果需要关闭其他Office应用程序&#xff0c;那么通常可以使用GetObject的方式获取该应用程序的引用&#xff0c;然后再关闭&#xff0c;有时需要重复多次以关闭多进程。如果希望关闭的应用程序并非Office组件&#xff0c;那么GetObject方式有时就…

裸露土方智能识别算法 python

裸露土方智能识别算法通过opencvpython网络模型框架算法&#xff0c;裸露土方智能识别算法能够准确识别现场土堆的裸露情况&#xff0c;并对超过40%部分裸露的土堆进行抓拍预警。此次算法用到的Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很快就变得非常流…

NOR型flash vs NAND型flash

FLASH是一种存储芯片&#xff0c;全名叫Flash EEPROM Memory&#xff0c;通过程序可以修改数据&#xff0c;即平时所说的“闪存”。 闪存可以在软件的控制下写入和擦写数据。其存储空间被分割成相对较大的可擦除单元&#xff0c;成为擦除块&#xff08;erase block&#xff09…

嵌入式岗位笔试面试专栏 - 岗位介绍

文章目录 一、嵌入式岗位的分类二、热门领域及公司三、发展前景四、技能要求沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解嵌入岗位的工作职责 。 一、嵌入式岗位的分类 嵌入式软件工程师大致可以分为两种类型: 应用开发工程师驱动开发工程师应用工程…

【炼气境】HashMap原理以及如何使用

系列文章目录 文章目录 系列文章目录前言1、数据结构2、工作原理3、当两个对象的 hashCode 相同会发生什么&#xff1f;4、你知道 hash 的实现吗&#xff1f;为什么要这样实现&#xff1f;5、为什么要用异或运算符&#xff1f;6、HashMap 的 table 的容量如何确定&#xff1f;l…

PHP8内置函数中的变量函数-PHP8知识详解

在php8中&#xff0c;与变量相关的内置函数比较多&#xff0c;本文说一些比较重要的、常见的内置函数。今日着重讲解了5个&#xff0c;分别是&#xff1a;检测变量是否为空的函数empty()、判断变量是否定义过的函数isset()、销毁指定的变量的函数unset()、获取变量的类型的函数…

【力扣周赛】第 359 场周赛(选择区间型DP⭐⭐⭐⭐⭐新题型 双指针)

文章目录 竞赛链接Q1&#xff1a;7004. 判别首字母缩略词&#xff08;模拟&#xff09;Q2&#xff1a;6450. k-avoiding 数组的最小总和解法1——贪心哈希表解法2——数学公式 Q3&#xff1a;7006. 销售利润最大化⭐⭐⭐线性DP相似题目列表2008. 出租车的最大盈利&#xff08;和…

2023.9.1 简单认识 JVM

目录 JVM 内存划分 本地方法栈 虚拟机栈 程序计数器 堆区 元数据区 JVM 类加载机制 加载 验证 准备 解析 初始化 类被加载的几种情况&#xff08;懒汉模式 ---> 只要被用到才会被加载&#xff09; 双亲委派模型 JVM 内存划分 JVM 是一个应用程序&#xff0c;在…

SSM(Spring-Mybatis-SpringMVC)

文章目录 1. 介绍1.1 概念介绍 2 SSM整合框架3. SSM功能模块开发4 测试4.1 业务层接口测试4.2 表现层接口测试 5.优化 -表现层数据封装6.异常处理 1. 介绍 1.1 概念介绍 SSM项目是指基于SpringSpringMVCMyBatis框架搭建的Java Web项目。 Spring是负责管理和组织项目的IOC容器和…

二、C#—第一个c#程序(2)

&#x1f33b;&#x1f33b; 目录 一、编写第一个C#程序1.1 使用Visual Studio创建c#程序的步骤1.2 编写第一个程序“Hello Word”1.3 c#程序的基本结构1.3.1 c#中的命名空间1.3.2 c#中的类1.3.3 c#中的程序启动器——Main方法1.3.4 c#中的标识符1.3.5 c#中的关键字1.3.6 c#中的…

Vavido IP核Independent Clocks Block RAM FIFO简述

文章目录 1 FIFO&#xff08;先入先出&#xff09;1.1 概念1.2 应用场景1.3 FIFO信号1.4 FIFO读写时序1.4.1 FIFO读时序1.4.2 FIFO写时序 参考 1 FIFO&#xff08;先入先出&#xff09; 1.1 概念 FIFO&#xff08;First in First out&#xff09;即先入先出队列&#xff0c;是…

个性化定制界面还是极简版原装界面?我的选择是……

个性化定制界面和极简版原装界面&#xff0c;哪一个你用起来更加顺手呢&#xff0c;相比之下你更喜欢哪一个&#xff1f;来聊一聊原因吧&#xff01; 一、我的观点和选择 个性化定制界面和极简版原装界面&#xff0c;二者各有优缺点。 &#xff08;一&#xff09;极简版原装…

windows vmware17虚拟机导出、导入

我采用的是vmware17版本的虚拟机软件 直接拷贝VM虚拟机文件 导出 查看虚拟机所在路径 复制整个文件夹&#xff0c;可以先压缩介绍文件大小&#xff0c;拷贝到需要还原该虚拟机的电脑上 导入 在目的电脑上需要安装vnware17版本的虚拟机软件 直接打开vmware17&#xff0c; 选…

Mysql--对varchar字段用int数值来查询的问题

在工作中遇到的一个问题。就是mysql的一个表中的一个字段是varchar类型的&#xff0c;这个字段用来存储身份证&#xff0c;身份证正好是18位的。但是在根据身份证号来查询用户的时候&#xff0c;忘了给这个查询条件的身份证号加上’""&#xff0c;然后就产生了问题。…

Pinia能否替代 Vuex 的更强大状态管理工具

前序 Pinia 是 Vue.js 团队成员专门为 Vue 开发的一个全新的状态管理库&#xff0c;也相当于vuex5&#xff0c;下面关于vuex5的提案是不是觉得很像 Vuex5 的提案 Pinia和Vuex的函数 Vuex&#xff1a; State、Getters、Mutations(同步)、Actions(异步) Pinia&#xff1a; St…

【C++进阶】模板进阶

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

SpringBoot项目集成Druid

文章目录 一、前言二、Druid相关内容1、Druid简介1.1数据库连接池 2、项目集成Druid2.1、环境准备2.2、依赖准备2.3、编写配置文件2.4、测试访问 3、功能介绍3.1、查看数据源3.2、SQL监控3.3、URI监控 三、总结提升 一、前言 本文将介绍Druid的相关内容以及项目如何集成Druid&…