该练习显然旨在通过使用该ptrace()
工具来解决,因为该strace
实用程序没有打印系统调用号的选项(据我所知)。
从技术上讲,您可以使用类似的东西
printf '#include <sys/syscall.h>\n' | gcc -dD -E - | awk '$1 == "#define" { m[$2] = $3 } END { for (name in m) if (name ~ /^SYS_/) { v = name; while (v in m) v = m[v]; sub(/^SYS_/, "", name); printf "%s %s\n", v, name } }'
生成多syscall-number syscall-name
行,用于将系统调用名称映射回系统调用编号,但这很愚蠢且容易出错。愚蠢,因为能够使用ptrace()
比使用实用程序给你更多的控制strace
,并且使用像上面这样的“聪明的黑客”只是意味着你避免学习如何做到这一点,在我看来,这在定义上是弄巧成拙的,因此完全愚蠢; 并且容易出错,因为绝对不能保证安装的头文件与正在运行的架构相匹配。这在多架构架构上尤其成问题,您可以使用编译器选项在 32 位-m32
和-m64
64 位架构之间切换。它们通常具有完全不同的系统调用号。
本质上,您的程序应该:
fork()
一个子进程。
在子进程中:
通过调用启用 ptracingprctl(PR_SET_DUMPABLE, 1L)
通过调用使父进程跟踪器ptrace(PTRACE_TRACEME, (pid_t)0, (void *)0, (void *)0)
(可选)设置跟踪选项。例如,调用ptrace(PTRACE_SETOPTIONS, getpid(), PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK)
以便您至少捕获clone()
、fork()
和 exec() 系列的系统调用。
如果不设置该PTRACE_O_TRACEEXEC
选项,则此时应使用 eg 停止子进程raise(SIGSTOP);
,以便父进程可以开始跟踪该子进程。
使用 eg 执行要跟踪的命令execv()
。特别是,如果第一个命令行参数是要运行的命令,可选地后跟其选项,则可以使用execvp(argv[1], argv + 1);
.
如果您设置了PTRACE_O_TRACEEXEC
上面的选项,那么内核将在执行新的二进制文件之前自动暂停子进程。
如果执行失败,子进程应该退出。我喜欢使用exit(127);
, 返回退出状态 127。
在父进程中,waitpid(childpid, &status, WUNTRACED | WCONTINUED
在循环中使用,以捕获子进程中的事件。
第一个事件应该是最初的暂停,即为WIFSTOPPED(status)
真。(如果没有,还有其他问题。)
waitpid(childpid, &status, WUNTRACED | WCONTINUED)
可能返回的三个不同原因:
当孩子退出时(WIFEXITED(status)
将是真的)。这显然应该结束跟踪,并让父跟踪器进程也退出。
当孩子恢复执行时(WIFCONTINUED(status)
将为真)。
在父进程收到此信号之前,您不能假设 a PTRACE_SYSCALL
、等命令实际上已导致子进程继续运行。换句话说,您不能只向子进程发出命令,并期望它们以有序的方式发生!设施是异步的,调用会立即返回;您需要了解事件的类型才能知道子进程听从了命令。PTRACE_SYSEMU
PTRACE_CONT
ptrace()
ptrace()
waitpid()
WIFCONTINUED(status)
当内核停止子进程时(使用SIGTRAP
),因为子进程即将执行系统调用。(在父母中,WIFSTOPPED(status)
将是真实的。)
每当子进程因为即将执行系统调用而停止时,您需要使用ptrace(PTRACE_GETREGS, childpid, (void *)0, ®s)
获取系统调用执行时子进程中的 CPU 寄存器状态。
regs
是 类型struct user
,在 中定义<sys/user.h>
。对于 Intel/AMD 架构,regs.regs.eax
(对于 32 位)或regs.regs.rax
(对于 64 位)包含系统调用号(SYS_foo
如<sys/syscall.h>
.
然后您需要调用ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)
以告诉内核执行该系统调用,并waitpid()
再次等待WIFCONTINUED(status)
通知它执行的事件。
系统调用完成时将发生下一个WIFSTOPPED(status)
类型事件。waitpid()
如果需要,可以再次使用orPTRACE_GETREGS
来检查,其中包含系统调用返回值;在 Intel/AMD 上,如果发生错误,它将是一个负 errno 值(即、或类似的值。)regs.regs.eax
regs.regs.rax
-EACCES
-EINVAL
您需要调用ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)
以告诉内核继续运行子进程,直到下一个系统调用。
网上有很多例子显示了上面的一些细节,虽然我个人看到的大多数在错误检查方面都相当松懈,偶尔会忽略检查WIFCONTINUED(status)
waitpid()
事件。我什至写了一个答案,详细说明了如何在 StackOverflow 上停止和继续各个线程。由于该技术可以用作非常强大的自定义调试工具,因此我建议您尝试学习该工具,以便在工作中利用它,而不是仅仅复制粘贴一些现有代码以在练习中获得及格分数。