As you have seen in the previous sessions, there are several applications already ported that you can use with Unikraft. Bu what if the application you need is not among them? We will focus today on how to port a new simple application.
We will use the GNU coreutils
applications bundle.
It contains a bunch of commonly used applications, like echo
, cp
, mv
, mkdir
, stat
, tr
, etc.
Since there are a lot of applications, we will not port all of them, we will just chose a few as an example.
echo
#We will start with porting one of the simpler application from coreutils
, echo
.
We will use the c-hello
application as a starting point.
As you noticed in the previous sessions, there are several files that we are interested in when using the build system, namely Makefile
and Makefile.uk
.
Since the echo
command will use a libc, the first step is to add Musl
to the build.
For this, we have to follow few steps:
setup.sh
script, so we can have the Musl
repository in our setup:if ! test -d workdir/libs; thenmkdir workdir/libsficheck_exists_and_create_symlink "libs/musl"
setup.sh
script again, you should have a new directory: workdir/libs/musl
.$ ls workdir/libs/muslabort.c Makefile.rules Makefile.uk.musl.errno Makefile.uk.musl.locale ......
Makefile
, change UK_LIBS
to UK_LIBS ?= $(LIBS_BASE)/musl
.
After all that, when you run make menuconfig
, you should see musl: A C standard library
under Library Configuration -->
.After all that, we can start the actual porting.
In order to not get confused between multiple files, we will create two directories, include/
and src/
, where we will copy the header and source files that we need.
$ mkdir include/$ mkdir src/
After that, we can clone the coreutils
and extract the files necessary for echo
:
$ git clone https://github.com/coreutils/coreutils coreutils$ cp coreutils/src/echo.c src/$ cp coreutils/src/*.h include/
To add the sources and header files to the buid, we must modify the Makefile.uk
file, remove the hello.c
line and add the following:
APPCHELLO_SRCS-y += $(APPCHELLO_BASE)/src/echo.cAPPCHELLO_CINCLUDES-y += -I$(APPCHELLO_BASE)/include
This tells the build system to use the echo.c
file as a source file, and the include/
directory as a path to search for header files.
After all this is done, we can try to run make
.
We will receive a lor of build errors, as expected.
We need to solve them one by one.
First, we will receive some errors about missing headers, like config.h
, timespec.h
, etc.
To solve this, we will use the very blunt approach, kill them all
.
We remove all the #include
lines from the echo.c
file, and go from there.
Note that this is obviously a bad idea for most applications.
We should find the headers and copy them, but in our case, since echo
does not need much, we can figure out what to add on the way.
For now, we leave only the stdio.h
and sys/types.h
as include statements.
We will add more of them later.
Now, we receive some errors regarding undeclared things:
/projects/unikraft/catalog-core/c-hello/src/echo.c:29:30: error: ‘false’ undeclared here (not in a function)29 | enum { DEFAULT_ECHO_TO_XPG = false };/projects/unikraft/catalog-core/c-hello/src/echo.c:37:21: error: ‘EXIT_SUCCESS’ undeclared (first use in this function)37 | affirm (status == EXIT_SUCCESS);......
This is fine, it happens because we removed all the headers, as expected. We can add some headers now.
#include <stdbool.h>#include <stdlib.h>
This way we get rid of some of the undefined symbols.
Next, we see some weird undefined functions called in the usage()
function:
/projects/unikraft/catalog-core/c-hello/src/echo.c:39:3: warning: implicit declaration of function ‘affirm’ [-Wimplicit-function-declaration]39 | affirm (status == EXIT_SUCCESS);| ^~~~~~/projects/unikraft/catalog-core/c-hello/src/echo.c:41:11: warning: implicit declaration of function ‘_’ [-Wimplicit-function-declaration]41 | printf (_("\| ^
Let's skip the usage function, we can just have the usage function exit.
voidusage (int status){exit (status);}
Finally, we only have one screen of errors to solve.
We get an LC_ALL undeclared
error, we need to #include <locale.h>
.
There are some more GNU-specific functions, like version_etc
, proper_name
, bindtextdomain
, etc.
They have to do just with the program medatada, like authors, licensing and versioning, so we can delete them (the following lines:)
initialize_main (&argc, &argv);set_program_name (argv[0]);....bindtextdomain (PACKAGE, LOCALEDIR);textdomain (PACKAGE);....version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,(char *) nullptr);
We can also remove the FALLTHROUGH
line from the main switch statement, since it is used just to silence a warning.
Finally, when we try to make
again, we only have 3 main errors left: undefined reference to STREQ
, undefined reference to close_stdout
, and implicit decalration of c_isxdigit
.
Other than that, there are the FALLTHROUGH
warnings from above.
For the STREQ
, we can search that in the coreutils
repo:
$ grep -r STREQ coreutils...coreutils/tests/df/skip-duplicates.sh: #define STREQ(a, b) (strcmp (a, b) == 0)...
This makes sense, it's just a wrapper over strcmp
, so let's copy it into our source file, after the #include
lines:
#define STREQ(a, b) (strcmp (a, b) == 0)
We also need to #include <string.h>
, since we use strcmp
.
The close_stdout
function is nowhere in the coreutils/
repo, so we can assume that it's a function that closes the standard output descriptor, since it's called at the program exit.
We add it to our source file:
void close_stdout(void){close(STDOUT_FILENO);}
For this, we need to also include unistd.h
.
Now, the only thing left is the c_isxdigit
function.
Again, it's not found in the coreutils
repository, but we can assume it's just the isxdigit
function from libc, so we replace c_isxdigit
with isxdigit
and we include the ctypes.h
header.
With all of this, out application finally build.
We can run it as usual, using qemu
:
$ qemu-system-x86_64 -nographic -kernel workdir/build/c-hello_qemu-x86_64Powered byo. .o _ _ __ _Oo Oo ___ (_) | __ __ __ _ ' _) :_oO oO ' _ `| | |/ / _)' _` | |_| _)oOo oOO| | | | | (| | | (_) | _) :_OoOoO ._, ._:_:_,\_._, .__,_:_, \___)Pan 0.19.0~9603a4ab[ 2.467667] Info: [libukboot] <boot.c @ 472> Pre-init table at 0x253148 - 0x253148[ 2.468544] Info: [libukboot] <boot.c @ 483> Constructor table at 0x253148 - 0x253148[ 2.469429] Info: [libukboot] <boot.c @ 498> Environment variables:[ 2.469967] Info: [libukboot] <boot.c @ 500> PATH=/bin[ 2.470469] Info: [libukboot] <boot.c @ 506> Calling main(1, ['workdir/build/c-hello_qemu-x86_64'])(an empty newline here)
This will lead to nothing being printed, as we would run echo
with no arguments.
To pass arguments to our application, we can use the -append
flag:
$ qemu-system-x86_64 -nographic -kernel workdir/build/c-hello_qemu-x86_64 -append "Hello from Unikraft"Powered byo. .o _ _ __ _Oo Oo ___ (_) | __ __ __ _ ' _) :_oO oO ' _ `| | |/ / _)' _` | |_| _)oOo oOO| | | | | (| | | (_) | _) :_OoOoO ._, ._:_:_,\_._, .__,_:_, \___)Pan 0.19.0~9603a4ab[ 2.470131] Info: [libukboot] <boot.c @ 472> Pre-init table at 0x253148 - 0x253148[ 2.470999] Info: [libukboot] <boot.c @ 483> Constructor table at 0x253148 - 0x253148[ 2.471872] Info: [libukboot] <boot.c @ 498> Environment variables:[ 2.472398] Info: [libukboot] <boot.c @ 500> PATH=/bin[ 2.472891] Info: [libukboot] <boot.c @ 506> Calling main(4, ['workdir/build/c-hello_qemu-x86_64', 'Hello', 'from', 'Unikraft'])Hello from Unikraft
So finally, echo
works.
pwd
#Using the same steps, try to port the pwd
command, located in pwd.c
.
Some tips:
#define nullptr NULL
typedef long int idx_t;
roubst_getcwd
, since Unikraft is posix-compatible, so it will not use those functions.x*alloc
functions to simple malloc
s.When you run the unikernel, it should print /
, as it is in the root directory.
You can use a filesystem and change the working directory to test that it works fine, you can see the nginx
example on how to run an application using a filesystem.
Feel free to ask questions, report issues, and meet new people.