Merge remote-tracking branch 'upstream/main' into update Change-Id: Ib2e2c8f152573f8dda27e9421819284d68fa0e37
diff --git a/examples/01.hello_world/xmake.lua b/examples/01.hello_world/xmake.lua index 3483e64..752f61a 100644 --- a/examples/01.hello_world/xmake.lua +++ b/examples/01.hello_world/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/02.hello_compartment/xmake.lua b/examples/02.hello_compartment/xmake.lua index bd282cf..5ed4492 100644 --- a/examples/02.hello_compartment/xmake.lua +++ b/examples/02.hello_compartment/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/03.hello_safe_compartment/xmake.lua b/examples/03.hello_safe_compartment/xmake.lua index 32d5908..9992817 100644 --- a/examples/03.hello_safe_compartment/xmake.lua +++ b/examples/03.hello_safe_compartment/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/04.temporal_safety/xmake.lua b/examples/04.temporal_safety/xmake.lua index 8cd8402..849aabc 100644 --- a/examples/04.temporal_safety/xmake.lua +++ b/examples/04.temporal_safety/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/05.sealing/xmake.lua b/examples/05.sealing/xmake.lua index f3b4547..d20bc47 100644 --- a/examples/05.sealing/xmake.lua +++ b/examples/05.sealing/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/06.producer-consumer/xmake.lua b/examples/06.producer-consumer/xmake.lua index 5e4a157..8de70ef 100644 --- a/examples/06.producer-consumer/xmake.lua +++ b/examples/06.producer-consumer/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/07.error_handling/xmake.lua b/examples/07.error_handling/xmake.lua index f56bbc1..c929e37 100644 --- a/examples/07.error_handling/xmake.lua +++ b/examples/07.error_handling/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/08.memory_safety/xmake.lua b/examples/08.memory_safety/xmake.lua index 93f38d5..29ba2cb 100644 --- a/examples/08.memory_safety/xmake.lua +++ b/examples/08.memory_safety/xmake.lua
@@ -3,9 +3,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/examples/09.javascript/xmake.lua b/examples/09.javascript/xmake.lua index 78f53c5..cb3fdd3 100644 --- a/examples/09.javascript/xmake.lua +++ b/examples/09.javascript/xmake.lua
@@ -6,9 +6,6 @@ includes(sdkdir) set_toolchains("cheriot-clang") --- Support libraries -includes(path.join(sdkdir, "lib")) - option("board") set_default("sail")
diff --git a/scripts/run_clang_tidy_format.sh b/scripts/run_clang_tidy_format.sh index 6b17baf..aecda51 100755 --- a/scripts/run_clang_tidy_format.sh +++ b/scripts/run_clang_tidy_format.sh
@@ -35,7 +35,7 @@ # FreeRTOS-Compat headers follow FreeRTOS naming conventions and should be # excluded for now. Eventually they should be included for everything except # the identifier naming checks. -HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat) +HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v setjmp.h | grep -v unwind.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat) SOURCES=$(find ${DIRECTORIES} -name '*.cc' | grep -v /build/ | grep -v third_party | grep -v arith64.c) echo Headers: ${HEADERS}
diff --git a/sdk/compartment.ldscript b/sdk/compartment.ldscript index 6419245..ba46ec4 100644 --- a/sdk/compartment.ldscript +++ b/sdk/compartment.ldscript
@@ -13,7 +13,9 @@ # delta. The final layout will be the compartment import table # followed by the text segment. There won't be any padding, because # the compartment import table is more strongly aligned than text. - LONG(DEFINED(compartment_error_handler) ? compartment_error_handler - __compartment_code_start + SIZEOF(.compartment_import_table) : -1); + SHORT(DEFINED(compartment_error_handler) ? compartment_error_handler - __compartment_code_start + SIZEOF(.compartment_import_table) : 0xffff); + # The stackless compartment error handler, if it is defined. + SHORT(DEFINED(compartment_error_handler_stackless) ? compartment_error_handler_stackless - __compartment_code_start + SIZEOF(.compartment_import_table) : 0xffff); # Array of compartment exports *(.compartment_exports .compartment_exports.*); } @@ -34,6 +36,7 @@ # If there is a compartment error handler, make sure that it is before # anything that can have linker relaxations so that its displacement # from __compartment_code_start is a constant. + *(.compartment_error_handler_stackless)); *(.compartment_error_handler); *(.text .text.*); }
diff --git a/sdk/core/allocator/main.cc b/sdk/core/allocator/main.cc index 8f5776f..8568af9 100644 --- a/sdk/core/allocator/main.cc +++ b/sdk/core/allocator/main.cc
@@ -828,10 +828,10 @@ } // namespace -__cheriot_minimum_stack(0x80) ssize_t +__cheriot_minimum_stack(0x90) ssize_t heap_quota_remaining(struct SObjStruct *heapCapability) { - STACK_CHECK(0x80); + STACK_CHECK(0x90); LockGuard g{lock}; auto *cap = malloc_capability_unseal(heapCapability); if (cap == nullptr) @@ -841,9 +841,9 @@ return cap->quota; } -__cheriot_minimum_stack(0xb0) void heap_quarantine_empty() +__cheriot_minimum_stack(0xc0) void heap_quarantine_empty() { - STACK_CHECK(0xb0); + STACK_CHECK(0xc0); LockGuard g{lock}; while (gm->heapQuarantineSize > 0) { @@ -857,12 +857,12 @@ } } -__cheriot_minimum_stack(0x200) void *heap_allocate(Timeout *timeout, +__cheriot_minimum_stack(0x210) void *heap_allocate(Timeout *timeout, SObj heapCapability, size_t bytes, uint32_t flags) { - STACK_CHECK(0x200); + STACK_CHECK(0x210); if (!check_timeout_pointer(timeout)) { return nullptr; @@ -882,10 +882,10 @@ return malloc_internal(bytes, std::move(g), cap, timeout, false, flags); } -__cheriot_minimum_stack(0x1b0) ssize_t +__cheriot_minimum_stack(0x1c0) ssize_t heap_claim(SObj heapCapability, void *pointer) { - STACK_CHECK(0x1b0); + STACK_CHECK(0x1c0); LockGuard g{lock}; auto *cap = malloc_capability_unseal(heapCapability); if (cap == nullptr) @@ -912,18 +912,18 @@ return 0; } -__cheriot_minimum_stack(0xe0) int heap_can_free(SObj heapCapability, +__cheriot_minimum_stack(0xf0) int heap_can_free(SObj heapCapability, void *rawPointer) { - STACK_CHECK(0xe0); + STACK_CHECK(0xf0); LockGuard g{lock}; return heap_free_internal(heapCapability, rawPointer, false); } -__cheriot_minimum_stack(0x250) int heap_free(SObj heapCapability, +__cheriot_minimum_stack(0x260) int heap_free(SObj heapCapability, void *rawPointer) { - STACK_CHECK(0x250); + STACK_CHECK(0x260); LockGuard g{lock}; int ret = heap_free_internal(heapCapability, rawPointer, true); if (ret != 0) @@ -942,9 +942,9 @@ return 0; } -__cheriot_minimum_stack(0x180) ssize_t heap_free_all(SObj heapCapability) +__cheriot_minimum_stack(0x190) ssize_t heap_free_all(SObj heapCapability) { - STACK_CHECK(0x180); + STACK_CHECK(0x190); LockGuard g{lock}; auto *capability = malloc_capability_unseal(heapCapability); if (capability == nullptr) @@ -981,13 +981,13 @@ return freed; } -__cheriot_minimum_stack(0x200) void *heap_allocate_array(Timeout *timeout, +__cheriot_minimum_stack(0x210) void *heap_allocate_array(Timeout *timeout, SObj heapCapability, size_t nElements, size_t elemSize, uint32_t flags) { - STACK_CHECK(0x200); + STACK_CHECK(0x210); if (!check_timeout_pointer(timeout)) { return nullptr; @@ -1158,14 +1158,14 @@ return nullptr; } -__cheriot_minimum_stack(0x270) SObj +__cheriot_minimum_stack(0x280) SObj token_sealed_unsealed_alloc(Timeout *timeout, SObj heapCapability, SKey key, size_t sz, void **unsealed) { - STACK_CHECK(0x270); + STACK_CHECK(0x280); if (!check_timeout_pointer(timeout)) { return INVALID_SOBJ; @@ -1185,12 +1185,12 @@ return INVALID_SOBJ; } -__cheriot_minimum_stack(0x250) SObj token_sealed_alloc(Timeout *timeout, +__cheriot_minimum_stack(0x260) SObj token_sealed_alloc(Timeout *timeout, SObj heapCapability, SKey rawKey, size_t sz) { - STACK_CHECK(0x250); + STACK_CHECK(0x260); return allocate_sealed_unsealed( timeout, heapCapability, rawKey, sz, {Permission::Seal}) .first; @@ -1218,11 +1218,11 @@ return unseal_if_valid(obj); } -__cheriot_minimum_stack(0x250) int token_obj_destroy(SObj heapCapability, +__cheriot_minimum_stack(0x260) int token_obj_destroy(SObj heapCapability, SKey key, SObj object) { - STACK_CHECK(0x250); + STACK_CHECK(0x260); void *unsealed; { LockGuard g{lock}; @@ -1240,11 +1240,11 @@ return heap_free(heapCapability, unsealed); } -__cheriot_minimum_stack(0xe0) int token_obj_can_destroy(SObj heapCapability, +__cheriot_minimum_stack(0xf0) int token_obj_can_destroy(SObj heapCapability, SKey key, SObj object) { - STACK_CHECK(0xe0); + STACK_CHECK(0xf0); void *unsealed; { LockGuard g{lock};
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc index 11cb2c2..a72541f 100644 --- a/sdk/core/loader/boot.cc +++ b/sdk/core/loader/boot.cc
@@ -855,6 +855,9 @@ // Stack pointer points to the top of the stack. stack.address() += config.stack.size(); + // Reserve space at the start of the stack for error handling and so + // on. + stack.address() -= STACK_ENTRY_RESERVED_SPACE; Debug::log("Thread's stack is {}", stack); threadTStack->csp = stack; @@ -881,6 +884,8 @@ threadTStack->frameoffset = offsetof(TrustedStack, frames[1]); threadTStack->frames[0].calleeExportTable = build(compartment.exportTable); + // Special case: The first frame has the initial csp. + threadTStack->frames[0].csp = stack; Debug::log("Thread's trusted stack is {}", threadTStack);
diff --git a/sdk/core/loader/types.h b/sdk/core/loader/types.h index fea0a7e..ec488c9 100644 --- a/sdk/core/loader/types.h +++ b/sdk/core/loader/types.h
@@ -1022,10 +1022,17 @@ /** * The offset of the compartment's error handler from the start of - * `pcc`. This must always be positive, a value of -1 is used to - * indicate that this compartment does not provide an error handler. + * `pcc`. A value of 0xffff indicates that this compartment does not + * provide an error handler. */ - ptrdiff_t errorHandler; + uint16_t errorHandler; + + /** + * The offset of the compartment's stackless error handler from the + * start of `pcc`. A value of 0xffff indicates that this compartment + * does not provide a stackless error handler. + */ + uint16_t errorHandlerStackless; }; /**
diff --git a/sdk/core/scheduler/main.cc b/sdk/core/scheduler/main.cc index 95ef0e2..18c0d7b 100644 --- a/sdk/core/scheduler/main.cc +++ b/sdk/core/scheduler/main.cc
@@ -434,10 +434,10 @@ return ret; } -__cheriot_minimum_stack(0x80) int __cheri_compartment("sched") +__cheriot_minimum_stack(0x90) int __cheri_compartment("sched") thread_sleep(Timeout *timeout, uint32_t flags) { - STACK_CHECK(0x80); + STACK_CHECK(0x90); if (!check_timeout_pointer(timeout)) { return -EINVAL; @@ -449,12 +449,12 @@ return 0; } -__cheriot_minimum_stack(0xa0) int futex_timed_wait(Timeout *timeout, +__cheriot_minimum_stack(0xb0) int futex_timed_wait(Timeout *timeout, const uint32_t *address, uint32_t expected, uint32_t flags) { - STACK_CHECK(0xa0); + STACK_CHECK(0xb0); if (!check_timeout_pointer(timeout) || !check_pointer<PermissionSet{Permission::Load}>(address)) { @@ -536,9 +536,9 @@ return 0; } -__cheriot_minimum_stack(0x90) int futex_wake(uint32_t *address, uint32_t count) +__cheriot_minimum_stack(0xa0) int futex_wake(uint32_t *address, uint32_t count) { - STACK_CHECK(0x90); + STACK_CHECK(0xa0); if (!check_pointer<PermissionSet{Permission::Store}>(address)) { return -EINVAL; @@ -577,13 +577,13 @@ return woke; } -__cheriot_minimum_stack(0x50) int multiwaiter_create( +__cheriot_minimum_stack(0x60) int multiwaiter_create( Timeout *timeout, struct SObjStruct *heapCapability, MultiWaiter **ret, size_t maxItems) { - STACK_CHECK(0x50); + STACK_CHECK(0x60); int error; // Don't bother checking if timeout is valid, the allocator will check for // us. @@ -597,20 +597,20 @@ return write_result(reinterpret_cast<void **>(ret), mw); } -__cheriot_minimum_stack(0x60) int multiwaiter_delete( +__cheriot_minimum_stack(0x70) int multiwaiter_delete( struct SObjStruct *heapCapability, MultiWaiter *mw) { - STACK_CHECK(0x60); + STACK_CHECK(0x70); return deallocate<MultiWaiterInternal>(heapCapability, mw); } -__cheriot_minimum_stack(0xb0) int multiwaiter_wait(Timeout *timeout, +__cheriot_minimum_stack(0xc0) int multiwaiter_wait(Timeout *timeout, MultiWaiter *waiter, EventWaiterSource *events, size_t newEventsCount) { - STACK_CHECK(0xb0); + STACK_CHECK(0xc0); return typed_op<MultiWaiterInternal>(waiter, [&](MultiWaiterInternal &mw) { if (newEventsCount > mw.capacity()) { @@ -699,9 +699,9 @@ } // namespace [[cheri::interrupt_state(disabled)]] __cheriot_minimum_stack( - 0x20) const uint32_t *interrupt_futex_get(struct SObjStruct *sealed) + 0x30) const uint32_t *interrupt_futex_get(struct SObjStruct *sealed) { - STACK_CHECK(0x20); + STACK_CHECK(0x30); auto *interruptCapability = Handle::unseal<InterruptCapability>(sealed); uint32_t *result = nullptr; if (interruptCapability && interruptCapability->state.mayWait) @@ -719,9 +719,9 @@ } [[cheri::interrupt_state(disabled)]] __cheriot_minimum_stack( - 0x10) int interrupt_complete(struct SObjStruct *sealed) + 0x20) int interrupt_complete(struct SObjStruct *sealed) { - STACK_CHECK(0x10); + STACK_CHECK(0x20); auto *interruptCapability = Handle::unseal<InterruptCapability>(sealed); if (interruptCapability && interruptCapability->state.mayComplete) {
diff --git a/sdk/core/switcher/entry.S b/sdk/core/switcher/entry.S index 716248d..8ef9cca 100644 --- a/sdk/core/switcher/entry.S +++ b/sdk/core/switcher/entry.S
@@ -227,6 +227,8 @@ #endif zero_stack t2, s0, gp after_zero: + // Reserve space for unwind state and so on. + cincoffset csp, csp, -STACK_ENTRY_RESERVED_SPACE #ifdef CONFIG_MSHWM // store new stack top as stack high water mark csrw CSR_MSHWM, sp @@ -256,6 +258,8 @@ // At this point, we have already truncated the stack and so the length of // the stack is the length that the callee can use. cgetlen t2, csp + // Include the space we reserved for the unwind state. + addi t2, t2, -STACK_ENTRY_RESERVED_SPACE bgtu tp, t2, .Lstack_too_small // Get the flags field into tp @@ -481,24 +485,11 @@ csrw CSR_MSHWMB, x1 #endif cspecialw mepcc, ct2 - csb zero, TrustedStack_offset_inForcedUnwind(csp) // c2 is csp, which will be loaded last and will overwrite the trusted // stack pointer with the thread's stack pointer. reloadRegisters c1, cgp, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, csp mret -// If we detect an invalid entry and there is no error handler installed, we want -// to resume rather than unwind. -.Linvalid_entry: -// Mark this threads as in the middle of a forced unwind. - li a0, 1 - csb a0, TrustedStack_offset_inForcedUnwind(ctp) -// Make sure we don't leak anything to the compartment. -// Registers might been used by the call and therefore need zeroing. - zeroAllRegistersExcept a0, s0, s1, sp, a2, gp -// Store an error value in return registers, which will be passed to the -// caller on unwind. a1 is zeroed by zeroAllRegistersExcept. - li a0, -1 // We are starting a forced unwind. This is reached either when we are unable // to run an error handler, or when we do run an error handler and it instructs // us to return. This treats all register values as undefined on entry. @@ -506,57 +497,32 @@ // Pop the trusted stack frame. cjal .Lpop_trusted_stack_frame cmove cra, ca2 -.Lout_of_trusted_stack: - cmove ct0, csp - // Fetch the trusted stack pointer. - cspecialr csp, mtdc - // csp now points to the save reg frame that we can use. - // Spill all of the registers that we want to propagate to the caller: - // c1(cra), c2(csp), c3(cgp), c8(cs0), c9(cs1), c10(ca0), c11(ca1) - csc ct0, TrustedStack_offset_csp(csp) - spillRegisters c1, cgp, c8, c9, c10, c11 - // Store an unsealed version of cra in the mepcc slot, where it will be - // used for mret later. mret requires an unsealed capability in mepcc, so - // we have to unseal it if it is sealed. - LoadCapPCC cs0, compartment_switcher_sealing_key - // ca2 at this point was loaded by .Lpop_trusted_stack_frame from the pcc - // in the trusted stack and so should always be sealed as a sentry type. - cgettype gp, cra - csetaddr cs0, cs0, gp - cunseal cra, cra, cs0 - csc cra, TrustedStack_offset_mepcc(csp) - clw t0, TrustedStack_offset_mstatus(csp) - // If gp==2 then the we need to disable interrupts on return, otherwise we - // need to enable them. The interrupt enable bit is bit 7. We want to set - // bit 7 if interrupts are enabled, clear it if they are disabled, but not - // toggle any other bits. - // Clear the interrupt enable bit unconditionally - andi t0, t0, ~0x80 - // Set it again if we should have interrupts enabled - li a3, 2 - beq gp, a3, .Ldo_not_enable - ori t0, t0, 0x80 -.Ldo_not_enable: - csw t0, TrustedStack_offset_mstatus(csp) + // Zero all registers apart from RA, GP, SP and return args. + // cra, cs0, cs1, and cgp were restored from the compartment's stack + // csp restored from the trusted stack. + // ca0, used for first return value + // ca1, used for second return value + zeroAllRegistersExcept ra, sp, gp, s0, s1, a0, a1 + li a0, -ECOMPARTMENTFAIL + li a1, 0 + cret - // Zero all registers that we aren't explicitly restoring to avoid leaks - // from the faulting callee to the caller. - csc cnull, TrustedStack_offset_c4(csp) - csc cnull, TrustedStack_offset_c5(csp) - csc cnull, TrustedStack_offset_c6(csp) - csc cnull, TrustedStack_offset_c7(csp) - csc cnull, TrustedStack_offset_c12(csp) - csc cnull, TrustedStack_offset_c13(csp) - csc cnull, TrustedStack_offset_c14(csp) - csc cnull, TrustedStack_offset_c15(csp) - // Mark this threads as in the middle of a forced unwind. - li a0, 1 - csb a0, TrustedStack_offset_inForcedUnwind(csp) - // Spill a fake status and cap cause (CHERI fault, no cause) - li a0, 0x1c - csw a0, TrustedStack_offset_mcause(csp) - csrw mtval, zero - // Fall through to handle error + +// If we have run out of trusted stack, then just restore the caller's state +// and return an error value. +.Lout_of_trusted_stack: + // Restore the spilled values + clc cs0, SPILL_SLOT_cs0(csp) + clc cs1, SPILL_SLOT_cs1(csp) + clc cra, SPILL_SLOT_pcc(csp) + clc cgp, SPILL_SLOT_cgp(csp) + cincoffset csp, csp, SPILL_SLOT_SIZE + // Set the return registers + li a0, -ENOTENOUGHTRUSTEDSTACK + li a1, 0 + // Zero everything else + zeroAllRegistersExcept ra, sp, gp, s0, s1, a0, a1 + cret // If we have a possibly recoverable error, see if we have a useful error // handler. At this point, the register state will have been saved in the @@ -593,40 +559,85 @@ // Load the interrupted thread's stack pointer into ct0 clc ct0, TrustedStack_offset_csp(csp) - // Fetch the base of compartment stack before cincoffset for later - // comparison. The subsequent cincoffset could cause the base to change, if - // the capability becomes unrepresentable. That would clear the tag, and so - // we'd fault later. Faulting in the switcher will then trigger a forced - // unwind. - cgetbase tp, ct0 - // Allocate space for the register save frame on the stack. If we didn't - // have enough space here, we'll fault setting up the call frame, which - // will detect a trap in the switcher and force unwind. - cincoffset ct0, ct0, -(16*8) // See if we can find a handler: clhu tp, TrustedStack_offset_frameoffset(csp) li t1, TrustedStack_offset_frames - beq tp, t1, .Lend_of_stack + beq tp, t1, .Lreset_mepcc_and_install_context addi tp, tp, -TrustedStackFrame_size + // ctp points to the current available trusted stack frame. cincoffset ctp, csp, tp - // ct1 now contains the export table for the callee + // a0 indicates whether we're calling a stackless error handler (0: stack, + // 1: stackless) + li a0, 0 + + // Allocate space for the register save frame on the stack. + cincoffset ct0, ct0, -(16*8) + + // WARNING: ENCODING SPECIFIC. + // The following depends on the fact that before-the-start values are not + // representable in the CHERIoT encoding and so will clear the tag. If + // this property changes then this will need to be replaced by a check that + // against the base of the stack. Note that this check can't be a simple + // cgetbase on ct0, because moving the address below the base sufficiently + // far that it's out of *representable* bounds will move the reported base + // value (base is a displacement from the address). + cgettag t1, ct0 + // If there isn't enough space on the stack, see if there's a stackless + // handler. + beqz t1, .Ltry_stackless_handler + clc ct1, TrustedStackFrame_offset_calleeExportTable(ctp) - // Reset the export table pointer to point to the *start* of the export + // Set the export table pointer to point to the *start* of the export // table. It will currently point to the entry point that was raised. // TODO: We might want to pass this to the error handler, it might be // useful for providing per-entry-point error results. cgetbase s0, ct1 csetaddr ct1, ct1, s0 - clw s0, ExportTable_offset_errorHandler(ct1) - // A value of -1 indicates no error handler - // Give up if there is no error handler for this compartment. - addi s1, s0, 1 - beqz s1, .Lno_handler_found + clhu s0, ExportTable_offset_errorHandler(ct1) + // A value of 0xffff indicates no error handler + // If we found one, use it, otherwise fall through and try to find a + // stackless handler. + li s1, 0xffff + bne s0, s1, .Lhandler_found - // If we have found a handler, mark this threads as no longer on the - // force-unwind path. Any future fault will trigger a forced unwind. - csb zero, TrustedStack_offset_inForcedUnwind(csp) +.Ltry_stackless_handler: + clc ct1, TrustedStackFrame_offset_calleeExportTable(ctp) + // Set the export table pointer to point to the *start* of the export + // table. It will currently point to the entry point that was raised. + cgetbase s0, ct1 + csetaddr ct1, ct1, s0 + clhu s0, ExportTable_offset_errorHandlerStackless(ct1) + // A value of 0xffff indicates no error handler + // Give up if there is no error handler for this compartment. + li s1, 0xffff + beq s0, s1, .Lforce_unwind + + // The stack may have had its tag cleared at this point, so for stackless + // handlers we need to restore the on-entry stack. + // Get the previous trusted stack frame + + // Load the caller's csp + clc ca0, TrustedStackFrame_offset_csp(ctp) + + // If this is the top stack frame, then the csp field is the value on + // entry. If it's any other frame then we need to go to the previous one + cincoffset cs1, csp, TrustedStack_offset_frames + beq s1, t1, .Lrecovered_stack + + // The address of the stack pointer will point to the bottom of the + // caller's save area, so we set the bounds to be the base up to the + // current address. + cgetaddr a1, ca0 + cgetbase a2, ca0 + sub a1, a1, a2 + csetaddr ca0, ca0, a2 + // The code that installs the context expects csp to be in ct0 + csetboundsexact ct0, ca0, a1 +.Lrecovered_stack: + li a0, 1 + +.Lhandler_found: // Increment the handler invocation count. clhu s1, TrustedStackFrame_offset_errorHandlerCount(ctp) @@ -656,6 +667,16 @@ csetaddr cra, cra, s1 cincoffset cra, cra, s0 + // If we're in an error handler with a stack, set up the stack, otherwise + // we just need to set up argument registers. + beqz a0, .Lset_up_stack_handler + clw a0, TrustedStack_offset_mcause(csp) + csrr a1, mtval + li a2, 0 + cmove csp, ct0 + j .Linvoke_error_handler + +.Lset_up_stack_handler: // Set up the on-stack context for the callee clc cs1, 0(csp) ccleartag cs1, cs1 @@ -671,6 +692,8 @@ clw a1, TrustedStack_offset_mcause(csp) csrr a2, mtval cmove csp, ca0 + +.Linvoke_error_handler: // Clear all registers except: // cra is set by cjalr. csp and cgp are needed for the called compartment. // ca0, used for the register state @@ -751,23 +774,11 @@ j .Lhandle_error -// We have reached the end of the stack. If we are in a forced unwind then we -// just install the context, if we've gone off the top of the stack then we -// should report this gracefully. -.Lend_of_stack: - clb a2, TrustedStack_offset_inForcedUnwind(csp) - bnez a2, .Lreset_mepcc_and_install_context // Value 24 is reserved for custom use. .Lset_mcause_and_exit_thread: csrw mcause, 24 j .Lthread_exit -// No handler was found. If we are in the middle of unwinding, then we want to -// just install the context but if this is a fault then we keep going up the -// stack. -.Lno_handler_found: - clb a2, TrustedStack_offset_inForcedUnwind(csp) - beqz a2, .Lforce_unwind // The continue-resume path expects the location that we will mret to to be // in ct2. If we're just resuming, then resume from the stashed link // register value. @@ -876,8 +887,24 @@ // two instructions. cspecialr ca0, mtdc clhu a1, TrustedStack_offset_frameoffset(ca0) + addi a1, a1, -TrustedStackFrame_size cincoffset ca0, ca0, a1 clc ca0, TrustedStackFrame_offset_csp(ca0) + // If this is the first frame, then the recovered stack will be the stack + // on entry. If this is not the first frame then then we need to find the + // saved CSP from the caller and reset the bounds. The address of the + // saved CSP will be the value after the switcher spilled registers and so + // will be the top of the callee's stack. + li a2, TrustedStack_offset_frames + beq a1, a2, 0f + + // Find the previous frame's csp and reset the bounds + cgetaddr a1, ca0 + cgetbase a2, ca0 + sub a1, a1, a2 + csetaddr ca0, ca0, a2 + csetboundsexact ca0, ca0, a1 +0: cret .section .text, "ax", @progbits @@ -988,6 +1015,25 @@ csrr a0, CSR_MSHWM cret + .section .text, "ax", @progbits + .p2align 2 + .type __Z39switcher_handler_invocation_count_resetv,@function +__Z39switcher_handler_invocation_count_resetv: + // Trusted stack pointer in ca1 + cspecialr ca1, mtdc + // Offset of the current trusted stack frame to a1 + clhu a0, TrustedStack_offset_frameoffset(ca1) + addi a0, a0, -TrustedStackFrame_size + // Current trusted stack frame to ca1, a0 is dead + cincoffset ca1, ca1, a0 + // Current invocation count (for return) in a0 + clh a0, TrustedStackFrame_offset_errorHandlerCount(ca1) + // Reset invocation count + csh zero, TrustedStackFrame_offset_errorHandlerCount(ca1) + // Zero trusted stack frame pointer register + li a1, 0 + cret + // The linker expects export tables to start with space for cgp and pcc, then // the compartment error handler. We should eventually remove that requirement // for library export tables, but since they don't consume RAM after loading @@ -1022,3 +1068,4 @@ export __Z28switcher_thread_hazard_slotsv export __Z13thread_id_getv export __Z25stack_lowest_used_addressv +export __Z39switcher_handler_invocation_count_resetv
diff --git a/sdk/core/switcher/export-table-assembly.h b/sdk/core/switcher/export-table-assembly.h index 3d446ef..5581f78 100644 --- a/sdk/core/switcher/export-table-assembly.h +++ b/sdk/core/switcher/export-table-assembly.h
@@ -10,3 +10,4 @@ EXPORT_ASSEMBLY_OFFSET(ExportTable, pcc, 0) EXPORT_ASSEMBLY_OFFSET(ExportTable, cgp, 8) EXPORT_ASSEMBLY_OFFSET(ExportTable, errorHandler, 16) +EXPORT_ASSEMBLY_OFFSET(ExportTable, errorHandlerStackless, 18)
diff --git a/sdk/core/switcher/trusted-stack-assembly.h b/sdk/core/switcher/trusted-stack-assembly.h index f321548..02fc248 100644 --- a/sdk/core/switcher/trusted-stack-assembly.h +++ b/sdk/core/switcher/trusted-stack-assembly.h
@@ -28,12 +28,12 @@ // Size of everything up to this point # define TSTACK_REGFRAME_SZ (19 * 8) -// frameoffset, inForcedUnwind and padding +// frameoffset and padding # define TSTACK_HEADER_SZ 16 #else // Size of everything up to this point # define TSTACK_REGFRAME_SZ ((17 * 8) + (2 * 4)) -// frameoffset, inForcedUnwind and padding +// frameoffset and padding # define TSTACK_HEADER_SZ 8 #endif // The basic trusted stack is the size of the save area, 8 bytes of state for @@ -47,7 +47,6 @@ TSTACK_REGFRAME_SZ + TSTACK_HEADER_SZ) EXPORT_ASSEMBLY_OFFSET(TrustedStack, frameoffset, TSTACK_REGFRAME_SZ) EXPORT_ASSEMBLY_OFFSET(TrustedStack, threadID, TSTACK_REGFRAME_SZ + 2) -EXPORT_ASSEMBLY_OFFSET(TrustedStack, inForcedUnwind, TSTACK_REGFRAME_SZ + 4) EXPORT_ASSEMBLY_OFFSET(TrustedStackFrame, csp, 0) EXPORT_ASSEMBLY_OFFSET(TrustedStackFrame, calleeExportTable, 8) @@ -66,3 +65,10 @@ * Load, Store, LoadStoreCapability, LoadMutable StoreLocal and LoadGlobal */ #define COMPARTMENT_STACK_PERMISSIONS 0x7e + +/** + * Space reserved at the top of a stack on entry to the compartment. + * + * This *must* be a multiple of 16, which is the stack alignment. + */ +#define STACK_ENTRY_RESERVED_SPACE 16
diff --git a/sdk/core/switcher/tstack.h b/sdk/core/switcher/tstack.h index 0875249..45af06e 100644 --- a/sdk/core/switcher/tstack.h +++ b/sdk/core/switcher/tstack.h
@@ -59,17 +59,12 @@ * The ID of the current thread. Never modified during execution. */ uint16_t threadID; - /** - * Flag indicating whether this thread is in the process of a forced - * unwind. If so, this is one, otherwise it is zero. - */ - uint8_t inForcedUnwind; // Padding up to multiple of 16-bytes. uint8_t padding[ #ifdef CONFIG_MSHWM - 11 + 12 #else - 3 + 4 #endif ]; /**
diff --git a/sdk/include/cdefs.h b/sdk/include/cdefs.h index ad3b9ae..2242e7e 100644 --- a/sdk/include/cdefs.h +++ b/sdk/include/cdefs.h
@@ -115,4 +115,8 @@ */ unsigned __builtin_strlen(const char *str) __asm__("_Z6strlenPKc"); +#if !defined(CLANG_TIDY) && !__has_builtin(__builtin_cheri_top_get) +# error Your compiler is too old for this version of CHERIoT RTOS, please upgrade to a newer version +#endif + #endif // _CDEFS_H_
diff --git a/sdk/include/errno.h b/sdk/include/errno.h index 07cf199..35aa193 100644 --- a/sdk/include/errno.h +++ b/sdk/include/errno.h
@@ -89,6 +89,7 @@ #define ENOTRECOVERABLE 131 // State not recoverable. #define EOVERFLOW 139 // Value too large to be stored in data type. #define ENOTENOUGHSTACK 140 // Insufficient stack space for cross-compartment call. +#define ENOTENOUGHTRUSTEDSTACK 141 // Insufficient stack space for cross-compartment call. #define EWOULDBLOCK EAGAIN // Operation would block. #define ENOTSUP EOPNOTSUPP // Not supported. #define __ELASTERROR 2000 // Users can add values starting here.
diff --git a/sdk/include/fail-simulator-on-error.h b/sdk/include/fail-simulator-on-error.h index 52a65d3..d26dceb 100644 --- a/sdk/include/fail-simulator-on-error.h +++ b/sdk/include/fail-simulator-on-error.h
@@ -56,20 +56,17 @@ frame->get_register_value<CHERI::RegisterNumber::CSP>()}; CHERI::Capability returnCapability{ frame->get_register_value<CHERI::RegisterNumber::CRA>()}; + // The top of the stack is 16 bytes above the stack pointer on entry, + // to provide space for unwind lists and so on. if (registerNumber == CHERI::RegisterNumber::CRA && returnCapability.address() == 0 && exceptionCode == CHERI::CauseCode::TagViolation && - stackCapability.top() == stackCapability.address()) + (stackCapability.top() - 16) == stackCapability.address()) { // looks like thread exit -- just log it then ForceUnwind DebugErrorHandler::log( "Thread exit CSP={}, PCC={}", stackCapability, frame->pcc); } - else if (exceptionCode == CHERI::CauseCode::None) - { - // An unwind occurred from a called compartment, just resume. - return ErrorRecoveryBehaviour::InstallContext; - } else { // An unexpected error -- log it and end the simulation
diff --git a/sdk/include/setjmp.h b/sdk/include/setjmp.h new file mode 100644 index 0000000..d8d3cdf --- /dev/null +++ b/sdk/include/setjmp.h
@@ -0,0 +1,64 @@ +// Copyright Microsoft and CHERIoT Contributors. +// SPDX-License-Identifier: MIT +#pragma once + +/** + * This is a minimal implementation of setjmp/longjmp. + * + * CHERIoT cannot store a `jmp_buf` anywhere other than the stack without + * clearing tags (which will then cause `longjmp` to fail). + */ + +#include <stddef.h> +#include <stdint.h> + +/** + * Jump buffer for setjmp/longjmp. + */ +struct __jmp_buf +{ + uintptr_t __cs0; + uintptr_t __cs1; + uintptr_t __csp; + uintptr_t __cra; +}; + +/** + * C requires that `setjmp` and `longjmp` take a `jmp_buf` by reference and so + * this ends up being defined as an array of one element, which allows it to + * both be allocated and passed by reference. + */ +typedef struct __jmp_buf jmp_buf[1]; + +/** + * C `setjmp` function. Returns (up to) twice. First returns 0, returns a + * value passed to `longjmp` on the second return. + */ +__attribute__((returns_twice)) extern "C" int setjmp(jmp_buf env); +__asm__(".section .text.setjmp,\"awG\",@progbits,setjmp,comdat\n" + ".globl setjmp\n" + ".p2align 2\n" + ".type setjmp,@function\n" + "setjmp:\n" + " csc cs0, 0(ca0)\n" + " csc cs1, 8(ca0)\n" + " csc csp, 16(ca0)\n" + " csc cra, 24(ca0)\n" + " li a0, 0\n" + " cret\n"); + +/** + * C `longjmp` function. Does not return, jumps back to the `setjmp` call. + */ +extern "C" void longjmp(jmp_buf env, int val); +__asm__(".section .text.longjmp,\"awG\",@progbits,longjmp,comdat\n" + ".globl longjmp\n" + ".p2align 2\n" + ".type longjmp,@function\n" + "longjmp:\n" + " clc cs0, 0(ca0)\n" + " clc cs1, 8(ca0)\n" + " clc csp, 16(ca0)\n" + " clc cra, 24(ca0)\n" + " mv a0, a1\n" + " cjr cra\n");
diff --git a/sdk/include/switcher.h b/sdk/include/switcher.h index a1088b6..5bfa480 100644 --- a/sdk/include/switcher.h +++ b/sdk/include/switcher.h
@@ -1,6 +1,7 @@ #pragma once #include <cdefs.h> #include <stddef.h> +#include <stdint.h> /** * Returns true if the trusted stack contains at least `requiredFrames` frames @@ -56,3 +57,26 @@ * compartment invocation. */ __cheri_libcall ptraddr_t stack_lowest_used_address(void); + +/** + * Resets the switcher's count of invocations. + * + * Switcher place a limit on the number of times a compartment invocation may + * fault (default 512). This prevents a compartment from getting stuck + * partially recovering from errors. This function resets the count to zero. + * It should be called from outer compartments' run loops and from places where + * the caller is certain that error handling is making forward progress. + * + * Note: If this is called from an error handler that subsequently returns with + * with install-context, the switcher will subtract one from this and set the + * switcher error count to -1. Because this is odd, the switcher will detect + * the next invocation of the error handler as a double fault and will force + * unwind. Do not call this if you are currently in an error handler unless + * you are jumping out via some mechanism that does *not* involve returning to + * the switcher. + * + * Returns the previous value of the invocation count. The low bit is set if + * an error handler is currently running, the remaining bits are the count. + */ +__cheri_libcall uint16_t switcher_handler_invocation_count_reset(void); +
diff --git a/sdk/include/unwind.h b/sdk/include/unwind.h new file mode 100644 index 0000000..7a1c898 --- /dev/null +++ b/sdk/include/unwind.h
@@ -0,0 +1,99 @@ +#pragma once +#include <cdefs.h> +#include <setjmp.h> +#include <switcher.h> + +/** + * On-stack linked list of cleanup handlers. + */ +struct CleanupList +{ + /// Next pointer. + CleanupList *next; + /// Jump buffer to return to. + __jmp_buf env; +}; + +/** + * Head of the cleanup list. + * + * This is stored in the space that the switcher reserves at the top of the + * stack. The stack is zeroed on entry to a compartment and so this will be + * null until explicitly written to. + */ +__always_inline static inline struct CleanupList **cleanup_list_head() +{ + void *csp = __builtin_cheri_stack_get(); + ptraddr_t top = __builtin_cheri_top_get(csp); + csp = __builtin_cheri_address_set(csp, top - 8); + return (struct CleanupList **)csp; +} + +/** + * Unwind the stack to the most recent `CHERIOT_HANDLER` block. + */ +__always_inline static inline void cleanup_unwind(void) +{ + CleanupList **__head = cleanup_list_head(); + CleanupList *__top = *__head; + *__head = __top->next; + switcher_handler_invocation_count_reset(); + longjmp(&__top->env, 1); +} + +/** + * Simple error handling macros. These are modelled on the OpenStep exception + * macros and are similarly built on top of `setjmp`. Code between + * `CHERIOT_DURING` and `CHERIOT_HANDLER` corresponds to a `try` block. Code + * between `CHERIOT_HANDLER` and `CHERIOT_END_HANDLER` corresponds to a `catch` + * block, though no exception value is actually thrown. + * + * Any automatic-storage values accessed in both blocks must be declared + * `volatile`. + */ +#define CHERIOT_DURING \ + { \ + CleanupList cleanupListEntry; \ + auto **__head = cleanup_list_head(); \ + cleanupListEntry.next = *__head; \ + *__head = &cleanupListEntry; \ + if (setjmp(&cleanupListEntry.env) == 0) \ + { +/// See CHERIOT_DURING. +#define CHERIOT_HANDLER \ + *__head = cleanupListEntry.next; \ + } \ + else \ + { \ + *__head = cleanupListEntry.next; + +/// See CHERIOT_DURING. +#define CHERIOT_END_HANDLER \ + } \ + } + +#ifdef __cplusplus + +/** + * On-error helper. Invokes `fn` and, if `cleanup_unwind` is called, invokes + * `err`. Destructors in between `fn` and the frame that calls + * `cleanup_unwind` are not called, but this function returns normally and so + * destructors of objects above this on the stack will be called normally. + */ +static inline void on_error(auto fn, auto err) +{ + CHERIOT_DURING + fn(); + CHERIOT_HANDLER + err(); + CHERIOT_END_HANDLER +} + +/** + * On-error helper with no error handler (returns normally from forced unwind). + */ +static inline void on_error(auto fn) +{ + on_error(fn, []() {}); +} +#endif
diff --git a/sdk/lib/README.md b/sdk/lib/README.md index d412525..8d272c6 100644 --- a/sdk/lib/README.md +++ b/sdk/lib/README.md
@@ -7,11 +7,18 @@ This collection currently includes: - [atomic](atomic/) provides atomic support functions. + - [compartment_helpers](compartment_helpers/) contains helpers for checking / ensuring that pointers are valid. - [crt](crt/) provides C runtime functions that the compiler may emit. - [cxxrt](cxxrt/) provides a minimal C++ runtime (no exceptions or RTTI support). + - [debug](debug/) contains functions to support the debug logging APIs. + - [event_group](event_group/) contains a FreeRTOS-like event-group API. - [freestanding](freestanding/) provides a minimal free-standing C implementation. + - [locks](locks/) contains functions for various kinds of lock. + - [microvium](microvium/) builds the [microvium](https://github.com/coder-mike/microvium) JavaScript VM to provide an on-device JavaScript interpreter. + - [queue](queue/) contains functions for message queues. - [stdio](stdio/) provides a *very* limited subset of `stdio.h` for debugging. - [string](string/) provides `string.h` functions. - [thread_pool](thread_pool) provides a simple thread pool that other threads can dispatch work to for asynchronous execution. - - [microvium](microvium/) builds the [microvium](https://github.com/coder-mike/microvium) JavaScript VM to provide an on-device JavaScript interpreter. + - [unwind_error_handler](unwind_error_handler) provides an error handler that unwinds the stack. +
diff --git a/sdk/lib/unwind_error_handler/README.md b/sdk/lib/unwind_error_handler/README.md new file mode 100644 index 0000000..e7632d5 --- /dev/null +++ b/sdk/lib/unwind_error_handler/README.md
@@ -0,0 +1,10 @@ +Unwind error handler library +---------------------------- + +This library provides an error handler that unwinds using the APIs in [`unwind.h`](../../include/unwind.h). + +This error handler can work even in cases of stack overflow. + +Note: Unlike most libraries, this is *not* built as a shared library. +Error handlers must be part of the compartment that invokes them. +As a result, this must be added as a dependency of each compartment that wishes to use it, rather than as a dependency of the firmware target.
diff --git a/sdk/lib/unwind_error_handler/unwind.S b/sdk/lib/unwind_error_handler/unwind.S new file mode 100644 index 0000000..6e56a98 --- /dev/null +++ b/sdk/lib/unwind_error_handler/unwind.S
@@ -0,0 +1,39 @@ +.section .compartment_error_handler_stackless,"aw",@progbits +.globl compartment_error_handler_stackless +.p2align 2 +.type compartment_error_handler_stackless,@function +compartment_error_handler_stackless: +// Get the head of the error list. + cgettop t0, csp + csetaddr csp, csp, t0 + clc cs0, -8(csp) + beqz s0, .Lforce_unwind +// Pop the top error from the list. */ + clc ct0, 0(cs0) + csc ct0, -8(csp) +// Mark this error handler as having finished. We may still trap again +// and reenter this, but now that we've popped the top element from the +// stack we will run some different cleanup code next time. */ +.Llookup_reset: + auipcc ct2, %cheriot_compartment_hi(__library_import_libcalls__Z39switcher_handler_invocation_count_resetv) + clc ct2, %cheriot_compartment_lo_i(.Llookup_reset)(ct2) + cjalr ct2 +// longjmp to the error handler. + clc cs1, 16(cs0) + clc csp, 24(cs0) + clc cra, 32(cs0) + clc cs0, 8(cs0) + cjr cra +.Lforce_unwind: + li a0, 1 + cret + + .section .compartment_imports,"aG",@progbits,__library_import_libcalls__Z39switcher_handler_invocation_count_resetv,comdat + .type __library_import_libcalls__Z39switcher_handler_invocation_count_resetv,@object + .weak __library_import_libcalls__Z39switcher_handler_invocation_count_resetv + .p2align 3 +__library_import_libcalls__Z39switcher_handler_invocation_count_resetv: + .word __library_export_libcalls__Z39switcher_handler_invocation_count_resetv+1 + .word 0 + .size __library_import_libcalls__Z39switcher_handler_invocation_count_resetv, 8 +
diff --git a/sdk/lib/unwind_error_handler/xmake.lua b/sdk/lib/unwind_error_handler/xmake.lua new file mode 100644 index 0000000..b27593a --- /dev/null +++ b/sdk/lib/unwind_error_handler/xmake.lua
@@ -0,0 +1,3 @@ +target("unwind_error_handler") + set_kind("object") + add_files("unwind.S")
diff --git a/sdk/lib/xmake.lua b/sdk/lib/xmake.lua index 66701dd..4833910 100644 --- a/sdk/lib/xmake.lua +++ b/sdk/lib/xmake.lua
@@ -11,4 +11,5 @@ "queue", "stdio", "string", - "thread_pool") + "thread_pool", + "unwind_error_handler")
diff --git a/sdk/xmake.lua b/sdk/xmake.lua index bac906e..da170b7 100644 --- a/sdk/xmake.lua +++ b/sdk/xmake.lua
@@ -747,7 +747,7 @@ table.insert(objects, dep:targetfile()) end end) - batchcmds:vrunv(target:tool("ld"), table.join({"--script=" .. linkerscript, "--relax", "-o", target:targetfile(), "--compartment-report=" .. target:targetfile() .. ".json" }, objects), opt) + batchcmds:vrunv(target:tool("ld"), table.join({"-n", "--script=" .. linkerscript, "--relax", "-o", target:targetfile(), "--compartment-report=" .. target:targetfile() .. ".json" }, objects), opt) batchcmds:show_progress(opt.progress, "Creating firmware report " .. target:targetfile() .. ".json") batchcmds:show_progress(opt.progress, "Creating firmware dump " .. target:targetfile() .. ".dump") batchcmds:vexecv(target:tool("objdump"), {"-glxsdrS", "--demangle", target:targetfile()}, table.join(opt, {stdout = target:targetfile() .. ".dump"}))
diff --git a/tests/allocator-test.cc b/tests/allocator-test.cc index c64eaac..97f19c9 100644 --- a/tests/allocator-test.cc +++ b/tests/allocator-test.cc
@@ -615,7 +615,7 @@ /** * Allocator test entry point. */ -void test_allocator() +int test_allocator() { GlobalConstructors::run(); @@ -682,4 +682,5 @@ TEST(quotaLeft == MALLOC_QUOTA, "After alloc and free from 0x100000-byte quota, {} bytes left", quotaLeft); + return 0; }
diff --git a/tests/check_pointer-test.cc b/tests/check_pointer-test.cc index a678de8..c079185 100644 --- a/tests/check_pointer-test.cc +++ b/tests/check_pointer-test.cc
@@ -128,7 +128,8 @@ "with a raw capability."); } -void test_check_pointer() +int test_check_pointer() { check_pointer_strict_mode(&object); + return 0; }
diff --git a/tests/compartment_calls-test.cc b/tests/compartment_calls-test.cc index 1049560..2f0cff1 100644 --- a/tests/compartment_calls-test.cc +++ b/tests/compartment_calls-test.cc
@@ -44,7 +44,7 @@ TEST(ret == 0, "compartment_call_inner returend {}", ret); } -void test_compartment_call() +int test_compartment_call() { bool outTestFailed = false; int ret = 0; @@ -58,9 +58,7 @@ TEST(!trusted_stack_has_space(9), "Trusted stack should not have space for 9 more calls"); - register char *cspRegister asm("csp"); - asm("" : "=C"(cspRegister)); - CHERI::Capability<void> csp{cspRegister}; + CHERI::Capability<void> csp{__builtin_cheri_stack_get()}; CHERI::Capability<void> originalCSP{switcher_recover_stack()}; csp.address() = originalCSP.address(); TEST(csp == originalCSP, @@ -69,10 +67,9 @@ csp); test_number_of_arguments(); - ret = test_incorrect_export_table_with_handler(nullptr); - TEST(ret == -1, "Test incorrect entry point with error handler failed"); test_incorrect_export_table(nullptr, &outTestFailed); TEST(outTestFailed == false, "Test incorrect entry point without error handler failed"); + return 0; }
diff --git a/tests/compartment_calls.h b/tests/compartment_calls.h index a93c81d..b740f31 100644 --- a/tests/compartment_calls.h +++ b/tests/compartment_calls.h
@@ -43,6 +43,6 @@ bool *outTestFailed); __cheri_compartment( "compartment_calls_inner_with_" - "handler") int test_incorrect_export_table_with_handler(__cheri_callback void (*fn)()); + "handler") int test_incorrect_export_table_with_handler(__cheri_callback int (*fn)()); __cheri_compartment("compartment_calls_outer") void compartment_call_outer(); -constexpr int ConstantValue = 0x41414141; \ No newline at end of file +constexpr int ConstantValue = 0x41414141;
diff --git a/tests/compartment_calls_inner_with_handler.cc b/tests/compartment_calls_inner_with_handler.cc deleted file mode 100644 index d82ca8f..0000000 --- a/tests/compartment_calls_inner_with_handler.cc +++ /dev/null
@@ -1,33 +0,0 @@ -// Copyright Microsoft and CHERIoT Contributors. -// SPDX-License-Identifier: MIT - -#define TEST_NAME "Compartment calls (inner compartment)" -#include "compartment_calls.h" -#include "tests.hh" -#include <cheri.hh> -#include <errno.h> -#include <tuple> - -using namespace CHERI; - -extern "C" ErrorRecoveryBehaviour -compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval) -{ - return ErrorRecoveryBehaviour::ForceUnwind; -} - -int test_incorrect_export_table_with_handler(__cheri_callback void (*fn)()) -{ - /* - * Trigger a cross-compartment call with an invalid export entry. - */ - - debug_log( - "test an incorrect export table entry with error handler installed"); - - fn(); - - TEST(false, "Should be unreachable"); - - return 0; -} \ No newline at end of file
diff --git a/tests/crash_recovery-test.cc b/tests/crash_recovery-test.cc index 9048335..0254654 100644 --- a/tests/crash_recovery-test.cc +++ b/tests/crash_recovery-test.cc
@@ -3,32 +3,40 @@ #define TEST_NAME "Crash recovery (main runner)" #include "crash_recovery.h" +#include <atomic> #include <cheri.hh> #include <errno.h> -int crashes = 0; +int crashes = 0; +std::atomic<bool> expectFault; extern "C" enum ErrorRecoveryBehaviour compartment_error_handler(struct ErrorState *frame, size_t mcause, size_t mtval) { - debug_log("Test saw error for PCC {}", frame->pcc); - debug_log("Error cause: {}, mtval: {}", mcause, mtval); + crashes++; if (mcause == 0x2) { + if (expectFault) + { + expectFault = false; + frame->pcc = static_cast<char *>(frame->pcc) + 2; + return ErrorRecoveryBehaviour::InstallContext; + } debug_log("Test hit assertion failure, unwinding"); return ErrorRecoveryBehaviour::ForceUnwind; } + debug_log("Test saw error for PCC {}", frame->pcc); + debug_log("Error cause: {}, mtval: {}", mcause, mtval); TEST((mcause == 0x1c) && (mtval == 0), "mcause should be 0x1c (CHERI), is {}, mtval should be 0 (force " "unwind), is {})", mcause, mtval); - crashes++; debug_log("Resuming test at failure location"); return ErrorRecoveryBehaviour::InstallContext; } -void test_crash_recovery() +int test_crash_recovery() { debug_log("Calling crashy compartment indirectly"); test_crash_recovery_outer(0); @@ -38,28 +46,54 @@ "nested call crashed"); debug_log("Calling crashy compartment to fault and unwind"); - test_crash_recovery_inner(0); + void *ret = test_crash_recovery_inner(0); check_stack(); - debug_log("Calling crashy compartment returned (crashes: {})", crashes); - TEST(crashes == 1, "Failed to notice crash"); + debug_log("Calling crashy compartment returned ({})", ret); + TEST(crashes == 0, "Should not have crashed"); + TEST(ret != nullptr, "Failed to notice crash"); debug_log("Calling crashy compartment to return normally"); - test_crash_recovery_inner(1); + ret = test_crash_recovery_inner(1); check_stack(); debug_log("Calling crashy compartment returned (crashes: {})", crashes); - TEST(crashes == 1, "Should not have crashed"); + TEST(crashes == 0, "Should not have crashed"); + TEST(ret == nullptr, "Failed to notice crash"); debug_log("Returning normally from crash test"); debug_log("Calling crashy compartment to double fault and unwind"); - test_crash_recovery_inner(2); + ret = test_crash_recovery_inner(2); check_stack(); debug_log("Calling crashy compartment returned (crashes: {})", crashes); - TEST(crashes == 2, "Failed to notice crash"); + TEST(crashes == 0, "Should not have crashed"); + TEST(ret != nullptr, "Failed to notice crash"); debug_log( "Calling crashy compartment to corrupt CSP in stack pointer and unwind"); - test_crash_recovery_inner(3); + ret = test_crash_recovery_inner(3); check_stack(); debug_log("Calling crashy compartment returned (crashes: {})", crashes); - TEST(crashes == 3, "Failed to notice crash"); + TEST(crashes == 0, "Should not have crashed"); + TEST(ret != nullptr, "Failed to notice crash"); + + ptraddr_t handlerCount = switcher_handler_invocation_count_reset(); + TEST(handlerCount == crashes * 2, + "Should have called handler 3 times (3 entries, 3 exits giving a " + "total of {}), was {}", + crashes * 2, + handlerCount); + + // By default, we will be force unwound if we exceed 512 error-handler + // invocations. + constexpr int MaxCrashes = 600; + for (int i = 0; i < MaxCrashes; i++) + { + switcher_handler_invocation_count_reset(); + expectFault = true; + // Crash with a guaranteed 16-bit instruction. This cannot use + // `__builtin_trap` because the compiler knows that `__builtin_trap` + // does not return and so will not generate code following it. + asm volatile("c.unimp"); + } + TEST(crashes == MaxCrashes, "Failed to notice crash"); + return 0; }
diff --git a/tests/debug-test.c b/tests/debug-test.c index 03c008c..9be9854 100644 --- a/tests/debug-test.c +++ b/tests/debug-test.c
@@ -1,7 +1,7 @@ #include <compartment.h> #include <debug.h> -__cheri_compartment("debug_test") void test_debug_c() +__cheri_compartment("debug_test") int test_debug_c() { unsigned char x = 'c'; _Bool t = true; @@ -22,4 +22,5 @@ CHERIOT_INVARIANT(true, "Testing C++ invariant failure"); CHERIOT_INVARIANT( true, "Testing C++ invariant failure: 42:{}", 42, 1, 3, 4, "oops"); + return 0; }
diff --git a/tests/debug-test.cc b/tests/debug-test.cc index e44bb65..5828f38 100644 --- a/tests/debug-test.cc +++ b/tests/debug-test.cc
@@ -2,7 +2,7 @@ #include <compartment.h> #include <debug.h> -void test_debug_cxx() +int test_debug_cxx() { unsigned char x = 'c'; CHERIOT_DEBUG_LOG("Debug messages", @@ -19,4 +19,5 @@ CHERIOT_INVARIANT(true, "Testing C++ invariant failure"); CHERIOT_INVARIANT( true, "Testing C++ invariant failure: 42:{}", 42, 1, 3, 4, "oops"); + return 0; }
diff --git a/tests/eventgroup-test.cc b/tests/eventgroup-test.cc index aed68c1..12cebed 100644 --- a/tests/eventgroup-test.cc +++ b/tests/eventgroup-test.cc
@@ -25,7 +25,7 @@ } } // namespace -void test_eventgroup() +int test_eventgroup() { EventGroup *group; @@ -70,4 +70,5 @@ ret = eventgroup_clear(&t, group, &bits, 0b100); TEST(ret == 0, "Failed to clear event group bits: {}", ret); TEST(bits == 0b1000, "Bits should be 0b1000, but is {}", bits); + return 0; }
diff --git a/tests/futex-test.cc b/tests/futex-test.cc index 970253f..4abfe6c 100644 --- a/tests/futex-test.cc +++ b/tests/futex-test.cc
@@ -21,7 +21,7 @@ true); #endif -void test_futex() +int test_futex() { static uint32_t futex; int ret; @@ -185,4 +185,5 @@ "PI futex with a zero thread ID returned {}, should be {}", ret, -EINVAL); + return 0; }
diff --git a/tests/list-test.cc b/tests/list-test.cc index 7db652e..5ea7700 100644 --- a/tests/list-test.cc +++ b/tests/list-test.cc
@@ -63,7 +63,7 @@ */ ds::linked_list::Sentinel<LinkedObject::ObjectRing> objects = {}; -void test_list() +int test_list() { debug_log("Testing the list implementation."); @@ -238,4 +238,5 @@ heapAtEnd); debug_log("Done testing the list."); + return 0; }
diff --git a/tests/locks-test.cc b/tests/locks-test.cc index 7b8db43..d85fea2 100644 --- a/tests/locks-test.cc +++ b/tests/locks-test.cc
@@ -396,7 +396,7 @@ } // namespace -void test_locks() +int test_locks() { test_lock(flagLock); test_lock(flagLockPriorityInherited); @@ -412,4 +412,5 @@ test_ticket_lock_ordering(); test_ticket_lock_overflow(); test_recursive_mutex(); + return 0; }
diff --git a/tests/misc-test.cc b/tests/misc-test.cc index 28352b1..f537067 100644 --- a/tests/misc-test.cc +++ b/tests/misc-test.cc
@@ -170,7 +170,7 @@ permissions); } -void test_misc() +int test_misc() { check_timeouts(); check_memchr(); @@ -200,4 +200,5 @@ void, test_word, true, false, false, false), 4, {Permission::Global, Permission::Load}); + return 0; }
diff --git a/tests/mmio-test.cc b/tests/mmio-test.cc index 4b090e5..bfffdc1 100644 --- a/tests/mmio-test.cc +++ b/tests/mmio-test.cc
@@ -14,7 +14,7 @@ p); } -void test_mmio() +int test_mmio() { check_permissions( MMIO_CAPABILITY_WITH_PERMISSIONS(Uart, uart, false, false, false, false), @@ -74,4 +74,5 @@ Permission::Store, Permission::LoadStoreCapability, Permission::LoadMutable}); + return 0; }
diff --git a/tests/multiwaiter-test.cc b/tests/multiwaiter-test.cc index b899be9..d0f380f 100644 --- a/tests/multiwaiter-test.cc +++ b/tests/multiwaiter-test.cc
@@ -14,7 +14,7 @@ using namespace CHERI; using namespace thread_pool; -void test_multiwaiter() +int test_multiwaiter() { static uint32_t futex = 0; static uint32_t futex2 = 0; @@ -130,4 +130,5 @@ free(queueMemory); multiwaiter_delete(MALLOC_CAPABILITY, mw); + return 0; }
diff --git a/tests/queue-test.cc b/tests/queue-test.cc index 5239f19..da18b87 100644 --- a/tests/queue-test.cc +++ b/tests/queue-test.cc
@@ -185,10 +185,11 @@ debug_log("All FreeRTOS queue tests successful"); } -void test_queue() +int test_queue() { test_queue_unsealed(); test_queue_sealed(); test_queue_freertos(); debug_log("All queue tests successful"); + return 0; }
diff --git a/tests/stack-test.cc b/tests/stack-test.cc index 71ebd27..c814bf9 100644 --- a/tests/stack-test.cc +++ b/tests/stack-test.cc
@@ -125,19 +125,19 @@ * - compartment stack with incorrect permissions * - invalid compartment stack */ -void test_stack() +int test_stack() { - int ret = test_with_small_stack(128); - TEST(ret == 0, - "test_with_small_stack failed, returned {} with 128-byte stack", - ret); - ret = test_with_small_stack(144); + int ret = test_with_small_stack(144); TEST(ret == 0, "test_with_small_stack failed, returned {} with 144-byte stack", ret); - ret = test_with_small_stack(112); + ret = test_with_small_stack(160); + TEST(ret == 0, + "test_with_small_stack failed, returned {} with 160-byte stack", + ret); + ret = test_with_small_stack(128); TEST(ret == -ENOTENOUGHSTACK, - "test_with_small_stack failed, returned {} with 112-byte stack", + "test_with_small_stack failed, returned {} with 128-byte stack", ret); __cheri_callback void (*callback)() = cross_compartment_call; @@ -178,4 +178,5 @@ expect_handler(false); test_stack_invalid_on_call(callback); + return 0; }
diff --git a/tests/static_sealing-test.cc b/tests/static_sealing-test.cc index 9730719..046d704 100644 --- a/tests/static_sealing-test.cc +++ b/tests/static_sealing-test.cc
@@ -15,9 +15,10 @@ test, 42); -void test_static_sealing() +int test_static_sealing() { // Get a pointer to it and ask for it to be unsealed. Sealed<TestType> value{STATIC_SEALED_VALUE(test)}; test_static_sealed_object(value); + return 0; }
diff --git a/tests/stdio-test.cc b/tests/stdio-test.cc index a23aa0b..b7f2ac2 100644 --- a/tests/stdio-test.cc +++ b/tests/stdio-test.cc
@@ -3,7 +3,7 @@ #include "tests.hh" #include <stdio.h> -void test_stdio() +int test_stdio() { debug_log("Printing 'Hello, world!' to stdout"); printf("Hello, world!\n"); @@ -19,4 +19,5 @@ TEST(strcmp(buffer, "-42") == 0, "snprintf(\"%d\", -42) gave {}", std::string_view{buffer, BufferSize}); + return 0; }
diff --git a/tests/test-runner.cc b/tests/test-runner.cc index dfc1d10..0c85f14 100644 --- a/tests/test-runner.cc +++ b/tests/test-runner.cc
@@ -11,6 +11,9 @@ namespace { + /// Have we detected a crash in any of the compartments? + volatile bool crashDetected = false; + /** * Read the cycle counter. */ @@ -33,16 +36,28 @@ */ void run_timed(const char *msg, auto &&fn) { - int startCycles = rdcycle(); - fn(); + bool failed = false; + int startCycles = rdcycle(); + if constexpr (std::is_same_v<std::invoke_result_t<decltype(fn)>, void>) + { + fn(); + } + else + { + failed = (fn() != 0); + } int cycles = rdcycle(); - debug_log("{} finished in {} cycles", msg, cycles - startCycles); + + if (failed) + { + debug_log("{} failed", msg); + crashDetected = true; + } + else + debug_log("{} finished in {} cycles", msg, cycles - startCycles); } } // namespace -/// Have we detected a crash in any of the compartments? -volatile bool crashDetected = false; - extern "C" enum ErrorRecoveryBehaviour compartment_error_handler(struct ErrorState *frame, size_t mcause, size_t mtval) { @@ -111,10 +126,20 @@ std::string_view{testString, 13}); const std::string S = "I am a walrus"s; debug_log("Trying to print std::string: {}", S); + // Test stack pointer recovery in the root compartment. + CHERI::Capability<void> csp{__builtin_cheri_stack_get()}; + CHERI::Capability<void> originalCSP{switcher_recover_stack()}; + csp.address() = originalCSP.address(); + TEST(csp == originalCSP, + "Original stack pointer: {}\ndoes not match current stack pointer: {}", + originalCSP, + csp); + run_timed("All tests", []() { run_timed("Debug helpers (C++)", test_debug_cxx); run_timed("Debug helpers (C)", test_debug_c); run_timed("MMIO", test_mmio); + run_timed("Unwind cleanup", test_unwind_cleanup); run_timed("stdio", test_stdio); run_timed("Static sealing", test_static_sealing); run_timed("Crash recovery", test_crash_recovery);
diff --git a/tests/tests.hh b/tests/tests.hh index 4193ed3..14780ed 100644 --- a/tests/tests.hh +++ b/tests/tests.hh
@@ -5,24 +5,25 @@ #include <debug.hh> #include <thread.h> -__cheri_compartment("eventgroup_test") void test_eventgroup(); -__cheri_compartment("mmio_test") void test_mmio(); -__cheri_compartment("allocator_test") void test_allocator(); -__cheri_compartment("thread_pool_test") void test_thread_pool(); -__cheri_compartment("futex_test") void test_futex(); -__cheri_compartment("queue_test") void test_queue(); -__cheri_compartment("locks_test") void test_locks(); -__cheri_compartment("list_test") void test_list(); -__cheri_compartment("crash_recovery_test") void test_crash_recovery(); -__cheri_compartment("multiwaiter_test") void test_multiwaiter(); -__cheri_compartment("stack_test") void test_stack(); -__cheri_compartment("compartment_calls_test") void test_compartment_call(); -__cheri_compartment("check_pointer_test") void test_check_pointer(); -__cheri_compartment("misc_test") void test_misc(); -__cheri_compartment("static_sealing_test") void test_static_sealing(); -__cheri_compartment("stdio_test") void test_stdio(); -__cheri_compartment("debug_test") void test_debug_cxx(); -__cheri_compartment("debug_test") void test_debug_c(); +__cheri_compartment("eventgroup_test") int test_eventgroup(); +__cheri_compartment("mmio_test") int test_mmio(); +__cheri_compartment("allocator_test") int test_allocator(); +__cheri_compartment("thread_pool_test") int test_thread_pool(); +__cheri_compartment("futex_test") int test_futex(); +__cheri_compartment("queue_test") int test_queue(); +__cheri_compartment("locks_test") int test_locks(); +__cheri_compartment("list_test") int test_list(); +__cheri_compartment("crash_recovery_test") int test_crash_recovery(); +__cheri_compartment("multiwaiter_test") int test_multiwaiter(); +__cheri_compartment("stack_test") int test_stack(); +__cheri_compartment("compartment_calls_test") int test_compartment_call(); +__cheri_compartment("check_pointer_test") int test_check_pointer(); +__cheri_compartment("misc_test") int test_misc(); +__cheri_compartment("static_sealing_test") int test_static_sealing(); +__cheri_compartment("stdio_test") int test_stdio(); +__cheri_compartment("debug_test") int test_debug_cxx(); +__cheri_compartment("debug_test") int test_debug_c(); +__cheri_compartment("unwind_cleanup_test") int test_unwind_cleanup(); // Simple tests don't need a separate compartment. void test_global_constructors();
diff --git a/tests/thread_pool-test.cc b/tests/thread_pool-test.cc index 4499d09..52a08e0 100644 --- a/tests/thread_pool-test.cc +++ b/tests/thread_pool-test.cc
@@ -44,7 +44,7 @@ return ErrorRecoveryBehaviour::InstallContext; } -void test_thread_pool() +int test_thread_pool() { // We can't share stack variables, so create a heap allocation that we can // capture as an explicit pointer. @@ -137,7 +137,7 @@ Timeout t{3}; thread_sleep(&t); TEST(interrupted, "Worker thread was not interrupted"); - return; + return 0; static cheriot::atomic<uint32_t> barrier{3}; auto barrierWait = []() { uint32_t value = barrier--; @@ -156,4 +156,5 @@ async(barrierWait); barrierWait(); debug_log("Thread pool quiesced"); + return 0; }
diff --git a/tests/unwind_cleanup-test.cc b/tests/unwind_cleanup-test.cc new file mode 100644 index 0000000..e368562 --- /dev/null +++ b/tests/unwind_cleanup-test.cc
@@ -0,0 +1,144 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#define TEST_NAME "Test unwind cleanup" +#include "locks.hh" +#include "tests.hh" +#include "unwind.h" + +extern "C" ErrorRecoveryBehaviour +compartment_error_handler(ErrorState *, size_t, size_t) +{ + cleanup_unwind(); + return ErrorRecoveryBehaviour::ForceUnwind; +} + +namespace +{ + void test_setjmp() + { + jmp_buf env; + volatile int x = 0; + if (int r = setjmp(env); r == 0) + { + TEST_EQUAL(x, 0, "setjmp should return 0 the first time"); + x = 42; + longjmp(env, 1); + } + else + { + TEST_EQUAL(r, 1, "setjmp should return 1 the second time"); + TEST_EQUAL( + x, 42, "On the second return, x should have been modified"); + x = 53; + } + TEST_EQUAL(x, 53, "After longjmp, x should have been modified"); + } + + FlagLock flagLock; + + void test_on_error() + { + LockGuard g(flagLock); + on_error([&]() { cleanup_unwind(); }, [&]() { g.unlock(); }); + TEST(!g, "on_error should lock the lock"); + } + + void test_on_error_raii_inner() + { + LockGuard g(flagLock); + // No handler. g's destructor runs after on_error returns. + on_error([&]() { cleanup_unwind(); }); + } + + void test_on_error_raii() + { + test_on_error_raii_inner(); + TEST(flagLock.try_lock(), "raii should have been dropped the lock"); + flagLock.unlock(); + } + + void test_c_macros() + { + volatile int x = 0; + CHERIOT_DURING + { + x = 42; + cleanup_unwind(); + } + CHERIOT_HANDLER + { + TEST_EQUAL(x, 42, "In the handler, x should have been modified"); + x = 53; + } + CHERIOT_END_HANDLER + TEST_EQUAL(x, 53, "After longjmp, object should have been modified"); + } + + __noinline void overflow_stack(volatile int *x) + { + int hugeBuffer[4096]; + debug_log("Overflowing stack: {}", hugeBuffer); + } + + /** + * Make sure that we can unwind out of a trap. This will invoke the normal + * error handler. + */ + void test_from_trap() + { + volatile int x = 0; + CHERIOT_DURING + { + x = 42; + __builtin_trap(); + } + CHERIOT_HANDLER + { + TEST_EQUAL(x, 42, "In the handler, x should have been modified"); + x = 53; + } + CHERIOT_END_HANDLER + TEST_EQUAL( + x, 53, "After error handler, object should have been modified"); + } + + /** + * Make sure that we can unwind out of a stack overflow. This will invoke + * the stackless error handler. + */ + void test_from_stack_overflow() + { + volatile int x = 0; + CHERIOT_DURING + { + x = 42; + overflow_stack(&x); + } + CHERIOT_HANDLER + { + debug_log("Error handler"); + TEST_EQUAL(x, 42, "In the handler, x should have been modified"); + x = 53; + } + CHERIOT_END_HANDLER + TEST_EQUAL(x, 53, "After handler, object should have been modified"); + } + +} // namespace + +int test_unwind_cleanup() +{ + test_setjmp(); + test_on_error(); + test_c_macros(); + // Try these in both orders to make sure that both error handlers correctly + // clean up. + test_from_trap(); + test_from_stack_overflow(); + + test_from_stack_overflow(); + test_from_trap(); + debug_log("Test unwind_cleanup passed"); + return 0; +}
diff --git a/tests/xmake.lua b/tests/xmake.lua index bbc1d78..7ecc6cb 100644 --- a/tests/xmake.lua +++ b/tests/xmake.lua
@@ -79,8 +79,6 @@ test("stack") compartment("compartment_calls_inner") add_files("compartment_calls_inner.cc") -compartment("compartment_calls_inner_with_handler") - add_files("compartment_calls_inner_with_handler.cc") test("compartment_calls") test("check_pointer") -- Test various APIs that are too small to deserve their own test file @@ -88,6 +86,8 @@ on_load(function(target) target:values_set("shared_objects", { exampleK = 1024, test_word = 4 }, {expand = false}) end) +test("unwind_cleanup") + add_deps("unwind_error_handler") includes(path.join(sdkdir, "lib")) @@ -117,11 +117,12 @@ add_deps("multiwaiter_test") add_deps("ccompile_test") add_deps("stack_test", "stack_integrity_thread") - add_deps("compartment_calls_test", "compartment_calls_inner", "compartment_calls_inner_with_handler") + add_deps("compartment_calls_test", "compartment_calls_inner") add_deps("check_pointer_test") add_deps("misc_test") add_deps("stdio_test") add_deps("debug_test") + add_deps("unwind_cleanup_test") -- Set the thread entry point to the test runner. on_load(function(target) target:values_set("board", "$(board)")