ptrace在内核中的实现是sys_ptrace函数,也可以说是SYSCALL_DEFINE4(ptrace, ...)。
其中sys_ptrace负责attach相关请求的处理,之后调用arch_ptrace函数来处理其余请求,而arch_ptrace函数也只处理一部分请求,剩下的请求都由ptrace_request函数来处理。
sys_ptrace | ----arch_ptrace | ----ptrace_request
相关代码太多,代码主要位于/kernel/ptrace.c。
ptrace函数peekdata获取目标进程的内存数据的代码实现在/kernel/ptrace.c。具体函数调用过程如下:
generic_ptrace_peekdata | ----ptrace_access_vm | ----__access_remote_vm
__access_remote_vm 函数读取数据的关键部分代码,最终是通过每个vma的access操作函数来拿取数据,也就是vma->vm_ops->access(...),详细如下。
while (len) { int bytes, ret, offset; void *maddr; struct page *page = NULL; ret = get_user_pages_remote(tsk, mm, addr, 1, gup_flags, &page, &vma, NULL); if (ret <= 0) { #ifndef CONFIG_HAVE_IOREMAP_PROT break; #else /* * Check if this is a VM_IO | VM_PFNMAP VMA, which * we can access using slightly different code. */ vma = find_vma(mm, addr); if (!vma || vma->vm_start > addr) break; if (vma->vm_ops && vma->vm_ops->access) ret = vma->vm_ops->access(vma, addr, buf, len, write); if (ret <= 0) break; bytes = ret; #endif } else { bytes = len; offset = addr & (PAGE_SIZE-1); if (bytes > PAGE_SIZE-offset) bytes = PAGE_SIZE-offset; maddr = kmap(page); if (write) { copy_to_user_page(vma, page, addr, maddr + offset, buf, bytes); set_page_dirty_lock(page); } else { copy_from_user_page(vma, page, addr, buf, maddr + offset, bytes); } kunmap(page); put_page(page); } len -= bytes; buf += bytes; addr += bytes; }
该实现主要是检查子进程与父进程是否在一个命名空间内以及凭证信息,若都通过设置进程描述符的ptrace标志位。
if (request == PTRACE_TRACEME) { ret = ptrace_traceme(); if (!ret) arch_ptrace_attach(current); goto out; }
ptrace函数pokedata将数据写入目标进程的内存的代码实现在/kernel/ptrace.c。具体函数调用过程如下:
generic_ptrace_pokedata | ----ptrace_access_vm | ----__access_remote_vm
可以看出其实现过程与peekdata差不多。
以上请求都是通过ptrace_resume函数实现。
if (request == PTRACE_SYSCALL) set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); else clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); #ifdef TIF_SYSCALL_EMU if (request == PTRACE_SYSEMU || request == PTRACE_SYSEMU_SINGLESTEP) set_tsk_thread_flag(child, TIF_SYSCALL_EMU); else clear_tsk_thread_flag(child, TIF_SYSCALL_EMU); #endif if (is_singleblock(request)) { if (unlikely(!arch_has_block_step())) return -EIO; user_enable_block_step(child); } else if (is_singlestep(request) || is_sysemu_singlestep(request)) { if (unlikely(!arch_has_single_step())) return -EIO; user_enable_single_step(child); } else { user_disable_single_step(child); }
set_tsk_thread_flag函数实现内容为设置进程描述符中thread_info->stack标志,设置为TIF_SYSCALL_TRACE、TIF_SINGLESTEP或TIF_SYSCALL_EMU。user_enable_single_step函数也是调用set_tsk_thread_flag函数。
从USER区域中读取一个字节,偏移量为addr。其实现函数为ptrace_read_user,定义如下。
static int ptrace_read_user(struct task_struct *tsk, unsigned long off, unsigned long __user *ret) { unsigned long tmp; if (off & 3) return -EIO; tmp = 0; if (off == PT_TEXT_ADDR) tmp = tsk->mm->start_code; else if (off == PT_DATA_ADDR) tmp = tsk->mm->start_data; else if (off == PT_TEXT_END_ADDR) tmp = tsk->mm->end_code; else if (off < sizeof(struct pt_regs)) tmp = get_user_reg(tsk, off >> 2); else if (off >= sizeof(struct user)) return -EIO; return put_user(tmp, ret); }
向USER区域中写入一个字节,偏移量为addr。其实现函数为ptrace_write_user,定义如下。
static int ptrace_write_user(struct task_struct *tsk, unsigned long off, unsigned long val) { if (off & 3 || off >= sizeof(struct user)) return -EIO; if (off >= sizeof(struct pt_regs)) return 0; return put_user_reg(tsk, off >> 2, val); }
获取寄存器信息,通过copy_regset_to_user函数实现的,该函数通过调用寄存器集user_regset的操作函数get来获取寄存器集的数据信息。
static inline int copy_regset_to_user(struct task_struct *target, const struct user_regset_view *view, unsigned int setno, unsigned int offset, unsigned int size, void __user *data) { const struct user_regset *regset = &view->regsets[setno]; if (!regset->get) return -EOPNOTSUPP; if (!access_ok(VERIFY_WRITE, data, size)) return -EFAULT; return regset->get(target, regset, offset, size, NULL, data); }
其中,view参数的内容来自user_arm_view变量,它是一个全局变量,记录当前寄存器的状态信息。