zoukankan      html  css  js  c++  java
  • strace跟踪多进程与内核的交互

    1、ptrace的说明

    ptrace原型:

     #include <sys/ptrace.h>
     long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

    ptrace跟踪多进程时,需要通过ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions),即PTRACE_SETOPTIONS设置ptrace的相关属性,也就是将data指向的值设定为ptrace的选项,可选的有:

    (1)PTRACE_O_TRACEFORK:被跟踪进程在下次调用fork()时停止执行,并自动跟踪新产生的进程,新产生的进程刚开始运行就收到SIGSTOP信号。其新产生的进程的pid可以通过PTRACE_GETEVENTMSG得到(即:ptrace(PTRACE_GETEVENTMSG, child_waited, 0, &new_pid)。

    (2) PTRACE_O_TRACEVFORK:被跟踪进程在下次调用vfork()时停止执行,并自动跟踪新产生的进程,新产生的进程刚开始收到SIGSTOP信号。其新产生的进程的pid可以通过PTRACE_GETEVENTMSG得到。

    (3) PTRACE_O_TRACECLONE:被跟踪进程在下一次调用clone()时将其停止,并自动跟踪新产生的进程,新产生的进程刚开始收到SIGSTOP信号。其新产生的进程的pid可以通过PTRACE_GETEVENTMSG得到。

    (4)PTRACE_O_TRACEEXEC:被跟踪进程在下一次调用exec()函数时使其停止,PTRACE_GETEVENTMSG可用于获取发生execve的old_pid(即:ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid))。

    (5)PTRACE_O_EXITKILL:当跟踪进程退出时,向所有被跟踪进程发送SIGKILL信号将其退出,这个参数可以防止被跟踪进程脱离跟踪进程的控制。

    (6)PTRACE_O_TRACEEXIT:被跟踪进程在退出是停止其执行,被跟踪进程的退出状态可通过PTRACE_GETEVENTMSG获得。

    2、strace与linux内核的交互

    strace-4.8,linux-3.10.1

    以下就针对上面加粗部分,分析strace对新进程的处理以及linux内核对这部分功能的支持

    fork、vfork、clone系统调用最后都会调用do_fork,只是传递的参数不同。

    SYSCALL_DEFINE0(fork)
    {
    #ifdef CONFIG_MMU
        return do_fork(SIGCHLD, 0, 0, NULL, NULL);
    #else
        /* can not support in nommu mode */
        return(-EINVAL);
    #endif
    }
    SYSCALL_DEFINE0(vfork)
    {
        return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 
                0, NULL, NULL);
    }
    SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
             int __user *, parent_tidptr,
             int __user *, child_tidptr,
             int, tls_val)
    {
        return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
    }

    在do_fork中,此处截取和ptrace相关的部分:

    long do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr)
    {
        ...
    
        /*
         * Determine whether and which event to report to ptracer.  When
         * called from kernel_thread or CLONE_UNTRACED is explicitly
         * requested, no event is reported; otherwise, report if the event
         * for the type of forking is enabled.
         */
        if (!(clone_flags & CLONE_UNTRACED)) {
            if (clone_flags & CLONE_VFORK)//根据cloneflags判断陷入strace的event
                trace = PTRACE_EVENT_VFORK;
            else if ((clone_flags & CSIGNAL) != SIGCHLD)
                trace = PTRACE_EVENT_CLONE;
            else
                trace = PTRACE_EVENT_FORK;
    
            if (likely(!ptrace_event_enabled(current, trace)))//if(value),判断task->ptrace没设置该ptrace event则将trace置0
                trace = 0;
        }
    
        p = copy_process(clone_flags, stack_start, stack_size,
                 child_tidptr, NULL, trace);//trace是ptrace中相应的setoptions(fork等陷入
        /*
         * Do this prior waking up the new thread - the thread pointer
         * might get invalid after that point, if the thread exits quickly.
         */
        if (!IS_ERR(p)) {
            struct completion vfork;
    
            trace_sched_process_fork(current, p);
    
            nr = task_pid_vnr(p);
    
            ...
    
            wake_up_new_task(p);//将进程唤醒并添加到就绪队列中等待调度
    
            /* forking complete and child started to run, tell ptracer*/
            if (unlikely(trace))//不进入执行可能性大,但是如果strace设置了该trace事件陷入,则进入执行
                ptrace_event(trace, nr);//此处nr是子进程pid,为了让strace可以通过PTRACE_GETEVENTMSG获得该子进程pid
    
            ...
        } else {
            nr = PTR_ERR(p);
        }
        return nr;
    }  

    在copy_process中,有

    if (likely(p->pid)) {
            ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);//设置子进程被ptrace
    ...
        }

    最重要的部分,对应“并自动跟踪新产生的进程,新产生的进程刚开始运行就收到SIGSTOP信号”:

    /**
     * ptrace_init_task - initialize ptrace state for a new child
     * @child:        new child task
     * @ptrace:        true if child should be ptrace'd by parent's tracer
     *
     * This is called immediately after adding @child to its parent's children
     * list.  @ptrace is false in the normal case, and true to ptrace @child.
     *
     * Called with current's siglock and write_lock_irq(&tasklist_lock) held.
     */
    static inline void ptrace_init_task(struct task_struct *child, bool ptrace)
    {//设置子进程ptrace,如果子进程应该被追踪父进程的strace追踪的话,此ptrace值为1
        INIT_LIST_HEAD(&child->ptrace_entry);
        INIT_LIST_HEAD(&child->ptraced);
    #ifdef CONFIG_HAVE_HW_BREAKPOINT
        atomic_set(&child->ptrace_bp_refcnt, 1);
    #endif
        child->jobctl = 0;
        child->ptrace = 0;
        child->parent = child->real_parent;
    
        if (unlikely(ptrace) && current->ptrace) {
            //父进程被trace的标志给子进程,因为有了相应的ptrace标志(PT_TRACED),导致内核在投递信号时,先检查task_struct的ptrace字段的该标志,若有,则设置进程状态TASK_STOPPED,以中断执行子进程,而后notify_parent和CHLD信号用于通知tracer,tracer可设置PTRACE_SYSCALL使该进程之后发生和完成syscall也会陷入到strace,即没通过attach做这个跟踪,而是内核直接设置了标志,更加直接,不用用户层的系统调用ptrace
            child->ptrace = current->ptrace;
            __ptrace_link(child, current->parent);
    
            if (child->ptrace & PT_SEIZED)
                task_set_jobctl_pending(child, JOBCTL_TRAP_STOP);
            else
                sigaddset(&child->pending.signal, SIGSTOP);//给子进程child设置SIGSTOP信号
    
            set_tsk_thread_flag(child, TIF_SIGPENDING);//给子进程child设置TIF_SIGPENDING标志,说明该进程有延迟的信号(SIGSTOP)要等待处理。该子进程被调度时,在进程返回用户空间前,会调用do_notify_resume()处理该进程的信号
        }
    }

    此处就设置了子进程有未处理的SIGSTOP信号,导致该子进程被调度时,收到SIGSTOP信号,又因为该子进程被strace跟踪,所以该子进程因SIGSTOP陷入到strace中,strace就在第一时刻捕获到该子进程,strace会发现已被追踪链表中没有该子进程pid,则为该子进程新建file和tcb,并设置相应option。

    进程的do_fork中在copy_process后会wake_up_new_process把子进程放到运行队列中,然后执行到ptrace_event,会导致父进程SIGTRAP陷入到strace中。此处对应“被跟踪进程在下次调用fork()时停止执行,新产生的进程的pid可以通过PTRACE_GETEVENTMSG得到”:

    /**
     * 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_GETEVENTMSG可获得fork等的子进程pid(strace没用这个,strace是通过wait到新pid就是新进程,但获取发生execve的old_pid用
            ptrace_notify((event << 8) | SIGTRAP);//如果ptrace设置了该event事件选项,则SIGTRAP陷入strace程序(可以通过geteventmsg得到old_pid
        } else if (event == PTRACE_EVENT_EXEC) {//发生execve则SIGTRAP陷入strace
            /* legacy EXEC report via SIGTRAP */
            if ((current->ptrace & (PT_PTRACED|PT_SEIZED)) == PT_PTRACED)
                send_sig(SIGTRAP, current, 0);
        }
    }

    补充:

    用户态:ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid)

    走到内核是:SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,unsigned long, data)->arch_ptrace->ptrace_request->ret = put_user(child->ptrace_message, datalp);也就是这样把message传给用户态来获取。

  • 相关阅读:
    第二个冲刺 6.3.4.学术诚信与职业道德
    第二个冲刺 Sprint
    css之清除浮动
    style和getComputedStyle(ff)和currentStyle
    php 中间件
    Vue 和 angular
    img 分区响应图
    PHP composer
    php实现文件上传,下载的常见文件配置
    php 命名空间
  • 原文地址:https://www.cnblogs.com/beixiaobei/p/9559875.html
Copyright © 2011-2022 走看看