-1

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

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

strace -o filename.txt ls

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

#include <stdio.h>
#include <stdlib.h>

int main(){
     system("strace -o filename.txt ls");
     return 0;
}

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

4

1 回答 1

0

该练习显然旨在通过使用该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-m6464 位架构之间切换。它们通常具有完全不同的系统调用号。

本质上,您的程序应该:

  1. fork()一个子进程。

    在子进程中:

    1. 通过调用启用 ptracingprctl(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选项,则此时应使用 eg 停止子进程raise(SIGSTOP);,以便父进程可以开始跟踪该子进程。

    4. 使用 eg 执行要跟踪的命令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)将为真)。

      在父进程收到此信号之前,您不能假设 a PTRACE_SYSCALL、等命令实际上已导致子进程继续运行。换句话说,您不能只向子进程发出命令,并期望它们以有序的方式发生!设施是异步的,调用会立即返回;您需要了解事件的类型才能知道子进程听从了命令。PTRACE_SYSEMUPTRACE_CONTptrace()ptrace()waitpid()WIFCONTINUED(status)

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

  4. 每当子进程因为即将执行系统调用而停止时,您需要使用ptrace(PTRACE_GETREGS, childpid, (void *)0, &regs)获取系统调用执行时子进程中的 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.eaxregs.regs.rax-EACCES-EINVAL

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

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

于 2017-05-22T20:49:26.113 回答