Я написал второй тестовый пример. Мне пришлось добавить отдельный ответ, поскольку он был слишком длинным, чтобы вписаться в первый, с включенным примером вывода.
Во-первых, здесь 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, ®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;
}
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], ®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.
*/
}
}
После вышесказанного, все задачи должны быть приложены и в ожидаемом состоянии, так что, например, PTRACE_CONT работает без дальнейших трюков.
Если поведение меняется в будущих ядрах - я полагаю, что взаимодействие между сигналами STOP/CONT и ptracing - это то, что может измениться; по крайней мере, вопрос разработчикам LKML об этом поведении был бы оправдан! -, вышеуказанная процедура будет по-прежнему работать надежно. (Erring на стороне осторожности, используя несколько циклов в PTRACE_SINGLESTEP, также может быть хорошей идеей.)
Разница с PTRACE_CONT заключается в том, что если поведение изменится в будущем, начальный PTRACE_CONT может фактически продолжить этот процесс вызывает ptrace()
, которые следуют за ним, чтобы потерпеть неудачу. С помощью PTRACE_SINGLESTEP процесс будет останавливаться, позволяя дальнейшим вызовам ptrace()
, чтобы добиться успеха.
Вопросы?
Большое спасибо за это! Да, я также обнаружил, что вы можете использовать только команды перезапуска (например, SINGLESTEP), когда вы находитесь в остановленном состоянии, так что обычно вы будете ждать SINGLESTEP +. Тем не менее, мне не удалось заставить работу CONT возобновить один поток, несмотря на то, что даже отправил 'kill (pid, SIGCONT)' процесс после присоединения всех детей. То, что я, наконец, закончил, заключалось в том, что я * отделил * тот поток, который я хочу запустить, а затем снова подключив его. Но я еще не пытался сделать PTRACE_CONT, а затем ждать WIFCONTINUED; возможно, это необходимо ... –
@ KerrekSB: Я не верю, что PTRACE_CONT будет работать именно так. Я полагаю, что у вас есть в основном три варианта (для запуска одного потока при сохранении остальных): 1) отсоедините от одного потока, который вы хотите запустить, 2) PTRACE_SINGLESTEP для одношагового потока или 3) PTRACE_SYSCALL для запуска этого перейдите к следующему syscall. Я не знаю вашего конкретного варианта использования, но, возможно, третий вариант в цикле был бы подходящим? Это позволяет быстро выполнять вычисление, но только до следующего syscall; Мне нравится этот баланс между контролем и эффективностью ... –
Правильно, спасибо ... мой usecase заключается в том, что я хочу, чтобы трассировка делала fork + abort для создания дампа ядра (но теперь с четко определенным состоянием потоков, которое Я могу собрать урожай в родительской части вилки). Интересно, почему PTRACE_CONT не работает? Это только для возобновления после сигнала? (Отладка работает отлично, кстати. Singlestepping классный, но мне это не понадобится.) –