diff --git a/default.nix b/default.nix index 496ddf3..2ff7811 100644 --- a/default.nix +++ b/default.nix @@ -10,6 +10,7 @@ let dhallPackages = super.dhallPackages // (callPackage ./dhall { }); genode = callPackage ./upstream { }; nova = callPackage ./NOVA { }; + solo5 = callPackage ./solo5 { }; }; toolchainOverlay = import ./toolchain-overlay; diff --git a/release.nix b/release.nix index abf4f07..1c35774 100644 --- a/release.nix +++ b/release.nix @@ -8,7 +8,7 @@ let pkgs = import ./default.nix { inherit nixpkgs; }; in { build.x86_64 = { - inherit (pkgs) nova stdenv; + inherit (pkgs) nova stdenv solo5; genode = removeAttrs pkgs.genode [ "override" "overrideDerivation" ]; }; diff --git a/solo5/default.nix b/solo5/default.nix new file mode 100644 index 0000000..0ef1550 --- /dev/null +++ b/solo5/default.nix @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: Emery Hemingway +# +# SPDX-License-Identifier: LicenseRef-Hippocratic-1.1 + +{ stdenv, buildPackages, fetchurl, llvmPackages }: + +let version = "0.6.2"; +in stdenv.mkDerivation { + pname = "solo5"; + inherit version; + + outputs = [ "out" "tests" ]; + + src = fetchurl { + url = "https://github.com/Solo5/solo5/releases/download/v${version}/solo5-v${version}.tar.gz"; + sha256 = "0wi836wzv41carkxh16ajq2gzg65ns622byyibfc5ii8lvzww0ri"; + }; + + patches = [ ./genode.patch ]; + + enableParallelBuilding = true; + + configurePhase = "HOSTCC=${buildPackages.stdenv.cc}/bin/cc sh configure.sh"; + + installPhase = '' + mkdir -p $out/bin $tests/bin + make install-opam-genode PREFIX=$out + for test in tests/*/*.genode; do + cp $test $tests/bin/solo5-`basename $test .genode` + done + ''; + + meta = with stdenv.lib; { + description = "Sandboxed execution environment."; + homepage = "https://github.com/solo5/solo5"; + license = licenses.isc; + maintainers = [ maintainers.ehmry ]; + }; + +} diff --git a/solo5/genode.patch b/solo5/genode.patch new file mode 100644 index 0000000..9cb4e28 --- /dev/null +++ b/solo5/genode.patch @@ -0,0 +1,513 @@ +diff --git a/GNUmakefile b/GNUmakefile +index e168853..9cc46a7 100644 +--- a/GNUmakefile ++++ b/GNUmakefile +@@ -100,7 +100,7 @@ install-opam-%: all opam/solo5-bindings-%.pc force-install + $(PREFIX)/lib/solo5-bindings-$* \ + $(PREFIX)/include/solo5-bindings-$*/solo5 \ + $(PREFIX)/include/solo5-bindings-$*/crt +- cp -R include/solo5 include/crt $(PREFIX)/include/solo5-bindings-$* ++ cp -R include/solo5 $(PREFIX)/include/solo5-bindings-$* + ifndef CONFIG_GENODE + cp bindings/$*/solo5_$*.o bindings/$*/solo5_$*.lds \ + $(PREFIX)/lib/solo5-bindings-$* +@@ -109,7 +109,6 @@ else + $(PREFIX)/lib/solo5-bindings-$* + endif + cp opam/solo5-bindings-$*.pc $(PREFIX)/lib/pkgconfig +- cp elftool/solo5-elftool $(PREFIX)/bin + ifdef CONFIG_HVT + cp tenders/hvt/solo5-hvt tenders/hvt/solo5-hvt-configure $(PREFIX)/bin + - [ -f tenders/hvt/solo5-hvt-debug ] && \ +diff --git a/Makefile.common b/Makefile.common +index 5ccc2ea..a7d5671 100644 +--- a/Makefile.common ++++ b/Makefile.common +@@ -31,6 +31,7 @@ include $(TOPDIR)/Makeconf + # + CC := $(MAKECONF_CC) + CFLAGS := -std=c11 -Wall -Wextra -Werror -O2 -g ++CXXLAGS += -std=gnu++17 -Wall -Wextra -Werror -O2 -g + CFLAGS += -ffreestanding -fstack-protector-strong $(MAKECONF_CFLAGS) + CPPFLAGS := -isystem $(TOPDIR)/include/crt -I$(TOPDIR)/include/solo5 + LD := $(MAKECONF_LD) +@@ -53,6 +54,12 @@ define COMPILE.c + mv -f $*.Td $*.d && touch $@ + endef + ++define COMPILE.cc ++ @echo "CXX $<" ++ $(CXX) $(DEPFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@ ++ mv -f $*.Td $*.d && touch $@ ++endef ++ + define COMPILE.S + @echo "AS $<" + $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS) -DASM_FILE -c $< -o $@ +@@ -63,7 +70,7 @@ endef + # The following variables and recpies apply to building tenders, i.e. + # artifacts built to run on the (Solo5 tender's) *host*. + # +-HOSTCC := $(MAKECONF_CC) ++HOSTCC := $(MAKECONF_HOSTCC) + HOSTCFLAGS := -Wall -Werror -std=c11 -fstack-protector-strong -O2 -g + HOSTCPPFLAGS := -I$(TOPDIR)/include/solo5 + HOSTLDFLAGS := +@@ -71,7 +78,7 @@ HOSTLDLIBS := + HOSTAR := ar + + define HOSTCOMPILE.c +- @echo "HOSTCC $<" ++ @echo "HOSTCC $(HOSTCC) $(DEPFLAGS) $(HOSTCFLAGS) $(HOSTCPPFLAGS) -c $< -o $@" + $(HOSTCC) $(DEPFLAGS) $(HOSTCFLAGS) $(HOSTCPPFLAGS) -c $< -o $@ + mv -f $*.Td $*.d && touch $@ + endef +diff --git a/bindings/GNUmakefile b/bindings/GNUmakefile +index 147c245..d3efb36 100644 +--- a/bindings/GNUmakefile ++++ b/bindings/GNUmakefile +@@ -57,7 +57,7 @@ muen_SRCS := muen/start.c $(common_SRCS) $(common_hvt_SRCS) \ + muen/muen-clock.c muen/muen-console.c muen/muen-net.c \ + muen/muen-platform_lifecycle.c muen/muen-yield.c muen/muen-sinfo.c + +-genode_SRCS := genode/stubs.c ++genode_SRCS := genode/bindings.cc + + CPPFLAGS+=-D__SOLO5_BINDINGS__ + +@@ -70,6 +70,9 @@ endif + %.o: %.c %.d + $(COMPILE.c) + ++%.o: %.cc %.d ++ $(COMPILE.cc) ++ + %.o: %.S %.d + $(COMPILE.S) + +@@ -139,9 +142,7 @@ muen/solo5_muen.o: $(muen_OBJS) + endif + + ifdef CONFIG_GENODE +- genode_OBJS := $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(genode_SRCS))) +- +-$(genode_OBJS): CFLAGS += -Wno-unused-parameter ++ genode_OBJS := $(patsubst %.cc,%.o,$(patsubst %.S,%.o,$(genode_SRCS))) + + GENODE_LDFLAGS := -nostdlib -z max-page-size=$(CONFIG_GUEST_PAGE_SIZE) -shared \ + -gc-sections --eh-frame-hdr --entry=0x0 -T genode/genode_rel.ld +diff --git a/bindings/genode/bindings.cc b/bindings/genode/bindings.cc +index eb55ffb..1c1d082 100644 +--- a/bindings/genode/bindings.cc ++++ b/bindings/genode/bindings.cc +@@ -35,10 +35,12 @@ + #include + #include + ++#define restrict __restrict__ ++ + /* Solo5 includes */ + extern "C" { + #include "../bindings.h" +-extern struct mft_note __solo5_manifest_note; ++extern struct mft1_note __solo5_mft1_note; + } + + // Compile the MFT utilities as C++ +@@ -162,7 +164,7 @@ struct Solo5::Net_device final : Device + Net_device(struct mft_entry &me, + Genode::Env &env, + Range_allocator &alloc, +- solo5_handle_set_t ready_set, ++ solo5_handle_set_t &ready_set, + solo5_handle_t handle) + : _nic(env, &alloc, NIC_BUFFER_SIZE, NIC_BUFFER_SIZE, me.name) + , _signal_handler(env.ep(), *this, &Net_device::_handle_signal) +@@ -330,7 +332,7 @@ struct Solo5::Platform + static Platform *instance; + static Device *devices[MFT_MAX_ENTRIES]; + +- struct mft &mft; ++ struct mft const &mft; + + /** + * Reference to the Genode base enviroment +@@ -376,7 +378,7 @@ struct Solo5::Platform + * + * TODO: periodic RTC synchronization + */ +- Genode::uint64_t _initial_epoch { rtc_epoch(env) }; ++ Genode::uint64_t _initial_epoch { 0 }; + + /** + * Commandline buffer +@@ -387,7 +389,8 @@ struct Solo5::Platform + /** + * Constructor + */ +- Platform(struct mft &mft, Genode::Env &env) : mft(mft), env(env) ++ Platform(struct mft const &mft, Genode::Env &env) ++ : mft(mft), env(env) + { + /** + * Acquire and attach a ROM dataspace (shared +@@ -400,7 +403,7 @@ struct Solo5::Platform + + // Copy-out the cmdline if configured. + try { cmdline = config.sub_node("cmdline").decoded_content(); } +- catch (...) { } ++ catch (Genode::Xml_node::Nonexistent_sub_node) { } + + for (solo5_handle_t i = 0U; i < MFT_MAX_ENTRIES; ++i) { + devices[i] = &invalid_device; +@@ -422,6 +425,20 @@ struct Solo5::Platform + ** Solo5 bindings ** + ********************/ + ++ solo5_time_t clock_wall() ++ { ++ if (_initial_epoch == 0) { ++ try { _initial_epoch = rtc_epoch(env); } ++ catch (Genode::Service_denied) { ++ Genode::error("Solo5: RTC time service not available. Aborting."); ++ solo5_abort(); ++ } ++ } ++ ++ return _initial_epoch * 1000000000ULL ++ + timer.curr_time().trunc_to_plain_us().value * 1000ULL; ++ } ++ + void + yield(solo5_time_t deadline_ns, solo5_handle_set_t *ready_set) + { +@@ -526,9 +543,7 @@ solo5_time_t solo5_clock_monotonic(void) + + solo5_time_t solo5_clock_wall(void) + { +- return Platform::instance->_initial_epoch * 1000000000ULL +- + Platform::instance->timer.curr_time() +- .trunc_to_plain_us().value * 1000ULL; ++ return Platform::instance->clock_wall(); + } + + +@@ -616,16 +631,19 @@ solo5_set_tls_base(uintptr_t base) + void Component::construct(Genode::Env &env) + { + /* Validate the device manifest */ +- struct mft &mft = __solo5_manifest_note.m; +- size_t mft_size = __solo5_manifest_note.h.descsz; +- if (mft_validate(&mft, mft_size) != 0) { +- Genode::error("Solo5: Built-in manifest validation failed. Aborting"); ++ const struct mft *mft; ++ size_t mft_size; ++ ++ mft_get_builtin_mft1(&__solo5_mft1_note, &mft, &mft_size); ++ ++ if (mft_validate(mft, mft_size) != 0) { ++ Genode::error("Solo5: Built-in manifest validation failed. Aborting."); + env.parent().exit(~0); + return; + } + + /* Construct a statically allocated platform object */ +- static Solo5::Platform inst(mft, env); ++ static Solo5::Platform inst(*mft, env); + Platform::instance = &inst; + + static struct solo5_start_info si { +@@ -641,12 +659,14 @@ void Component::construct(Genode::Env &env) + if (si.heap_size > 1<<20) + si.heap_size -= 1<<19; + +- /* allocate a contiguous memory region for the application */ +- Genode::Dataspace_capability heap_ds = +- env.pd().alloc(si.heap_size); ++ { ++ /* allocate a contiguous memory region for the application */ ++ Genode::Dataspace_capability heap_ds = ++ env.pd().alloc(si.heap_size); + +- /* attach into our address-space */ +- si.heap_start = env.rm().attach(heap_ds); ++ /* attach into our address-space */ ++ si.heap_start = env.rm().attach(heap_ds); ++ } + + /* block for application then exit */ + env.parent().exit(solo5_app_main(&si)); +diff --git a/configure.sh b/configure.sh +index d5afdab..ab5419f 100755 +--- a/configure.sh ++++ b/configure.sh +@@ -142,6 +142,10 @@ case ${CC_MACHINE} in + CONFIG_ARCH=x86_64 CONFIG_HOST=OpenBSD + CONFIG_GUEST_PAGE_SIZE=0x1000 + ;; ++ x86_64-*genode*) ++ CONFIG_ARCH=x86_64 CONFIG_HOST=Genode ++ CONFIG_GUEST_PAGE_SIZE=0x1000 ++ ;; + *) + die "Unsupported toolchain target: ${CC_MACHINE}" + ;; +@@ -287,6 +291,21 @@ case "${CONFIG_HOST}" in + [ "${CONFIG_ARCH}" = "x86_64" ] && CONFIG_MUEN=1 + CONFIG_GENODE= + ;; ++ Genode) ++ cc_is_clang || die "Only 'clang' is supported on Genode" ++ [ "${CONFIG_ARCH}" = "x86_64" ] || ++ die "Only 'x86_64' is supported on Genode" ++ ld_is_lld || die "Using GNU 'ld' is not supported on Genode" ++ ++ MAKECONF_CFLAGS="-mno-retpoline -nostdlibinc" ++ MAKECONF_LDFLAGS="-nopie" ++ ++ CONFIG_HVT= ++ CONFIG_SPT= ++ CONFIG_VIRTIO= ++ CONFIG_MUEN= ++ CONFIG_GENODE=1 ++ ;; + *) + die "Unsupported build OS: ${CONFIG_HOST}" + ;; +@@ -299,7 +318,7 @@ esac + # GNU make. Given the differences in quoting rules between the two + # (unable to sensibly use VAR="VALUE"), our convention is as follows: + # +-# 1. GNU make parses the entire file, i.e. all variables defined below are ++# 1. GNU make parses the entire file, i.e. all variables defined below are + # available to Makefiles. + # + # 2. Shell scripts parse the subset of *lines* starting with "CONFIG_". I.e. +@@ -323,6 +342,7 @@ CONFIG_HOST=${CONFIG_HOST} + CONFIG_GUEST_PAGE_SIZE=${CONFIG_GUEST_PAGE_SIZE} + MAKECONF_CC=${CC} + MAKECONF_LD=${LD} ++MAKECONF_HOSTCC=${HOSTCC} + CONFIG_SPT_NO_PIE=${CONFIG_SPT_NO_PIE} + EOM + +diff --git a/include/solo5/mft_abi.h b/include/solo5/mft_abi.h +index 537c7bc..c51c90e 100644 +--- a/include/solo5/mft_abi.h ++++ b/include/solo5/mft_abi.h +@@ -154,7 +154,8 @@ struct mft1_note { + * Internal alignment of (m) within struct mft1_note. Must be passed to + * elf_load_note() as note_align when loading. + */ +-#define MFT1_NOTE_ALIGN offsetof(struct { char c; struct mft m; }, m) ++struct foo { char c; struct mft m; }; ++#define MFT1_NOTE_ALIGN offsetof(struct foo, m) + + _Static_assert((offsetof(struct mft1_note, m) & (MFT1_NOTE_ALIGN - 1)) == 0, + "struct mft1_note.m is not aligned to a MFT1_NOTE_ALIGN boundary"); +diff --git a/tenders/common/mft.c b/tenders/common/mft.c +index 56024ab..7d05713 100644 +--- a/tenders/common/mft.c ++++ b/tenders/common/mft.c +@@ -37,11 +37,11 @@ int mft_validate(const struct mft *mft, size_t mft_size) + * as untrusted data. + */ + if (mft_size < sizeof(*mft)) +- return -1; ++ return __LINE__; + if (mft->version != MFT_VERSION) +- return -1; ++ return __LINE__; + if (mft->entries > MFT_MAX_ENTRIES) +- return -1; ++ return __LINE__; + /* + * mft_size must be the exact expected structure size, including the space + * required for manifest entires. +@@ -51,17 +51,17 @@ int mft_validate(const struct mft *mft, size_t mft_size) + */ + if (mft_size != (sizeof(struct mft) + + (mft->entries * sizeof(struct mft_entry)))) +- return -1; ++ return __LINE__; + /* + * The manifest must contain at least one entry, and the first entry must + * be of type MFT_RESERVED_FIRST with an empty name. + */ + if (mft->entries < 1) +- return -1; ++ return __LINE__; + if (mft->e[0].type != MFT_RESERVED_FIRST) +- return -1; ++ return __LINE__; + if (mft->e[0].name[0] != 0) +- return -1; ++ return __LINE__; + + for (unsigned i = 0; i != mft->entries; i++) { + /* +@@ -69,7 +69,7 @@ int mft_validate(const struct mft *mft, size_t mft_size) + * in the array itself. + */ + if (mft->e[i].name[MFT_NAME_MAX] != 0) +- return -1; ++ return __LINE__; + /* + * Sanitize private fields (to be used by the tender/bindings): + * +@@ -77,7 +77,7 @@ int mft_validate(const struct mft *mft, size_t mft_size) + * uninitialised). + */ + if (mft->e[i].attached != false) +- return -1; ++ return __LINE__; + } + + return 0; +diff --git a/tests/test_tls/GNUmakefile b/tests/test_tls/GNUmakefile +deleted file mode 100644 +index cc7f951..0000000 +--- a/tests/test_tls/GNUmakefile ++++ /dev/null +@@ -1,23 +0,0 @@ +-# Copyright (c) 2015-2019 Contributors as noted in the AUTHORS file +-# +-# This file is part of Solo5, a sandboxed execution environment. +-# +-# Permission to use, copy, modify, and/or distribute this software +-# for any purpose with or without fee is hereby granted, provided +-# that the above copyright notice and this permission notice appear +-# in all copies. +-# +-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +-# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +-# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +-# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +- +-include $(TOPDIR)/Makefile.common +- +-test_NAME := test_tls +- +-include ../Makefile.tests +diff --git a/tests/test_tls/manifest.json b/tests/test_tls/manifest.json +deleted file mode 100644 +index 1100fc5..0000000 +--- a/tests/test_tls/manifest.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "type": "solo5.manifest", +- "version": 1, +- "devices": [ ] +-} +diff --git a/tests/test_tls/test_tls.c b/tests/test_tls/test_tls.c +deleted file mode 100644 +index ce441b9..0000000 +--- a/tests/test_tls/test_tls.c ++++ /dev/null +@@ -1,101 +0,0 @@ +-/* +- * Copyright (c) 2015-2019 Contributors as noted in the AUTHORS file +- * +- * This file is part of Solo5, a sandboxed execution environment. +- * +- * Permission to use, copy, modify, and/or distribute this software +- * for any purpose with or without fee is hereby granted, provided +- * that the above copyright notice and this permission notice appear +- * in all copies. +- * +- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +- * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +- * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +- * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +- * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +- */ +- +-#include "solo5.h" +-#include "../../bindings/lib.c" +- +-#if defined(__x86_64__) || defined(__powerpc64__) +-/* Variant II */ +-struct tcb { +- volatile uint64_t _data; +- void *tp; +-}; +- +-#define PPC64_TLS_OFFSET 0x7000 +- +-#elif defined(__aarch64__) +-/* Variant I */ +-struct tcb { +- void *tp; +- void *pad; +- volatile uint64_t _data; +-}; +-#else +-#error Unsupported architecture +-#endif +- +-struct tcb tcb1; +-struct tcb tcb2; +- +-static void puts(const char *s) +-{ +- solo5_console_write(s, strlen(s)); +-} +- +-#if defined(__OpenBSD__) +-/* __thread is not supported in OpenBSD (this test fails on it). */ +-volatile uint64_t _data; +-#else +-__thread volatile uint64_t _data; +-#endif +- +-uint64_t __attribute__ ((noinline)) get_data() +-{ +- return _data; +-} +- +-void __attribute__ ((noinline)) set_data(uint64_t data) +-{ +- _data = data; +-} +- +-int solo5_app_main(const struct solo5_start_info *si __attribute__((unused))) +-{ +- puts("\n**** Solo5 standalone test_tls ****\n\n"); +- +-#if defined (__powerpc64__) +- tcb1.tp = (void *)&tcb1._data + PPC64_TLS_OFFSET; +- tcb2.tp = (void *)&tcb2._data + PPC64_TLS_OFFSET; +-#else +- tcb1.tp = &tcb1.tp; +- tcb2.tp = &tcb2.tp; +-#endif +- +- if (solo5_set_tls_base((uintptr_t)tcb1.tp) != SOLO5_R_OK) +- return 1; +- set_data(1); +- +- if (solo5_set_tls_base((uintptr_t)tcb2.tp) != SOLO5_R_OK) +- return 2; +- set_data(2); +- +- if (solo5_set_tls_base((uintptr_t)tcb1.tp) != SOLO5_R_OK) +- return 3; +- if (get_data() != 1) +- return 4; +- +- if (solo5_set_tls_base((uintptr_t)tcb2.tp) != SOLO5_R_OK) +- return 5; +- if (get_data() != 2) +- return 6; +- +- puts("SUCCESS\n"); +- return SOLO5_EXIT_SUCCESS; +-} diff --git a/tests/default.nix b/tests/default.nix index a1617d3..f399fc3 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -8,6 +8,7 @@ let libc = call ./libc.nix { }; log = call ./log.nix { }; signal = call ./signal.nix { }; + solo5 = call ./solo5.nix { }; }; in { pkgs ? (import ./.. { }) }: diff --git a/tests/solo5.dhall b/tests/solo5.dhall new file mode 100644 index 0000000..54393b7 --- /dev/null +++ b/tests/solo5.dhall @@ -0,0 +1,18 @@ +let Genode = env:DHALL_GENODE + +in λ(_ : {}) + → { hello = + Genode.Init.Start.defaults + ⫽ { binary = "solo5-test_hello" + , resources = { caps = 256, ram = Genode.units.MiB 16 } + , routes = [ Genode.ServiceRoute.parent "Timer" ] + , config = + Genode.Prelude.XML.text + '' + + "Hello_Solo5" + + '' + } + : Genode.Init.Start.Type + } diff --git a/tests/solo5.nix b/tests/solo5.nix new file mode 100644 index 0000000..2c9df5b --- /dev/null +++ b/tests/solo5.nix @@ -0,0 +1,15 @@ +{ pkgs, lib }: +with pkgs; + +rec { + name = "solo5"; + meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ehmry ]; + + testConfig = lib.renderDhallInit ./solo5.dhall "{=}"; + + testScript = '' + file link -s solo5.lib.so ${solo5}/lib/solo5-bindings-genode/solo5.lib.so + file link -s solo5-test_hello ${solo5.tests}/bin/solo5-test_hello + run_genode_until "child .* exited with exit value 0.*\n" 30 + ''; +}