Helper scripts
Because the command line required to compile exercises is quite unwieldy, we've created a wrapper script to help out, shown below. If you've checked out this repository it's present in tools/ccc
. The usage is:
ccc <arch> [...]
Supported architectures:
aarch64 - conventional AArch64
morello-hybrid - AArch64 Morello supporting CHERI
morello-purecap - AArch64 Morello pure-capability
riscv64 - conventional RISC-V 64-bit
riscv64-hybrid - RISC-V 64-bit supporting CHERI
riscv64-purecap - RISC-V 64-bit pure-capability
and it can be used in place of your compiler.
For the exercises in this book you will use the riscv64
and riscv64-purecap
architectures. The riscv64-hybrid
architecture instantiates appropriately annotated pointers as capabilities leaving the rest as conventional integer addresses, but is not used here.
If you have built a compiler and sysroot using cheribuild
in the default location (~/cheri
) then it should work out of the box. If you've configured a different location you can set the CHERIBUILD_SDK
environment variable to point to to the location of your SDK. Alternatively, you can set the CLANG
variable to point to the respective location.
#!/bin/sh
#
# ccc - Cross compilation script
set -e
set -u
name=$(basename "$0")
VERBOSE=${VERBOSE:-0}
QUIET=${QUIET:-0}
usage()
{
cat <<EOF
$name <arch> [...]
Supported architectures:
aarch64 - conventional AArch64
morello-hybrid - AArch64 Morello supporting CHERI
morello-purecap - AArch64 Morello pure-capability
riscv64 - conventional RISC-V 64-bit
riscv64-hybrid - RISC-V 64-bit supporting CHERI
riscv64-purecap - RISC-V 64-bit pure-capability
EOF
exit 1
}
err()
{
ret=$1
shift
echo >&2 "$@"
exit "$ret"
}
warn()
{
echo >&2 "$@"
}
debug()
{
if [ "$VERBOSE" -ne 0 ]; then
echo >&2 "$@"
fi
}
info()
{
if [ "$QUIET" -eq 0 ]; then
echo >&2 "$@"
fi
}
run()
{
debug # add space before normal multiline output
info "Running:" "$@"
"$@"
}
if [ $# -eq 0 ]; then
usage
fi
arch=$1
shift
cheri_arch_basename=${arch%%-*}
cheri_sdk_name=sdk
case $arch in
aarch64)
cheri_arch_basename=morello
cheri_sdk_name=morello-sdk
arch_flags="-target aarch64-unknown-freebsd -march=morello+noa64c"
microkit_ldflags="-lmicrokit -lutils"
;;
morello-hybrid)
cheri_sdk_name=morello-sdk
arch_flags="-target aarch64-unknown-freebsd -march=morello -Xclang -morello-vararg=new"
microkit_ldflags="-lmicrokit -lutils"
;;
morello-purecap)
cheri_sdk_name=morello-sdk
arch_flags="-target aarch64-unknown-freebsd -march=morello -mabi=purecap -Xclang -morello-vararg=new"
microkit_ldflags="-lmicrokit_purecap -lutils_purecap"
;;
riscv64)
cheri_sdk_name=cheri-alliance-sdk
arch_flags="-target riscv64-unknown-elf -march=rv64gc -mabi=lp64d -mno-relax"
microkit_ldflags="-lmicrokit -lutils"
;;
riscv64-hybrid)
cheri_sdk_name=cheri-alliance-sdk
arch_flags="-target riscv64-unknown-elf -march=rv64gc_zcherihybrid -mabi=lp64d -mno-relax"
microkit_ldflags="-lmicrokit -lutils"
;;
riscv64-purecap)
cheri_sdk_name=cheri-alliance-sdk
arch_flags="-target riscv64-unknown-elf -march=rv64gc_zcherihybrid -mabi=l64pc128d -mno-relax"
microkit_ldflags="-lmicrokit_purecap -lutils_purecap"
;;
*)
err 1 "Unsupported architecture '$arch'"
;;
esac
# Find our SDK, using the first of these that expands only defined variables:
# ${CHERIBUILD_SDK_${cheri_sdk_name}} (if that syntax worked)
# ${CHERIBUILD_SDK}
# ${CHERIBUILD_OUTPUT}/${cheri_sdk_name}
# ${CHERIBUILD_SOURCE}/output/${cheri_sdk_name}
# ~/cheri/output/${cheri_sdk_name}
SDKDIR_SOURCE=${CHERIBUILD_SOURCE:-${HOME}/cheri}
SDKDIR_OUTPUT=${CHERIBUILD_OUTPUT:-${SDKDIR_SOURCE}/output}
SDKDIR_SDK=${CHERIBUILD_SDK:-${SDKDIR_OUTPUT}/${cheri_sdk_name}}
SDKDIR=$(eval echo \${CHERIBUILD_SDK_"${cheri_arch_basename}":-})
SDKDIR=${SDKDIR:-${SDKDIR_SDK}}
enverr()
{
echo >&2 $1
echo "Perhaps set or adjust one of the following environment variables:"
for v in SOURCE OUTPUT SDK; do
echo " " CHERIBUILD_$v \(currently: \
$(eval echo \${CHERIBUILD_$v:-unset, tried \$SDKDIR_$v})\)
done
A="CHERIBUILD_SDK_${cheri_arch_basename}"
echo " " "$A" \(currently: $(eval echo \${$A:-unset, tried \$SDKDIR})\)
echo " " "$2" \(currently: $(eval echo \${$2:-unset, tried \$SDK_$2})\)
err 1 "Please check your build environment"
}
SDK_CLANG=${CLANG:-${SDKDIR}/bin/clang}
case $name in
*clang|*cc) prog="${SDK_CLANG}" ;;
*clang++|*c++) prog="${SDK_CLANG}++" ;;
*) err 1 "Unsupported program name '$name'" ;;
esac
if [ ! -x "$prog" ]; then
enverr "Target compiler '$prog' not found." "CLANG"
fi
debug "prog: $prog"
MICROKIT_SDK=${MICROKIT_SDK:-${SDKDIR}/baremetal/baremetal-riscv64-zpurecap/microkit-sdk-2.0.1-dev}
if [ ! -d "$MICROKIT_SDK" ]; then
enverr "Microkit '$MICROKIT_SDK' does not exist." "MICROKIT_SDK"
fi
debug "microkit: $MICROKIT_SDK"
debug "arch_flags: $arch_flags"
debug_flags="-g"
debug "debug_flags: $debug_flags"
opt_flags="-O2"
debug "opt_flags: $opt_flags"
microkit_flags="-L'$MICROKIT_SDK/board/qemu_virt_riscv64/cheri/lib' -I'$MICROKIT_SDK/board/qemu_virt_riscv64/cheri/include' -Tmicrokit.ld -nostdlib -ffreestanding"
debug "microkit_flags: $microkit_flags"
linker_flags="-fuse-ld=lld"
debug "linker_flags: $linker_flags"
diag_flags="-Wall -Wcheri"
debug "diag_flags: $diag_flags"
all_flags="$arch_flags $debug_flags $opt_flags $linker_flags $diag_flags $microkit_flags $microkit_ldflags"
all_flags_rev=
# shellcheck disable=SC2086 # intentional
eval 'for flag in '$all_flags'; do
all_flags_rev="'"'"'$flag'"'"'${all_flags_rev:+ $all_flags_rev}"
done'
# shellcheck disable=SC2086 # intentional
eval 'for flag in '$all_flags_rev'; do
set -- "$flag" "$@"
done'
run "$prog" "$@"
The second script is to generate a bootable image and run it on QEMU; it's present in tools/run_qemu
. The usage is:
run_qemu <image.elf | image.img>
If you pass it an ELF file generated by ccc
, it will wrap it, along with the CHERI-seL4 kernel, CHERI-Microkit libraries, loader, monitor, etc. to give you a bootable image and run it directly on QEMU by passing it as a -kernel image. The following is the script's content:
#!/bin/sh
set -e
# --- Configuration ---
# Find path to this script and to gen_image
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
GEN_IMAGE="$SCRIPT_DIR/gen_image"
cheri_sdk_name=cheri-alliance-sdk
# Setup SDK path
SDKDIR_SOURCE=${CHERIBUILD_SOURCE:-$HOME/cheri}
SDKDIR_OUTPUT=${CHERIBUILD_OUTPUT:-$SDKDIR_SOURCE/output}
SDKDIR_SDK=${CHERIBUILD_SDK:-$SDKDIR_OUTPUT/${cheri_sdk_name}}
SDKDIR=${SDKDIR:-$SDKDIR_SDK}
QEMU_BIN="$SDKDIR/bin/qemu-system-riscv64cheri"
# Default BIOS path, relative to SDKDIR
BIOS="$SDKDIR/cheri-alliance-opensbi/riscv64/share/opensbi/l64pc128/generic/firmware/fw_jump.elf"
# --- Input Parsing ---
if [ $# -lt 1 ]; then
echo "Usage: $0 <image.elf | image.img>"
exit 1
fi
INPUT="$1"
shift # Remaining args are passed to QEMU
QEMU_EXTRA_ARGS="$@"
# --- ELF detection using 'file' ---
FILE_TYPE=$(file -b "$INPUT")
case "$FILE_TYPE" in
*"ELF "*)
echo "Detected ELF binary. Generating image..."
"$GEN_IMAGE" "$INPUT"
KERNEL_IMAGE="loader.img"
;;
*)
echo "Detected non-ELF image. Using it directly."
KERNEL_IMAGE="$INPUT"
;;
esac
# --- Run QEMU ---
CMD="$QEMU_BIN -M virt -cpu codasip-a730,cheri_levels=2 -smp 1 -serial pty -m 2G -nographic -bios \"$BIOS\" -kernel \"$KERNEL_IMAGE\" $QEMU_EXTRA_ARGS"
echo "Running QEMU command:"
echo "$CMD"
eval "$CMD"
This run_qemu
script uses another gen_image
script that generates a bootable CHERI-Microkit image as shown below:
#!/bin/sh
#
# gen_image - Generate bootable Microkit image script
set -e
set -u
name=$(basename "$0")
VERBOSE=${VERBOSE:-0}
QUIET=${QUIET:-0}
# Print usage information
usage() {
echo "Usage: $0 [-o output_image] input1 [input2 ...]"
echo ""
echo " -o output_image Optional output image name (default: loader.img)"
echo " inputN One or more input ELF (or binary) files"
echo ""
echo "Example:"
echo " $0 -o myos.img hello foo bar"
exit 1
}
err()
{
ret=$1
shift
echo >&2 "$@"
exit "$ret"
}
warn()
{
echo >&2 "$@"
}
debug()
{
if [ "$VERBOSE" -ne 0 ]; then
echo >&2 "$@"
fi
}
info()
{
if [ "$QUIET" -eq 0 ]; then
echo >&2 "$@"
fi
}
run()
{
debug # add space before normal multiline output
info "Running:" "$@"
"$@"
}
if [ $# -eq 0 ]; then
usage
fi
cheri_sdk_name=cheri-alliance-sdk
# Find our SDK, using the first of these that expands only defined variables:
# ${CHERIBUILD_SDK_${cheri_sdk_name}} (if that syntax worked)
# ${CHERIBUILD_SDK}
# ${CHERIBUILD_OUTPUT}/${cheri_sdk_name}
# ${CHERIBUILD_SOURCE}/output/${cheri_sdk_name}
# ~/cheri/output/${cheri_sdk_name}
SDKDIR_SOURCE=${CHERIBUILD_SOURCE:-${HOME}/cheri}
SDKDIR_OUTPUT=${CHERIBUILD_OUTPUT:-${SDKDIR_SOURCE}/output}
SDKDIR_SDK=${CHERIBUILD_SDK:-${SDKDIR_OUTPUT}/${cheri_sdk_name}}
SDKDIR=${SDKDIR:-${SDKDIR_SDK}}
enverr()
{
echo >&2 $1
echo "Perhaps set or adjust one of the following environment variables:"
for v in SOURCE OUTPUT SDK; do
echo " " CHERIBUILD_$v \(currently: \
$(eval echo \${CHERIBUILD_$v:-unset, tried \$SDKDIR_$v})\)
done
err 1 "Please check your build environment"
}
SDK_MICROKIT=${CLANG:-${SDKDIR}/bin/clang}
MICROKIT_SDK=${MICROKIT_SDK:-${SDKDIR}/baremetal/baremetal-riscv64-zpurecap/microkit-sdk-2.0.1-dev}
if [ ! -d "$MICROKIT_SDK" ]; then
enverr "Microkit '$MICROKIT_SDK' does not exist." "MICROKIT_SDK"
fi
debug "microkit: $MICROKIT_SDK"
MICROKIT_TOOL=${MICROKIT_SDK}/bin/microkit
debug "MICROKIT_TOOL: $MICROKIT_TOOL"
# Parse args
OUTPUT_FILE="generated.system"
IMAGE_NAME="loader.img"
INPUT_FILES=""
SEARCH_PATH="$(pwd)"
while [ $# -gt 0 ]; do
case "$1" in
-o)
shift
[ $# -eq 0 ] && echo "Error: -o requires an argument" && usage
IMAGE_NAME="$1"
;;
-*)
echo "Error: Unknown option: $1"
usage
;;
*)
INPUT_FILES="$INPUT_FILES $1"
;;
esac
shift
done
[ -z "$INPUT_FILES" ] && echo "Error: No input files provided" && usage
# Generate XML
{
echo '<?xml version="1.0" encoding="UTF-8"?>'
echo '<system>'
for file in $INPUT_FILES; do
base=$(basename "$file")
echo " <protection_domain name=\"$base\">"
echo " <program_image path=\"$base\" />"
echo " </protection_domain>"
done
echo '</system>'
} > "$OUTPUT_FILE"
echo "Generated $OUTPUT_FILE from input files:$INPUT_FILES"
echo "Running Microkit tool to generate image: $IMAGE_NAME"
echo "$MICROKIT_TOOL $OUTPUT_FILE -o $IMAGE_NAME --search-path $SEARCH_PATH"
"$MICROKIT_TOOL" "$OUTPUT_FILE" -o "$IMAGE_NAME" --search-path "$SEARCH_PATH" --board "qemu_virt_riscv64" --config "cheri"
Thus, over these exercises, you'll usually be using mostly using just two scripts (given you either include them in your $PATH, or use relative/absolute paths when running them):
# ccc riscv64-purecap exercise_c_files.c -o exercise.elf
# run_qemu exercise.elf