子プロセスのstrace
straceコマンド-ffオプションは、traceするプロセスの子プロセスのシステムコールも取得、係る実相はptraceシステムコールのPT_SEIZEDオプション。
straceコマンドのprint/putsの書込みサイズ表示は、サイズ値を文字列とし、\nを付加した書込み文字列を追加しての表示故 )=サイズが下位行に表示される、システムコールに係る表示でなく、straceコマンドにおける馬鹿げた実装
grep -A 1 : 取得行の下位1行も取得。(AFTER)
grep -a : バイナリ行は無視しアスキー文字列行に係る検索。カーネル下使用のgccのsyscall.hに定義されてないユーザ参照されないシステムコールは、バイナリ表示となる。
2>&1 : &はファイルIDで、ファイル名でない。
子プロセスのstrace
[root@north trace]# cat babakaka.c
[root@north trace]# ./babakaka.out
[root@north trace]# strace ./babakaka.out 2>&1 | grep -A 1 write
[root@north trace]# strace -ff ./babakaka.out 2>&1 | grep -A 1 write
ptraceされているプロセスからのforkの子プロセスは、fork親プロセスのreal_parentがptrace親プロセスのparentに設定されるが、ptrace属性は設定されない故、係る子プロセスはトレースされないが、PT_SEIZEDでptraceされたプロセスは child->ptrace=PT_PTRACED | PT_SEIZEDで、このプロセスのfork子プロセスは、ptrace_event_enabled()で、PT_SEIZEDが設定されてるなら、childのptrace属性とparentがchildのfork子プロセスに設定される故、childの子プロセスのシステムコールのレジスタもchildの子プロセス->parentに設定されたchild->parentに設定される。
サンプル
straceコマンドのprint/putsの書込みサイズ表示は、サイズ値を文字列とし、\nを付加した書込み文字列を追加しての表示故 )=サイズが下位行に表示される、システムコールに係る表示でなく、straceコマンドにおける馬鹿げた実装
grep -A 1 : 取得行の下位1行も取得。(AFTER)
grep -a : バイナリ行は無視しアスキー文字列行に係る検索。カーネル下使用のgccのsyscall.hに定義されてないユーザ参照されないシステムコールは、バイナリ表示となる。
2>&1 : &はファイルIDで、ファイル名でない。
子プロセスのstrace
[root@north trace]# cat babakaka.c
#include <stdio.h> #include <unistd.h> void main() { if (fork()) { puts("parent"); } else { puts("child"); } }
[root@north trace]# ./babakaka.out
parent child
[root@north trace]# strace ./babakaka.out 2>&1 | grep -A 1 write
write(1, "parent\n", 7parent ) = 7
[root@north trace]# strace -ff ./babakaka.out 2>&1 | grep -A 1 write
write(1, "parent\n", 7parent ) = 7 write(1, "child\n", 6child ) = 6
カーネル
struct task_struct *child child->real_parent (fork の親プロセス) child->parent (ptarceの親プロセス) child->ptrace = PT_PTRACED (ptarceによる設定で、システムコール呼出時にチェックされ、parentにシステムコール引数レジスタを設定) child->parent->state = __TASK_TRACED (childがシステムコール引数レジスタをchild->parent->audit_context->argv[]に設定した child->parent->audit_context->argv[0] = ax child->parent->audit_context->argv[1] = bx child->parent->audit_context->argv[2] = cx child->parent->audit_context->argv[3] = dx child->parentはstateが__TASK_TRACEDでptrace_do_wait()の待機から離脱し、 childが設定したaudit_context->argv[]のシステムコール引数レジスタを取得する)
ptraceされているプロセスからのforkの子プロセスは、fork親プロセスのreal_parentがptrace親プロセスのparentに設定されるが、ptrace属性は設定されない故、係る子プロセスはトレースされないが、PT_SEIZEDでptraceされたプロセスは child->ptrace=PT_PTRACED | PT_SEIZEDで、このプロセスのfork子プロセスは、ptrace_event_enabled()で、PT_SEIZEDが設定されてるなら、childのptrace属性とparentがchildのfork子プロセスに設定される故、childの子プロセスのシステムコールのレジスタもchildの子プロセス->parentに設定されたchild->parentに設定される。
SYSCALL_DEFINE0(fork) { struct pt_regs *regs = task_pt_regs(current); return do_fork(SIGCHLD, regs->gprs[15], regs, 0, NULL, NULL); } long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; if (!(clone_flags & CLONE_UNTRACED) && likely(user_mode(regs))) { if (clone_flags & CLONE_VFORK) 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))) <- current->ptraceのPT_SEIZEDのチェック trace = 0; } p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace); <- PT_SEIZEDのtrace!=0なら、子プロセスのptrace/parentも設定 return nr; } static inline bool ptrace_event_enabled(struct task_struct *task, int event) { return task->ptrace & PT_EVENT_FLAG(event); } static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { int retval; struct task_struct *p; int cgroup_callbacks_done = 0; : retval = -ENOMEM; p = dup_task_struct(current); : p->pid = pid_nr(pid); p->tgid = p->pid; if (clone_flags & CLONE_THREAD) p->tgid = current->tgid; : if (likely(p->pid)) { ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace); : } : return p; } static inline void ptrace_init_task(struct task_struct *child, bool ptrace) { 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; <- ptraceの親プロセスを設定 if (unlikely(ptrace) && current->ptrace) { child->ptrace = current->ptrace; <- PT_SEIZEDなら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); set_tsk_thread_flag(child, TIF_SIGPENDING); } }
捕捉
PT_SEIZEDは、PTRACE_ATTACHのプロセスIDに係る実装で、fork子プロセスのカレントプロセスに係るPTRACE_TRACEMEの実装されず、故にfork子プロセスのPT_SEIZEDは、カレントプロセスでなく子プロセスIDでの設定ptrace_attach(PTRACE_ATTACH,pid): pidのtask->ptrace = PT_PTRACED ptrace_attach(PTRACE_SEIZE ,pid): pidのtask->ptrace = PT_PTRACED | PT_SEIZED SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, unsigned long, data) { struct task_struct *child; long ret; if (request == PTRACE_TRACEME) { <- fork子プロセスのカレントプロセスにトレース属性設定 ret = ptrace_traceme(); if (!ret) arch_ptrace_attach(current); goto out; } child = ptrace_get_task_struct(pid); if (IS_ERR(child)) { ret = PTR_ERR(child); goto out; } if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) { <- 引数pidプロセスにトレース属性設定 ret = ptrace_attach(child, request, addr, data); if (!ret) arch_ptrace_attach(child); goto out_put_task_struct; } ret = ptrace_check_attach(child, request == PTRACE_KILL || request == PTRACE_INTERRUPT); if (ret < 0) goto out_put_task_struct; ret = arch_ptrace(child, request, addr, data); out_put_task_struct: put_task_struct(child); out: return ret; }引数requestがPTRACE_SEIZなら task->ptrace=PT_PTRACED | PT_SEIZEDのトレース属性設定、PT_SEIZEDはforkの子プロセス作成時にチェックされ、設定されてるなら、子プロセスのptrace/parentに、親プロセスのptrace/parentが設定されて、子の子プロセスもトレースされる。
static int ptrace_attach(struct task_struct *task, long request, unsigned long addr, unsigned long flags) { bool seize = (request == PTRACE_SEIZE); int retval; retval = -EIO; if (seize) { if (addr != 0) goto out; if (flags & ~(unsigned long)PTRACE_O_MASK) goto out; flags = PT_PTRACED | PT_SEIZED | (flags << PT_OPT_FLAG_SHIFT); } else { flags = PT_PTRACED; } audit_ptrace(task); retval = -EPERM; if (unlikely(task->flags & PF_KTHREAD)) goto out; if (same_thread_group(task, current)) goto out; retval = -ERESTARTNOINTR; if (mutex_lock_interruptible(&task->signal->cred_guard_mutex)) goto out; task_lock(task); retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH); task_unlock(task); if (retval) goto unlock_creds; write_lock_irq(&tasklist_lock); retval = -EPERM; if (unlikely(task->exit_state)) goto unlock_tasklist; if (task->ptrace) goto unlock_tasklist; if (seize) flags |= PT_SEIZED; if (ns_capable(task_user_ns(task), CAP_SYS_PTRACE)) flags |= PT_PTRACE_CAP; task->ptrace = flags; __ptrace_link(task, current); if (!seize) send_sig_info(SIGSTOP, SEND_SIG_FORCED, task); spin_lock(&task->sighand->siglock); if (task_is_stopped(task) && task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) signal_wake_up(task, 1); spin_unlock(&task->sighand->siglock); retval = 0; unlock_tasklist: write_unlock_irq(&tasklist_lock); unlock_creds: mutex_unlock(&task->signal->cred_guard_mutex); out: if (!retval) { wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT, ptrace_trapping_sleep_fn, TASK_UNINTERRUPTIBLE); proc_ptrace_connector(task, PTRACE_ATTACH); } return retval; }