使用strace跟踪子进程
1. 概述
在本教程中,我们将研究如何在 Linux 中使用strace 工具跟踪子进程。
我们将从创建子进程的脚本开始。然后,我们将在没有任何选项的情况下使用strace跟踪它。最后,我们将使用strace跟踪它,并使用对跟踪子进程有用的特定选项。
2. 脚本先决条件
在一个空目录中,让我们创建一个名为create_child_processes.py的 Python 脚本:
#!/usr/bin/python3
import os
def fork_many_processes():
print(f"I'm the original process and my id is: {os.getpid()}")
n = os.fork()
if n == 0:
print(f"I'm the child process and my id is: {os.getpid()}")
n = os.fork()
if n == 0:
print(f"I'm the grandchild process and my id is: {os.getpid()}")
n = os.fork()
if n == 0:
print(f"I'm the great-grandchild process and my id is: {os.getpid()}")
fork_many_processes()
我们使用 Python 是因为它比编译 C/C++ 源代码和执行二进制文件要容易得多。但本教程也适用于其他语言。
2.1. 分叉进程
分叉一个进程意味着创建一个相同且独立的进程。在 Python 中,我们使用os模块中的fork方法:
n = os.fork()
对于当前进程,该方法返回子进程 id。一个新的过程也诞生了。该进程继承父进程的所有内容,并在fork方法中恢复执行。但这一次,该方法返回 0。
因此,我们可以通过检查fork方法的返回值来区分哪个进程是父进程,哪个进程是子进程。如果返回值为0,那么脚本的进程就是子进程:
if n == 0:
当然,子进程也可以使用相同的机制创建另一个子进程。
2.2. 执行脚本
首先,我们需要确保文件是可执行的:
chmod +x create_child_processes.py
然后我们可以运行它:
$ ./create_child_processes.py
I'm the original process and my id is: 912212
I'm the child process and my id is: 912213
I'm the grandchild process and my id is: 912214
I'm the great-grandchild process and my id is: 912215
该进程创建了一个子进程。这个子进程创建了另一个子进程,它是原始进程的孙子进程。然后这个孙进程创建了另一个子进程,它是原始进程的曾孙。
3. 使用strace
我们可以使用strace来观察 Python 脚本执行的系统调用:
$ strace ./create_child_processes.py
...(Preceding output truncated)
read(3, "#!/usr/bin/python3\nimport os\n\nde"..., 4096) = 504
read(3, "", 4096) = 0
close(3) = 0
getpid() = 993199
write(1, "I'm the original process and my "..., 46I'm the original process and my id is: 993199
) = 46
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f687ce1aa10) = 993200
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f687d02c210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f687d02c210}, 8) = 0
I'm the child process and my id is: 993200
I'm the grandchild process and my id is: 993201
I'm the great-grandchild process and my id is: 993202
sigaltstack(NULL, {ss_sp=0x252c460, ss_flags=0, ss_size=16384}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0) = ?
+++ exited with 0 +++
我们可以识别一些系统调用行,例如getpid() = 993199。Python 代码os.getpid()调用了这个系统调用。很容易推断出 Python 脚本中的print方法执行了write系统调用。然后,我们终于看到了clone系统调用。
我们知道,Python 脚本中的os.fork方法调用了这个系统调用。
但是,我们看不到子进程调用的系统调用。我们只看到子进程产生的输出。
3.1. 使用strace跟踪子进程
strace具有可用于跟踪子进程的选项。我们可以使用-f*选项*:
$ strace -f ./create_child_processes.py
...(Preceding output truncated)
getpid() = 994450
write(1, "I'm the original process and my "..., 46I'm the original process and my id is: 994450
) = 46
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 994451 attached
, child_tidptr=0x7f48eab43a10) = 994451
[pid 994451] set_robust_list(0x7f48eab43a20, 24) = 0
[pid 994450] rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, 8) = 0
[pid 994451] getpid() = 994451
[pid 994451] write(1, "I'm the child process and my id "..., 43I'm the child process and my id is: 994451
) = 43
[pid 994451] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f48eab43a10) = 994452
strace: Process 994452 attached
[pid 994452] set_robust_list(0x7f48eab43a20, 24) = 0
[pid 994451] rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, 8) = 0
[pid 994452] getpid() = 994452
[pid 994452] write(1, "I'm the grandchild process and m"..., 48I'm the grandchild process and my id is: 994452
) = 48
...(Subsequent output truncated)
子进程生成的系统调用以*[pid …]开头。括号中的数字是进程ID,意味着我们可以跟踪哪个子进程产生了这个系统调用。我们还可以跟踪这个子进程的诞生时间,因为克隆*系统调用显示了它产生的子进程 ID。
3.2. 使用strace跟踪带有文件的子进程
很难从终端的输出中分析子进程。也许我们应该将其保存到文件中以供稍后分析?不,我们不能使用重定向技术:
$ strace -f ./create_child_processes.py > dump
$ cat dump
I'm the original process and my id is: 32086
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the grandchild process and my id is: 32088
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the grandchild process and my id is: 32088
I'm the great-grandchild process and my id is: 32089
显然,strace不会在stdout中显示输出。要将输出保存到文件,我们可以使用-o*选项和文件名作为该选项的参数*:
$ strace -o strace_log -f ./create_child_processes.py
I'm the original process and my id is: 32262
I'm the child process and my id is: 32263
I'm the grandchild process and my id is: 32264
I'm the great-grandchild process and my id is: 32265
$ tail strace_log
32265 sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, <unfinished ...>
32264 sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, <unfinished ...>
32265 <... sigaltstack resumed>NULL) = 0
32264 <... sigaltstack resumed>NULL) = 0
32265 exit_group(0 <unfinished ...>
32264 exit_group(0 <unfinished ...>
32265 <... exit_group resumed>) = ?
32264 <... exit_group resumed>) = ?
32265 +++ exited with 0 +++
32264 +++ exited with 0 +++
如我们所见,子进程行以进程 id 开头,如本例中的 32265 或 32264。
3.3. 使用strace跟踪具有单独文件的子进程
很难像我们以前那样在一个地方跟踪许多子进程的活动。如果我们可以在不同的地方查看每个子进程的活动不是很好吗?我们可以通过使用带有-ff选项和-o选项的strace*来做到这一点:*
$ strace -o output -ff ./create_child_processes.py
I'm the original process and my id is: 31117
I'm the child process and my id is: 31118
I'm the grandchild process and my id is: 31119
I'm the great-grandchild process and my id is: 31120
-o选项接受strace将用于创建文件的文件名模式。这些文件将包含子进程的活动:
$ ls output*
output.31117 output.31118 output.31119 output.31120
作为文件扩展名附加的数字是原始进程和子进程的进程ID。具有最低 id 的转储文件(在此示例中为output.31117)是原始进程的转储文件。我们可以确认:
$ grep origin output.31117
write(1, "I'm the original process and my "..., 45) = 45
我们可以打开其他转储文件并确认它们是子进程的转储文件:
$ cat output.31118
set_robust_list(0x7f5995cc9a20, 24) = 0
getpid() = 31118
write(1, "I'm the child process and my id "..., 42) = 42
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5995cc9a10) = 31119
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5995edb210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5995edb210}, 8) = 0
sigaltstack(NULL, {ss_sp=0x167d460, ss_flags=0, ss_size=16384}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0) = ?
+++ exited with 0 +++