【问题标题】:Get system calls IDs and store them in a .txt file(LINUX)获取系统调用 ID 并将它们存储在 .txt 文件中(LINUX)
【发布时间】:2017-05-22 14:47:06
【问题描述】:

所以我一直在努力做这个练习。我必须获取我选择的任何给定 Linux 命令(即 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。

【问题讨论】:

  • 从自己的程序中执行 shell 命令有什么意义?如果你被要求在 c 中这样做,你可能应该看看man ptrace
  • 1) C 不是脚本语言。 2) @FelixPalmen 是对的:这毫无意义。

标签: c linux operating-system system monitors


【解决方案1】:

这个练习显然是为了使用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 实用程序为您提供了更多的控制权,并且使用像上面这样的“聪明的黑客”只是意味着您避免学习如何做到这一点,在我看来,这就是自我定义- 失败,因此完全愚蠢;并且容易出错,因为绝对不能保证安装的头文件与正在运行的架构相匹配。这在多架构架构上尤其成问题,您可以使用 -m32-m64 编译器选项在 32 位和 64 位架构之间切换。它们通常具有完全不同的系统调用号。

基本上,您的程序应该:

  1. fork() 一个子进程。

    在子进程中:

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

    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, &amp;status, WUNTRACED | WCONTINUED,捕获子进程中的事件。

    第一个事件应该是初始暂停,即WIFSTOPPED(status) 为真。 (如果没有,其他地方出了问题。)

  3. waitpid(childpid, &amp;status, WUNTRACED | WCONTINUED) 可能返回的三种不同原因:

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

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

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

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

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

    regsstruct user 类型,在&lt;sys/user.h&gt; 中定义。对于 Intel/AMD 架构,regs.regs.eax(32 位)或regs.regs.rax(64 位)包含系统调用号(SYS_foo,如&lt;sys/syscall.h&gt; 中定义的那样。

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

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

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

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

【讨论】:

    猜你喜欢
    • 2013-10-07
    • 2012-11-27
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多