In this session we look into running applications using the binary compatibility layer as well as understanding the inner workings of the system call shim layer.
One of the obstacles when trying to use Unikraft is the porting effort of new applications. This process can be made painless through the use of Unikraft’s binary compatibility layer. Binary compatibility is the possibility to take pre-built Linux ELF binaries and run them on top of Unikraft. This is done without any porting effort while maintaining the benefits of Unikraft: reduced memory footprint, high degree of configurability of library components.
For this, Unikraft must provide a similar ABI (Application Binary Interface) with the Linux kernel. This means that Unikraft has to provide a similar system call interface that Linux kernel provides, a POSIX compatible interface. For this, the system call shim layer (also called syscall shim) was created. The system call shim layer provides Linux-style mappings of system call numbers to actual system call handler functions.
01. The Process of Loading and Running an Application with Binary Compatibility
For Unikraft to achieve binary compatibility there are two main objectives that need to be met:
- The ability to pass the Linux ELF binary to Unikraft at boot time.
- The ability to load the passed ELF binary into memory and jump to its entry point.
The dominant format for executables is the Executable and Linkable File format (ELF), so, in order to run executables we need an ELF loader. The job of the ELF Loader is to load the executable into the main memory. It does so by reading the program headers located in the ELF formatted executable and acting accordingly.
As an overview of the whole process, when we want to run an application on Unikraft using binary compatibility, the first step is to pass the executable file to the unikernel as an initial ram disk. Once the unikernel gets the executable, it reads the executable segments and loads them accordingly. After the program is loaded, the last step is to jump to its entry point and start executing.
The unikernel image is the
This application parses the ELF file and then loads it accordingly.
It’s a custom application developed for Unikraft.
We require PIE (position-independent executable) ELFs. This is fine, as default Linux executables are built as PIE.
We have collected PIE executables in:
dynamic-appsrepository - storing dynamically-linked executables
static-pie-appsrepository - storing statically-linked executables
SummaryThe binary compatibility layer is a very important part of the Unikraft unikernel. It helps us run applications that were not build for Unikraft while, at the same time, keeps the classic benefits of Unikraft: speed, security and small memory footprint.
To easily setup, build and run Linux ELFs with
app-elfloader, best way is to use the
scripts repository on your machine to get started.
$ git clone https://github.com/unikraft-upb/scripts $ cd scripts/ $ cd make-based/app-elfloader/ $ ./do.sh setup $ ./do.sh run 'run' command requires target application as argument Target applications: helloworld_static server_static helloworld_go_static server_go_static helloworld_cpp_static helloworld_rust_static_musl helloworld_rust_static_gnu nginx_static redis_static sqlite3 bc_static gzip_static helloworld server helloworld_go server_go helloworld_cpp helloworld_rust nginx redis sqlite3 bc gzip $ ./do.sh run helloworld [...] # many messages $ ./do.sh run sqlite3 [...] # many messages
The last commands run the dynamic versions of
sqlite3 applications, the ones in the
There is a lot of output because, by default, a pre-build version of
app-elfloader is being used, with debugging enabled.
01. Run Binary Applications
Run as many executables as possible from the list of applications listed by the command:
$ ./do.sh run 'run' command requires target application as argument Target applications: helloworld_static server_static helloworld_go_static server_go_static helloworld_cpp_static helloworld_rust_static_musl helloworld_rust_static_gnu nginx_static redis_static sqlite3 bc_static gzip_static helloworld server helloworld_go server_go helloworld_cpp helloworld_rust nginx redis sqlite3 bc gzip
do.sh run we ran the prebuilt images from the
Let’s now also build
Run the following commands:
$ ./do.sh clean $ ./do.sh setup $ ./do.sh build $ ./do.sh run_built ...
In the last command, replace
... with the name of the application you need to run.
If you want to remove all the pesky debugging information, use
setup_plain, such as the commands below:
$ ./do.sh clean $ ./do.sh setup_plain $ ./do.sh build $ ./do.sh run_built ...
If, on the other side, you want to have more debugging information, use
setup_debug, such as the commands below:
$ ./do.sh clean $ ./do.sh setup_debug $ ./do.sh build $ ./do.sh run_built ...
03. Doing It Manually
Let’s see what happens behind the scenes.
.../scripts/make-based/app-elfloader$ cd ../../workdir/apps/run-app-elfloader/ .../workdir/apps/run-app-elfloader$ ls app-elfloader_kvm-x86_64* app-elfloader_kvm-x86_64_full-debug.dbg* debug.sh* out/ rootfs/ run.sh* app-elfloader_kvm-x86_64_full-debug* app-elfloader_kvm-x86_64_plain* defaults README.md run_app.sh* utils/
Follow the instructions in the
README.md file to run as many applications as possible directly.
That means through the use of the
04. Create your Own Application
Create your own application or get an existing application, build it and run it in binary compatability mode.
See the existing examples in the
dynamic-apps repository or the