gpt4 book ai didi

c - 如何使用 PTRACE 获得多线程的一致 View ?

转载 作者:IT王子 更新时间:2023-10-28 23:52:17 25 4
gpt4 key购买 nike

我在工作时 on this question ,我遇到了一个可能的想法,使用 ptrace ,但我无法正确理解 ptrace与线程交互。

假设我有一个给定的多线程主进程,我想附加到其中的特定线程(可能来自 fork 的子进程)。

  • 我可以附加到特定线程吗? (手册在这个问题上存在分歧。)
  • 如果是这样,这是否意味着单步执行仅执行该线程的指令?它会停止所有进程的线程吗?
  • 如果是这样,当我拨打 PTRACE_SYSCALL 时,所有其他线程是否保持停止状态?或 PTRACE_SINGLESTEP ,还是所有线程都继续?有没有办法只在一个线程中前进但保证其他线程保持停止?

  • 基本上,我想通过强制所有线程停止来同步原始程序,然后通过单步跟踪一个跟踪线程只执行一小组单线程指令。

    到目前为止,我个人的尝试看起来有点像这样:
    pid_t target = syscall(SYS_gettid);   // get the calling thread's ID
    pid_t pid = fork();

    if (pid > 0)
    {
    waitpid(pid, NULL, 0); // synchronise main process

    important_instruction();
    }
    else if (pid == 0)
    {
    ptrace(target, PTRACE_ATTACH, NULL, NULL); // does this work?

    // cancel parent's "waitpid" call, e.g. with a signal

    // single-step to execute "important_instruction()" above

    ptrace(target, PTRACE_DETACH, NULL, NULL); // parent's threads resume?

    _Exit(0);
    }

    但是,我不确定,也找不到合适的引用文献,这是否同时正确,并且 important_instruction()保证仅在所有其他线程停止时才执行。我也明白当 parent 从其他地方接收信号时可能会出现竞争条件,我听说我应该使用 PTRACE_SEIZE相反,但这似乎并非无处不在。

    任何澄清或引用将不胜感激!

    最佳答案

    我写了第二个测试用例。我不得不添加一个单独的答案,因为它太长而无法放入包含示例输出的第一个答案。

    首先,这里是tracer.c :

    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/ptrace.h>
    #include <sys/prctl.h>
    #include <sys/wait.h>
    #include <sys/user.h>
    #include <dirent.h>
    #include <string.h>
    #include <signal.h>
    #include <errno.h>
    #include <stdio.h>
    #ifndef SINGLESTEPS
    #define SINGLESTEPS 10
    #endif

    /* Similar to getline(), except gets process pid task IDs.
    * Returns positive (number of TIDs in list) if success,
    * otherwise 0 with errno set. */
    size_t get_tids(pid_t **const listptr, size_t *const sizeptr, const pid_t pid)
    {
    char dirname[64];
    DIR *dir;
    pid_t *list;
    size_t size, used = 0;

    if (!listptr || !sizeptr || pid < (pid_t)1) {
    errno = EINVAL;
    return (size_t)0;
    }

    if (*sizeptr > 0) {
    list = *listptr;
    size = *sizeptr;
    } else {
    list = *listptr = NULL;
    size = *sizeptr = 0;
    }

    if (snprintf(dirname, sizeof dirname, "/proc/%d/task/", (int)pid) >= (int)sizeof dirname) {
    errno = ENOTSUP;
    return (size_t)0;
    }

    dir = opendir(dirname);
    if (!dir) {
    errno = ESRCH;
    return (size_t)0;
    }

    while (1) {
    struct dirent *ent;
    int value;
    char dummy;

    errno = 0;
    ent = readdir(dir);
    if (!ent)
    break;

    /* Parse TIDs. Ignore non-numeric entries. */
    if (sscanf(ent->d_name, "%d%c", &value, &dummy) != 1)
    continue;

    /* Ignore obviously invalid entries. */
    if (value < 1)
    continue;

    /* Make sure there is room for another TID. */
    if (used >= size) {
    size = (used | 127) + 128;
    list = realloc(list, size * sizeof list[0]);
    if (!list) {
    closedir(dir);
    errno = ENOMEM;
    return (size_t)0;
    }
    *listptr = list;
    *sizeptr = size;
    }

    /* Add to list. */
    list[used++] = (pid_t)value;
    }
    if (errno) {
    const int saved_errno = errno;
    closedir(dir);
    errno = saved_errno;
    return (size_t)0;
    }
    if (closedir(dir)) {
    errno = EIO;
    return (size_t)0;
    }

    /* None? */
    if (used < 1) {
    errno = ESRCH;
    return (size_t)0;
    }

    /* Make sure there is room for a terminating (pid_t)0. */
    if (used >= size) {
    size = used + 1;
    list = realloc(list, size * sizeof list[0]);
    if (!list) {
    errno = ENOMEM;
    return (size_t)0;
    }
    *listptr = list;
    *sizeptr = size;
    }

    /* Terminate list; done. */
    list[used] = (pid_t)0;
    errno = 0;
    return used;
    }


    static int wait_process(const pid_t pid, int *const statusptr)
    {
    int status;
    pid_t p;

    do {
    status = 0;
    p = waitpid(pid, &status, WUNTRACED | WCONTINUED);
    } while (p == (pid_t)-1 && errno == EINTR);
    if (p != pid)
    return errno = ESRCH;

    if (statusptr)
    *statusptr = status;

    return errno = 0;
    }

    static int continue_process(const pid_t pid, int *const statusptr)
    {
    int status;
    pid_t p;

    do {

    if (kill(pid, SIGCONT) == -1)
    return errno = ESRCH;

    do {
    status = 0;
    p = waitpid(pid, &status, WUNTRACED | WCONTINUED);
    } while (p == (pid_t)-1 && errno == EINTR);

    if (p != pid)
    return errno = ESRCH;

    } while (WIFSTOPPED(status));

    if (statusptr)
    *statusptr = status;

    return errno = 0;
    }

    void show_registers(FILE *const out, pid_t tid, const char *const note)
    {
    struct user_regs_struct regs;
    long r;

    do {
    r = ptrace(PTRACE_GETREGS, tid, &regs, &regs);
    } while (r == -1L && errno == ESRCH);
    if (r == -1L)
    return;

    #if (defined(__x86_64__) || defined(__i386__)) && __WORDSIZE == 64
    if (note && *note)
    fprintf(out, "Task %d: RIP=0x%016lx, RSP=0x%016lx. %s\n", (int)tid, regs.rip, regs.rsp, note);
    else
    fprintf(out, "Task %d: RIP=0x%016lx, RSP=0x%016lx.\n", (int)tid, regs.rip, regs.rsp);
    #elif (defined(__x86_64__) || defined(__i386__)) && __WORDSIZE == 32
    if (note && *note)
    fprintf(out, "Task %d: EIP=0x%08lx, ESP=0x%08lx. %s\n", (int)tid, regs.eip, regs.esp, note);
    else
    fprintf(out, "Task %d: EIP=0x%08lx, ESP=0x%08lx.\n", (int)tid, regs.eip, regs.esp);
    #endif
    }


    int main(int argc, char *argv[])
    {
    pid_t *tid = 0;
    size_t tids = 0;
    size_t tids_max = 0;
    size_t t, s;
    long r;

    pid_t child;
    int status;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
    fprintf(stderr, "\n");
    fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
    fprintf(stderr, " %s COMMAND [ ARGS ... ]\n", argv[0]);
    fprintf(stderr, "\n");
    fprintf(stderr, "This program executes COMMAND in a child process,\n");
    fprintf(stderr, "and waits for it to stop (via a SIGSTOP signal).\n");
    fprintf(stderr, "When that occurs, the register state of each thread\n");
    fprintf(stderr, "is dumped to standard output, then the child process\n");
    fprintf(stderr, "is sent a SIGCONT signal.\n");
    fprintf(stderr, "\n");
    return 1;
    }

    child = fork();
    if (child == (pid_t)-1) {
    fprintf(stderr, "fork() failed: %s.\n", strerror(errno));
    return 1;
    }

    if (!child) {
    prctl(PR_SET_DUMPABLE, (long)1);
    prctl(PR_SET_PTRACER, (long)getppid());
    fflush(stdout);
    fflush(stderr);
    execvp(argv[1], argv + 1);
    fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
    return 127;
    }

    fprintf(stderr, "Tracer: Waiting for child (pid %d) events.\n\n", (int)child);
    fflush(stderr);

    while (1) {

    /* Wait for a child event. */
    if (wait_process(child, &status))
    break;

    /* Exited? */
    if (WIFEXITED(status) || WIFSIGNALED(status)) {
    errno = 0;
    break;
    }

    /* At this point, only stopped events are interesting. */
    if (!WIFSTOPPED(status))
    continue;

    /* Obtain task IDs. */
    tids = get_tids(&tid, &tids_max, child);
    if (!tids)
    break;

    printf("Process %d has %d tasks,", (int)child, (int)tids);
    fflush(stdout);

    /* Attach to all tasks. */
    for (t = 0; t < tids; t++) {
    do {
    r = ptrace(PTRACE_ATTACH, tid[t], (void *)0, (void *)0);
    } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
    if (r == -1L) {
    const int saved_errno = errno;
    while (t-->0)
    do {
    r = ptrace(PTRACE_DETACH, tid[t], (void *)0, (void *)0);
    } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
    tids = 0;
    errno = saved_errno;
    break;
    }
    }
    if (!tids) {
    const int saved_errno = errno;
    if (continue_process(child, &status))
    break;
    printf(" failed to attach (%s).\n", strerror(saved_errno));
    fflush(stdout);
    if (WIFCONTINUED(status))
    continue;
    errno = 0;
    break;
    }

    printf(" attached to all.\n\n");
    fflush(stdout);

    /* Dump the registers of each task. */
    for (t = 0; t < tids; t++)
    show_registers(stdout, tid[t], "");
    printf("\n");
    fflush(stdout);

    for (s = 0; s < SINGLESTEPS; s++) {
    do {
    r = ptrace(PTRACE_SINGLESTEP, tid[tids-1], (void *)0, (void *)0);
    } while (r == -1L && errno == ESRCH);
    if (!r) {
    for (t = 0; t < tids - 1; t++)
    show_registers(stdout, tid[t], "");
    show_registers(stdout, tid[tids-1], "Advanced by one step.");
    printf("\n");
    fflush(stdout);
    } else {
    fprintf(stderr, "Single-step failed: %s.\n", strerror(errno));
    fflush(stderr);
    }
    }

    /* Detach from all tasks. */
    for (t = 0; t < tids; t++)
    do {
    r = ptrace(PTRACE_DETACH, tid[t], (void *)0, (void *)0);
    } while (r == -1 && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
    tids = 0;
    if (continue_process(child, &status))
    break;
    if (WIFCONTINUED(status)) {
    printf("Detached. Waiting for new stop events.\n\n");
    fflush(stdout);
    continue;
    }
    errno = 0;
    break;
    }
    if (errno)
    fprintf(stderr, "Tracer: Child lost (%s)\n", strerror(errno));
    else
    if (WIFEXITED(status))
    fprintf(stderr, "Tracer: Child exited (%d)\n", WEXITSTATUS(status));
    else
    if (WIFSIGNALED(status))
    fprintf(stderr, "Tracer: Child died from signal %d\n", WTERMSIG(status));
    else
    fprintf(stderr, "Tracer: Child vanished\n");
    fflush(stderr);

    return status;
    }
    tracer.c执行指定的命令,等待命令收到 SIGSTOP信号。 ( tracer.c 不会自行发送;您可以让被跟踪者自行停止,也可以向外部发送信号。)

    当命令停止时, tracer.c将 ptrace 附加到每个线程,并将其中一个线程单步执行固定数量的步骤( SINGLESTEPS 编译时常量),显示每个线程的相关寄存器状态。

    之后,它与命令分离,并向其发送 SIGCONT信号,让它继续正常运行。

    这是一个简单的测试程序, worker.c ,我用于测试:
    #include <pthread.h>
    #include <signal.h>
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>

    #ifndef THREADS
    #define THREADS 2
    #endif

    volatile sig_atomic_t done = 0;

    void catch_done(int signum)
    {
    done = signum;
    }

    int install_done(const int signum)
    {
    struct sigaction act;

    sigemptyset(&act.sa_mask);
    act.sa_handler = catch_done;
    act.sa_flags = 0;
    if (sigaction(signum, &act, NULL))
    return errno;
    else
    return 0;
    }

    void *worker(void *data)
    {
    volatile unsigned long *const counter = data;

    while (!done)
    __sync_add_and_fetch(counter, 1UL);

    return (void *)(unsigned long)__sync_or_and_fetch(counter, 0UL);
    }

    int main(void)
    {
    unsigned long counter = 0UL;
    pthread_t thread[THREADS];
    pthread_attr_t attrs;
    size_t i;

    if (install_done(SIGHUP) ||
    install_done(SIGTERM) ||
    install_done(SIGUSR1)) {
    fprintf(stderr, "Worker: Cannot install signal handlers: %s.\n", strerror(errno));
    return 1;
    }

    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 65536);
    for (i = 0; i < THREADS; i++)
    if (pthread_create(&thread[i], &attrs, worker, &counter)) {
    done = 1;
    fprintf(stderr, "Worker: Cannot create thread: %s.\n", strerror(errno));
    return 1;
    }
    pthread_attr_destroy(&attrs);

    /* Let the original thread also do the worker dance. */
    worker(&counter);

    for (i = 0; i < THREADS; i++)
    pthread_join(thread[i], NULL);

    return 0;
    }

    使用例如编译两者
    gcc -W -Wall -O3 -fomit-frame-pointer worker.c -pthread -o worker
    gcc -W -Wall -O3 -fomit-frame-pointer tracer.c -o tracer

    并在单独的终端中或在后台运行,例如使用
    ./tracer ./worker &

    跟踪器显示 worker 的 PID:
    Tracer: Waiting for child (pid 24275) events.

    此时, child 运行正常。当您发送 SIGSTOP 时,操作开始给 child 。跟踪器检测到它,执行所需的跟踪,然后分离并让 child 正常继续:
    kill -STOP 24275

    Process 24275 has 3 tasks, attached to all.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a65, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a58, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a65, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a58, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a5d, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Task 24275: RIP=0x0000000000400a5d, RSP=0x00007fff6895c428.
    Task 24276: RIP=0x0000000000400a5d, RSP=0x00007f399cfb7ee8.
    Task 24277: RIP=0x0000000000400a63, RSP=0x00007f399cfa6ee8. Advanced by one step.

    Detached. Waiting for new stop events.

    您可以根据需要多次重复上述操作。请注意,我选择了 SIGSTOP信号作为触发器,因为这样 tracer.c也可用作为每个请求生成复杂的多线程核心转储的基础(因为多线程进程可以通过向自身发送 SIGSTOP 来简单地触发它)。
    worker()的拆解函数线程在上面的例子中都在旋转:
    0x400a50: eb 0b                 jmp          0x400a5d
    0x400a52: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
    0x400a58: f0 48 83 07 01 lock addq $0x1,(%rdi) = fourth step
    0x400a5d: 8b 05 00 00 00 00 mov 0x0(%rip),%eax = first step
    0x400a63: 85 c0 test %eax,%eax = second step
    0x400a65: 74 f1 je 0x400a58 = third step
    0x400a67: 48 8b 07 mov (%rdi),%rax
    0x400a6a: 48 89 c2 mov %rax,%rdx
    0x400a6d: f0 48 0f b1 07 lock cmpxchg %rax,(%rdi)
    0x400a72: 75 f6 jne 0x400a6a
    0x400a74: 48 89 d0 mov %rdx,%rax
    0x400a77: c3 retq

    现在,这个测试程序只展示了如何停止一个进程,附加到它的所有线程,单步执行一个线程所需数量的指令,然后让所有线程正常继续;它尚未证明同样适用于让特定线程正常继续(通过 PTRACE_CONT )。然而,我在下面描述的细节表明,对我来说,同样的方法应该适用于 PTRACE_CONT .

    我在编写上述测试程序时遇到的主要问题或惊喜是
    long r;

    do {
    r = ptrace(PTRACE_cmd, tid, ...);
    } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));

    循环,特别是对于 ESRCH案例(我仅根据 ptrace man page 描述添加的其他案例)。

    您会看到,大多数 ptrace 命令仅在任务停止时才被允许。但是,当任务仍在完成时不会停止,例如单步命令。因此,使用上面的循环——也许添加一个毫秒纳秒 sleep 或类似的东西以避免浪费 CPU——确保在我们尝试提供新的之前,先前的 ptrace 命令已经完成(因此任务停止)。

    Kerrek SB,我相信至少您在测试程序中遇到的一些麻烦是由于这个问题造成的?就我个人而言,这是一种 D'oh!意识到这当然是必要的,因为 ptracing 本质上是异步的,而不是同步的。

    (这种异步性也是我上面提到的 SIGCONT - PTRACE_CONT 交互的原因。我相信通过使用上面显示的循环进行正确处理,交互不再是一个问题——实际上是可以理解的。)

    添加对此答案的评论:

    Linux 内核使用 task_struct 结构中的一组任务状态标志(定义见 include/linux/sched.h )来跟踪每个任务的状态。 ptrace() 面向用户空间的一面在 kernel/ptrace.c 中定义.

    PTRACE_SINGLESTEPPTRACE_CONT被称为, kernel/ptrace.c : ptrace_continue() 处理大部分细节。它通过调用 wake_up_state(child, __TASK_TRACED) 完成( kernel/sched/core.c::try_to_wake_up(child, __TASK_TRACED, 0) )。

    当进程通过 SIGSTOP 停止时信号,所有任务都将停止,并最终处于“已停止,未跟踪”状态。

    附加到每个任务(通过 PTRACE_ATTACH 或 PTRACE_SEIZE,见 kernel/ptrace.c : ptrace_attach() )修改任务状态。但是,ptrace 状态位(参见 include/linux/ptrace.h:PT_ constants )与任务可运行状态位(参见 include/linux/sched.h:TASK_ constants )是分开的。

    附加到任务后,发送进程 SIGCONT信号,停止状态不会立即修改(我相信),因为任务也在被跟踪。执行 PTRACE_SINGLESTEP 或 PTRACE_CONT 的结果是 kernel/sched/core.c::try_to_wake_up(child, __TASK_TRACED, 0) ,更新任务状态,并将任务移动到运行队列。

    现在,我还没有找到代码路径的复杂部分是在下一次调度任务时如何在内核中更新任务状态。我的测试表明,对于单步执行(这是另一个任务状态标志),只有任务状态被更新,单步标志被清除。似乎 PTRACE_CONT 不是那么可靠;我相信这是因为单步标志“强制”了任务状态的变化。也许存在“竞争条件”。继续信号传递和状态改变?

    (进一步编辑:内核开发人员肯定希望 wait() 被调用,例如参见 this thread 。)

    换句话说,在注意到进程已经停止之后(请注意,如果进程不是子进程并且尚未附加到,则可以使用 /proc/PID/stat/proc/PID/status),我相信以下过程是最健壮的:
    pid_t  pid, p; /* Process owning the tasks */
    tid_t *tid; /* Task ID array */
    size_t tids; /* Tasks */
    long result;
    int status;
    size_t i;

    for (i = 0; i < tids; i++) {
    while (1) {
    result = ptrace(PTRACE_ATTACH, tid[i], (void *)0, (void *)0);
    if (result == -1L && (errno == ESRCH || errno == EBUSY || errno == EFAULT || errno == EIO)) {
    /* To avoid burning up CPU for nothing: */
    sched_yield(); /* or nanosleep(), or usleep() */
    continue;
    }
    break;
    }
    if (result == -1L) {
    /*
    * Fatal error. First detach from tid[0..i-1], then exit.
    */
    }
    }

    /* Send SIGCONT to the process. */
    if (kill(pid, SIGCONT)) {
    /*
    * Fatal error, see errno. Exit.
    */
    }

    /* Since we are attached to the process,
    * we can wait() on it. */
    while (1) {
    errno = 0;
    status = 0;
    p = waitpid(pid, &status, WCONTINUED);
    if (p == (pid_t)-1) {
    if (errno == EINTR)
    continue;
    else
    break;
    } else
    if (p != pid) {
    errno = ESRCH;
    break;
    } else
    if (WIFCONTINUED(status)) {
    errno = 0;
    break;
    }
    }
    if (errno) {
    /*
    * Fatal error. First detach from tid[0..tids-1], then exit.
    */
    }

    /* Single-step each task to update the task states. */
    for (i = 0; i < tids; i++) {
    while (1) {
    result = ptrace(PTRACE_SINGLESTEP, tid[i], (void *)0, (void *)0);
    if (result == -1L && errno == ESRCH) {
    /* To avoid burning up CPU for nothing: */
    sched_yield(); /* or nanosleep(), or usleep() */
    continue;
    }
    break;
    }
    if (result == -1L) {
    /*
    * Fatal error. First detach from tid[0..i-1], then exit.
    */
    }
    }

    /* Obtain task register structures, to make sure the single-steps
    * have completed and their states have stabilized. */
    for (i = 0; i < tids; i++) {
    struct user_regs_struct regs;

    while (1) {
    result = ptrace(PTRACE_GETREGS, tid[i], &regs, &regs);
    if (result == -1L && (errno == ESRCH || errno == EBUSY || errno == EFAULT || errno == EIO)) {
    /* To avoid burning up CPU for nothing: */
    sched_yield(); /* or nanosleep(), or usleep() */
    continue;
    }
    break;
    }
    if (result == -1L) {
    /*
    * Fatal error. First detach from tid[0..i-1], then exit.
    */
    }
    }

    在上述之后,所有任务都应该被附加并处于预期状态,以便例如PTRACE_CONT 无需其他技巧即可工作。

    如果 future 内核中的行为发生变化——我确实相信 STOP/CONT 信号和 ptracing 之间的交互可能会发生变化;至少有必要向 LKML 开发人员提出有关此行为的问题! --,上述程序仍将稳健运行。 (谨慎起见,通过多次使用 PTRACE_SINGLESTEP 循环,也可能是一个好主意。)

    与 PTRACE_CONT 的不同之处在于,如果将来行为发生变化,初始 PTRACE_CONT 可能实际上会继续该过程,从而导致 ptrace()跟随它失败。使用 PTRACE_SINGLESTEP,进程将停止,允许进一步 ptrace()呼唤成功。

    问题?

    关于c - 如何使用 PTRACE 获得多线程的一致 View ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18577956/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com