Skip to content

How to start development

imlk edited this page Aug 23, 2021 · 3 revisions

This document is intended to help later developers become quickly familiar with the project development process.

Project Structure

Our project is organized in the Cargo Workspace,

It contains two packages.

  • proot-rs The main part of the project, which is the entry point for the proot-rs application
  • loader-shim A simple loader program that is used when translating the execve() function. The package is compiled separately into a static executable as part of the proot-rs executable.

Build Dependencies

cargo

Our project is built using the cargo tool, so you must first install the Rust development kit.

cargo-make

To simplify the build, we also use cargo-make to manage the build scripts. cargo-make is very similar to make. Makefile.toml is our build script file. You can install cargo-make with the following commands:

# Install stable rust toolchain
rustup toolchain install stable
# Install cargo-make
cargo +stable install --force cargo-make

Note: We recommend using the stable toolchain to install cargo-make in order to avoid installation failures

cross (optional)

If you want to cross-compile proot-rs for other platforms, I recommend using cross to avoid some weird and complicated linker configuration (especially when compiling for the Android platform).

You can use the following command to install cross

cargo install cross

Build

To build the full proot-rs, directly run:

cargo make build

The command basically consists of the following steps:

  • Run cargo build on loader-shim package to compile the loader executable.
  • Copy the loader executable loader-shim to proot-rs/src/kernel/execve/loader-shim
  • Run cargo build on proot-rs package to build the final executable.

If you just want to build loader-shim, run.

cargo make build-loader

If you want to build the release version (which usually means executable size optimization and speed optimization) instead of the debug version, you can append the --profile=production flag to these commands above.

Note: This --profile option comes from cargo-make, which has a different meaning than the profile in cargo. And it is processed by cargo-make and will not be passed to cargo.

Cross-Compiling

Supported Targets

Cross-compiling to these targets is currently supported:

  • x86_64-unknown-linux-musl
  • x86_64-unknown-linux-gnu
  • x86_64-linux-android
  • i686-unknown-linux-musl
  • i686-unknown-linux-gnu
  • i686-linux-android
  • armv7-unknown-linux-musleabihf
  • armv7-unknown-linux-gnueabihf
  • arm-linux-androideabi
  • aarch64-unknown-linux-musl
  • aarch64-unknown-linux-gnu
  • aarch64-linux-android

Cross-Compiling with cargo

If you want to build proot-rs for other platforms, use the -CARGO_BUILD_TARGET environment variable.

For example, to compile to a 32-bit arm platform Linux device, and using glibc as dynamic linker.

  • First you need to install the appropriate target for the current rust toolchain.

    rustup target add armv7-unknown-linux-gnueabihf
  • Additional steps for the Android targets

    If you are compiling to the Android platform, you will also need to install the NDK in your environment. you can download a copy from here and unzip it.

    In addition, you need to configure the appropriate linker path in .cargo/config.toml. Since we have already configured the appropriate linker name in .cargo/config.toml, you only need to add <path-to-ndk-dir>/toolchains/llvm/prebuilt/linux-x86_64/bin/ to the PATH.

    Of course, for people who want to keep their PATH clean, you can manually modify .cargo/config.toml. For example, if you are building arm-linux-androideabi you can write

    [target.arm-linux-androideabi]
    linker = "<path-to-ndk-dir>/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang"
  • Compile.

    CARGO_BUILD_TARGET=armv7-unknown-linux-gnueabihf cargo make build

    Note: This command may fail for compiling to some targets because the linker reports some error. In this case, you may need to install an additional gcc/ clang toolchain on your computer, and specify the appropriate linker path in the .cargo/config.toml file

Cross-Compiling with cross

A better way to do cross-compilation is to use cross, which saves you the trouble of manually managing targets and configuring the NDK. cross relies on docker to run, so you need to install docker and start the docker daemon first.

We also integrate cross in the build script, which you can turn on with USE_CROSS=true.

For example, if you want to to cross-compile to arm-linux-androideabi, you can directly run

USE_CROSS=true CARGO_BUILD_TARGET=arm-linux-androideabi cargo make build

Run

Build and run proot-rs:

cargo make run -- "<args-of-proot-rs>"

Build and run release version of proot-rs:

cargo make run --profile=production -- "<args-of-proot-rs>"

Test

Setup New Rootfs for Testing

Typically, we need to specify a new rootfs path for testing proot-rs.

This script provided below can be used to create one:

# This will create a busybox-based temporary rootfs at ./rootfs/
bash scripts/mkrootfs.sh

Unit Test

Start running unit tests:

cargo make unit-test

Note: Add the option --profile=production if you want to test a release build of proot-rs

By default, By default, ./rootfs/ will be used as the root filesystem for testing purposes. But you can set the environment variable PROOT_TEST_ROOTFS to change this behavior.

export PROOT_TEST_ROOTFS="<absolute-path-to-a-rootfs>"

Integration Test

For the section on running integration tests, please read the Integration Testing documentation

Cross-Platform Testing

Note that while we can cross-compile to several different targets, we are not currently able to run tests for other targets. This is because cross relies on user-mode qemu to provide this cross-platform testing capability. But qemu cannot emulate ptrace(), which means that it is not possible to run any programs that rely on ptrace() in user-mode qemu, nor can proot-rs.

Contributing

We use git hooks to check files staged for commit to ensure the consistency of Rust code style.

Before you start, please run the following command to setup git hooks:

git config core.hooksPath .githooks

To format code manually:

cargo fmt

Some Commonly Used Crates

Our project uses a lot of third-party libraries, and here are some of the common ones

  • libc: Officially provided by rust, an FFI binding to the libc library on the platform.
  • nix: This library provides bindings to the APIs on the *nix platform, and is more Rust-friendly than libc above.
  • sc: This library provides a list of system calls, supports many platforms, and provides a way to launch primitive system calls.
  • nc: This library provides a more Rust-friendly wrapper than sc, but the project is not well-developed, so it is currently only used in unit tests for now.