所以我一直在努力练习。我必须得到我选择的任何给定Linux命令(I.E. 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?我正在使用&#34; stdlib&#34;库因为在我的研究中我发现它与系统调用ID有一些关系,但是没有找到关于如何获取它们的任何指示。基本上我必须读取我创建的文件并让它为每个系统调用它的ID。
答案 0 :(得分: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
实用程序和使用&#34;聪明的黑客&#34;提供了更多的控制权。如上所述只是意味着你避免学习如何做到这一点,在我看来,根据定义是弄巧成拙,因此完全愚蠢;并且容易出错,因为绝对无法保证已安装的标头与正在运行的体系结构相匹配。这在多架构架构上尤其成问题,您可以使用-m32
和-m64
编译器选项在32位和64位架构之间切换。它们通常具有完全不同的系统调用号码。
基本上,你的程序应该:
fork()
子进程。
在子进程中:
(可选)设置跟踪选项。例如,调用ptrace(PTRACE_SETOPTIONS, getpid(), PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACEFORK)
以便至少捕获clone()
,fork()
和exec()系列调用。
如果您未设置PTRACE_O_TRACEEXEC
选项,则应使用以下选项停止子进程: raise(SIGSTOP);
,以便父进程可以开始跟踪此子进程。
执行使用例如跟踪的命令。 execv()
。特别是,如果第一个命令行参数是要运行的命令,可选地后跟其选项,则可以使用execvp(argv[1], argv + 1);
。
如果您在上面设置PTRACE_O_TRACEEXEC
选项,那么内核将在执行新二进制文件之前自动暂停子进程。
如果exec失败,子进程应该退出。我喜欢使用exit(127);
来返回退出状态127。
在父进程中,在循环中使用waitpid(childpid, &status, WUNTRACED | WCONTINUED
来捕获子进程中的事件。
第一个事件应该是初始暂停,即WIFSTOPPED(status)
为真。 (如果没有,则出现其他问题。)
waitpid(childpid, &status, WUNTRACED | WCONTINUED)
可能返回的原因有三个:
当孩子退出时(WIFEXITED(status)
将为真)。
这显然应该结束跟踪,并让父跟踪器进程退出。
当孩子恢复执行时(WIFCONTINUED(status)
将为真)。
您无法假设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()
类型事件。如果需要,可以再次使用PTRACE_GETREGS
来检查regs.regs.eax
或regs.regs.rax
,其中包含系统调用返回值;在Intel / AMD上,如果发生错误,它将是一个负的errno值(即-EACCES
,-EINVAL
或类似值。)
你需要调用ptrace(PTRACE_SYSCALL, childpid, (void *)0, (void *)0)
告诉内核继续运行子进程,直到下一次系统调用。
有很多示例在线显示上面的一些细节,尽管我个人看到的大部分内容在错误检查方面都非常宽松,偶尔也会忽略检查WIFCONTINUED(status)
waitpid()
事件。我甚至写了一篇详细说明如何在StackOverflow上停止和继续单个线程的答案。由于该技术可以用作非常强大的自定义调试工具,我建议您尝试学习该工具,以便在工作中利用它,而不是仅仅复制粘贴一些现有代码以获得练习的及格分数。