Skip to content

Introduction to proot‐rs workflow

imlk edited this page Aug 23, 2021 · 1 revision

how does proot-rs work

CLI

This part deals with command line arguments, we use the library clap to parse the arguments, the code is in proot-rs/cli.rs

Initialize FileSystem

Before starting the first subroutine, we need to complete the initialization of the filesystem. This mainly consists of checking if the path exists and regularizing the path.

Start the First Subprocess (init Process)

Before entering the event loop, proot-rs starts the init process. Specifically, the proot-rs master process first spawns a new process by fork(), and then calls execvp() according to the file path and argument list specified in the command line arguments. At the same time, the parent process creates the first Tracee instance based on the pid of the child process and the FileSystem initialized before, and registers it in the tracee list of the current survivor.

Before calling execvp(), the child process first uses ptrace(PTRACE_TRACEME) to let the parent process trace itself.

Then it sends a SIGSTOP signal to itself, which is used to synchronize with the proot-rs tracer process. The latter needs to call ptrace(PTRACE_SETOPTIONS) at this time, to set the ptrace option.

After waiting for the main process to acknowledge this signal, the child process continues to execute execvp(), switching to execute the user-specified program.

ptrace event loop

The event loop part is the main part of the proot-rs master process, the proot-rs processes use waitpid(-1, &status, __WALL) in a loop to wait for events from all tracees, which basically fall into the following categories.

  • Exited: tracee exits actively, which generally means that the process called exit().
  • Signaled: tracee is killed by a signal
  • Stopped: tracee is suspended because a signal is about to be delivered to tracee, this is called signal-delivery-stop
  • PtraceEvent: tracee is paused because a PTRACE_EVENT_* event has occurred, this is called PTRACE_EVENT stops.
  • PtraceSyscall: tracee was paused because it issued a system call, which is called syscall-stop
  • Continued: tracee resumes from suspended state because it received the SIGCONT signal. proot-rs does not need to pay attention to this event
  • StillAlive: proot-rs does not need to pay attention to this event

For some long-ago reason, the documentation on waitpid() and ptrace() is rather obscure and developers are prone to make mistakes in these areas.

Here is a sheet that describes the meaning of the status value of the waitpid() function: https://docs.google.com/spreadsheets/d/1JAm2fOYctoluSsMfNP8Dbt7HytdfEtRPm6vtGs8Kgr8/edit# gid=0

How to exit

Normally, proot-rs will exit after all tracees have exited, which means that proot-rs may sometimes wait for a daemon to finish, even if the init process has long since finished. This is necessary because if the proot-rs master process exits before the tracee, it can cause subsequent system calls to go untranslated. In the original version of proot a --kill-on-exit option was used to force the killing of the rest of the child processes after the init process exits. However, so far proot-rs does not implement this option.

The proot-rs master process will record the exit code of the init process and use it as its own exit code when it exits. However, if it detects that the init process was killed by a signal rather than exiting normally, then the exit code will be the value of that signal plus 128. This is consistent with the behavior of Bash: https://tldp.org/LDP/abs/html/exitcodes.html

Sometimes it is also possible that a bug in proot-rs itself causes an abnormal exit, in which case the exit code is 1 and an error log will be printed.

Note that for whatever reason the main proot-rs process terminates before the tracee process, all tracees are killed to prevent accidental escape of the tracee. This is done with the option PTRACE_O_EXITKILL.