解释器文件

解释器文件,是一种文本文件,例如 PHP 写的解释器文件 demo.php

#!/usr/bin/php
<?php

echo 'Hello World!'

chmod u+x demo.php 赋予可执行权限,即可直接以 ./demo.php 执行。

解释器,是可执行文件 ELF,例如 /usr/bin/php

bash 进程启动过程

bash 进程是如何创建的?

  1. 直接在 centos、ubuntu等终端直接登录,由 login 服务开启 bash 进程。

  2. 通过网络方式登录到 shell 终端,由 sshd 服务开启 bash 进程。

    sshd 服务默认监听 22 端口。

进程观察的几个命令:

  1. pstree 查看 Linux 进程间的关系,显示进程树。

    pstree -ap

        ├─sshd,9408
        │   └─sshd,444836    
        │       └─bash,444875
        │           └─pstree,444893 -ap
    

    ctrl+c 产生一个中断信号,退出当前终端正在执行的进程。

    ctrl+z 把当前台进程丢到后台去暂停。

  2. strace,跟踪系统调用和信号。

    通过 strace -f -s 65500 -p 9408 -o ssh.log 追踪 pid = 9408 的 sshd 进程的系统调用,来分析 bash 进程是如何创建的。

    strace: Process 9408 attached
    strace: Process 447115 attached
    
    ...
    
    strace: Process 447151 attached
    ^Cstrace: Process 9408 detached
    strace: Process 447115 detached
    strace: Process 447143 detached
    

    ssh.log 部分内容:

    9408  select(95, [3], NULL, NULL, NULL) = 1 (in [3])
    9408  accept(3, {sa_family=AF_INET, sin_port=htons(4143), sin_addr=inet_addr("1xx.1x9.xx3.1xx")}, [128->16]) = 4
    // accept 用来接收一个 socket 客户端连接
    
    ...
    
    9408  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1fc4482210) = 447115
    // clone = fork 用来创建一个子进程
    
    447115 execve("/usr/sbin/sshd", ["/usr/sbin/sshd", "-D", "-R"], 0x55d325ea09c0 /* 7 vars */) = 0
    // execve 用来执行一个程序
    
    447115 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD <unfinished ...>
    447143 set_robust_list(0x7fa0e70b0220, 24 <unfinished ...>
    447115 <... clone resumed>, child_tidptr=0x7fa0e70b0210) = 447143
    447143 <... set_robust_list resumed>)   = 0
    // 创建子进程 447143
    
    ...
    
    447143 execve("/bin/bash", ["-bash"], 0x563bf3853680 /* 25 vars */) = 0
    // 执行 bash 程序
    
    

    a. 通过 accept 接收 ssh 客户端连接;

    b. 通过 clone 创建一个子进程;

    c. 通过 execve 执行 sshd 程序;

    d. 创建一个子进程;

    e. 执行 bash 程序。

解释器文件在 bash 中运行的过程

通过 strace -f -s 65500 -o demo_with_php.log php demo3.php 追踪 php demo3.php 形式的运行过程:

demo_with_php.log 部分内容:

// system call
505   execve("/usr/bin/php", ["php", "demo3.php"], 0x7ffdaed67dc0 /* 53 vars */) = 0

...

// 打开解释器文件
505   openat(AT_FDCWD, "demo3.php", O_RDONLY) = 3

...

// 读取解释器文件
505   read(3, "#!/usr/bin/php\n<?php\n\necho \"Hello World!\";\n", 4096) = 43

// 解释执行
505   close(3)                          = 0
505   write(1, "Hello World!", 12)      = 12
505   close(0)                          = 0

通过 strace -f -s 65500 -o demo.log php demo3.php 追踪 php demo3.php 形式的运行过程:

demo.log 部分内容:

// execve 函数在载入文件的时候,一般是当做 ELF 可执行文件载入,读取 ELF Header。
// 如果不是 ELF 文件,就当做解释器文件读取文件的第一行,得到解释器的完整路径。
596   execve("./demo3.php", ["./demo3.php"], 0x7ffd6a87e5f8 /* 53 vars */) = 0

...

// 打开解释器文件
596   openat(AT_FDCWD, "./demo3.php", O_RDONLY) = 3

...

// 解释执行
596   read(3, "#!/usr/bin/php\n<?php\n\necho \"Hello World!\";\n", 4096) = 43
596   close(3)                          = 0
596   write(1, "Hello World!", 12)      = 12
596   close(0) 

解释器文件在 bash 中运行的过程