如何使用PTRACE获得多线程的一致视图?
在我工作时,我遇到了一个可能的想法,即使用如何使用PTRACE获得多线程的一致视图?,c,linux,multithreading,pthreads,ptrace,C,Linux,Multithreading,Pthreads,Ptrace,在我工作时,我遇到了一个可能的想法,即使用ptrace,但我无法正确理解ptrace如何与线程交互 假设我有一个给定的多线程主进程,并且我希望附加到其中的一个特定线程(可能来自一个分叉的子进程) 我可以连接到特定的线程吗?(手册在这个问题上存在分歧。) 如果是这样的话,这是否意味着单步执行只能执行一个线程的指令?它会停止进程的所有线程吗 如果是这样,那么在我调用PTRACE\u SYSCALL或PTRACE\u SINGLESTEP时,是否所有其他线程都保持停止状态,还是所有线程都继续?有没有办
ptrace
,但我无法正确理解ptrace
如何与线程交互
假设我有一个给定的多线程主进程,并且我希望附加到其中的一个特定线程(可能来自一个分叉的子进程)
PTRACE\u SYSCALL
或PTRACE\u 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\u指令()。我也明白,当家长从其他地方接收信号时,可能存在竞争条件,我听说我应该使用PTRACE\u-take
,但这似乎并不存在于所有地方
如有任何澄清或参考,将不胜感激
它会停止进程的所有线程吗
对
它跟踪进程,该进程的所有线程都将停止。
想象一下,你怎么能在IDE中看到不同的线程呢
从手册中:
ptrace()系统调用提供了一种方法,通过这种方法,一个进程(“跟踪程序”)可以观察和控制另一个进程(“跟踪对象”)的执行
要附加的示例代码:
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);
if((res=ptrace(ptrace_SINGLESTEP,Tpid,0,signo))<0){
perror(“Ptrace单步错误”);
出口(1);
}
res=等待(&stat);
请参见此处:进程中的每个线程都是单独跟踪的(每个线程都可能由不同的跟踪进程跟踪,或者不被跟踪)。当您调用ptraceattach时,您总是只连接到一个线程。只有该线程将停止,其他线程将继续运行
ptrace()
手册页的最新版本非常清楚地说明了这一点:
附件和后续命令是每个线程的:在多线程中
进程中,每个线程都可以单独连接到
不同)跟踪器,或未连接,因此未调试。
因此,“tracee”总是指(一个)线程,而不是(可能的)线程
多线程)进程”。Ptrace命令始终发送到
使用表单调用的特定tracee
ptrace(PTRACE_foo, pid, ...)
其中pid是对应Linux线程的线程ID
(请注意,在本页中,“多线程进程”是指线程
由使用克隆创建的线程组成的组(2)
克隆线程
标志。)
单步执行只影响您所指向的线程。如果其他线程正在运行,它们将继续运行,如果它们处于跟踪停止状态,它们将保持在跟踪停止状态。(这意味着,如果正在单步执行的线程尝试获取另一个非运行线程所持有的互斥锁或类似同步资源,它将无法获取该互斥锁)
如果要在单步执行一个线程时停止进程的所有线程,则需要附加到所有线程。还有一个额外的复杂性,即如果进程在您尝试附加到它时正在运行,那么在您枚举它们时可能会创建新线程
我可以连接到特定的线程吗
是的,至少在当前内核上是这样
这是否意味着单步执行只能执行一个线程的指令?它会停止进程的所有线程吗
对。它不会停止其他线程,只停止连接的线程
有没有办法只在一个线程中前进一步,但保证其他线程保持停止
对。将SIGSTOP
发送到进程(使用waitpid(PID,WUNTRACED)
等待进程停止),然后将PTRACE\u ATTACH
发送到进程中的每个线程。发送SIGCONT
(使用waitpid(PID,WCONTINUED)
等待进程继续)
由于附加时所有线程都已停止,并且附加会停止线程,因此在传递SIGCONT
信号后,所有线程都将保持停止状态。您可以按自己喜欢的顺序单步执行线程
我发现这很有趣,足以制作一个测试用例。(好吧,事实上,我怀疑没有人会相信我的话,所以我决定最好拿出证据证明你可以自己复制。) 我的系统似乎遵循中所述的,Kerrisk似乎非常擅长将它们与内核行为保持同步。一般来说,我更喜欢kernel.org源码wrt。将Linux内核移植到其他源代码 总结:
- 附加到进程本身(TID==PID)只会停止原始线程,而不是所有线程
- 附加到特定线程(使用
中的TID)会停止该线程。(换句话说,TID==PID的线程并不特殊。)/proc/PID/task/
- 将
发送到进程将停止所有线程,但SIGSTOP
仍然可以正常工作ptrace()
- 如果您向进程发送了
,请不要调用SIGSTOP
ptrace(ptrace\u CONT,TID)
#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
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
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
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
#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, ®s, ®s); } 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; }
#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 &
Tracer: Waiting for child (pid 24275) events.
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.
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
long r; do { r = ptrace(PTRACE_cmd, tid, ...); } while (r == -1L && (errno == EBUSY || errno == EFAULT || errno == ESRCH));
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], ®s, ®s); 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. */ } }