Repository: CHERIoT-Platform/cheriot-rtos (language: cpp)

Issue:
Using == on std::string causes undefined symbol: __library_export_libcalls_memcmp
When I use `==` or `compare` between objects of type `std::string` the linker complains as follows:
```
error: ld.lld: error: undefined symbol: __library_export_libcalls_memcmp
>>> referenced by hello.cc
>>>               build/cheriot/cheriot/debug/hello.compartment:()
```

I get this error with the following minimal example.
hello.cc:
```
#include <compartment.h>
#include <debug.hh>
#include <string>

using Debug = ConditionalDebug<true, "Hello world compartment">;

/// Thread entry point.
void __cheri_compartment("hello") say_hello()
{
	std::string first = "first";
	std::string second = "second";
	if (first == second) {
		Debug::log("Equal!");
	}
}
```
xmake.lua:
```
set_project("CHERIoT Hello World")
sdkdir = "../../sdk"
includes(sdkdir)
set_toolchains("cheriot-clang")

option("board")
    set_default("sail")

compartment("hello")
    add_deps("freestanding", "debug", "string")
    add_files("hello.cc")

firmware("hello_world")
    add_deps("hello")
    on_load(function(target)
        target:values_set("board", "$(board)")
        target:values_set("threads", {
            {
                compartment = "hello",
                priority = 1,
                entry_point = "say_hello",
                stack_size = 0x300,
                trusted_stack_frames = 3
            }
        }, {expand = false})
    end)
```



Discussion / hints:
This may be an issue with the compiler. I think we fixed something similar for `memset` and / or `memcpy`. Let me pull in @resistor .
Memcmp is exported with the mangled name. We should probably just stop doing that and export them h mangled, as we do with the atomics.
Is this what you had in mind? https://github.com/resistor/llvm-project-1/tree/memcmp
I think so, there’s also some stuff in the back end that explicitly handles these (memcpy is expanded multiple times in LLVM, we need to turn it into a libcall if it’s expanded late and that also includes mangling).

It looks as if we’ve missed one case for memcmp, and I suspect it’s easier to just export our freestanding lib calls with unmangled names than to keep fixing things in the compiler.

As a transition thing, I think we can modify the freestanding lib to export the symbols under both names, then eventually remove the mangled one.
Thank you all for the quick triage!

The patched compiler solves the issue, though I'd like to know if @davidchisnall's freestanding lib patch would be easier in my use case.

I'm using CHERIoT to create a project for students, which they can then work on in the devcontainer.
Replacing the compiler in the devcontainer is fine on x86, but I'd like an image for ARM as well and I'm not fond of compiling clang through qemu...

Do you have any advice?
The devcontainer has an image for ARM64 and once a fix is merged we'll rebuild it. Will that work?

Exciting to hear you're using CHERIoT with students. Don't hesitate to get touch if you need any more help!
I think we'll do the freestanding lib patch soon and then slowly migrate the compiler.  Please can you test #370, it should allow the old or new compiler to work.  We can gradually then migrate to not mangling these symbols.

A few other folks are interested in projects for student, it would be great if you could join [the Signal chat](https://signal.group/#CjQKIElxAs3t3MUEMOEmQEuMHRK4rErUk2xVeFzjAjFXAShzEhCK9qQwEMFKGLGZnCjrQ7zm) and share what you're working on!
The freestanding lib patch works as expected with the unpatched compiler, so thank you for that!
I'll use this patch going forward and let you know if I encounter issues.

With the patched compiler I get duplicate symbols for mem*:
```
[ 82%]: linking library freestanding.library
/cheriot-tools/bin/ld.lld --script=/workspaces/cheriot-rtos/sdk/library.ldscript --compartment --gc-sections --relax -o build/cheriot/cheriot/debug/freestanding.library build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/compat.S.o build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memcmp.c.o build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memcpy.c.o build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memset.c.o
ld.lld: error: duplicate symbol: __library_export_libcalls_memset
>>> defined at build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/compat.S.o:(__library_export_libcalls_memcmp)
>>> defined at __cpp_memset.c.c
>>>            build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memset.c.o:(.compartment_exports+0x0)

ld.lld: error: duplicate symbol: __library_export_libcalls_memcmp
>>> defined at build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/compat.S.o:(__library_export_libcalls_memcmp)
>>> defined at __cpp_memcmp.c.c
>>>            build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memcmp.c.o:(.compartment_exports+0x0)

ld.lld: error: duplicate symbol: __library_export_libcalls_memcpy
>>> defined at build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/compat.S.o:(__library_export_libcalls_memcmp)
>>> defined at __cpp_memcpy.c.c
>>>            build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memcpy.c.o:(.compartment_exports+0x0)

ld.lld: error: duplicate symbol: __library_export_libcalls_memmove
>>> defined at build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/compat.S.o:(__library_export_libcalls_memcmp)
>>> defined at __cpp_memcpy.c.c
>>>            build/.objs/freestanding/cheriot/cheriot/debug/__/__/sdk/lib/freestanding/memcpy.c.o:(.compartment_exports+0x4)
```

Good to hear other people are using CHERIoT in education.
Once I've finished the assignment I'll share something about it in the Signal group!
Ah, yes, that will cause problems.  I'll add the other bit of the patch to make it explicitly set symbol names and then it should be agnostic to the compiler.  Thanks!
Please can you test the updated version of #370?  This should now work with both versions of the compiler.

Fix patch:
diff --git a/sdk/include/string.h b/sdk/include/string.h
index 965a02b6a..2dd75f1b7 100644
--- a/sdk/include/string.h
+++ b/sdk/include/string.h
@@ -6,21 +6,27 @@
 #include <stddef.h>
 #include <stdint.h>
 
-int __cheri_libcall   memcmp(const void *str1, const void *str2, size_t count);
-void *__cheri_libcall memcpy(void *dest, const void *src, size_t n);
-void *__cheri_libcall memset(void *, int, size_t);
-void *__cheri_libcall memmove(void *dest, const void *src, size_t n);
-void *__cheri_libcall memchr(const void *, int, size_t);
-void *__cheri_libcall memrchr(const void *, int, size_t);
-size_t __cheri_libcall      strlen(const char *str);
-int __cheri_libcall         strncmp(const char *s1, const char *s2, size_t n);
-char *__cheri_libcall       strncpy(char *dest, const char *src, size_t n);
-int __cheri_libcall         strcmp(const char *s1, const char *s2);
-char *__cheri_libcall       strnstr(const char *haystack,
-                                    const char *needle,
-                                    size_t      haystackLength);
-char *__cheri_libcall       strchr(const char *s, int c);
-size_t __cheri_libcall      strlcpy(char *dest, const char *src, size_t n);
+int __cheri_libcall    memcmp(const void *str1,
+                              const void *str2,
+                              size_t      count) __asm__("memcmp");
+void *__cheri_libcall  memcpy(void       *dest,
+                              const void *src,
+                              size_t      n) __asm__("memcpy");
+void *__cheri_libcall  memset(void *, int, size_t) __asm__("memset");
+void *__cheri_libcall  memmove(void       *dest,
+                               const void *src,
+                               size_t      n) __asm__("memmove");
+void *__cheri_libcall  memchr(const void *, int, size_t);
+void *__cheri_libcall  memrchr(const void *, int, size_t);
+size_t __cheri_libcall strlen(const char *str);
+int __cheri_libcall    strncmp(const char *s1, const char *s2, size_t n);
+char *__cheri_libcall  strncpy(char *dest, const char *src, size_t n);
+int __cheri_libcall    strcmp(const char *s1, const char *s2);
+char *__cheri_libcall  strnstr(const char *haystack,
+                               const char *needle,
+                               size_t      haystackLength);
+char *__cheri_libcall  strchr(const char *s, int c);
+size_t __cheri_libcall strlcpy(char *dest, const char *src, size_t n);
 
 /**
  * Explicit bzero is a memset variant that the compiler is not permitted to
diff --git a/sdk/lib/freestanding/compat.S b/sdk/lib/freestanding/compat.S
new file mode 100644
index 000000000..b6b90c7b5
--- /dev/null
+++ b/sdk/lib/freestanding/compat.S
@@ -0,0 +1,31 @@
+// Copyright CHERIoT Contributors.
+// SPDX-License-Identifier: MIT
+
+/**
+ * This file contains compatibility aliases for exporting the freestanding
+ * library functions as both mangled and unmangled symbols.
+ */
+
+/**
+ * Given a function named `function_name`, export it as a library function
+ * named `export_function_name`.
+ */
+.macro EXPORT_COMPATIBILITY_ALIAS export_function_name, function_name, flags
+	.section .compartment_exports,"aR",@progbits
+	.type    __library_export_libcalls_\export_function_name\(),@object
+	.global  __library_export_libcalls_\export_function_name\()
+    .p2align 2
+  __library_export_libcalls_\export_function_name\():
+	.half \function_name - __compartment_pcc_start
+	// Stack usage: Ignored for library exports
+	.byte 0
+	// Flags, only interrupt state is used for library exports, 0 is inherited
+	.byte \flags
+	.size __library_export_libcalls_\export_function_name, 40
+	.previous
+.endm
+
+EXPORT_COMPATIBILITY_ALIAS _Z6memcmpPKvS0_j, memcmp, 3
+EXPORT_COMPATIBILITY_ALIAS _Z6memcpyPvPKvj, memcpy, 3
+EXPORT_COMPATIBILITY_ALIAS _Z6memsetPvij, memset, 2
+EXPORT_COMPATIBILITY_ALIAS _Z7memmovePvPKvj, memmove, 3
diff --git a/sdk/lib/freestanding/memcmp.c b/sdk/lib/freestanding/memcmp.c
index 8f3eef9d6..9a3a5185e 100644
--- a/sdk/lib/freestanding/memcmp.c
+++ b/sdk/lib/freestanding/memcmp.c
@@ -34,11 +34,12 @@
 
 #include <cdefs.h>
 #include <stddef.h>
+#include <string.h>
 
 /*
  * Compare memory regions.
  */
-int __cheri_libcall
+int
 memcmp(const void *s1, const void *s2, size_t n)
 {
 	if (n != 0) {
diff --git a/sdk/lib/freestanding/memcpy.c b/sdk/lib/freestanding/memcpy.c
index 5b7c7915a..80e880a05 100644
--- a/sdk/lib/freestanding/memcpy.c
+++ b/sdk/lib/freestanding/memcpy.c
@@ -45,6 +45,7 @@
 
 #include <cdefs.h>
 #include <stddef.h>
+#include <string.h>
 
 typedef void *word;
 
@@ -54,7 +55,7 @@ _Static_assert((wsize & (wsize - 1)) == 0);
 
 #define wmask (wsize - 1)
 
-void *__cheri_libcall memcpy(void *dst0, const void *src0, size_t length)
+void *memcpy(void *dst0, const void *src0, size_t length)
 {
 	char *      dst = dst0;
 	const char *src = src0;
@@ -119,7 +120,7 @@ void *__cheri_libcall memcpy(void *dst0, const void *src0, size_t length)
 	return (dst0);
 }
 
-void *__cheri_libcall memmove(void *dst0, const void *src0, size_t length)
+void *memmove(void *dst0, const void *src0, size_t length)
 {
 	return memcpy(dst0, src0, length);
 }
diff --git a/sdk/lib/freestanding/memset.c b/sdk/lib/freestanding/memset.c
index c9e4c0a31..8945d5c10 100644
--- a/sdk/lib/freestanding/memset.c
+++ b/sdk/lib/freestanding/memset.c
@@ -34,11 +34,12 @@
 
 #include <cdefs.h>
 #include <stddef.h>
+#include <string.h>
 
 #define wsize sizeof(unsigned int)
 #define wmask (wsize - 1)
 
-void *__cheri_libcall memset(void *dst0, int c0, size_t length)
+void *memset(void *dst0, int c0, size_t length)
 {
 	size_t         t;
 	unsigned int   c;
diff --git a/sdk/lib/freestanding/xmake.lua b/sdk/lib/freestanding/xmake.lua
index 62f10fb8f..6daf6c5ce 100644
--- a/sdk/lib/freestanding/xmake.lua
+++ b/sdk/lib/freestanding/xmake.lua
@@ -1,2 +1,2 @@
 library("freestanding")
-  add_files("memcmp.c", "memcpy.c", "memset.c")
+  add_files("memcmp.c", "memcpy.c", "memset.c", "compat.S")


Test patch:
diff --git a/tests/misc-test.cc b/tests/misc-test.cc
index dc09d775c..2de724a1a 100644
--- a/tests/misc-test.cc
+++ b/tests/misc-test.cc
@@ -10,211 +10,235 @@
 
 using namespace CHERI;
 
-/**
- * Test timeouts.
- *
- * This test checks the following:
- *
- * - A timeout of zero would not block.
- * - `elapse` saturates values, i.e., a `remaining` value of zero will still be
- *   zero after a call to `elapse`, and an `elapsed` value of `UINT32_MAX`
- *   would still be `UINT32_MAX` after a call to `elapse`.
- * - An unlimited timeout is really unlimited, i.e., a call to `elapse` does
- *   not modify its `remaining` value, which blocks.
- */
-void check_timeouts()
+namespace
 {
-	debug_log("Test timeouts.");
-
-	// Create a zero timeout.
-	Timeout t{0};
-	// Ensure that a zero timeout does not block.
-	TEST(!t.may_block(), "A zero timeout should not block.");
-
-	// Create a zero timer with maximum elapsed time.
-	t = Timeout{UINT32_MAX /* elapsed */, 0 /* remaining */};
-	// Ensure that a call to `elapse` saturates both `elapsed` and
-	// `remaining`.
-	t.elapse(42);
-	TEST(t.remaining == 0,
-	     "`elapse` does not saturate the `remaining` value of a zero timer.");
-	TEST(t.elapsed == UINT32_MAX,
-	     "`elapse` does not saturate the `elapsed` value of a zero timer.");
-
-	// Create an unlimited timeout.
-	t = Timeout{UnlimitedTimeout /* remaining */};
-	// Ensure that a call to `elapse` does not modify the `remaining` value
-	// of the unlimited timeout.
-	t.elapse(42);
-	TEST(t.remaining == UnlimitedTimeout,
-	     "`elapse` alters the remaining value of an unlimited timeout.");
-	// Ensure that an unlimited timeout blocks.
-	TEST(t.may_block(), "An unlimited timeout should block.");
-}
-
-/**
- * Test memchr.
- *
- * This test checks the following:
- *
- * - memchr finds the first occurrence of the character when it is present
- *   (test for different values, particularly the first and the last one).
- * - memchr returns NULL when the string does not contain the character (test
- *   for non-NULL terminated string).
- * - memchr does not stop at \0 characters.
- * - memchr returns NULL for 0-size pointers.
- */
-void check_memchr()
-{
-	debug_log("Test memchr.");
 
-	char string[] = {'C', 'H', 'E', 'R', 'R', 'I', 'E', 'S'};
-
-	TEST(memchr(string, 'C', sizeof(string)) == &string[0],
-	     "memchr must return the first occurence of the character.");
-	TEST(memchr(string, 'R', sizeof(string)) == &string[3],
-	     "memchr must return the first occurence of the character.");
-	TEST(memchr(string, 'S', sizeof(string)) == &string[7],
-	     "memchr must return the first occurence of the character.");
-	TEST(memchr(string, 'X', sizeof(string)) == NULL,
-	     "memchr must return NULL when a character is not present.");
+	/**
+	 * Test timeouts.
+	 *
+	 * This test checks the following:
+	 *
+	 * - A timeout of zero would not block.
+	 * - `elapse` saturates values, i.e., a `remaining` value of zero will still
+	 * be zero after a call to `elapse`, and an `elapsed` value of `UINT32_MAX`
+	 *   would still be `UINT32_MAX` after a call to `elapse`.
+	 * - An unlimited timeout is really unlimited, i.e., a call to `elapse` does
+	 *   not modify its `remaining` value, which blocks.
+	 */
+	void check_timeouts()
+	{
+		debug_log("Test timeouts.");
+
+		// Create a zero timeout.
+		Timeout t{0};
+		// Ensure that a zero timeout does not block.
+		TEST(!t.may_block(), "A zero timeout should not block.");
+
+		// Create a zero timer with maximum elapsed time.
+		t = Timeout{UINT32_MAX /* elapsed */, 0 /* remaining */};
+		// Ensure that a call to `elapse` saturates both `elapsed` and
+		// `remaining`.
+		t.elapse(42);
+		TEST(
+		  t.remaining == 0,
+		  "`elapse` does not saturate the `remaining` value of a zero timer.");
+		TEST(t.elapsed == UINT32_MAX,
+		     "`elapse` does not saturate the `elapsed` value of a zero timer.");
+
+		// Create an unlimited timeout.
+		t = Timeout{UnlimitedTimeout /* remaining */};
+		// Ensure that a call to `elapse` does not modify the `remaining` value
+		// of the unlimited timeout.
+		t.elapse(42);
+		TEST(t.remaining == UnlimitedTimeout,
+		     "`elapse` alters the remaining value of an unlimited timeout.");
+		// Ensure that an unlimited timeout blocks.
+		TEST(t.may_block(), "An unlimited timeout should block.");
+	}
 
-	char stringWithNull[] = {'Y', 'E', 'S', '\0', 'N', 'O', '\0'};
+	/**
+	 * Test memchr.
+	 *
+	 * This test checks the following:
+	 *
+	 * - memchr finds the first occurrence of the character when it is present
+	 *   (test for different values, particularly the first and the last one).
+	 * - memchr returns NULL when the string does not contain the character
+	 * (test for non-NULL terminated string).
+	 * - memchr does not stop at \0 characters.
+	 * - memchr returns NULL for 0-size pointers.
+	 */
+	void check_memchr()
+	{
+		debug_log("Test memchr.");
 
-	TEST(memchr(stringWithNull, 'N', sizeof(stringWithNull)) ==
-	       &stringWithNull[4],
-	     "memchr must not stop at NULL characters.");
+		char string[] = {'C', 'H', 'E', 'R', 'R', 'I', 'E', 'S'};
 
-	TEST(memchr(stringWithNull, 'N', 0) == NULL,
-	     "memchr must return NULL for zero-size pointers.");
-}
+		TEST(memchr(string, 'C', sizeof(string)) == &string[0],
+		     "memchr must return the first occurence of the character.");
+		TEST(memchr(string, 'R', sizeof(string)) == &string[3],
+		     "memchr must return the first occurence of the character.");
+		TEST(memchr(string, 'S', sizeof(string)) == &string[7],
+		     "memchr must return the first occurence of the character.");
+		TEST(memchr(string, 'X', sizeof(string)) == NULL,
+		     "memchr must return NULL when a character is not present.");
 
-/**
- * Test memrchr.
- *
- * This test checks the following:
- *
- * - memrchr finds the first occurrence of the character when it is present
- *   (test for different values, particularly the first and the last one).
- * - memrchr returns NULL when the string does not contain the character (test
- *   for non-NULL terminated string).
- * - memrchr does not stop at \0 characters.
- * - memrchr returns NULL for 0-size pointers.
- */
-void check_memrchr()
-{
-	debug_log("Test memrchr.");
+		char stringWithNull[] = {'Y', 'E', 'S', '\0', 'N', 'O', '\0'};
 
-	char string[] = {'C', 'H', 'E', 'R', 'R', 'I', 'O', 'T'};
+		TEST(memchr(stringWithNull, 'N', sizeof(stringWithNull)) ==
+		       &stringWithNull[4],
+		     "memchr must not stop at NULL characters.");
 
-	TEST(memchr(string, 'C', sizeof(string)) == &string[0],
-	     "memrchr must return the first occurence of the character.");
-	TEST(memrchr(string, 'R', sizeof(string)) == &string[4],
-	     "memrchr must return the first occurence of the character.");
-	TEST(memrchr(string, 'T', sizeof(string)) == &string[7],
-	     "memrchr must return the first occurence of the character.");
-	TEST(memrchr(string, 'X', sizeof(string)) == NULL,
-	     "memrchr must return NULL when a character is not present.");
+		TEST(memchr(stringWithNull, 'N', 0) == NULL,
+		     "memchr must return NULL for zero-size pointers.");
+	}
 
-	char stringWithNull[] = {'F', 'U', '\0', 'B', 'A', 'R', '\0'};
+	/**
+	 * Test memrchr.
+	 *
+	 * This test checks the following:
+	 *
+	 * - memrchr finds the first occurrence of the character when it is present
+	 *   (test for different values, particularly the first and the last one).
+	 * - memrchr returns NULL when the string does not contain the character
+	 * (test for non-NULL terminated string).
+	 * - memrchr does not stop at \0 characters.
+	 * - memrchr returns NULL for 0-size pointers.
+	 */
+	void check_memrchr()
+	{
+		debug_log("Test memrchr.");
 
-	TEST(memrchr(stringWithNull, 'F', sizeof(stringWithNull)) ==
-	       &stringWithNull[0],
-	     "memrchr must not stop at NULL characters.");
+		char string[] = {'C', 'H', 'E', 'R', 'R', 'I', 'O', 'T'};
 
-	TEST(memrchr(stringWithNull, 'Y', 0) == NULL,
-	     "memrchr must return NULL for zero-size pointers.");
-}
+		TEST(memchr(string, 'C', sizeof(string)) == &string[0],
+		     "memrchr must return the first occurence of the character.");
+		TEST(memrchr(string, 'R', sizeof(string)) == &string[4],
+		     "memrchr must return the first occurence of the character.");
+		TEST(memrchr(string, 'T', sizeof(string)) == &string[7],
+		     "memrchr must return the first occurence of the character.");
+		TEST(memrchr(string, 'X', sizeof(string)) == NULL,
+		     "memrchr must return NULL when a character is not present.");
 
-/**
- * Test pointer utilities.
- *
- * Not comprehensive, would benefit from being expanded at some point.
- */
-void check_pointer_utilities()
-{
-	debug_log("Test pointer utilities.");
+		char stringWithNull[] = {'F', 'U', '\0', 'B', 'A', 'R', '\0'};
 
-	int                              integer        = 42;
-	int                             *integerPointer = &integer;
-	ds::pointer::proxy::Pointer<int> pointer{integerPointer};
+		TEST(memrchr(stringWithNull, 'F', sizeof(stringWithNull)) ==
+		       &stringWithNull[0],
+		     "memrchr must not stop at NULL characters.");
 
-	TEST((pointer == integerPointer) && (*pointer == 42),
-	     "The pointer proxy does not return the value of its proxy.");
+		TEST(memrchr(stringWithNull, 'Y', 0) == NULL,
+		     "memrchr must return NULL for zero-size pointers.");
+	}
 
-	int                              anotherInteger        = -100;
-	int                             *anotherIntegerPointer = &anotherInteger;
-	ds::pointer::proxy::Pointer<int> anotherPointer{anotherIntegerPointer};
+	/**
+	 * Test pointer utilities.
+	 *
+	 * Not comprehensive, would benefit from being expanded at some point.
+	 */
+	void check_pointer_utilities()
+	{
+		debug_log("Test pointer utilities.");
 
-	pointer = anotherPointer;
+		int                              integer        = 42;
+		int                             *integerPointer = &integer;
+		ds::pointer::proxy::Pointer<int> pointer{integerPointer};
 
-	TEST((pointer == anotherIntegerPointer) && (*pointer == -100),
-	     "The pointer proxy `=` operator does not correctly set the pointer.");
-}
+		TEST((pointer == integerPointer) && (*pointer == 42),
+		     "The pointer proxy does not return the value of its proxy.");
 
-void check_shared_object(const char      *name,
-                         Capability<void> object,
-                         size_t           size,
-                         PermissionSet    permissions)
-{
-	debug_log("Checking shared object {}.", object);
-	TEST(object.length() == size,
-	     "Object {} is {} bytes, expected {}",
-	     name,
-	     object.length(),
-	     size);
-	TEST(object.permissions() == permissions,
-	     "Object {} has permissions {}, expected {}",
-	     name,
-	     PermissionSet{object.permissions()},
-	     permissions);
-}
+		int  anotherInteger        = -100;
+		int *anotherIntegerPointer = &anotherInteger;
+		ds::pointer::proxy::Pointer<int> anotherPointer{anotherIntegerPointer};
 
-// This test is somewhat intimately familiar with parameters of CHERIoT's
-// capability encoding and so might need revision if that changes.
-void check_capability_set_inexact_at_most()
-{
-	void *p = malloc(3128);
+		pointer = anotherPointer;
 
-	debug_log("Test Capability::BoundsProxy::set_inexact_at_most with {}", p);
+		TEST(
+		  (pointer == anotherIntegerPointer) && (*pointer == -100),
+		  "The pointer proxy `=` operator does not correctly set the pointer.");
+	}
 
-	// Too many bits for mantissa, regardless of base alignment
+	void check_shared_object(const char      *name,
+	                         Capability<void> object,
+	                         size_t           size,
+	                         PermissionSet    permissions)
 	{
-		Capability<void> q      = {p};
-		size_t           reqlen = 2047;
-		q.bounds().set_inexact_at_most(reqlen);
-		debug_log("Requesting 2047 gives {}: {}", q.length(), q);
-		TEST(q.is_valid(), "set_inexact_at_most untagged");
-		TEST(q.length() < 2047, "set_inexact_at_most failed to truncate");
-		TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		debug_log("Checking shared object {}.", object);
+		TEST(object.length() == size,
+		     "Object {} is {} bytes, expected {}",
+		     name,
+		     object.length(),
+		     size);
+		TEST(object.permissions() == permissions,
+		     "Object {} has permissions {}, expected {}",
+		     name,
+		     PermissionSet{object.permissions()},
+		     permissions);
 	}
 
-	// Fits in mantissa, but not reachable from misaligned base
+	// This test is somewhat intimately familiar with parameters of CHERIoT's
+	// capability encoding and so might need revision if that changes.
+	void check_capability_set_inexact_at_most()
 	{
-		Capability<void> q = {p};
-		q.address() += 2;
-		size_t reqlen = 1024;
-		q.bounds().set_inexact_at_most(reqlen);
-		debug_log("Requesting 1024 at align 2 gives {}: {}", q.length(), q);
-		TEST(q.is_valid(), "set_inexact_at_most untagged");
-		TEST(q.length() < 1024, "set_inexact_at_most failed to truncate");
-		TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		void *p = malloc(3128);
+
+		debug_log("Test Capability::BoundsProxy::set_inexact_at_most with {}",
+		          p);
+
+		// Too many bits for mantissa, regardless of base alignment
+		{
+			Capability<void> q      = {p};
+			size_t           reqlen = 2047;
+			q.bounds().set_inexact_at_most(reqlen);
+			debug_log("Requesting 2047 gives {}: {}", q.length(), q);
+			TEST(q.is_valid(), "set_inexact_at_most untagged");
+			TEST(q.length() < 2047, "set_inexact_at_most failed to truncate");
+			TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		}
+
+		// Fits in mantissa, but not reachable from misaligned base
+		{
+			Capability<void> q = {p};
+			q.address() += 2;
+			size_t reqlen = 1024;
+			q.bounds().set_inexact_at_most(reqlen);
+			debug_log("Requesting 1024 at align 2 gives {}: {}", q.length(), q);
+			TEST(q.is_valid(), "set_inexact_at_most untagged");
+			TEST(q.length() < 1024, "set_inexact_at_most failed to truncate");
+			TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		}
+
+		// Fits in mantissa and reachable from misaligned base
+		{
+			Capability<void> q = {p};
+			q.address() += 1;
+			size_t reqlen = 511;
+			q.bounds().set_inexact_at_most(reqlen);
+			debug_log("Requesting 511 at align 1 gives {}: {}", q.length(), q);
+			TEST(q.is_valid(), "set_inexact_at_most untagged");
+			TEST(q.length() == 511,
+			     "set_inexact_at_most truncated unnecessarily");
+			TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		}
+
+		free(p);
 	}
 
-	// Fits in mantissa and reachable from misaligned base
+	/**
+	 * This is a regression test for #368.  There are many different ways for
+	 * the compiler to generate a memcmp call and this manages to trigger one of
+	 * the ones that wasn't being mangled the same way as others.  The run-time
+	 * behaviour of this test is irrelevant, we should get a linker failure if
+	 * the freestanding library and the compiler disagree on function names.
+	 */
+	void check_odd_memcmp()
 	{
-		Capability<void> q = {p};
-		q.address() += 1;
-		size_t reqlen = 511;
-		q.bounds().set_inexact_at_most(reqlen);
-		debug_log("Requesting 511 at align 1 gives {}: {}", q.length(), q);
-		TEST(q.is_valid(), "set_inexact_at_most untagged");
-		TEST(q.length() == 511, "set_inexact_at_most truncated unnecessarily");
-		TEST(q.base() == q.address(), "set_inexact_at_most nonzero offset");
+		std::string first  = "first";
+		std::string second = "second";
+		TEST((first == second) == false,
+		     "This test should never fail but exists to make sure that a "
+		     "comparison result is used");
 	}
-
-	free(p);
-}
+} // namespace
 
 int test_misc()
 {
@@ -247,5 +271,6 @@ int test_misc()
 	                      void, test_word, true, false, false, false),
 	                    4,
 	                    {Permission::Global, Permission::Load});
+	check_odd_memcmp();
 	return 0;
 }