Compiling "Hello World"

These instructions are intended for use on an Arm Morello board, and install hybrid ABI versions of key toolchain and utilities using pkg64. The commands will (eventually) work the same on CHERI-RISC-V, but output details and registers in GDB will differ.

Toolchain installation

You will need the Morello LLVM toolchain and Morello GDB for this exercise. To install LLVM, use the command:

pkg64 install llvm-base

If this is the first time you are using pkg64 on this system, you will be prompted to bootstrap the pkg package before the package database is downloaded and you are prompted to confirm before installing llvm and its dependencies:

root@cheribsd:~ # pkg64 install llvm-base
The package management tool is not yet installed on your system.
Do you want to fetch and install it now? [y/N]: y
Bootstrapping pkg from http://pkg.CheriBSD.org/CheriBSD:20260710:aarch64, please wait...
Verifying signature with trusted certificate pkg.cheribsd.org.2022032901... done
Installing pkg-1.20.5_1...
Extracting pkg-1.20.5_1: 100%
Updating CheriBSD repository catalogue...
Fetching meta.conf: 100%    179 B   0.2kB/s    00:01
Fetching packagesite.pkg: 100%    7 MiB   7.2MB/s    00:01
Processing entries: 100%
CheriBSD repository update completed. 26229 packages processed.
All repositories are up to date.
Updating database digests format: 100%
The following 3 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        llvm: 17,1
        llvm-base: 20240315
        llvm-morello: 17.0.d20260205_4

Number of packages to be installed: 3

The process will require 818 MiB more space.
116 MiB to be downloaded.

Proceed with this action? [y/N]: y
[1/3] Fetching llvm-base-20240315.pkg: 100%    1 KiB   1.1kB/s    00:01
[2/3] Fetching llvm-morello-17.0.d20260205_4.pkg: 100%  116 MiB  60.8MB/s    00:02
[3/3] Fetching llvm-17,1.pkg: 100%   12 KiB  11.8kB/s    00:01
Checking integrity... done (0 conflicting)
[1/3] Installing llvm-morello-17.0.d20260205_4...
[1/3] Extracting llvm-morello-17.0.d20260205_4: 100%
[2/3] Installing llvm-17,1...
[2/3] Extracting llvm-17,1: 100%
[3/3] Installing llvm-base-20240315...
[3/3] Extracting llvm-base-20240315: 100%

Note: By default FreeBSD ships with the vi and ee editors. You may wish to install the nano or vim package to access a more familiar editor. Currently only hybrid packages installable with pkg64 are available.

Source code

#include <stdio.h>

int
main(void)
{
	printf("Hello world\n");
}

Building for CheriABI

To build a CheriABI Hello World program use:

cc -g -O2 -Wall -o helloworld helloworld.c

You can verify this is a CheriABI binary with the file command:

user@cheribsd:~ $ file helloworld
helloworld: ELF 64-bit LSB pie executable, ARM aarch64, C64, CheriABI, version 1 (SYSV), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 15.0 (1500040), FreeBSD-style, with debug_info, not stripped

Building for the Benchmark ABI

To target the Benchmark ABI, add the argument -mabi=purecap-benchmark to the cc command line:

cc -g -O2 -Wall -mabi=purecap-benchmark -o helloworld helloworld.c

You can verify this is a Benchmark ABI binary with the file command:

user@cheribsd:~ $ file helloworld
helloworld: ELF 64-bit LSB pie executable, ARM aarch64, C64, CheriABI, version 1 (SYSV), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 15.0 (1500040), FreeBSD-style, pure-capability benchmark ABI, with debug_info, not stripped

Running

Run the program:

user@cheribsd:~ $ ./helloworld
Hello world

Debugging

First, if it is not already installed, install the CHERI GDB debugger:

pkg64 install gdb-cheri

You can then debug helloworld by running GDB and setting a breakpoint on main (note the output here is for the CheriABI helloworld binary, not the Benchmark ABI one):

user@cheribsd:~ $ gdb ./helloworld
GNU gdb (GDB) 14.1 [GDB v14.1.d20260511 for FreeBSD]
...
Reading symbols from ./helloworld...
(gdb) b main
Breakpoint 1 at 0x107fc: file helloworld.c, line 6.
(gdb) r
Starting program: /home/user/helloworld

Breakpoint 1, main () at helloworld.c:6
6               printf("Hello world\n");
(gdb) 

If you then step into the printf (or puts depending on optimization level) call you will see that GDB prints the capability argument with expanded information including bounds and permissions:

(gdb) s
puts (s=0x100651 [rR,0x100651-0x10065d] "Hello world")
    at /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c:54
warning: 54     /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c: No such file or directory

This information is also included in backtraces:

(gdb) bt
#0  puts (s=0x100651 [rR,0x100651-0x10065d] "Hello world")
    at /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c:54
#1  0x0000000000110808 in main () at helloworld.c:6

The argument can also be examined directly as either an integer or capability register:

(gdb) info reg x0
x0             0x100651            1050193
(gdb) info reg c0
c0             0x905c4000465d06510000000000100651 0x100651 [rR,0x100651-0x10065d]

If a capability (either in memory or a register) contains a null-derived value, it is displayed as a simple scalar value without any attributes. If a capability is not null-derived but is untagged, the string "(invalid)" is displayed after the bounds.

Capability tags in memory can also be examined by passing the "m" flag to the eXamine command. For example, compare the output of the internal FILE structure used for stdout in libc:

(gdb) p *__stdoutp
$1 = {_p = 0x0, _r = 0, _w = 0, _flags = 8, _file = 1, _bf = {_base = 0x0,
    _size = 0}, _lbfsize = 0,
  _cookie = 0x403ec7f0 [rwRW,0x403ec620-0x403ecb90],
  _close = 0x402dece1 <__sclose> [rxRE,0x401f1200-0x403c7000] (sentry),
  _read = 0x402deca1 <__sread> [rxRE,0x401f1200-0x403c7000] (sentry),
  _seek = 0x402decd9 <__sseek> [rxRE,0x401f1200-0x403c7000] (sentry),
  _write = 0x402decbd <__swrite> [rxRE,0x401f1200-0x403c7000] (sentry), _ub = {
    _base = 0x0, _size = 0}, _up = 0x0, _ur = 0, _ubuf = "\000\000",
  _nbuf = "", _lb = {_base = 0x0, _size = 0}, _blksize = 0, _offset = 0,
  _fl_mutex = 0x0, _fl_owner = 0x0, _fl_count = 0, _orientation = 0,
  _mbstate = {__mbstate8 = '\000' <repeats 127 times>, _mbstateL = 0,
    _mbstateP = 0x0 }, _flags2 = 0}
(gdb) x/16gxm __stdoutp
<CHERI Tag 0 for range [0x403ec7f0,0x403ec800)>
0x403ec7f0:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ec800,0x403ec810)>
0x403ec800:       0x0000000000000000      0x0000000000010008
<CHERI Tag 0 for range [0x403ec810,0x403ec820)>
0x403ec810:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ec820,0x403ec830)>
0x403ec820:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ec830,0x403ec840)>
0x403ec830:       0x0000000000000000      0x0000000000000000
<CHERI Tag 1 for range [0x403ec840,0x403ec850)>
0x403ec840:       0x00000000403ec7f0      0xdc5d40004b90c620
<CHERI Tag 1 for range [0x403ec850,0x403ec860)>
0x403ec850:       0x00000000402dece1      0xb05dc000b1c77c49
<CHERI Tag 1 for range [0x403ec860,0x403ec870)>
0x403ec860:       0x00000000402deca1      0xb05dc000b1c77c49

Walking up the stack frames we can see the different parts of the thread's stack in use:

(gdb) info reg csp
csp            0xdc5c40003ffdbfff0000fffffff7fe40 0xfffffff7fe40 [rwRW,0xffffbff80000-0xfffffff80000]
(gdb) f 1
#1  0x0000000000110808 in main () at helloworld.c:6
6               printf("Hello world\n");
(gdb) info reg csp
csp            0xdc5c40003ffdbfff0000fffffff7ff60 0xfffffff7ff60 [rwRW,0xffffbff80000-0xfffffff80000]

Debugging with compartmentalization

By default, CheriBSD runs without linkage-based compartmentalization enabled. For more information on this technology, and the various ways it can be enabled or disabled, see Userlevel software compartmentalization this guide. First, enable compartmentalization for this binary:

user@cheribsd:~ $ elfctl -e +cheric18n helloworld

Then verify it runs as before:

user@cheribsd:~ $ ./helloworld
Hello world

Now, run it under GDB again and this time set a breakpoint on puts directly (or printf, depending on what function you stepped into before):

user@cheribsd:~ $ gdb ./helloworld
GNU gdb (GDB) 14.1 [GDB v14.1.d20260511 for FreeBSD]
...
Reading symbols from ./helloworld...
(gdb) b puts
Function "puts" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (puts) pending.
(gdb) r
Starting program: /home/user/helloworld

Breakpoint 1, puts (s=0x100651 [rR,0x100651-0x10065d] "Hello world")
    at /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c:54
warning: 54     /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c: No such file or directory
(gdb) 

This time, if we look at a backtrace, there is an extra <cross-compartment call> frame, since the call from main to puts is between two different compartments, requiring a domain transition:

(gdb) bt
#0  puts (s=0x100651 [rR,0x100651-0x10065d] "Hello world")
    at /local/scratch/jenkins/workspace/CheriBSD-pipeline_releng_26.07/cheribsd/lib/libc/stdio/puts.c:54
#1  <cross-compartment call>, from "/helloworld" (ID: 3) to "[TCB]" (ID: 1)
#2  main () at helloworld.c:7

The info compartments command will list all compartments present in the userspace process:

(gdb) info compartments
  Id Name        Libraries
   0 [RTLD]      /libexec/ld-elf.so.1
   1 [TCB]       libc.so.7 libthr.so.3 libsys.so.7
   2 [libunwind] libgcc_s.so.1
   3 /helloworld /helloworld

We can still inspect the argument register as before, which is unchanged:

(gdb) info reg x0
x0             0x100651            1050193
(gdb) info reg c0
c0             0x905c4000465d06510000000000100651 0x100651 [rR,0x100651-0x10065d]

If we look at __stdoutp, the various sentry capabilities present before no longer point to the corresponding symbols (e.g. _close no longer points to __sclose), since these function pointers are automatically wrapped by trampolines to ensure any cross-compartment calls undergo a domain transition:

(gdb) p *__stdoutp
$1 = {_p = 0x0, _r = 0, _w = 0, _flags = 8, _file = 1, _bf = {_base = 0x0,
    _size = 0}, _lbfsize = 0,
  _cookie = 0x403ed7f0 [rwRW,0x403ed620-0x403edb90],
  _close = 0x408e752d [rxRE,0x408e7500-0x408e7668] (sentry),
  _read = 0x408e780d [rxRE,0x408e77e0-0x408e7948] (sentry),
  _seek = 0x408e7aed [rxRE,0x408e7ac0-0x408e7c28] (sentry),
  _write = 0x408e797d [rxRE,0x408e7950-0x408e7ab8] (sentry), _ub = {
    _base = 0x0, _size = 0}, _up = 0x0, _ur = 0, _ubuf = "\000\000",
  _nbuf = "", _lb = {_base = 0x0, _size = 0}, _blksize = 0, _offset = 0,
  _fl_mutex = 0x0, _fl_owner = 0x0, _fl_count = 0, _orientation = 0,
  _mbstate = {__mbstate8 = '\000' <repeats 127 times>, _mbstateL = 0,
    _mbstateP = 0x0 }, _flags2 = 0}
(gdb) x/16gxm __stdoutp
<CHERI Tag 0 for range [0x403ed7f0,0x403ed800)>
0x403ed7f0:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ed800,0x403ed810)>
0x403ed800:       0x0000000000000000      0x0000000000010008
<CHERI Tag 0 for range [0x403ed810,0x403ed820)>
0x403ed810:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ed820,0x403ed830)>
0x403ed820:       0x0000000000000000      0x0000000000000000
<CHERI Tag 0 for range [0x403ed830,0x403ed840)>
0x403ed830:       0x0000000000000000      0x0000000000000000
<CHERI Tag 1 for range [0x403ed840,0x403ed850)>
0x403ed840:       0x00000000403ed7f0      0xdc5d40005b90d620
<CHERI Tag 1 for range [0x403ed850,0x403ed860)>
0x403ed850:       0x00000000408e752d      0xb05fc000f6687500
<CHERI Tag 1 for range [0x403ed860,0x403ed870)>
0x403ed860:       0x00000000408e780d      0xb05fc000f94877e0

We can also see that different stacks are being used between the different compartments, not just parts of the same single stack:

(gdb) info reg csp
csp            0xdc5c40003456b4570000000040f44e80 0x40f44e80 [rwRW,0x40b45000-0x40f45000]
(gdb) f 2
#2  main () at helloworld.c:7
7       }
(gdb) info reg csp
csp            0xdc5e40002006a0070000000041dfffc0 0x41dfffc0 [rwRW,0x41a00000-0x41e00000]