wait() hangs when CLONE_THREAD



我正在使用ptrace跟踪一些进程及其子进程。我正在尝试打印特定的系统调用(使用通知ptrace的Seccomp过滤器,参见此博客文章)。

在大多数情况下,我的代码(见下文)工作得很好。但是,当我跟踪java程序(从默认的jre包)时,后者使用CLONE_THREAD标志进行克隆。出于某种原因,我的示踪挂(我相信),因为我不能接收信号从克隆的过程。我认为原因是(根据这个讨论)子进程实际上成为了原始进程父进程的子进程,而不是原始进程的子进程。

我通过使用一个简单的程序再现了这个问题,该程序只调用带有标志的clone()并执行操作。当我使用CLONE_THREAD | CLONE_SIGHAND | CLONE_VM标志时(正如clone()文档指定的那样,它们应该自Linux 2.6.0以来一起出现),至少我能够正确地跟踪一切,直到两个线程中的一个完成。

我想独立地跟踪两个线程。这可能吗?

更重要的是,我需要跟踪Java程序,而且我不能更改它。下面是Java程序克隆调用的一个片段:

[...]
4665  clone(child_stack=0x7fb166e95fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[4666], tls=0x7fb166e96700, child_tidptr=0x7fb166e969d0) = 4666
[...]

所以Java似乎尊重这些规则。我想通过实验来理解:我排除了任何与线程无关的标志(即,' CLONE_FS | CLONE_FILES | CLONE_SYSVSEM)。

下面是用不同的标志组合运行我的测试程序的结果(我知道,我真的很绝望):

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS:只能从父母得到跟踪

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID:不一致;从两个对象获取跟踪,直到父对象完成

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID:不一致;从两者获取跟踪,直到子

    完成
  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID:只有跟踪来自父母

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_CHILD_CLEARTID:只从父节点获取trace

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID|CLONE_SETTLS:只从父节点获取trace

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID:不一致;从两者获取跟踪,直到子

    完成
  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID|CLONE_SETTLS:只从父节点获取trace

  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_CHILD_CLEARTID|CLONE_PARENT_SETTID:不一致;从两者获取跟踪,直到子

    完成
  • CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID:只从父节点

    获取trace

所以至少我从我的程序和Java程序中得到相同的行为:它不工作。

我怎样才能使它工作?例如,strace如何成功地跟踪任何类型的克隆?我试图深入研究它的代码,但我找不到他们是如何做到的。

任何帮助会欣赏!最好的祝福,

跟踪程序代码(使用g++ tracer.cpp -o tracer -g -lseccomp -lexplain编译):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <libexplain/waitpid.h>
#include <tuple>
#include <vector>

#define DEFAULT_SIZE 1000
#define MAX_SIZE 1000
int process_signals();
int inspect(pid_t);
void read_string_into_buff(const pid_t, unsigned long long, char *, unsigned int);
int main(int argc, char **argv){
pid_t pid;
int status;
if (argc < 2) {
fprintf(stderr, "Usage: %s <prog> <arg1> ... <argN>n", argv[0]);
return 1;
}
if ((pid = fork()) == 0) {
/* If execve syscall, trace */
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_getpid, 0, 1),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRACE),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short) (sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
ptrace(PTRACE_TRACEME, 0, 0, 0);
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
perror("prctl(PR_SET_NO_NEW_PRIVS)");
return 1;
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
perror("when setting seccomp filter");
return 1;
}
kill(getpid(), SIGSTOP);
return execvp(argv[1], argv + 1);
} else {
waitpid(pid, &status, 0);
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESECCOMP | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACEVFORK );
ptrace(PTRACE_CONT, pid, 0, 0);
process_signals();
return 0;
}
}

int process_signals(){
int status;
while (1){
pid_t child_pid;
// When child status changes
if ((child_pid = waitpid(-1, &status, 0)) < 0){
fprintf(stderr, "%sn", explain_waitpid(child_pid, &status, 0));
exit(EXIT_FAILURE);
}
//printf("Sigtrap receivedn");
// Checking if it is thanks to seccomp
if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))){
// Perform argument inspection with ptrace
int syscall = inspect(child_pid);
}
// Resume no matter what
ptrace(PTRACE_CONT, child_pid, 0, 0);
}
}
int inspect(pid_t pid){
printf("From PID: %dn", pid);
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid, 0, &regs);
// Get syscall number
int syscall = regs.orig_rax;
printf("------nCaught syscall: %dn", syscall);
if (syscall == __NR_getpid){
printf("Getpid detectedn");
}
return syscall;
}
void read_string_into_buff(const pid_t pid, unsigned long long addr, char * buff, unsigned int max_len){
/* Are we aligned on the "start" front? */
unsigned int offset=((unsigned long)addr)%sizeof(long);
addr-=offset;
unsigned int i=0;
int done=0;
int word_offset=0;
while( !done ) {
unsigned long word=ptrace( PTRACE_PEEKDATA, pid, addr+(word_offset++)*sizeof(long), 0 );
// While loop to stop at the first '' char indicating end of string
while( !done && offset<sizeof(long) && i<max_len ) {
buff[i]=((char *)&word)[offset]; /* Endianity neutral copy */
done=buff[i]=='';
++i;
++offset;
}
offset=0;
done=done || i>=max_len;
}
}

示例程序(使用gcc sample.c -o sample编译):

#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define FLAGS CLONE_VM|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID
int fn(void *arg)
{
printf("nINFO: This code is running under child process.n");
int i = 0;
int n = atoi(arg);
for ( i = 1 ; i <= 10 ; i++ )
printf("[%d] %d * %d = %dn", getpid(), n, i, (n*i));
printf("n");
return 0;
}
void main(int argc, char *argv[])
{
printf("[%d] Hello, World!n", getpid());
void *pchild_stack = malloc(1024 * 1024);
if ( pchild_stack == NULL ) {
printf("ERROR: Unable to allocate memory.n");
exit(EXIT_FAILURE);
}
int pid = clone(fn, pchild_stack + (1024 * 1024), FLAGS, argv[1]);
if ( pid < 0 ) {
printf("ERROR: Unable to create the child process.n");
exit(EXIT_FAILURE);
}
fn(argv[1]);
wait(NULL);
free(pchild_stack);
printf("INFO: Child process terminated.n");
}

您可以通过运行./tracer ./sample来测试您想要的内容。您还可以测试原始测试用例./tracer java,并观察到跟踪程序和java都挂起了。

答:正如在评论中指出的那样,我在那个例子中遇到了一些问题,这些问题阻止了我处理孩子发出的信号。

在我的原始代码中(这里没有列出,因为太复杂),我只在进程启动后附加了ptrace…我只附在按树列出的PID上。我的错误是省略了线程(java是一个创建线程的程序),这解释了为什么我只跟踪java出现了问题。我修改了代码,附加到所有的子进程和线程(ps -L -g <Main_PID> -o tid=),一切又工作了。

您的示例程序有一个错误:它可能在第二个线程退出之前释放该线程的堆栈,从而导致SEGV。你的跟踪器不能很好地处理信号。

如果被跟踪的程序得到一个信号,你的跟踪器会拦截它,而不是把它传递给程序。当它继续执行程序时,它从导致SEGV的相同操作继续执行,所以它再次得到SEGV。无限。跟踪程序和跟踪对象似乎都挂起了,但实际上,它们处于无限循环中。

像下面这样重写延续符似乎可以工作:

if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))){
// Perform argument inspection with ptrace
int syscall = inspect(child_pid);
ptrace(PTRACE_CONT, child_pid, 0, 0);
} else if (WIFSTOPPED(status)) {
ptrace(PTRACE_CONT, child_pid, 0, WSTOPSIG(status));
} else {
ptrace(PTRACE_CONT, child_pid, 0, 0);
}

不确定Java,但它似乎在常规操作中获得segv…

最新更新