c - 获取系统调用 ID 并将其存储在.txt文件 (LINUX) 中



所以我一直在为这个练习而苦苦挣扎。我必须获取我选择的任何给定 Linux 命令(即 ls 或 cd)所做的系统调用,将它们列在 .txt 文件中,并在它们旁边列出它们的唯一 ID。

到目前为止,这是我得到的:

strace -o filename.txt ls

当在 Linux shell 中执行时,它会给我一个"文件名.txt"文件,其中包含 ls 命令的所有系统调用。现在在我的 C 脚本中:

#include <stdio.h>
#include <stdlib.h>
int main(){
system("strace -o filename.txt ls");
return 0;
}

这应该与前面的代码相同,但它不会返回我任何东西,尽管代码成功编译。我将如何解决这个问题,然后获取 ID?我正在使用"stdlib"库,因为在我的研究中,我发现它与系统调用ID有一些关系,但没有找到任何关于如何获取它们的迹象。基本上,我必须读取我创建的文件,并让它为每个系统调用其ID。

该练习显然旨在通过使用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 %sn", v, name } }'

生成许多syscall-number syscall-name行,用于将系统调用名称映射回系统调用号码,但这将是愚蠢且容易出错的。愚蠢,因为能够使用ptrace()比使用strace实用程序给你更多的控制,而使用像上面这样的"聪明的黑客"只是意味着你避免学习如何做到这一点,在我看来,根据定义,这是弄巧成拙的,因此完全愚蠢;并且容易出错,因为绝对不能保证安装的标头与正在运行的体系结构匹配。这在多架构架构上尤其成问题,您可以使用-m32-m64编译器选项在 32 位和 64 位架构之间切换。它们通常具有完全不同的系统呼叫号码。

从本质上讲,您的程序应该:

  1. fork()子进程。

    在子进程中:

    1. 通过调用prctl(PR_SET_DUMPABLE, 1L)启用跟踪

    2. 通过调用ptrace(PTRACE_TRACEME, (pid_t)0, (void *)0, (void *)0)使父进程成为跟踪器

    3. (可选)设置跟踪选项。例如,调用ptrace(PTRACE_SETOPTIONS, getpid(), PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK)以便至少捕获clone()fork()和 exec() 系列的系统调用。

      如果未设置PTRACE_O_TRACEEXEC选项,则应在此时停止子进程,例如raise(SIGSTOP);,以便父进程可以开始跟踪此子进程。

    4. 执行要跟踪的命令,例如execv().特别是,如果第一个命令行参数是要运行的命令,可以选择后跟其选项,则可以使用execvp(argv[1], argv + 1);

      如果设置了上面的PTRACE_O_TRACEEXEC选项,则内核将在执行新二进制文件之前自动暂停子进程。

      如果可执行文件失败,子进程应退出。 我喜欢使用exit(127);返回退出状态 127。

  2. 父进程中,在循环中使用waitpid(childpid, &status, WUNTRACED | WCONTINUED来捕获子进程中的事件。

    第一个事件应该是最初的暂停,即WIFSTOPPED(status)是真的。(如果没有,则其他问题。

  3. waitpid(childpid, &status, WUNTRACED | WCONTINUED)可能会返回的原因有三种:

    • 当孩子退出时(WIFEXITED(status)为真)。 这显然应该结束跟踪,并使父跟踪器进程也退出。

    • 当子项恢复执行时(WIFCONTINUED(status)将为真)。

      在父进程收到此信号之前,您不能假设PTRACE_SYSCALLPTRACE_SYSEMUPTRACE_CONT等命令实际上导致子进程继续。 换句话说,您不能只向子进程发出ptrace()命令,并期望它们以有序的方式发生!ptrace()设施是异步的,呼叫将立即返回;您需要waitpid()WIFCONTINUED(status)类型的事件,才能知道子进程是否响应了该命令。

    • 当内核停止子进程(使用SIGTRAP)时,因为子进程即将执行系统调用。(在父级中,WIFSTOPPED(status)为真。

  4. 每当子进程因即将执行系统调用而停止时,都需要使用ptrace(PTRACE_GETREGS, childpid, (void *)0, &regs)在执行系统调用时获取子进程中的 CPU 寄存器状态。

    regs的类型为struct user,定义于<sys/user.h>中。对于英特尔/AMD 架构,regs.regs.eax(对于 32 位)或regs.regs.rax(对于 64 位)包含<sys/syscall.h>中定义的系统调用编号 (SYS_foo

    然后,您需要调用ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)来告诉内核执行该系统调用,并再次waitpid()等待WIFCONTINUED(status)事件通知它执行了。

    来自waitpid()的下一个WIFSTOPPED(status)类型事件将在系统调用完成后发生。如果需要,可以再次使用PTRACE_GETREGS来检查包含 syscall 返回值的regs.regs.eaxregs.regs.rax;在英特尔/AMD 上,如果发生错误,它将是一个负的 errno 值(即-EACCES-EINVAL或类似内容。

    您需要调用ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)以告诉内核继续运行子程序,直到下一次系统调用。

网上有很多例子显示了上面的一些细节,尽管我个人看到的大多数例子在错误检查方面都非常松懈,偶尔会省略检查WIFCONTINUED(status)waitpid()事件。我甚至写了一个答案,详细说明了如何在StackOverflow上停止和继续各个线程。由于该技术可以用作非常强大的自定义调试工具,因此我建议您尝试学习该工具,以便您可以在工作中利用它,而不仅仅是复制粘贴一些现有代码以获得练习的及格分数。

相关内容

  • 没有找到相关文章

最新更新