我理解该strace
命令用于ptrace(PTRACE_PEEKUSER, child, __builtin_offsetof(struct user, regs.orig_eax))
查找被跟踪子进程所捕获的系统调用的索引。然后,为了将索引转换为系统调用函数名称,它通过 grepping 安装中存在的 Linux 源代码头文件建立了表。
这种方法肯定没有记录,而且容易失败,因为源代码声明的位置和语法没有记录,必须通过 grepping 找到,而且可能会以未知的方式发生变化。我这样说对吗?
如果是这样,那么为什么strace
不使用下面的方法,在我看来,这种方法更简单,只依赖于文档,因此是万无一失的。
在重启后第一次运行时,strace
发送一个测试系统调用(每个系统调用函数一个),捕获它,并观察子进程使用的系统调用索引。这将提供一个完整且正确的自定义表,该表可以存储在已知的文件中,以供进一步调用strace
。
我确信这种方法一定被考虑过,因为它并不是什么特别巧妙的方法。所以它一定有问题。问题是什么?
答案1
因为在如此低的层次上没有系统调用名称的概念。不可能strace
说“嘿,让我们打个电话看看号码是多少!”。它只能根据系统调用号本身进行调用。这是因为当系统调用号保存到或寄存器中,并且进程调用或时,fcntl()
系统调用才会进行。eax
rax
int 0x80
syscall
尽管它可以从 C 库调用包装器函数,但不能保证系统调用与包装器同名。例如,如果它试图open()
通过调用同名的 libc 包装器并检查所使用的系统调用号来找出系统调用号,它会错误地得出结论,认为它是系统调用 257,而实际上它是系统调用 2。这是因为该包装器函数实际上调用的是openat()
,而不是open()
。一个简单的演示 shell 日志:
$ cat open.c
#include <fcntl.h>
int main(void)
{
open("/dev/null", O_RDONLY);
}
$ gcc open.c
$ strace -e trace=%file -P "/dev/null" ./a.out
openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
+++ exited with 0 +++
现在轮到你能而是使用 来执行此操作syscall(SYS_open, "/dev/null", O_RDONLY)
,但这样一来您又要依赖标头中定义的常量,那么为什么不直接绕过中间人,直接strace
使用 C 标头中的系统调用列表进行构建呢?这就是它的strace
作用。