在 Linux 系统调用中,系统调用参数是否在系统调用完成后(在 sys_exit 跟踪点)保存在寄存器中?
In a Linux system call, are system call parameters preserved in registers after the syscall finished (at the sys_exit tracepoint)?
是否保证能够读取sys_exit
tracepoint处的所有系统调用参数?
sysdig driver 是一个内核模块,用于使用内核静态跟踪点捕获系统调用。在这个项目中,一些系统调用参数在 sys_enter
tracepoint 读取,其他一些参数在 sys_exit
读取(当然 return 值,以及用户空间中的内容以避免页面错误)。
为什么不读取 sys_exit
处的所有参数?这是因为某些参数可能在 sys_exit
中不可用吗?
Is it guaranteed to be able to read all the syscall parameters at sys_exit
tracepoint?
是... 也不是,我们需要区分参数和寄存器。 Linux 系统调用应该保留所有通用用户 space 寄存器 ,除了用于 return 值的寄存器(在某些架构上还有第二个寄存器以指示是否发生错误)。然而,这并不意味着系统调用的输入 parameters 不能在进入和退出之间改变:如果寄存器保存指向某些数据的指针的值,而寄存器本身不会改变,它指向的数据很可能会发生变化。
查看静态跟踪点 sys_exit
的 the code,您可以看到只有系统调用编号 (id
) 及其 return 值 (ret
) 被跟踪。有关更多信息,请参阅我的答案底部的注释。
Why not read all parameters at sys_exit
? Is this because some parameters may be not available at sys_exit
?
是的,我想说确保跟踪参数的正确性是为什么只在出口处跟踪不是一个好主意的主要原因。即使你得到了寄存器的值,你也无法知道系统调用退出时的真实参数。即使系统调用 本身 可以保证保存和恢复用户寄存器的状态,系统调用本身也可以更改作为参数传递的数据。例如,recvmsg
系统调用采用指向内存中 struct msghdr
的指针,该指针既用作输入参数又用作输出参数; poll
系统调用对指向 struct pollfd
的指针执行相同的操作。此外,另一个线程或程序在进行系统调用时可能已经很好地修改了程序的内存,因此改变了数据。
在特定情况下,系统调用在 returning 之前也可能需要很长时间(例如 sleep
,或终端上的阻塞 read
,accept
在监听套接字上,等等)。如果你只在出口处跟踪,你将得到非常不正确的时间信息,最重要的是你将不得不等待很长时间才能捕获到任何有意义的信息,即使该信息在入口点已经可用。
关于 sys_exit
跟踪点的注释
虽然您可以从理论上提取当前任务的已保存寄存器的值,但我不完全确定在 sys_exit
跟踪点中这样做的语义。我搜索了一些关于这个特定案例的文档,但没有成功,而且内核代码非常……复杂。
到达退出挂钩的调用链应该是:
- Arch 特定入口点(例如
entry_INT80_32
for x86 int 0x80
)
- Arch 特定入口处理程序(例如
do_int80_syscall_32()
for x86 int 0x80
)
如果在系统调用期间向进程传递了致命信号,而实际进程将永远不会到达系统调用的出口(即没有任何值 returned 给用户 space) ,跟踪点仍将被命中。当发生这种信号传递时,会使用一个特殊的内部 return 值,例如 -ERESTARTSYS
(see here)。这个值 不是 一个实际的系统调用 return 值(它不是 return 给用户 space),而只是为了内核使用。所以看起来 sys_exit
跟踪点被特殊的 -ERESTARTSYS
击中,如果进程收到致命信号。例如在 SIGSTOP
+ SIGCONT
的情况下不会发生这种情况。不过,请对此持保留态度,因为我无法为此找到合适的文档。
是否保证能够读取sys_exit
tracepoint处的所有系统调用参数?
sysdig driver 是一个内核模块,用于使用内核静态跟踪点捕获系统调用。在这个项目中,一些系统调用参数在 sys_enter
tracepoint 读取,其他一些参数在 sys_exit
读取(当然 return 值,以及用户空间中的内容以避免页面错误)。
为什么不读取 sys_exit
处的所有参数?这是因为某些参数可能在 sys_exit
中不可用吗?
Is it guaranteed to be able to read all the syscall parameters at
sys_exit
tracepoint?
是... 也不是,我们需要区分参数和寄存器。 Linux 系统调用应该保留所有通用用户 space 寄存器 ,除了用于 return 值的寄存器(在某些架构上还有第二个寄存器以指示是否发生错误)。然而,这并不意味着系统调用的输入 parameters 不能在进入和退出之间改变:如果寄存器保存指向某些数据的指针的值,而寄存器本身不会改变,它指向的数据很可能会发生变化。
查看静态跟踪点 sys_exit
的 the code,您可以看到只有系统调用编号 (id
) 及其 return 值 (ret
) 被跟踪。有关更多信息,请参阅我的答案底部的注释。
Why not read all parameters at
sys_exit
? Is this because some parameters may be not available atsys_exit
?
是的,我想说确保跟踪参数的正确性是为什么只在出口处跟踪不是一个好主意的主要原因。即使你得到了寄存器的值,你也无法知道系统调用退出时的真实参数。即使系统调用 本身 可以保证保存和恢复用户寄存器的状态,系统调用本身也可以更改作为参数传递的数据。例如,recvmsg
系统调用采用指向内存中 struct msghdr
的指针,该指针既用作输入参数又用作输出参数; poll
系统调用对指向 struct pollfd
的指针执行相同的操作。此外,另一个线程或程序在进行系统调用时可能已经很好地修改了程序的内存,因此改变了数据。
在特定情况下,系统调用在 returning 之前也可能需要很长时间(例如 sleep
,或终端上的阻塞 read
,accept
在监听套接字上,等等)。如果你只在出口处跟踪,你将得到非常不正确的时间信息,最重要的是你将不得不等待很长时间才能捕获到任何有意义的信息,即使该信息在入口点已经可用。
关于 sys_exit
跟踪点的注释
虽然您可以从理论上提取当前任务的已保存寄存器的值,但我不完全确定在 sys_exit
跟踪点中这样做的语义。我搜索了一些关于这个特定案例的文档,但没有成功,而且内核代码非常……复杂。
到达退出挂钩的调用链应该是:
- Arch 特定入口点(例如
entry_INT80_32
for x86int 0x80
)- Arch 特定入口处理程序(例如
do_int80_syscall_32()
for x86int 0x80
)
- Arch 特定入口处理程序(例如
如果在系统调用期间向进程传递了致命信号,而实际进程将永远不会到达系统调用的出口(即没有任何值 returned 给用户 space) ,跟踪点仍将被命中。当发生这种信号传递时,会使用一个特殊的内部 return 值,例如 -ERESTARTSYS
(see here)。这个值 不是 一个实际的系统调用 return 值(它不是 return 给用户 space),而只是为了内核使用。所以看起来 sys_exit
跟踪点被特殊的 -ERESTARTSYS
击中,如果进程收到致命信号。例如在 SIGSTOP
+ SIGCONT
的情况下不会发生这种情况。不过,请对此持保留态度,因为我无法为此找到合适的文档。