• gdb的set followforkmode child如何工作


    一、 clone函数的man手册说明

    clone man手册的说明:
    /* Prototype for the glibc wrapper function */

    #include <sched.h>

    int clone(int (*fn)(void *), void *child_stack,
    int flags, void *arg, ...
    /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );

    /* Prototype for the raw system call */

    long clone(unsigned long flags, void *child_stack,
    void *ptid, void *ctid,
    struct pt_regs *regs);

    clone的初衷主要是为了在共享内存空间的前提下实现多线程。
    The main use of clone() is to implement threads: multiple threads of control in a program that run concurrently in a shared memory space.
    带入口函数参数(int (*fn)(void *))是glibc封装的,它的内部实现是先调用“raw system call”clone系统调用,新进程创建成功之后在其中执行用户提供的入口函数。
    When the child process is created with clone(), it executes the function fn(arg). (This differs from fork(2), where execution continues in the child from the point of the fork(2) call.) The fn argument is a
    pointer to a function that is called by the child process at the beginning of its execution. The arg argument is passed to the fn function.
    flags标志位中可以设置SIGCHLD信号,如果没有设置,新任务退出的时候父任务不会收到通知。这个SIGCHLD的设置比它看起来的更加重要,甚至可以说,是这个标志位决定了gdb认为它是一个新线程还是一个新进程(尽管常规意义上我们认为如果共享内存地址空间、文件系统才是线程)。
    The low byte of flags contains the number of the termination signal sent to the parent when the child dies. If this signal is specified as anything other than SIGCHLD, then the parent process must specify the
    __WALL or __WCLONE options when waiting for the child with wait(2). If no signal is specified, then the parent process is not signaled when the child terminates.

    二、 内核的代码

    fork和vfork默认设置了SIGCHLD标志位,而clone没有,并且内核内核线程还默认设置了CLONE_UNTRACED标志位。
    /*
    * Ok, this is the main fork-routine.
    *
    * It copies the process, and if successful kick-starts
    * it and waits for it to finish using the VM if required.
    */
    long do_fork(unsigned long clone_flags,
    unsigned long stack_start,
    unsigned long stack_size,
    int __user *parent_tidptr,
    int __user *child_tidptr)
    {
    struct task_struct *p;
    int trace = 0;
    long nr;

    /*
    * 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)
    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)))
    trace = 0;
    }
    ……
    /* forking complete and child started to run, tell ptracer */
    if (unlikely(trace))
    ptrace_event(trace, nr);
    ……
    }
    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    {
    return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
    (unsigned long)arg, NULL, NULL);
    }

    #ifdef __ARCH_WANT_SYS_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
    }
    #endif

    #ifdef __ARCH_WANT_SYS_VFORK
    SYSCALL_DEFINE0(vfork)
    {
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
    0, NULL, NULL);
    }
    #endif
    SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
    int __user *, parent_tidptr,
    int __user *, child_tidptr,
    int, tls_val)
    #endif
    {
    return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
    }

    三、 glibc的代码

    可以看到,和fork的逻辑类似,需要根据clone的返回值判断是当前进程还是新创建进程。如果是当前进程则函数返回,新进程则开始执行指定的函数。
    static int
    create_thread (struct pthread *pd, const struct pthread_attr *attr,
    STACK_VARIABLES_PARMS)
    {
    ……
    The termination signal is chosen to be zero which means no signal
    is sent. */
    int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
    | CLONE_SETTLS | CLONE_PARENT_SETTID
    | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
    | 0);
    ……
    }

    glibc-2.17\sysdeps\unix\sysv\linux\i386\clone.S
    .text
    ENTRY (BP_SYM (__clone))
    /* Sanity check arguments. */
    ……
    movl $SYS_ify(clone),%eax

    #ifdef RESET_PID
    /* Remember the flag value. */
    movl %ebx, (%ecx)
    #endif

    /* End FDE now, because in the child the unwind info will be
    wrong. */
    cfi_endproc

    int $0x80
    popl %edi
    popl %esi
    popl %ebx

    test %eax,%eax
    jl SYSCALL_ERROR_LABEL
    jz L(thread_start)

    ret

    L(thread_start):
    cfi_startproc;
    /* Clearing frame pointer is insufficient, use CFI. */
    cfi_undefined (eip);
    /* Note: %esi is zero. */
    movl %esi,%ebp /* terminate the stack frame */

    L(thread_start):

    四、 gdb的代码

    gdb-7.6.1\gdb\infrun.c
    /* Tell the target to follow the fork we're stopped at. Returns true
    if the inferior should be resumed; false, if the target for some
    reason decided it's best not to resume. */

    static int
    follow_fork (void)
    {
    ……
    /* If there were any forks/vforks that were caught and are now to be
    followed, then do so now. */
    switch (tp->pending_follow.kind)
    {
    case TARGET_WAITKIND_FORKED:
    case TARGET_WAITKIND_VFORKED:
    {
    ……
    /* Tell the target to do whatever is necessary to follow
    either parent or child. */
    if (target_follow_fork (follow_child))
    {
    /* Target refused to follow, or there's some other reason
    we shouldn't resume. */
    should_resume = 0;
    }
    ……
    }
    }

    五、 测试代码

    可以看到,如果执行clone是设置了SIGCHLD标志,gdb认为是一个新进程;如果没有设置则认为是一个新进程,而和是否共享地址空间(CLONE_VM)无关
    tsecer@harry: cat -n gdb.follow.fork.cpp
    1 #include <unistd.h>
    2 #include <stdlib.h>
    3 #include <syscall.h>
    4 #include <sched.h>
    5 #include "signal.h"
    6
    7 int x = 0;
    8 static int threadEntry(void * arg)
    9 {
    10 x++;
    11 return 0;
    12 }
    13
    14 int main()
    15 {
    16 const int STACKSIZE = 1024 * 1024 * 2;
    17 char *stack = (char*)malloc(STACKSIZE);
    18 stack += STACKSIZE - 256;
    19 const pid_t child = clone(threadEntry, stack, CLONE_FS
    20 #if ENABLE_SIGCHLD
    21 | SIGCHLD
    22 #endif
    23 , nullptr, nullptr, nullptr, nullptr);
    24 if (child > 0)
    25 {
    26 while (true)
    27 {
    28 sleep(1);
    29 }
    30 }
    31
    32 return 0;
    33 }
    tsecer@harry: g++ gdb.follow.fork.cpp -g
    tsecer@harry: gdb -quiet -- ./a.out
    Reading symbols from ./a.out...
    (gdb) b 10
    Breakpoint 1 at 0x4005df: file gdb.follow.fork.cpp, line 10.
    (gdb) r
    Starting program:  /home/tsecer/gdb.follow.fork/a.out
    [New LWP 4469]
    [Switching to LWP 4469]

    Thread 2 hit Breakpoint 1, threadEntry (arg=0x0) at gdb.follow.fork.cpp:10
    10 x++;
    (gdb) quit
    A debugging session is active.

    Inferior 1 [process 4465] will be killed.

    Quit anyway? (y or n) y
    tsecer@harry: g++ gdb.follow.fork.cpp -g -DENABLE_SIGCHLD=1
    tsecer@harry: gdb -quiet -- ./a.out
    Reading symbols from ./a.out...
    (gdb) b 10
    Breakpoint 1 at 0x4005df: file gdb.follow.fork.cpp, line 10.
    (gdb) r
    Starting program:  /home/tsecer/gdb.follow.fork/a.out
    [Detaching after fork from child process 4653]
    ^C
    Program received signal SIGINT, Interrupt.
    0x00007ffff72321a0 in __nanosleep_nocancel () from /lib64/libc.so.6
    (gdb) quit
    A debugging session is active.

    Inferior 1 [process 4649] will be killed.

    Quit anyway? (y or n) y
    tsecer@harry:

  • 相关阅读:
    合并两个排序的链表
    C#中调用C++的DLL文件
    C#获取进程的主窗口句柄
    在VS2008中编译纯c/c++程序并由c#调用过程 及 C++引用c#dll 模拟登陆实现
    C#多屏幕显示器编程
    Windows系统下的多显示器模式开发日记
    在 C# 中调用 C++
    C# 中调用C++ DLL (P/Invoke)
    C#多屏时控制窗体显示在哪个显示器上
    c# Winform 开发分屏显示应用程序
  • 原文地址:https://www.cnblogs.com/tsecer/p/16200332.html
Copyright © 2020-2023  润新知