Ptrace 防止信号中断被跟踪进程中的 pselect()
Ptrace prevents signal from interrupting pselect() in traced process
我正在尝试使用 ptrace 监视二进制文件的系统调用。二进制文件在 pselect() 中休眠并且没有 ptrace,SIGQUIT 使它从 pselect return。传递给 pselect 的阻塞信号掩码包括 SIGQUIT。
当使用 ptrace 执行时,它会从 sys_pselect6 退出,但不会完全退出 glibc 的 pselect。我在做什么可以防止 sys_pselect6 退出到用户代码?
示踪剂:
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <err.h>
#include <wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pid = fork(), sys_in = 1, status;
if (pid == 0) {
if (ptrace(PTRACE_TRACEME, getppid(), NULL, NULL) < 0)
err(1, "TRACEME()");
execl("./child", "./child", NULL);
err(1, "execl()");
}
if (waitpid(pid, &status, 0) != pid) err(1, "wait()");
for (;; sys_in ^= 1) {
if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) err(1, "SYSCALL");
if (waitpid(pid, &status, 0) != pid) err(1, "wait()");
if (sys_in) {
long long sys_no = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);
printf("syscall entry %lld\n", sys_no);
}
else printf("syscall exit\n");
}
return 0;
}
Child:
#include <stdio.h>
#include <sys/select.h>
#include <signal.h>
#include <err.h>
void handle_sigquit(int sig, siginfo_t* info, void *ctx)
{
}
int main()
{
sigset_t mask;
sigset_t orig_mask;
struct sigaction sa = {};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handle_sigquit;
sigaction(SIGQUIT, &sa, NULL);
sigemptyset(&mask);
sigaddset(&mask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) err(1, "sigprocmask()");
pselect(0, NULL, NULL, NULL, NULL, &orig_mask);
warn("pselect()");
return 0;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
每当您的调试器收到通知时,您只需假设该通知是关于系统调用的,并相应地处理它。事实并非如此。
您使用 wait
收到的一些通知是针对您的调试对象收到的 信号 。当这些发生时,您的 PTRACE_SYSCALL
调用中的最后一个 NULL 会消除并有效地屏蔽到达调试进程的信号。
处理 ptrace
结果时,您需要检查导致调试器唤醒的信号。至少,检查它是 SIGTRAP
还是其他东西。如果是其他原因,最好的办法是将其传递给调试进程。
查看此 small program 以了解执行此操作的简单方法。
我正在尝试使用 ptrace 监视二进制文件的系统调用。二进制文件在 pselect() 中休眠并且没有 ptrace,SIGQUIT 使它从 pselect return。传递给 pselect 的阻塞信号掩码包括 SIGQUIT。
当使用 ptrace 执行时,它会从 sys_pselect6 退出,但不会完全退出 glibc 的 pselect。我在做什么可以防止 sys_pselect6 退出到用户代码?
示踪剂:
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <err.h>
#include <wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pid = fork(), sys_in = 1, status;
if (pid == 0) {
if (ptrace(PTRACE_TRACEME, getppid(), NULL, NULL) < 0)
err(1, "TRACEME()");
execl("./child", "./child", NULL);
err(1, "execl()");
}
if (waitpid(pid, &status, 0) != pid) err(1, "wait()");
for (;; sys_in ^= 1) {
if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) err(1, "SYSCALL");
if (waitpid(pid, &status, 0) != pid) err(1, "wait()");
if (sys_in) {
long long sys_no = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);
printf("syscall entry %lld\n", sys_no);
}
else printf("syscall exit\n");
}
return 0;
}
Child:
#include <stdio.h>
#include <sys/select.h>
#include <signal.h>
#include <err.h>
void handle_sigquit(int sig, siginfo_t* info, void *ctx)
{
}
int main()
{
sigset_t mask;
sigset_t orig_mask;
struct sigaction sa = {};
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handle_sigquit;
sigaction(SIGQUIT, &sa, NULL);
sigemptyset(&mask);
sigaddset(&mask, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) err(1, "sigprocmask()");
pselect(0, NULL, NULL, NULL, NULL, &orig_mask);
warn("pselect()");
return 0;
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
每当您的调试器收到通知时,您只需假设该通知是关于系统调用的,并相应地处理它。事实并非如此。
您使用 wait
收到的一些通知是针对您的调试对象收到的 信号 。当这些发生时,您的 PTRACE_SYSCALL
调用中的最后一个 NULL 会消除并有效地屏蔽到达调试进程的信号。
处理 ptrace
结果时,您需要检查导致调试器唤醒的信号。至少,检查它是 SIGTRAP
还是其他东西。如果是其他原因,最好的办法是将其传递给调试进程。
查看此 small program 以了解执行此操作的简单方法。