2013-09-02 9 views
8

В то время как я работал on this question, я столкнулся с возможной идеей, которая использует ptrace, но я не могу правильно понять, как ptrace взаимодействует с потоками.Как использовать PTRACE для получения согласованного представления нескольких потоков?

Предположим, у меня есть заданный многопоточный основной процесс, и я хочу прикрепить его к определенному потоку (возможно, от раздвоенного ребенка).

  1. Могу я прикрепить к определенной теме? (Руководства расходятся по этому вопросу.)

  2. Если это так, означает ли это, что одноступенчатый шаг только по инструкциям одного потока? Остановляет ли он все потоки процесса?

  3. Если да, то все остальные потоки остаются остановлены в то время как я называю 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() гарантированно выполняется только тогда, когда все остальные потоки остановлены. Я также понимаю, что могут быть условия гонки, когда родитель получает сигналы из других источников, и я слышал, что вместо этого я должен использовать PTRACE_SEIZE, но это, кажется, не существует повсюду.

Любые разъяснения или ссылки были бы оценены с благодарностью!

ответ

18

Я написал второй тестовый пример. Мне пришлось добавить отдельный ответ, поскольку он был слишком длинным, чтобы вписаться в первый, с включенным примером вывода.

Во-первых, здесь 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 не отправляет его на себя, вы можете иметь Tracee остановить себя, или послать сигнал извне.)

Когда команда остановилась, 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 & 

Трассирующими показывает PID работника:

Tracer: Waiting for child (pid 24275) events. 

На этом этапе ребенок работает нормально. Действие начинается, когда вы отправляете SIGSTOP ребенку. Трассирующими обнаруживает его, делает нужную трассировку, затем отсоединяется и позволяет ребенку продолжать нормально:

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 разрешено только при остановке задачи. Однако задача не останавливается, когда она все еще завершается, например. одноступенчатая команда. Таким образом, используя вышеприведенный цикл - возможно, добавив миллисекунду нанослоя или подобное, чтобы избежать потери процессора - убедитесь, что предыдущая команда ptrace завершила (и, таким образом, остановилась), прежде чем мы попытаемся поставить новую.

Kerrek SB, я верю, что по крайней мере некоторые из проблем, которые у вас были с вашими тестовыми программами, вызваны этой проблемой? Для меня лично это было своего рода D'oh! момент, чтобы понять, что, конечно, это необходимо, поскольку ptracing по своей сути асинхронно, а не синхронно.

(Это асинхронность также является причиной для SIGCONT. - взаимодействие PTRACE_CONT я уже упоминал выше, я считаю, при правильной обработке с использованием петли не было показано выше, это взаимодействие уже не является проблемой. - и на самом деле вполне понятно)


Добавление комментариев к этому ответу:

ядро ​​Linux использует набор флагов государственных задач в структуре task_struct (см include/linux/sched.h для определения), чтобы следить за состоянием каждой задачи. Сторона, обращенная к пользовательскому пространству ptrace(), определена в kernel/ptrace.c.

Когда PTRACE_SINGLESTEP или PTRACE_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 работает без дальнейших трюков.

Если поведение меняется в будущих ядрах - я полагаю, что взаимодействие между сигналами STOP/CONT и ptracing - это то, что может измениться; по крайней мере, вопрос разработчикам LKML об этом поведении был бы оправдан! -, вышеуказанная процедура будет по-прежнему работать надежно. (Erring на стороне осторожности, используя несколько циклов в PTRACE_SINGLESTEP, также может быть хорошей идеей.)

Разница с PTRACE_CONT заключается в том, что если поведение изменится в будущем, начальный PTRACE_CONT может фактически продолжить этот процесс вызывает ptrace(), которые следуют за ним, чтобы потерпеть неудачу. С помощью PTRACE_SINGLESTEP процесс будет останавливаться, позволяя дальнейшим вызовам ptrace(), чтобы добиться успеха.

Вопросы?

+0

Большое спасибо за это! Да, я также обнаружил, что вы можете использовать только команды перезапуска (например, SINGLESTEP), когда вы находитесь в остановленном состоянии, так что обычно вы будете ждать SINGLESTEP +. Тем не менее, мне не удалось заставить работу CONT возобновить один поток, несмотря на то, что даже отправил 'kill (pid, SIGCONT)' процесс после присоединения всех детей. То, что я, наконец, закончил, заключалось в том, что я * отделил * тот поток, который я хочу запустить, а затем снова подключив его. Но я еще не пытался сделать PTRACE_CONT, а затем ждать WIFCONTINUED; возможно, это необходимо ... –

+0

@ KerrekSB: Я не верю, что PTRACE_CONT будет работать именно так. Я полагаю, что у вас есть в основном три варианта (для запуска одного потока при сохранении остальных): 1) отсоедините от одного потока, который вы хотите запустить, 2) PTRACE_SINGLESTEP для одношагового потока или 3) PTRACE_SYSCALL для запуска этого перейдите к следующему syscall. Я не знаю вашего конкретного варианта использования, но, возможно, третий вариант в цикле был бы подходящим? Это позволяет быстро выполнять вычисление, но только до следующего syscall; Мне нравится этот баланс между контролем и эффективностью ... –

+0

Правильно, спасибо ... мой usecase заключается в том, что я хочу, чтобы трассировка делала fork + abort для создания дампа ядра (но теперь с четко определенным состоянием потоков, которое Я могу собрать урожай в родительской части вилки). Интересно, почему PTRACE_CONT не работает? Это только для возобновления после сигнала? (Отладка работает отлично, кстати. Singlestepping классный, но мне это не понадобится.) –

-1

Остановляет ли он все потоки процесса?

Да Он отслеживает процесс, все потоки этого процесса останавливаются. Представьте, что это было не так, как вы могли видеть нитку в вашей среде IDE.

из руководства:

Системный вызов ptrace() обеспечивает средство, с помощью которого один процесс («трассировщик») может наблюдать и управлять выполнением другого процесса («Tracee»)

Пример кода для присоединения:

printf("Attaching to process %d\n",Tpid); 
if ((ptrace(PTRACE_ATTACH, Tpid, 0, 0)) != 0) {; 
    printf("Attach result %d\n",res); 
} 

Так да вы приписываются к теме да и он останавливает все потоки процесса.

if ((res = ptrace(PTRACE_SINGLESTEP, Tpid, 0, signo)) < 0) { 
perror("Ptrace singlestep error"); 
exit(1); 
} 
res = wait(&stat); 

возможно посмотреть здесь: http://www.secretmango.com/jimb/Whitepapers/ptrace/ptrace.html

+0

Это не помогает - как ptrace относится к отдельным потокам процесса трассировки? Например. как работает одношаговая работа? –

+0

жаль, что я попытался быть более четким – dzada

1

Каждый поток в процессе прослежена по отдельности (и каждый из них может быть потенциально прослежена другим процессом трассировки, или быть внеисто рической). Когда вы вызываете ptrace attach, вы всегда привязываетесь только к одному потоку. Только этот поток будет остановлен - остальные потоки будут продолжать работать так, как они были.

Последних версий страницы ptrace() людей сделать это очень ясно:

Привязанности и последующие команды на поток: в многопоточном процесса, каждый поток может быть индивидуально прикреплена к (потенциально разные) трассеру , или оставить не прикрепленным и, следовательно, не отлаживать. Следовательно, «tracee» всегда означает «(один) поток«, никогда »(возможно, многопоточный) процесс». Команды Ptrace всегда посылается к конкретному Tracee с помощью вызова формы

ptrace(PTRACE_foo, pid, ...) 

, где ИЗОДРОМНОЙ нить идентификатор соответствующего Linux потока.

(Обратите внимание, что на этой странице, «многопоточный процесс» означает поток группу, состоящую из нитей, созданных с использованием флага clone(2) CLONE_THREAD.)

пошагового влияет только на поток, который вы направить его в. Если другие потоки запущены, они продолжают работать, и если они находятся в режиме отслеживания, они остаются в режиме отслеживания. (Это означает, что если поток, который вы выполняете с одним шагом, пытается получить мьютекс или аналогичный ресурс синхронизации, который удерживается другим неиспользуемым потоком, он не сможет получить этот мьютекс).

Если вы хотите остановить все потоки процесса, пока вы используете одноэтапный поток, вам нужно будет подключиться ко всем потокам. Существует дополнительное усложнение, которое, если процесс выполняется, пока вы пытаетесь подключиться к нему, могут быть созданы новые потоки, пока вы их перечисляете.

+0

Спасибо, это очень полезно! Могу ли я уточнить, хотя: когда вы присоединяетесь к идентификатору потока, сигнал останова отправляется только в поток, а не весь процесс? Как именно вы это делаете, то есть как вы получаете правильный идентификатор потока и как вы его прикрепляете? Я не могу воспроизвести это (Linux 3.5). Что делать, если идентификатор потока равен идентификатору процесса? –

+0

Ничего, я ошибся в своем коде - действительно, теперь я вижу, что остановлен только один поток. –

+0

Да, остановка трассировки применяется только к прикрепленной резьбе. Идентификатор потока можно получить из 'SYS_gettid', как вы показали себя, или просмотрев файл'/proc//task/', который имеет запись для каждого потока процесса. Идентификатор потока равен идентификатору процесса для начального потока в процессе - присоединение к нему прикрепляется только к этой исходной нити. Невозможно присоединить к целому процессу - команды pthread всегда * направлены на один поток. – caf

5

Могу я подключиться к определенной теме?

Да, по крайней мере, на текущих ядрах.

Означает ли это, что одноступенчатые шаги выполняются только через инструкции одного потока? Остановляет ли он все потоки процесса?

Да. Он не останавливает другие потоки, только прикрепленный.

Есть ли способ сделать шаг вперед только в одном потоке, но гарантировать, что остальные потоки остаются остановленными?

Да. Отправьте SIGSTOP в процесс (используйте waitpid(PID,,WUNTRACED), чтобы дождаться завершения процесса), затем PTRACE_ATTACH для каждой темы процесса. Отправьте SIGCONT (используя waitpid(PID,,WCONTINUED), чтобы продолжить процесс).

Поскольку все потоки были остановлены, когда вы прикрепили, а крепление останавливает нить, все потоки остаются остановленными после подачи сигнала SIGCONT. Вы можете выполнять однопоточные потоки в любом порядке.


Я нашел это достаточно интересным, чтобы взломать тестовый чехол. (Хорошо, на самом деле я подозреваю, что никто не будет принимать мои слова для него в любом случае, так что я решил, что лучше показать доказательство вы можете дублировать самостоятельно вместо этого.)

Моя система кажется следовать man 2 ptrace, как описано в Linux man-pages project, и Кажется, что Kerrisk очень хорошо поддерживает их синхронизацию с поведением ядра. В общем, я предпочитаю источники kernel.org wrt. ядро Linux в другие источники.

Резюме:

  • Прикрепление к самому процессу (TID == PID) останавливает только оригинальную нить, а не все темы.

  • Прикрепление к определенной теме (с использованием TID от /proc/PID/task/) останавливает эту нить. (Другими словами, нить с TID == PID не является особенной.)

  • Отправка SIGSTOP в процесс остановит все потоки, но ptrace() все еще работает абсолютно нормально.

  • Если вы отправили SIGSTOP в процесс, не вызывайте ptrace(PTRACE_CONT, TID) перед отсоединением. PTRACE_CONT, похоже, мешает сигналу SIGCONT.

    Вы можете сначала отправить SIGSTOP, затем PTRACE_ATTACH, затем отправьте SIGCONT без каких-либо проблем; поток будет остановлен (из-за ptrace). Другими словами, PTRACE_ATTACH и PTRACE_DETACH хорошо сочетаются с SIGSTOP и SIGCONT без каких-либо побочных эффектов, которые я мог видеть.

  • SIGSTOP и SIGCONT влияют на весь процесс, даже если попытаться использовать tgkill() (или pthread_kill()), чтобы послать сигнал на определенную тему.

  • Для остановки и продолжения определенной темы, PTHREAD_ATTACH it; чтобы остановить и продолжить все потоки процесса, отправьте сигналы SIGSTOP и SIGCONT, соответственно.

Лично я считаю, что это подтверждает подход, который я предложил в этом другом вопросе.

Вот некрасиво тестовый код можно скомпилировать и запустить, чтобы проверить это для себя, traces.c:

#define GNU_SOURCE 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/wait.h> 
#include <sys/ptrace.h> 
#include <sys/syscall.h> 
#include <dirent.h> 
#include <pthread.h> 
#include <signal.h> 
#include <string.h> 
#include <errno.h> 
#include <stdio.h> 

#ifndef THREADS 
#define THREADS 3 
#endif 

static int tgkill(int tgid, int tid, int sig) 
{ 
    int retval; 

    retval = syscall(SYS_tgkill, tgid, tid, sig); 
    if (retval < 0) { 
     errno = -retval; 
     return -1; 
    } 

    return 0; 
} 

volatile unsigned long counter[THREADS + 1] = { 0UL }; 

volatile sig_atomic_t run = 0; 
volatile sig_atomic_t done = 0; 

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

int install_done(int signum) 
{ 
    struct sigaction act; 
    sigemptyset(&act.sa_mask); 
    act.sa_handler = handle_done; 
    act.sa_flags = 0; 
    if (sigaction(signum, &act, NULL)) 
     return errno; 
    return 0; 
} 

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

    while (!run) 
     ; 

    while (!done) 
     (*counter)++; 

    return (void *)(*counter); 
} 

pid_t *gettids(const pid_t pid, size_t *const countptr) 
{ 
    char   dirbuf[128]; 
    DIR   *dir; 
    struct dirent *ent; 

    pid_t   *data = NULL, *temp; 
    size_t   size = 0; 
    size_t   used = 0; 

    int   tid; 
    char   dummy; 

    if ((int)pid < 2) { 
     errno = EINVAL; 
     return NULL; 
    } 

    if (snprintf(dirbuf, sizeof dirbuf, "/proc/%d/task/", (int)pid) >= (int)sizeof dirbuf) { 
     errno = ENAMETOOLONG; 
     return NULL; 
    } 

    dir = opendir(dirbuf); 
    if (!dir) 
     return NULL; 

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

     if (sscanf(ent->d_name, "%d%c", &tid, &dummy) != 1) 
      continue; 

     if (tid < 2) 
      continue; 

     if (used >= size) { 
      size = (used | 127) + 129; 
      temp = realloc(data, size * sizeof data[0]); 
      if (!temp) { 
       free(data); 
       closedir(dir); 
       errno = ENOMEM; 
       return NULL; 
      } 
      data = temp; 
     } 

     data[used++] = (pid_t)tid; 
    } 
    if (errno) { 
     free(data); 
     closedir(dir); 
     errno = EIO; 
     return NULL; 
    } 
    if (closedir(dir)) { 
     free(data); 
     errno = EIO; 
     return NULL; 
    } 

    if (used < 1) { 
     free(data); 
     errno = ENOENT; 
     return NULL; 
    } 

    size = used + 1; 
    temp = realloc(data, size * sizeof data[0]); 
    if (!temp) { 
     free(data); 
     errno = ENOMEM; 
     return NULL; 
    } 
    data = temp; 

    data[used] = (pid_t)0; 

    if (countptr) 
     *countptr = used; 

    errno = 0; 
    return data; 
} 

int child_main(void) 
{ 
    pthread_t id[THREADS]; 
    int   i; 

    if (install_done(SIGUSR1)) { 
     fprintf(stderr, "Cannot set SIGUSR1 signal handler.\n"); 
     return 1; 
    } 

    for (i = 0; i < THREADS; i++) 
     if (pthread_create(&id[i], NULL, worker, (void *)&counter[i])) { 
      fprintf(stderr, "Cannot create thread %d of %d: %s.\n", i + 1, THREADS, strerror(errno)); 
      return 1; 
     } 

    run = 1; 

    kill(getppid(), SIGUSR1); 

    while (!done) 
     counter[THREADS]++; 

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

    printf("Final counters:\n"); 
    for (i = 0; i < THREADS; i++) 
     printf("\tThread %d: %lu\n", i + 1, counter[i]); 
    printf("\tMain thread: %lu\n", counter[THREADS]); 

    return 0; 
} 

int main(void) 
{ 
    pid_t *tid = NULL; 
    size_t tids = 0; 
    int  i, k; 
    pid_t child, p; 

    if (install_done(SIGUSR1)) { 
     fprintf(stderr, "Cannot set SIGUSR1 signal handler.\n"); 
     return 1; 
    } 

    child = fork(); 
    if (!child) 
     return child_main(); 

    if (child == (pid_t)-1) { 
     fprintf(stderr, "Cannot fork.\n"); 
     return 1; 
    } 

    while (!done) 
     usleep(1000); 

    tid = gettids(child, &tids); 
    if (!tid) { 
     fprintf(stderr, "gettids(): %s.\n", strerror(errno)); 
     kill(child, SIGUSR1); 
     return 1; 
    } 

    fprintf(stderr, "Child process %d has %d tasks.\n", (int)child, (int)tids); 
    fflush(stderr); 

    for (k = 0; k < (int)tids; k++) { 
     const pid_t t = tid[k]; 

     if (ptrace(PTRACE_ATTACH, t, (void *)0L, (void *)0L)) { 
      fprintf(stderr, "Cannot attach to TID %d: %s.\n", (int)t, strerror(errno)); 
      kill(child, SIGUSR1); 
      return 1; 
     } 

     fprintf(stderr, "Attached to TID %d.\n\n", (int)t); 

     fprintf(stderr, "Peeking the counters in the child process:\n"); 
     for (i = 0; i <= THREADS; i++) { 
      long v; 
      do { 
       errno = 0; 
       v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL); 
      } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH)); 
      fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v); 
     } 
     fprintf(stderr, "Waiting a short moment ... "); 
     fflush(stderr); 

     usleep(250000); 

     fprintf(stderr, "and another peek:\n"); 
     for (i = 0; i <= THREADS; i++) { 
      long v; 
      do { 
       errno = 0; 
       v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL); 
      } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH)); 
      fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v); 
     } 
     fprintf(stderr, "\n"); 
     fflush(stderr); 

     usleep(250000); 

     ptrace(PTRACE_DETACH, t, (void *)0L, (void *)0L); 
    } 

    for (k = 0; k < 4; k++) { 
     const pid_t t = tid[tids/2]; 

     if (k == 0) { 
      fprintf(stderr, "Sending SIGSTOP to child process ... "); 
      fflush(stderr); 
      kill(child, SIGSTOP); 
     } else 
     if (k == 1) { 
      fprintf(stderr, "Sending SIGCONT to child process ... "); 
      fflush(stderr); 
      kill(child, SIGCONT); 
     } else 
     if (k == 2) { 
      fprintf(stderr, "Sending SIGSTOP to TID %d ... ", (int)tid[0]); 
      fflush(stderr); 
      tgkill(child, tid[0], SIGSTOP); 
     } else 
     if (k == 3) { 
      fprintf(stderr, "Sending SIGCONT to TID %d ... ", (int)tid[0]); 
      fflush(stderr); 
      tgkill(child, tid[0], SIGCONT); 
     } 
     usleep(250000); 
     fprintf(stderr, "done.\n"); 
     fflush(stderr); 

     if (ptrace(PTRACE_ATTACH, t, (void *)0L, (void *)0L)) { 
      fprintf(stderr, "Cannot attach to TID %d: %s.\n", (int)t, strerror(errno)); 
      kill(child, SIGUSR1); 
      return 1; 
     } 

     fprintf(stderr, "Attached to TID %d.\n\n", (int)t); 

     fprintf(stderr, "Peeking the counters in the child process:\n"); 
     for (i = 0; i <= THREADS; i++) { 
      long v; 
      do { 
       errno = 0; 
       v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL); 
      } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH)); 
      fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v); 
     } 
     fprintf(stderr, "Waiting a short moment ... "); 
     fflush(stderr); 

     usleep(250000); 

     fprintf(stderr, "and another peek:\n"); 
     for (i = 0; i <= THREADS; i++) { 
      long v; 
      do { 
       errno = 0; 
       v = ptrace(PTRACE_PEEKDATA, t, &counter[i], NULL); 
      } while (v == -1L && (errno == EIO || errno == EFAULT || errno == ESRCH)); 
      fprintf(stderr, "\tcounter[%d] = %lu\n", i, (unsigned long)v); 
     } 
     fprintf(stderr, "\n"); 
     fflush(stderr); 

     usleep(250000); 

     ptrace(PTRACE_DETACH, t, (void *)0L, (void *)0L); 
    } 

    kill(child, SIGUSR1); 

    do { 
     p = waitpid(child, NULL, 0); 
     if (p == -1 && errno != EINTR) 
      break; 
    } while (p != child); 

    return 0; 
} 

компилировать и запускать с помощью, например,

gcc -DTHREADS=3 -W -Wall -O3 traces.c -pthread -o traces 
./traces 

Выход свалка дочернего процесса счетчиков (каждого из них увеличивается на единицу в отдельном потоке, в том числе исходный поток, который использует конечный счетчик). Сравните счетчики через короткое ожидание.Например:

Child process 18514 has 4 tasks. 
Attached to TID 18514. 

Peeking the counters in the child process: 
    counter[0] = 0 
    counter[1] = 0 
    counter[2] = 0 
    counter[3] = 0 
Waiting a short moment ... and another peek: 
    counter[0] = 18771865 
    counter[1] = 6435067 
    counter[2] = 54247679 
    counter[3] = 0 

Как видно выше, только начальный поток (TID которого == ПИД), который использует конечный счетчик, останавливается. То же самое происходит и для других трех нитей, тоже, которые используют первые три счетчика в следующем порядке:

Attached to TID 18515. 

Peeking the counters in the child process: 
    counter[0] = 25385151 
    counter[1] = 13459822 
    counter[2] = 103763861 
    counter[3] = 560872 
Waiting a short moment ... and another peek: 
    counter[0] = 25385151 
    counter[1] = 69116275 
    counter[2] = 120500164 
    counter[3] = 9027691 

Attached to TID 18516. 

Peeking the counters in the child process: 
    counter[0] = 25397582 
    counter[1] = 105905400 
    counter[2] = 155895025 
    counter[3] = 17306682 
Waiting a short moment ... and another peek: 
    counter[0] = 32358651 
    counter[1] = 105905400 
    counter[2] = 199601078 
    counter[3] = 25023231 

Attached to TID 18517. 

Peeking the counters in the child process: 
    counter[0] = 40600813 
    counter[1] = 111675002 
    counter[2] = 235428637 
    counter[3] = 32298929 
Waiting a short moment ... and another peek: 
    counter[0] = 48727731 
    counter[1] = 143870702 
    counter[2] = 235428637 
    counter[3] = 39966259 

Следующие два случая изучить SIGCONT/SIGSTOP WRT. весь процесс:

Sending SIGSTOP to child process ... done. 
Attached to TID 18516. 

Peeking the counters in the child process: 
    counter[0] = 56887263 
    counter[1] = 170646440 
    counter[2] = 235452621 
    counter[3] = 48077803 
Waiting a short moment ... and another peek: 
    counter[0] = 56887263 
    counter[1] = 170646440 
    counter[2] = 235452621 
counter[3] = 48077803 

Sending SIGCONT to child process ... done. 
Attached to TID 18516. 

Peeking the counters in the child process: 
    counter[0] = 64536344 
    counter[1] = 182359343 
    counter[2] = 253660731 
    counter[3] = 56422231 
Waiting a short moment ... and another peek: 
    counter[0] = 72029244 
    counter[1] = 182359343 
    counter[2] = 288014365 
    counter[3] = 63797618 

Как вы можете видеть, отправляя SIGSTOP остановит все темы, но не мешать с ptrace(). Аналогично, после SIGCONT потоки продолжают работать как обычно.

В последних двух случаях исследовать эффекты использования tgkill() отправить SIGSTOP/SIGCONT к конкретной нити (тот, который соответствует первому счетчику), в то время прикрепления к другому потоку:

Sending SIGSTOP to TID 18514 ... done. 
Attached to TID 18516. 

Peeking the counters in the child process: 
    counter[0] = 77012930 
    counter[1] = 183059526 
    counter[2] = 344043770 
    counter[3] = 71120227 
Waiting a short moment ... and another peek: 
    counter[0] = 77012930 
    counter[1] = 183059526 
    counter[2] = 344043770 
    counter[3] = 71120227 

Sending SIGCONT to TID 18514 ... done. 
Attached to TID 18516. 

Peeking the counters in the child process: 
    counter[0] = 88082419 
    counter[1] = 194059048 
    counter[2] = 359342314 
    counter[3] = 84887463 
Waiting a short moment ... and another peek: 
    counter[0] = 100420161 
    counter[1] = 194059048 
    counter[2] = 392540525 
    counter[3] = 111770366 

К сожалению, но, как и ожидалось, расположение (остановлено/работает) является общесистемным, а не зависящим от потока, как вы можете видеть выше. Это означает, что для остановки определенных потоков и обеспечения нормальной работы других потоков вам необходимо отдельно указать PTHREAD_ATTACH тем, которые вы хотите остановить.

Чтобы доказать все мои заявления выше, возможно, вам придется добавить тестовые примеры; У меня появилось довольно много копий кода, все слегка отредактированные, чтобы проверить все это, и я не уверен, что выбрал наиболее полный набор. Я был бы рад расширить программу тестирования, если вы найдете упущения.

Вопросы?

+0

Это фантастика, спасибо большое! Ваш тестовый код имеет замечательное сходство с моим по частям (но мой не работал) :-) ОК, вопросы: когда вы отправляете STOP/CONT через 'kill', вам не нужно ждать после этого, чтобы сигнал был доставлен? Я имею в виду, что не гарантируется, что сигнал поступает в любой момент времени, и я думаю, что я читал, что единственный способ убедиться, что все остановились, - это подождать? Наконец, чтобы быть уверенным, я хочу сделать обратное, а именно сохранить все потоки * остановленными *, продвигая только один. Ответ @ caf затрагивает это хорошо, но я думаю, что ваш код может быть легко адаптирован к этому. –

+0

@KerrekSB: Да, вам нужно подождать (например, вызывая 'waitpid (PID, & status, WUNTRACED)' в цикле до тех пор, пока ' WIFSTOPPED (статус) 'истинно). Вышеупомянутая программа - это очень грубая тестовая версия, поэтому я взял некоторые непростительные ярлыки. Чтобы остановить все потоки при продвижении только одного: отправьте 'SIGSTOP', дождитесь завершения процесса, затем' PTHREAD_ATTACH' для каждого потока, а затем отправьте 'SIGCONT'. Теперь вы можете продвигать любую нить или потоки, сохраняя при этом остальных. Возможно, я должен показать свой пример кода для этого? –

+0

Это имеет смысл, и я пробовал это в течение некоторого времени и, кажется, добился прогресса. Я на самом деле большую часть времени занимался изучением семантики PTRACE_IGNORE и PTRACE_LISTEN, и как именно я должен послать сигнал остановки: -S –