ptrace内核源码实现

2022/9/14 1:18:53

本文主要是介绍ptrace内核源码实现,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、ptrace系统调用

ptrace在内核中的实现是sys_ptrace函数,也可以说是SYSCALL_DEFINE4(ptrace, ...)。

其中sys_ptrace负责attach相关请求的处理,之后调用arch_ptrace函数来处理其余请求,而arch_ptrace函数也只处理一部分请求,剩下的请求都由ptrace_request函数来处理。

sys_ptrace
  |
  ----arch_ptrace
      |
      ----ptrace_request

 

相关代码太多,代码主要位于/kernel/ptrace.c。

二、相关请求处理的实现

1.PTRACE_PEEKTEXT, PTRACE_PEEKDATA的实现

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;
    }

 

2.PTRACE_TRACEME的实现

该实现主要是检查子进程与父进程是否在一个命名空间内以及凭证信息,若都通过设置进程描述符的ptrace标志位。

if (request == PTRACE_TRACEME) {
        ret = ptrace_traceme();
        if (!ret)
            arch_ptrace_attach(current);
        goto out;
}

 

3.PTRACE_POKETEXT, PTRACE_POKEDATA的实现

ptrace函数pokedata将数据写入目标进程的内存的代码实现在/kernel/ptrace.c。具体函数调用过程如下:

generic_ptrace_pokedata
    |
     ----ptrace_access_vm
             |
              ----__access_remote_vm

 

可以看出其实现过程与peekdata差不多。

4.PTRACE_SINGLESTEP、PTRACE_SINGLEBLOCK、PTRACE_SYSEMU、PTRACE_SYSEMU_SINGLESTEP、PTRACE_SYSCALL、PTRACE_KILL和PTRACE_CONT的实现

以上请求都是通过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函数。

5.PTRACE_PEEKUSR的实现

从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);
}

 

6.PTRACE_POKEUSR的实现

向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);
}

 

7.PTRACE_GETREGS的实现

获取寄存器信息,通过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变量,它是一个全局变量,记录当前寄存器的状态信息。

 



这篇关于ptrace内核源码实现的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程