Linux uprobe 图文详解(一)uprobe实现原理

目录

一、uprobe基本介绍

二、uprobe打桩时如何确定offset

三、uprobe 注册流程

四、uprobe 生效原理

六、MDSCR_EL1设置单步异常

七、代码实现


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

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

相关链接:

Linux uprobe 图文详解(二)uretprobe实现原理

Linux uprobe 图文详解(三)fork/spawn/exec针对uprobe的处理

Linux uprobe 图文详解(四)uretprobe 针对 longjmp/setjmp 的处理

一、uprobe基本介绍

        uprobe(user space probe),是Linux内核提供的一种动态探测用户态代码的机制,用于debug、动态跟踪、性能分析。被uprobe打桩的函数,在其函数入口、函数返回时都会执行对应的钩子函数,可以用于获取函数入参、返回值、执行时长等信息。

        我们知道,linux中一个ELF文件,可以对应多个可执行程序,通俗的讲,就是同一个ELF可执行程序,可以加载运行多次,在系统中可以同时存在同一个ELF可执行文件对应的多个独立的进程。 因此,uprobe可以针对某个ELF文件对应的所有可执行程序,进行实时探测打桩。

        以下介绍一下uprobe的基本使用方式,假设有一个ELF程序(名为test),其代码如下所示:

        该ELF文件反汇编后,func_1、func_2函数对应的汇编指令如下:

        在func_1、func_2入口处,放置探测点,然后后台运行程序:(其中0x620、0x644分别是func_1、func_2在test文件中的offset

更详细的uprobe使用介绍,可以参考以下链接:

Uprobe-tracer: Uprobe-based Event Tracing — The Linux Kernel documentation

二、uprobe打桩时如何确定offset

        相信细心的小伙伴看完上面的示例后会有一个疑惑:objdump反汇编出来的函数func_1的地址是 0x400620,而为何 uprobe打桩时 设置的 函数func_1 在ELF文件中的 offset 却是 0x620 ?

直接上结论:

        ELF文件objdump出来的所有汇编地址,都是编译地址(也可以简单理解为程序运行时的虚拟地址),并非是函数在文件中的offset。一个ELF文件,可能是静态链接 或 动态链接的,对于这两种场景,函数在ELF文件中的offset计算方式有所不同。

1)对于动态链接的ELF文件

        对于动态链接的可执行文件,其函数相对ELF文件的偏移,就是 objdump -d 出来的地址。

2)对于静态链接的ELF文件

        对于静态链接的可执行文件,其函数相对ELF文件的偏移,需要通过 objdump -d/-t  和  proc/task/maps 两者的信息来共同确认。

        因为是静态编译,所以objdump出来的函数地址,就是运行时在虚拟内存中的地址(编译地址),所以需要减去代码段的起始地址。

三、uprobe 注册流程

uprobe注册的过程中,有几个关键点:

1)根据page cache找到所有可被打桩的进程信息(build_map_info函数)

        根据 ELF文件 对应inode对象中的i_mmap成员,以及打桩函数在ELF文件中的offset,定位到当前系统中哪些进程地址空间中,映射了该打桩函数,并将所有映射了该函数的进程信息,记录到一系列map_info对象中;

/*【build_map_info函数功能】
 * 收集map_info信息, 记录相应进程的mm和对应虚拟地址vaddr
 */
info = build_map_info(struct address_space *mapping = uprobe->inode->i_mapping, uprobe->offset, is_register) {
	unsigned long pgoff = offset >> PAGE_SHIFT
	struct vm_area_struct *vma
	struct map_info *curr = NULL
	struct map_info *prev = NULL
	struct map_info *info
	
	vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
		if (!valid_vma(vma, is_register))
			continue
		
		if (!prev && !more) {
			prev = kmalloc(sizeof(struct map_info), GFP_NOWAIT | __GFP_NOMEMALLOC | __GFP_NOWARN)
			if (prev)
				prev->next = NULL
		}
		
		if (!mmget_not_zero(vma->vm_mm))
			continue
		
		info = prev
		prev = prev->next
		info->next = curr
		curr = info
		
		info->mm = vma->vm_mm						// 记录mm信息和vaddr信息
		info->vaddr = offset_to_vaddr(vma, offset)	// 计算文件偏移offset的内容在该vma中对应的虚拟地址 !!!!!
	}
	return curr										// 返回收集好的map_info链表头结点地址
}

        这一过程,主要依赖page cache机制,每个被映射的文件都会有一个唯一的address_space对象与之对应,表示文件页缓存(代表一个文件在内存中的映射),其中“i_mmap字段”是一个红黑树,用来保存这个文件的所有内存映射关系,每个节点对应一个进程的地址空间(指向一个vma对象)。

        进程 VMA 挂载到 inode->address_space->i_mmap 树上的时机:在进程加载过程中,映射ELF可加载段到进程虚拟地址空间时,会将对应的进程vma挂载到文件页page cache的红黑树上。

2)filter筛选需要打桩的进程

        在uprobe注册过程中,当找到所有映射了被打桩函数的进程并记录到map_info后,会根据consumer->filter()函数判断对应进程是否需要被uprobe打桩。这么做的目的是,仅针对需要探测的进程进行uprobe实时打桩,而不去影响那些不关注的进程。

        举例说明:假设要对 write 这个用户态函数进行打桩,可能当前系统中大部分进程的虚拟地址空间中有映射了fork对应的代码段page cache,但是并非需要探测所有进程,比方仅对pid为100的进程中的write函数进行打桩,那么这里的consumer->filter()就派上用处了。

3)uprobe替换指令打桩的实现细节

        通过给定的vaddr 和 进程mm_struct,获取对应的old_page,然后分配新的page,将old page内容拷贝给new page,最后替换new page中被打桩指令,并与 old page 进行交换。

set_swbp(&uprobe->arch, mm, vaddr)	// Note: 该函数既用于安装软断点, 也用于restore original instruction
	uprobe_write_opcode(auprobe, mm, vaddr, opcode = UPROBE_SWBP_INSN)
		// 1) 通过给定的vaddr和进程mm_struct, 获取对应page指定数量的用户空间页面,并将其映射到内核空间.
		get_user_pages_remote(mm, vaddr, 1, gup_flags, &old_page, &vma, NULL)
		
		verify_opcode	// 检查被探测指令地址上的内容是否为UPROBE_SWBP_INSN
		
		// 2) 为vma对象分配一个匿名VMA对象,并将其与vma对象关联
		anon_vma_prepare(vma)
		
		// 3) 为指定的VMA分配一个页面并返回页面指针
		new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vaddr)
		
		// 4) 将一个页面的uptodate标志位置为1,表示页面已经被更新
		__SetPageUptodate(new_page)
		
		// 5) 将一个页面的数据从旧页面复制到新页面
		copy_highpage(new_page, old_page)
		
		// 6) 用于将数据从内核空间复制到一个页面中, 即替换新页面中的vaddr上的指令
		copy_to_page(new_page, vaddr, &opcode, UPROBE_SWBP_INSN_SIZE)
		
		// 7) 交换vma对应的那个old page
		__replace_page(vma, vaddr, old_page, new_page)

四、uprobe 生效原理

        uprobe注册完毕后,说明所有需要被探测的用户进程中目标函数的入口指令,都已经被替换成BRK指令了。当用户程序执行到uprobe探测函数时,流程如上图所示。

        大致流程与之前 kprobe系列文章 中介绍的原理类似,都是通过触发异常陷入内核,由内核的异常处理流程接管,并做相应处理。

需要注意的是,uprobe在一些实现细节上还是与kprobe有所不同:

1)首次触发断点异常后的处理细节

        uprobe在第一次触发断点异常进入内核后执行完uprobe探测点上注册的handler并调整PC指向后,会给当前task置上TIF_SINGLESTEP标志,在任务从内核态返回用户态前夕,会将MDSCR_EL1寄存器置位DBG_MDSCR_SS这么做的目的:为了让任务在用户态执行完一条完整指令后,立即由硬件触发单步异常陷入内核(MDSCR_EL1设置单步的细节见下面)

2)指令槽的区别

        对于kprobe而已,用于存放被探测指令的指令槽,是一张内核全局共享的指令槽页。而对于uprobe而言,其作用的是不同用户进程,我们知道在linux中不同用户进程的地址空间是互相隔离的。所以,uprobe对于每个被探测进程执行到打桩点触发异常时,都会为每个进程分配属于其地址空间的指令槽页

// 分配一个用于xol的指令槽, 将被探测指令内容拷贝到指令槽中, 返回指令槽地址
xol_vaddr = xol_get_insn_slot(uprobe) {
	/* 获取一个进程的xol_area, 若不存在则分配. 该area用于存放指令槽 */
	struct xol_area *area = get_xol_area() {
		struct mm_struct *mm = current->mm
		if (!mm->uprobes_state.xol_area) {
			/*【__create_xol_area函数功能】
			 * 为当前进程分配指令槽, 并为其分配指令槽的页, 并创建对应vma添加到进程mm中
			 */
			__create_xol_area(0) {
				struct xol_area *area = kmalloc(sizeof(*area), GFP_KERNEL)
				uprobe_opcode_t insn = UPROBE_SWBP_INSN
				
				area->bitmap = kcalloc(BITS_TO_LONGS(UINSNS_PER_PAGE), sizeof(long), GFP_KERNEL)
				
				/*
				 * 指令槽对应的物理页, 是提前分配好的,只是没有与vma做映射,当访问到指令槽虚拟地址时,
				 * 会触发page_fault,在page_fault处理流程中做映射 !!!!
				 */
				area->xol_mapping.pages = area->pages
				area->pages[0] = alloc_page(GFP_HIGHUSER)
				
				area->vaddr = vaddr
				arch_uprobe_copy_ixol(area->pages[0], 0, &insn, UPROBE_SWBP_INSN_SIZE)		<<<<<< trampoline_vaddr
				
				xol_add_vma(mm, area)
					area->vaddr = get_unmapped_area
					vma = _install_special_mapping
						__install_special_mapping(mm, addr, len, vm_flags, void *priv = (void *)spec, const struct vm_operations_struct *ops = &special_mapping_vmops) {
							vma = vm_area_alloc(mm)
							
							vma->vm_start = addr
							vma->vm_end   = addr + len
							vma->vm_flags = vm_flags | mm->def_flags | VM_DONTEXPAND | VM_SOFTDIRTY
							vma->vm_page_prot = vm_get_page_prot(vma->vm_flags)
							
							vma->vm_ops = ops
							vma->vm_private_data = priv
							
							insert_vm_struct(mm, vma)
						}
			}
		}
		area = READ_ONCE(mm->uprobes_state.xol_area)
		return area
	}
	xol_vaddr = xol_take_insn_slot(area)	// 从area中搜寻一个空闲的slot

	// 将xol page中备份的被探测指令内容复制到进程的地址空间, 以便执行out-of-line breakpoints
	arch_uprobe_copy_ixol(area->pages[0], xol_vaddr, &uprobe->arch.ixol, sizeof(uprobe->arch.ixol))
	
	return xol_vaddr
}

需要额外注意的点:“被探测进程uprobe指令槽的虚拟地址与物理地址映射时机”

        指令槽的虚拟地址、物理地址映射实际是当第一次发生中断后,返回用户态,PC指向指令槽虚拟地址开始取指令执行时,触发page fault中进行映射的

        在第一次触发中断进入内核处理的流程中,会分配xol_area,其中会分配一个物理页,并保存到area->xol_mapping.pages中。

    触发page fault后,内核中进行page fault流程中会调用vma->vm_ops->fault(即: special_mapping_vmops.special_mapping_fault)获取保存在area->xol_mapping中的物理页page。

六、MDSCR_EL1设置单步异常

        实际上,在gdb中常用的step单步调试指令,在arm64中就是借助的MDSCR_EL1寄存器提供的单步调试能力!(这里做个小预告^_^:后续会更新gdb的各种底层原理)

        MDSCR(Monitor Debug System Control Register)中的第0位,是Software step control bit,用于single step。

        在每次进入内核(kernel_entry的时候、以及从内核返回用户态(ret_to_user)的时候,检查任务的flag是否有TIF_SINGLESTEP,然后调用disable_step_tsk、enable_step_tsk去清除、设置MDSCR_EL1寄存器中的DBG_MDSCR_SS位。 详见DDI0487J_a_a-profile_architecture_reference_manual.pdf D2.12 Software Step exceptions

七、代码实现

1、uprobe相关数据结构

struct uprobe_consumer {
	int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs);
	int (*ret_handler)(struct uprobe_consumer *self,
				unsigned long func,
				struct pt_regs *regs);
	bool (*filter)(struct uprobe_consumer *self,
				enum uprobe_filter_ctx ctx,
				struct mm_struct *mm);

	struct uprobe_consumer *next;
};

struct uprobe {
	struct rb_node			rb_node	/* node in the rb tree */
	refcount_t				ref
	
	struct rw_semaphore		register_rwsem		/* 注册/注销的时候, 需要获取写信号量; 在调用handler,ret_handler的时候需要获取读信号量 */
	
	struct rw_semaphore		consumer_rwsem		/* 保护对uprobe_consumers结构的读写访问与操作:
												 * 1)在add/del uprobe_consumer的时候write lock;
												 * 2)在处理异常filter_chain时read lock;
												 * 3)在prepare_uprobe的时候write lock, 是同步 "uprobe_mmap" 和 "uprobe_register/uprobe_unregister" 操作时,
												 *   对uprobe->flags和uprobe->arch.insn这两个变量读写操作的并发; */
	struct list_head		pending_list
	struct uprobe_consumer *consumers
	struct inode			*inode		/* Also hold a ref to inode */
	loff_t					offset
	loff_t					ref_ctr_offset
	unsigned long			flags

	/*
	 * The generic code assumes that it has two members of unknown type
	 * owned by the arch-specific code:
	 *
	 * 	insn -	copy_insn() saves the original instruction here for
	 *		arch_uprobe_analyze_insn().
	 *
	 *	ixol -	potentially modified instruction to execute out of
	 *		line, copied to xol_area by xol_get_insn_slot().
	 */
	struct arch_uprobe	arch;
}

struct arch_uprobe {
	union {							// <<<< 联合体 !!! 存的是"被探测指令内容". register的时候用insn, 准备指令槽的时候用ixol
		u8 insn[MAX_UINSN_BYTES];
		u8 ixol[MAX_UINSN_BYTES];
	};
	struct arch_probe_insn api;
	bool simulate;
};

2、注册uprobe

/*【uprobe_register】
 * 函数功能: 注册uprobe, 将被探测指令替换为breakpoint
 * 1) 分配一个uprobe对象, 若uprobes_tree中已存在同样inode:offset tuple的uprobe对象就直接返回该uprobe对象;
 * 2) 根据文件inode->i_mapping->i_mmap, 收集指定偏移量的虚拟地址在哪些进程内存空间中有映射的信息map_info(即: 收集需要install breakpoint的所有进程VMA信息);
 * 3) 根据收集到的map_info信息, 在所有用到inode对应文件的进程虚拟地址空间中install breakpoint, 并将被探测指令内容保存到uprobe->arch.insn中.
 */
uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc)
{
	/* inode : the file in which the probe has to be placed.
	 * offset: offset from the start of the file.
	 * uc    : information on howto handle the probe. */
	__uprobe_register(inode, offset, loff_t ref_ctr_offset = 0, uc) {
		if (!uc->handler && !uc->ret_handler)		/* Uprobe must have at least one set consumer */
			return -EINVAL
retry:
		### 分配一个uprobe, 若已存在同样inode:offset的uprobe就直接返回对应uprobe
		struct uprobe *uprobe = alloc_uprobe(inode, offset, ref_ctr_offset) {
			uprobe = kzalloc(sizeof(struct uprobe), GFP_KERNEL)
			uprobe->inode          = inode
			uprobe->offset         = offset
			uprobe->ref_ctr_offset = ref_ctr_offset
			init_rwsem(&uprobe->register_rwsem)
			init_rwsem(&uprobe->consumer_rwsem)
			
			/* add to uprobes_tree, sorted on inode:offset */
			cur_uprobe = insert_uprobe(uprobe) {
				spin_lock(&uprobes_treelock)
				u = __insert_uprobe(uprobe) {
					node = rb_find_add(&uprobe->rb_node, &uprobes_tree, __uprobe_cmp)	// 若已存在, 则插入失败并返回已存在的uprobe; 若不存在, 则插入成功, 返回NULL.
					if (node)
						return get_uprobe(__node_2_uprobe(node))						// ***** 若发现已存在同样uprobe, 将其ref加1 *****
					/* get access + creation ref */
					refcount_set(&uprobe->ref, 2)										// 对于新分配的uprobe, 引用计数为2, 分别代表: access refcount, creation refcount
					return NULL
				}
				spin_unlock(&uprobes_treelock)
				return u
			}
			
			/* a uprobe exists for this inode:offset combination */
			if (cur_uprobe) {
				if (cur_uprobe->ref_ctr_offset != uprobe->ref_ctr_offset) {				// 若已存在uprobe, 但其ref_ctr_offset与欲分配的uprobe's ref_ctr_offset不同, 则返回EINVAL
					ref_ctr_mismatch_warn(cur_uprobe, uprobe)
					put_uprobe(cur_uprobe)
					kfree(uprobe)
					return ERR_PTR(-EINVAL);
				}
				kfree(uprobe)
				uprobe = cur_uprobe														// 若已存在uprobe, 则释放新分配的uprobe, 将已存在的uprobe返回
			}
			return uprobe
		}
		
		down_write(&uprobe->register_rwsem)					// 获取register操作的读写信号量
		ret = -EAGAIN
		if (likely(uprobe_is_active(uprobe))) {
			consumer_add(uprobe, uc) {						// 将uprobe_consumer头插到uprobe->consumers链表上
				down_write(&uprobe->consumer_rwsem)
				uc->next = uprobe->consumers
				uprobe->consumers = uc
				up_write(&uprobe->consumer_rwsem)
			}
			
			/* register_for_each_vma接口, 用于install/remove breakpoint */
			ret = register_for_each_vma(uprobe, struct uprobe_consumer *new = uc) {
				bool is_register = !!new
				struct map_info *info
				
				percpu_down_write(&dup_mmap_sem)		<<<<<<<<<<<<<<<<<<<< 与fork->dup_mmap的时候互斥
				
				### 收集map_info信息, 记录相应进程的mm和对应虚拟地址vaddr
				info = build_map_info(struct address_space *mapping = uprobe->inode->i_mapping, uprobe->offset, is_register) {
					unsigned long pgoff = offset >> PAGE_SHIFT
					struct vm_area_struct *vma
					struct map_info *curr = NULL
					struct map_info *prev = NULL
					struct map_info *info
					
					vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
						if (!valid_vma(vma, is_register))
							continue
						
						if (!prev && !more) {
							prev = kmalloc(sizeof(struct map_info), GFP_NOWAIT | __GFP_NOMEMALLOC | __GFP_NOWARN)
							if (prev)
								prev->next = NULL
						}
						
						if (!mmget_not_zero(vma->vm_mm))
							continue
						
						info = prev
						prev = prev->next
						info->next = curr
						curr = info
						
						info->mm = vma->vm_mm						// 记录mm信息和vaddr信息
						info->vaddr = offset_to_vaddr(vma, offset)	// 计算文件偏移offset的内容在该vma中对应的虚拟地址 !!!!!
					}
					return curr										// 返回收集好的map_info链表头结点地址
				}
				
				### 根据收集到的map_info信息install breakpoint
				while (info) {
					struct mm_struct *mm = info->mm
					mmap_write_lock(mm)
					vma = find_vma(mm, info->vaddr)
					
					if (!vma || !valid_vma(vma, is_register) || file_inode(vma->vm_file) != uprobe->inode)
						goto unlock
					
					if (vma->vm_start > info->vaddr || vaddr_to_offset(vma, info->vaddr) != uprobe->offset)
						goto unlock
					
					if (is_register) {
						ret = consumer_filter(uc = new, ctx = UPROBE_FILTER_REGISTER, mm) {
							return !uc->filter || uc->filter(uc, ctx, mm)
						}
						### 若consumer->filter非空, 或者consumer->filter返回非0值代表mm对应的进程满足consumer的过滤条件, 则在mm进程中安装断点指令		<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
						if (ret) {
							###################################### uprobe安装软断点 ######################################
							/*【install_breakpoint函数】
							 * 1) 调用prepare_uprobe, 记录被探测指令内容, 并对指令内容做相应检查;
							 * 2) 调用set_swbp, 将被探测指令地址上的指令内容替换为软断点
							 * 3) 若software breakpoint设置成功, 则清除mm->flags中的MMF_RECALC_UPROBES位 */
							install_breakpoint(uprobe, mm, vma, unsigned long vaddr = info->vaddr) {
								/*【prepare_uprobe函数】
								 * 1) 保存被探测指令内容到uprobe->arch.insn中;
								 * 2) 检测被探测指令是否为UPROBE_SWBP_INSN指令, 若是的话则返回-ENOTSUPP;
								 * 3) 分析ARM64下该被探测指令类型, 若需要simulate, 则根据指令类型提供相应的simulate_handler, 保存在uprobe->arch->api->handler中 */
								ret = prepare_uprobe(uprobe, vma->vm_file, mm, vaddr) {
									if (test_bit(UPROBE_COPY_INSN, &uprobe->flags))		// 检查UPROBE_COPY_INSN标志, 看探测指令是否已经备份
										return ret
									
									down_write(&uprobe->consumer_rwsem)
									if (test_bit(UPROBE_COPY_INSN, &uprobe->flags))		// 再次检查UPROBE_COPY_INSN标志, 以防当前任务等待锁期间, 该标志被置位
										goto out
									
									ret = copy_insn(uprobe, file) {						// file代表包含probed instruction的文件
										struct address_space *mapping = uprobe->inode->i_mapping	// 文件映射的地址空间
										loff_t offs = uprobe->offset					// 指令在文件中的偏移
										void *insn = &uprobe->arch.insn					// 用于备份探测指令内容的空间
										int size = sizeof(uprobe->arch.insn)
										
										__copy_insn(mapping, filp, insn, len, offs) {	// 将文件映射的地址空间中被探测指令内容拷贝到insn中
											### 通过文件映射的地址空间和偏移量, 获取指令所在的页面
											struct page *page = read_mapping_page(mapping, offset >> PAGE_SHIFT, filp)
											copy_from_page(page, vaddr = offset, dst = insn, len = nbytes) {
												void *kaddr = kmap_atomic(page)
												memcpy(dst, kaddr + (vaddr & ~PAGE_MASK), len)
												kunmap_atomic(kaddr)
											}
											put_page(page)
										}
									}
									if (ret)
										goto out

									ret = -ENOTSUPP
									if (is_trap_insn((uprobe_opcode_t *)&uprobe->arch.insn))	// 若被探测指令就是UPROBE_SWBP_INSN指令, 则返回-ENOTSUPP
										goto out
									
									### 分析被探测指令类型, 若uprobe指令需要simulate执行, 则设置相应的simulate handler
									ret = arch_uprobe_analyze_insn(struct arch_uprobe *auprobe = &uprobe->arch, mm, vaddr) {
										if (mm->context.flags & MMCF_AARCH32)					// 目前不支持 AARCH32 instruction probing
											return -EOPNOTSUPP
										else if (!IS_ALIGNED(addr, AARCH64_INSN_SIZE))			// 检查被探测指令是否4字节对齐 #define AARCH64_INSN_SIZE 4
											return -EINVAL
										
										probe_opcode_t insn = *(probe_opcode_t *)(&auprobe->insn[0])
										
										ret = arm_probe_decode_insn(insn, &auprobe->api) {
											if (aarch64_insn_is_steppable(insn))
												return INSN_GOOD

											if (aarch64_insn_is_bcond(insn))
												api->handler = simulate_b_cond					// <<<<<< 根据指令类型, 提供相应的 simulate handler
											else if (aarch64_insn_is_cbz(insn) || aarch64_insn_is_cbnz(insn))
												api->handler = simulate_cbz_cbnz
											...
											else
												return INSN_REJECTED		// Instruction cannot be stepped out-of-line and we don't (yet) simulate it.

											return INSN_GOOD_NO_SLOT
										}
										switch (ret) {
										case INSN_REJECTED:
											return -EINVAL;
										case INSN_GOOD_NO_SLOT:
											auprobe->simulate = true
										}
										return 0
									}
									if (ret)
										goto out
									
									set_bit(UPROBE_COPY_INSN, &uprobe->flags)			// 设置UPROBE_COPY_INSN标志, 代表探测指令已备份且分析过了
								out:
									up_write(&uprobe->consumer_rwsem)
									return ret
								}
								
								### 无需继续替换uprobe点内存页的情况		<<<<<<<<<<<<<<<<<<<<<<<<<<
								/* Case1: uprobe->flags已经置上UPROBE_COPY_INSN位, 代表指令已经替换过了  */
								if (ret)
									return ret
								
								### 若当前进程未添加过uprobe点, 则给进程的mm设置MMF_HAS_UPROBES标志位
								bool first_uprobe = !test_bit(MMF_HAS_UPROBES, &mm->flags)
								if (first_uprobe)
									set_bit(MMF_HAS_UPROBES, &mm->flags)
								
								### Store breakpoint at a given address.
								### For mm @mm, store the breakpoint instruction at @vaddr.
								### @mm: the probed process address space.
								### @vaddr: the virtual address to insert the opcode.
								ret = set_swbp(struct arch_uprobe *auprobe = &uprobe->arch, mm, vaddr) {
									/*【uprobe_write_opcode函数】
									 * - write the opcode at a given virtual address.
									 * @auprobe: arch specific probepoint information.
									 * @mm     : the probed process address space.
									 * @vaddr  : the virtual address to store the opcode.
									 * @opcode : opcode to be written at @vaddr.
									 *
									 * [Note]: 该函数既用于安装软断点, 也用于restore original instruction
									 *
									 *【实现逻辑】
									 * 1) 检测被探测指令内容是否为UPROBE_SWBP_INSN;
									 * 2) 分配一个新的page;
									 * 3) 将原始page的内容拷贝到新分配的new_page中, 并将new_page中vaddr处内容替换为UPROBE_SWBP_INSN;
									 * 4) 将old_page替换成new_page
									 *
									 * For arm64, UPROBE_SWBP_INSN is BRK64_OPCODE_UPROBES A.K.A: AARCH64_BREAK_MON | (UPROBES_BRK_IMM << 5) */
									return uprobe_write_opcode(auprobe, mm, vaddr, uprobe_opcode_t opcode = UPROBE_SWBP_INSN) {
										struct page *old_page, *new_page
										
										is_register = is_swbp_insn(&opcode)
										
										uprobe = container_of(auprobe, struct uprobe, arch)
										
										### 通过给定的vaddr和进程mm_struct, 获取对应page指定数量的用户空间页面,并将其映射到内核空间.
										### 若执行成功,则会将获取到的页面指针存储到pages数组中,并将与这些页面相关的虚拟内存区域信息存储到vmas指针中
										get_user_pages_remote(mm, vaddr, 1, gup_flags, &old_page, &vma, NULL)
										
										### 检查被探测指令地址上的内容是否为UPROBE_SWBP_INSN, 对于注册的场景下若目标页上的指令已经是brk指令了就直接返回不做页替换了
										ret = verify_opcode(struct page *page = old_page, vaddr, uprobe_opcode_t *new_opcode = &opcode) {
											uprobe_opcode_t old_opcode
											bool is_swbp
											
											copy_from_page(page, vaddr, &old_opcode, UPROBE_SWBP_INSN_SIZE)		// 获取old_page vaddr地址上的指令内容
											is_swbp = is_swbp_insn(&old_opcode)
											
											if (is_swbp_insn(new_opcode))	// Case1: 针对install breakpoint的场景
												if (is_swbp)				// 若new_opcode为UPROBE_SWBP_INSN, 且old_page vaddr上内容也为UPROBE_SWBP_INSN
													return 0
											else							// Case2: 针对remove breakpoint的场景
												if (!is_swbp)				// 若new_opcode不是UPROBE_SWBP_INSN, 且old_page vaddr上内容也不是UPROBE_SWBP_INSN
													return 0
											return 1
										}
										if (ret <= 0)
											goto put_old
										
										### 为vma对象分配一个匿名VMA对象,并将其与vma对象关联
										anon_vma_prepare(vma)
											__anon_vma_prepare(vma) {
												struct anon_vma *anon_vma
												anon_vma = find_mergeable_anon_vma(vma)
												if (!anon_vma)
													anon_vma = anon_vma_alloc()
												vma->anon_vma = anon_vma
											}

										### 为指定的VMA分配一个页面并返回页面指针
										new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, vaddr)
										
										### 将原始page的内容拷贝到新分配的new_page中, 并将new_page中vaddr处内容替换为UPROBE_SWBP_INSN
										__SetPageUptodate(new_page)										// 将一个页面的uptodate标志位置为1,表示页面已经被更新
										copy_highpage(new_page, old_page)								// 将一个页面的数据从旧页面复制到新页面
										copy_to_page(new_page, vaddr, &opcode, UPROBE_SWBP_INSN_SIZE)	// 
										
										### 将old_page替换成new_page
										__replace_page(vma, vaddr, old_page, new_page)
										
										put_page(old_page)
									}
								}
								
								if (!ret)
									clear_bit(MMF_RECALC_UPROBES, &mm->flags)		// 若软断点设置成功, 则清除MMF_RECALC_UPROBES标志, 表示探针不需要重新计算
								else if (first_uprobe)
									clear_bit(MMF_HAS_UPROBES, &mm->flags)			// 若软断点设置失败, 且这是第一个探针, 则将MMF_HAS_UPROBES标志清0
								return ret
							}
						}
					} else if (test_bit(MMF_HAS_UPROBES, &mm->flags)) {
						### 只有当该uprobe上的所有consumer都注册了filter, 且所有filter过滤mm时, 都发现mm不满足条件时, 才移除断点
						if (!filter_chain(uprobe,UPROBE_FILTER_UNREGISTER, mm))
							err |= remove_breakpoint(uprobe, mm, info->vaddr)
					}
				unlock:
					mmap_write_unlock(mm)
				free:
					mmput(mm)
					info = free_map_info(info) {
						struct map_info *next = info->next
						kfree(info)
						return next
					}
				} // while (info)
			out:
				percpu_up_write(&dup_mmap_sem);
				return err
			}
			
			if (ret)
				__uprobe_unregister(uprobe, uc)
		}
		up_write(&uprobe->register_rwsem)					// 释放register操作的读写信号量
		
		put_uprobe(uprobe)									// ?????????
			refcount_dec_and_test(&uprobe->ref)
		
		if (unlikely(ret == -EAGAIN))
			goto retry
		
		return ret
	}
}

3、第一次uprobe断点异常触发的处理

########################################## 第一次触发软断点时的处理逻辑 ##########################################
/* 1) 判断断点异常类型, 发现是UPROBES_BRK_IMM, 调用对应处理函数uprobe_breakpoint_handler, 将current_thread_info置位TIF_UPROBE
 * 2) 调用exit_to_user_mode, 准备从异常处理中返回, 然后检查thread_flags, 发现存在_TIF_UPROBE位, 调用uprobe_notify_resume进行处理
 * 3) 清除当前任务flags的TIF_UPROBE位
 * 4) 调用handle_swbp函数, 该函数主要做以下处理:
 *   4.1) 遍历执行触发异常对应的uprobe上注册的所有consumer->handler
 *   4.2) 若注册的consumer中有注册ret_handler, 则在函数返回点打上断点
 *   4.3) 若被探测指令需要simulate执行, 则执行simulate该指令相应的handler, 执行完毕后直接返回
 *   4.4) 若被探测指令不需要simulate执行, 则准备需要单步执行的被探测指令, 分配指令槽, 指令槽中存入被探测指令内容, 将PC执行指令槽
 *   4.5) 将对应uprobe记录到当前任务的uprobe_task->active_uprobe中, 并设置TIF_SINGLESTEP flag, 同时将uprobe_task->state设为UTASK_SSTEP
 * 5) 调用ret_to_user并检查当前任务是否需要enable single step(即: 是否置位TIF_SINGLESTEP), 若需要则将 MDSCR_EL1寄存器置上DBG_MDSCR_SS位
 * 6) 回到用户态继续执行, 此时若分配了指令槽的话, PC指向指令槽
 */
##################################################################################################################
entry_handler {
	el0t_64_sync_handler {
		unsigned long esr = read_sysreg(esr_el1)
		
		case ESR_ELx_EC_BRK64:
		el0_dbg(regs, esr) {
			enter_from_user_mode(regs)
			
			do_debug_exception(far, esr, regs) {
				inf->fn(addr_if_watchpoint, esr, regs)
				A.K.A
				brk_handler
					call_break_hook
						if ((comment & ~hook->mask) == hook->imm)
							fn = hook->fn
						fn(regs, esr)
						A.K.A
						uprobe_breakpoint_handler {
							/* uprobe_pre_sstep_notifier gets called from interrupt context as part of
							 * notifier mechanism. Set TIF_UPROBE flag and indicate breakpoint hit. */
							uprobe_pre_sstep_notifier(regs) {
								set_thread_flag(flag = TIF_UPROBE)						// <<<< 将task_struct flag置上TIF_UPROBE位, 代表uprobe软断点已触发
									set_ti_thread_flag(current_thread_info(), flag)
							}
						}
			}
			
			local_daif_restore(DAIF_PROCCTX)

			exit_to_user_mode{
				prepare_exit_to_user_mode(struct pt_regs *regs) {
					local_daif_mask()
					
					flags = READ_ONCE(current_thread_info()->flags)
					if (unlikely(flags & _TIF_WORK_MASK)) {
						do_notify_resume(struct pt_regs *regs, unsigned long thread_flags = flags) {
							local_daif_restore(DAIF_PROCCTX)
							
							if (thread_flags & _TIF_UPROBE) {
								uprobe_notify_resume(struct pt_regs *regs) {
									clear_thread_flag(TIF_UPROBE)
									
									struct uprobe_task *utask = current->utask
									if (utask && utask->active_uprobe)
										handle_singlestep(utask, regs)			// 第二次单步异常会走这条分支
											//...Detail see below
									else {
										/* Run handler and ask thread to singlestep
										 * Ensure all non-fatal signals cannot interrupt thread while it singlesteps.
										 *【handle_swbp函数逻辑】
										 * 1) 获取触发软断点异常的指令地址
										 * 2) 根据异常地址找到对应vma, 并获取该vma对应的文件inode, 以及异常指令地址在文件中的偏移offset
										 * 3) 根据[inode,offset] tuple, 找到对应的uprobe对象
										 * 4) 为当前任务分配uprobe_task object
										 * 5) 遍历执行uprobe上挂载的所有consumer->handler, 并检查是否注册了consumer->ret_handler
										 * 6) 若第4步中发现有注册consumer->ret_handler, 则在被探测函数返回点打上断点
										 * 7) 若被探测指令需要simulate执行, 则执行simulate该指令相应的handler, 执行完毕后直接返回, 不再执行pre_ssout
										 * 8) 若被探测指令不需要simulate执行, 则调用pre_ssout, 准备需要单步执行的被探测指令
										 *   8.1) 分配一个用于xol的指令槽, 将被探测指令内容拷贝到指令槽中, 并返回指令槽地址
										 *   8.2) 将指令槽地址和触发第一次异常地址保存到当前任务的uprobe_task中的xol_vaddr和vaddr上
										 *   8.3) 准备uprobe探测点的软单步, 将任务PC指向utask->xol_vaddr, 并设置TIF_SINGLESTEP flag
										 *   8.4) 设置当前任务uprobe_task的状态信息(active_uprobe、state置为UTASK_SSTEP)
										 * 9) 从异常处理返回时, 检查当前任务是否需要enable single step, 若需要则将MDSCR_EL1寄存器置上DBG_MDSCR_SS位
										 */
										handle_swbp(regs) {						// 第一次软断点异常会走这条分支
											unsigned long bp_vaddr
											int is_swbp
											
											### step1) 获取触发软断点的指令地址
											bp_vaddr = uprobe_get_swbp_addr(regs) {
												return instruction_pointer(regs)
											}
											### 获取trampoline函数地址
											trampoline_vaddr = get_trampoline_vaddr() {
												struct xol_area *area = READ_ONCE(current->mm->uprobes_state.xol_area)
												if (area)
													trampoline_vaddr = area->vaddr
												
												return trampoline_vaddr
											}
											if (bp_vaddr == trampoline_vaddr) {			<<<<<<<<<<<<<<<<<< 处理uretprobe执行到被探测返回点的情况
												ret = handle_trampoline(regs) {
													/* Detail see below */
												}
												return ret
											}
											
											### step2/step3) 根据触发软断点指令地址, 找出对应uprobe
											uprobe = find_active_uprobe(bp_vaddr, &is_swbp) {
												struct mm_struct      *mm  = current->mm
												struct vm_area_struct *vma = vma_lookup(mm, bp_vaddr)
												if (vma) {
													// 判断vma是否是一个executable vma
													if (valid_vma(vma, false)) {
														struct inode *inode = file_inode(vma->vm_file)	// 获取该vma对应的文件inode
														loff_t offset = vaddr_to_offset(vma, bp_vaddr)	// 获取被探测指令在文件中的偏移
															return ((loff_t)vma->vm_pgoff << PAGE_SHIFT) + (vaddr - vma->vm_start)
														
														### 根据inode:offset tuple,在uprobes_tree中查找对应的uprobe
														uprobe = find_uprobe(inode, offset) {
															spin_lock(&uprobes_treelock)
															uprobe = __find_uprobe(inode, offset) {
																struct __uprobe_key key = {
																		.inode = inode,
																		.offset = offset,
																	};
																struct rb_node *node = rb_find(&key, &uprobes_tree, __uprobe_cmp_key)
																if (node)
																	return get_uprobe(__node_2_uprobe(node))
																return NULL
															}
															spin_unlock(&uprobes_treelock)
															return uprobe
														}
													}
													
													if (!uprobe) {
														*is_swbp = is_trap_at_addr(mm, bp_vaddr) {
															
														}
													}
												} else {
													*is_swbp = -EFAULT
												}
												return uprobe
											}
											
											if (!uprobe) {
												if (is_swbp > 0)
													force_sig(SIGTRAP)			/* No matching uprobe; signal SIGTRAP. */
												else
													instruction_pointer_set(regs, bp_vaddr)
											}
											
											instruction_pointer_set(regs, bp_vaddr)		// change it in advance for ->handler() and restart
											
											### step4) Allocate a uprobe_task object for the task if necessary
											get_utask() {
												if (!current->utask)
													current->utask = kzalloc(sizeof(struct uprobe_task), GFP_KERNEL)
												return current->utask
											}
											
											### step5) 遍历执行所有consumer->handler, 并根据是否注册了consumer->ret_handler, 决定是否要在被探测函数返回点打断点
											handler_chain(uprobe, regs) {
												struct uprobe_consumer *uc
												int     remove = UPROBE_HANDLER_REMOVE
												bool need_prep = false; /* prepare return uprobe, when needed */
												
												down_read(&uprobe->register_rwsem)
												
												### 遍历uprobe上注册的所有consumer, 执行consumer->handler, 并检查consumer是否注册了ret_handler
												for (uc = uprobe->consumers; uc; uc = uc->next) {
													int rc = 0
													if (uc->handler) {
														rc = uc->handler(uc, regs)						// <<<<<<< 执行用户注册在uprobe探测点上的所有钩子函数 <<<<<<<
													}
													if (uc->ret_handler)
														need_prep = true
													remove &= rc
												}
												
												### step6) 若consumer注册了ret_handler, 则在被探测函数的返回点加上breakpoint
												if (need_prep && !remove)
													prepare_uretprobe(uprobe, regs) {		 			/* put bp at return */
														/* Detail see content4 */
													}
												
												### 若handler返回非0, 移除uprobe
												if (remove && uprobe->consumers) {
													unapply_uprobe(uprobe, current->mm)
												}
												up_read(&uprobe->register_rwsem)
											}
											
											### step7) 若被探测指令需要simulate执行, 则执行simulate该指令相应的handler, 并返回true, 代表需要跳过单步;
											###        否则返回false, 代表不需要跳过单步
											ret = arch_uprobe_skip_sstep(struct arch_uprobe *auprobe = &uprobe->arch, regs) {
												if (!auprobe->simulate)
													return false

												insn = *(probe_opcode_t *)(&auprobe->insn[0])
												addr = instruction_pointer(regs)
												
												### 模拟执行被探测指令
												if (auprobe->api.handler)
													auprobe->api.handler(insn, addr, regs)
												
												return true
											}
											if (ret)					<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 模拟单步执行探测指令 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
												goto out
											
											### step8) 若被探测指令不需要simulate运行, 则准备需要单步执行的被探测指令
											pre_ssout(uprobe, regs, bp_vaddr) {
												struct uprobe_task *utask = get_utask() {
													if (!current->utask)
														current->utask = kzalloc(sizeof(struct uprobe_task), GFP_KERNEL)
													return current->utask
												}
												
												### step8.1) 分配一个用于xol的指令槽, 将被探测指令内容拷贝到指令槽中, 返回指令槽地址
												xol_vaddr = xol_get_insn_slot(uprobe) {
													/* 获取一个进程的xol_area, 若不存在则分配. 该area用于存放指令槽 */
													struct xol_area *area = get_xol_area() {
														struct mm_struct *mm = current->mm
														if (!mm->uprobes_state.xol_area) {
															__create_xol_area(0) {		 			<<<<<<<<<<<<<<<<<<< 指令槽内存分配  Detail see below!!!
																struct xol_area *area = kmalloc(sizeof(*area), GFP_KERNEL)
																uprobe_opcode_t insn = UPROBE_SWBP_INSN
																
																area->bitmap = kcalloc(BITS_TO_LONGS(UINSNS_PER_PAGE), sizeof(long), GFP_KERNEL)
																
																area->xol_mapping.pages = area->pages
																area->pages[0] = alloc_page(GFP_HIGHUSER)
																
																area->vaddr = vaddr
																arch_uprobe_copy_ixol(area->pages[0], 0, &insn, UPROBE_SWBP_INSN_SIZE)		<<<<<< trampoline_vaddr
																
																xol_add_vma(mm, area)
																	area->vaddr = get_unmapped_area
																	vma = _install_special_mapping
															}
														}
														area = READ_ONCE(mm->uprobes_state.xol_area)
														return area
													}
													xol_vaddr = xol_take_insn_slot(area)	// 从area中搜寻一个空闲的slot

													// 将xol page中备份的被探测指令内容复制到进程的地址空间, 以便执行out-of-line breakpoints
													arch_uprobe_copy_ixol(area->pages[0], xol_vaddr, &uprobe->arch.ixol, sizeof(uprobe->arch.ixol))
													
													return xol_vaddr
												}
												
												### step8.2) 将指令槽地址和触发第一次异常地址保存到当前任务的uprobe_task中
												utask->xol_vaddr = xol_vaddr
												utask->vaddr = bp_vaddr
												
												### step8.3) 准备uprobe探测点的软单步, 将任务PC指向utask->xol_vaddr, 并设置TIF_SINGLESTEP flag, 在返回用户态时会MDSCR_EL1寄存器中DBG_MDSCR_SS位置1
												arch_uprobe_pre_xol(struct arch_uprobe *auprobe = &uprobe->arch, regs) {
													struct uprobe_task *utask = current->utask
													current->thread.fault_code = UPROBE_INV_FAULT_CODE
													
													/* Instruction points to execute xol */
													instruction_pointer_set(regs, utask->xol_vaddr)            <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< PC指向指令槽 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
													
													user_enable_single_step(current) {
														struct thread_info *ti = task_thread_info(task)
														
														### If task has TIF_SINGLESTEP, then set DBG_MDSCR_SS to MDSCR_EL1 during ret_to_user
														test_and_set_ti_thread_flag(ti, TIF_SINGLESTEP)
														set_regs_spsr_ss(task_pt_regs(task))
															set_user_regs_spsr_ss(&(r)->user_regs)
																regs->pstate |= DBG_SPSR_SS
													}
												}
												
												### step8.4) 设置当前任务uprobe_task的状态信息
												utask->active_uprobe = uprobe
												utask->state = UTASK_SSTEP
											}
										out:
											put_uprobe(uprobe)
										}
									}
								}
							}
							
							local_daif_mask()
						}
					}
				}
			}
		}
	}
	
	b	ret_to_user
	A.K.A
	SYM_CODE_START_LOCAL(ret_to_user) {
		ldr	x19, [tsk, #TSK_TI_FLAGS]	// re-check for single-step
		
		### step9) 从异常处理返回时, 检查当前任务是否需要enable single step, 若需要则将MDSCR_EL1寄存器置上DBG_MDSCR_SS位
		enable_step_tsk x19, x2
		A.K.A
		{
			.macro	enable_step_tsk, flgs, tmp
			tbz	\flgs, #TIF_SINGLESTEP, 9990f				// ### If task has TIF_SINGLESTEP, then set DBG_MDSCR_SS to MDSCR_EL1
			mrs	\tmp, mdscr_el1
			orr	\tmp, \tmp, #DBG_MDSCR_SS					// <<<<<<<<<<< 设置单步异常控制硬件寄存器
			msr	mdscr_el1, \tmp
		}
		
		kernel_exit 0					// It will restore general-purpose register
	}
}

4、第二次uprobe单步异常触发的处理

######################################### 第二次触发单步异常时的处理逻辑 #########################################
/* 1) 判断断点异常类型, 发现是单步异常ESR_ELx_EC_SOFTSTP_LOW, 调用对应处理函数uprobe_single_step_handler
 * 2) 将uprobe_task->state置为UTASK_SSTEP_ACK, 并将current_thread_info置位TIF_UPROBE
 * 3) 调用exit_to_user_mode, 准备从异常处理中返回, 然后检查thread_flags, 发现存在_TIF_UPROBE位, 调用uprobe_notify_resume进行处理
 * 4) 发现当前任务的uprobe_task->active_uprobe非空, 说明当前任务触发了软单步异常, 则调用handle_singlestep进行处理
 * 5) 若state为UTASK_SSTEP_ACK, 则说明触发了uprobe探测点的第二次单步异常, 则将PC指向被探测指令地址+4的位置, 并清除thread_flag中的TIF_SINGLESTEP位(即: disable single step)
 * 6) uprobe_task->active_uprobe置NULL, 并将uprobe_task->state置为UTASK_RUNNING
 * 7) 回到用户态继续执行, 此时PC指向被探测指令地址的+4位置
 */
##################################################################################################################
entry_handler {
	el0t_64_sync_handler {
		unsigned long esr = read_sysreg(esr_el1)
		
		case ESR_ELx_EC_SOFTSTP_LOW:
		el0_dbg(regs, esr) {
			do_debug_exception(far, esr, regs) {
				const struct fault_info *inf = esr_to_debug_fault_info(esr)
				inf->fn
				A.K.A
				single_step_handler(unsigned long unused = addr_if_watchpoint, unsigned int esr = esr, struct pt_regs *regs = regs) {
					call_step_hook(regs, esr) {
						list = user_mode(regs) ? &user_step_hook : &kernel_step_hook
						/* single-step exception disables interrupt */
						list_for_each_entry_rcu(hook, list, node) {
							hook->fn(regs, esr)
							A.K.A
							uprobe_single_step_handler(regs, esr) {				  						// ############# Call uprobe single step handler #############
								struct uprobe_task *utask = current->utask
								WARN_ON(utask && (instruction_pointer(regs) != utask->xol_vaddr + 4))	// check whether PC is correct
								ret = uprobe_post_sstep_notifier(regs) {
									struct uprobe_task *utask = current->utask

									if (!current->mm || !utask || !utask->active_uprobe)				/* task is currently not uprobed */
										return 0

									utask->state = UTASK_SSTEP_ACK
									set_thread_flag(TIF_UPROBE)
									return 1
								}
								if (ret)
									return DBG_HOOK_HANDLED
							}
						}
					}
				}
			}

			exit_to_user_mode{
				prepare_exit_to_user_mode(struct pt_regs *regs) {
					local_daif_mask()
					flags = READ_ONCE(current_thread_info()->flags)
					if (unlikely(flags & _TIF_WORK_MASK)) {
						do_notify_resume(struct pt_regs *regs, unsigned long thread_flags = flags) {
							local_daif_restore(DAIF_PROCCTX)
							
							if (thread_flags & _TIF_UPROBE) {
								uprobe_notify_resume(struct pt_regs *regs) {	// #################### 处理uprobe ####################
									clear_thread_flag(TIF_UPROBE)
									
									struct uprobe_task *utask = current->utask
									if (utask && utask->active_uprobe) {
										/*【handle_singlestep函数逻辑】
										 * 1) 读取当前任务uprobe_task的state成员
										 * 2) 若state为UTASK_SSTEP_ACK, 则说明触发了uprobe探测点的第二次单步异常, 则将PC指向被探测指令地址+4的位置, 并清除thread_flag中的TIF_SINGLESTEP位(即: disable single step)
										 * 3) 若state为UTASK_SSTEP_TRAPPED, 则将PC置为被探测指令地址, 并disable single step
										 * 4) 将uprobe_task->active_uprobe置NULL, 并将uprobe_task->state置为UTASK_RUNNING
										 * 5) 释放当前任务uprobe占用的指令槽
										 */
										handle_singlestep(utask, regs) {		// 第二次单步异常会走这条分支
											uprobe = utask->active_uprobe
											
											if (utask->state == UTASK_SSTEP_ACK) {
												arch_uprobe_post_xol(&uprobe->arch, regs) {
													struct uprobe_task *utask = current->utask
													WARN_ON_ONCE(current->thread.fault_code != UPROBE_INV_FAULT_CODE)	// error check
													
													### Instruction points to execute next to breakpoint address
													instruction_pointer_set(regs, utask->vaddr + 4)				<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 恢复PC到被探测指令+4位置处
													user_disable_single_step(current) {
														clear_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP)
													}
												}
											}
											else if (utask->state == UTASK_SSTEP_TRAPPED) {
												arch_uprobe_abort_xol(&uprobe->arch, regs) {
													struct uprobe_task *utask = current->utask
													### Task has received a fatal signal, so reset back to probbed address
													instruction_pointer_set(regs, utask->vaddr)
													user_disable_single_step(current)
												}
											}
											
											put_uprobe(uprobe)
											utask->active_uprobe = NULL
											utask->state = UTASK_RUNNING
											xol_free_insn_slot(current)
											
											spin_lock_irq(&current->sighand->siglock)
											recalc_sigpending() { 				/* see uprobe_deny_signal() */
												if (!recalc_sigpending_tsk(current) && !freezing(current))
													clear_thread_flag(TIF_SIGPENDING)
											}
											spin_unlock_irq(&current->sighand->siglock)
											
											if (unlikely(err))
												force_sig(SIGILL)
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}

以上就是uprobe的所有实现原理,感谢观看!~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值