diff --git a/tests/ztest/error_hook/CMakeLists.txt b/tests/ztest/error_hook/CMakeLists.txt new file mode 100644 index 00000000000..91820905e7a --- /dev/null +++ b/tests/ztest/error_hook/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(integration) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) +zephyr_include_directories(include) diff --git a/tests/ztest/error_hook/README.txt b/tests/ztest/error_hook/README.txt new file mode 100644 index 00000000000..0f0a0b4e877 --- /dev/null +++ b/tests/ztest/error_hook/README.txt @@ -0,0 +1,198 @@ +Title: A common fatal error and assert fail handler + +Description: + +These two common handler is in order to reduce the redundancy code writing +for fatal and assert handler for error case testing. They can be used both +in kernel and userspace, and are also SMP safe. + + +Why doing this +============== + +When writing error testing case (or we call it negative test case), we might +have to write self-defined k_sys_fatal_handler or post_assert_handler to deal +with the errors we caught, in order to make test going on. This mean much +identical code would be written. So we try to make it as a common part and +let other test case or app can reuse it. + +And when error happened, it sometimes need a special action to make our testing +program back to normal, such as release some resource hold before error +happened. This is why we add a hook on it, in order to achieve that goal. + + +How to use it in you app +======================== + +(a) Usage for dealing with fatal error: + +Step1: Add CONFIG_ZTEST_FATAL_HOOK=y into prj.conf + +Step2: Include in C source file. + +Step3: (optional) Define a hook function call ztest_post_fatal_error_hook(). + +Step4: Call ztest_set_fault_valid(true) before where your target function + call. + + +(b) Usage for dealing with assert fail: + +Step1: Add CONFIG_ZTEST_ASSERT_HOOK=y into prj.conf + +Step2: Include in your C code. + +Step3: (optional) Define a hook function call ztest_post_assert_fail_hook(). + +Step4: call ztest_set_assert_valid(true) before where your target function + call. + + +You can choose to use one or both of them, depneds on your need. +If you use none of them, you can still defined your own fatal or assert handler +as usual. + + +Test example in this test set +============================= + +This test verifies if the common fatal error and assert fail handler works. +If the expected error got caught, the test case pass. + +test_catch_assert_fail + - To call a function then giving the condition to trigger the assert fail, + then catch it by the assert handler. + +test_catch_fatal_error + - start a thread to test trigger a null address then catch fatal error. + - start a thread to test trigger a illegal instruction then catch fatal + error. + - start a thread to test trigger a divide zero error then catch fatal error. + - start a thread to call k_oops() then catch fatal error. + - start a thread to call k_panel() then catch fatal error. + +test_catch_assert_in_isr + - start a thread to enter ISR context by calling irq_offload(), then trigger + an assert fail then catch it. + +test_catch_z_oops + - Pass illegal address by syscall, then inside the syscall handler, the + Z_OOPS macro will trigger a fatal error then got caught. + + + +Limitation of this usage +======================== + +Trigger an fatal error in ISR context, that will cause problem due to +the interrupt stack is already abnormal when we want to continue other +test case, we do not recover it so far. + + +--------------------------------------------------------------------------- + +Sample Output: + +Running test suite error_hook_tests +=================================================================== +START - test_catch_assert_fail +ASSERTION FAIL [a != ((void *)0)] @ WEST_TOPDIR/zephyr/tests/ztest/error_hook/src/main.c:41 + parameter a should not be NULL! +Caught assert failed +Assert error expected as part of test case. + PASS - test_catch_assert_fail +=================================================================== +START - test_catch_fatal_error +case type is 0 +E: Page fault at address (nil) (error code 0x4) +E: Linear address not present in page tables +E: Access violation: user thread not allowed to read +E: PTE: not present +E: EAX: 0x00000000, EBX: 0x00000000, ECX: 0x00000000, EDX: 0x0010fe51 +E: ESI: 0x00000000, EDI: 0x0012dfe8, EBP: 0x0012dfcc, ESP: 0x0012dfc4 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x00100439 +E: 0x001010ea (0x113068) +E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0 +E: Current thread: 0x114000 (unknown) +Caught system error -- reason 0 1 +Fatal error expected as part of test case. +case type is 1 +E: Page fault at address 0x12dfc4 (error code 0x15) +E: Access violation: user thread not allowed to execute +E: PTE: 0x12d000 -> 0x000000000012d000: RW US A D XD +E: EAX: 0x0012dfc4, EBX: 0x00000001, ECX: 0x00000001, EDX: 0x0010fe51 +E: ESI: 0x00000000, EDI: 0x0012dfe8, EBP: 0x0012dfcc, ESP: 0x0012dfc0 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x0012dfc4 +E: 0x001010ea (0x113068) +E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0 +E: Current thread: 0x114000 (unknown) +Caught system error -- reason 0 1 +Fatal error expected as part of test case. +case type is 2 +E: Invalid opcode +E: EAX: 0x00000000, EBX: 0x00000002, ECX: 0x00000002, EDX: 0x0010fe51 +E: ESI: 0x00000000, EDI: 0x0012dfe8, EBP: 0x0012dfcc, ESP: 0x0012dfc4 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x00100451 +E: 0x001010ea (0x113068) +E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0 +E: Current thread: 0x114000 (unknown) +Caught system error -- reason 0 1 +Fatal error expected as part of test case. +case type is 3 +E: EAX: 0x00000000, EBX: 0x00000003, ECX: 0x00000003, EDX: 0x0010fe51 +E: ESI: 0x00000000, EDI: 0x0012dfe8, EBP: 0x0012dfcc, ESP: 0x0012dfc0 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x0010045c +E: 0x001010ea (0x113068) +E: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0 +E: Current thread: 0x114000 (unknown) +Caught system error -- reason 3 1 +Fatal error expected as part of test case. +case type is 4 +E: EAX: 0x00000000, EBX: 0x00000004, ECX: 0x00000004, EDX: 0x0010fe51 +E: ESI: 0x00000000, EDI: 0x0012dfe8, EBP: 0x0012dfcc, ESP: 0x0012dfc0 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x00100465 +E: 0x001010ea (0x113068) +E: >>> ZEPHYR FATAL ERROR 3: Kernel oops on CPU 0 +E: Current thread: 0x114000 (unknown) +Caught system error -- reason 3 1 +Fatal error expected as part of test case. + PASS - test_catch_fatal_error +=================================================================== +START - test_catch_assert_in_isr +ASSERTION FAIL [a != ((void *)0)] @ WEST_TOPDIR/zephyr/tests/ztest/error_hook/src/main.c:41 + parameter a should not be NULL! +Caught assert failed +Assert error expected as part of test case. + PASS - test_catch_assert_in_isr +=================================================================== +START - test_catch_z_oops +E: Page fault at address (nil) (error code 0x4) +E: Linear address not present in page tables +E: Access violation: user thread not allowed to read +E: PTE: not present +E: EAX: 0x00000000, EBX: 0x0011303c, ECX: 0x00000000, EDX: 0x0011303c +E: ESI: 0x00000000, EDI: 0x00130fe8, EBP: 0x00130fc0, ESP: 0x00130fc0 +E: EFLAGS: 0x00000246 CS: 0x002b CR3: 0x001142c0 +E: call trace: +E: EIP: 0x00100544 +E: 0x00104808 (0x130033) +E: 0x001010ea (0x11303c) +E: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0 +E: Current thread: 0x1140a0 (unknown) +Caught system error -- reason 0 1 +Fatal error expected as part of test case. + PASS - test_catch_z_oops +=================================================================== +Test suite error_hook_tests succeeded +=================================================================== +PROJECT EXECUTION SUCCESSFUL diff --git a/tests/ztest/error_hook/prj.conf b/tests/ztest/error_hook/prj.conf new file mode 100644 index 00000000000..476cfce144d --- /dev/null +++ b/tests/ztest/error_hook/prj.conf @@ -0,0 +1,5 @@ +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y +CONFIG_IRQ_OFFLOAD=y +CONFIG_ZTEST_FATAL_HOOK=y +CONFIG_ZTEST_ASSERT_HOOK=y diff --git a/tests/ztest/error_hook/prj_no_userspace.conf b/tests/ztest/error_hook/prj_no_userspace.conf new file mode 100644 index 00000000000..d94a147feb5 --- /dev/null +++ b/tests/ztest/error_hook/prj_no_userspace.conf @@ -0,0 +1,4 @@ +CONFIG_ZTEST=y +CONFIG_IRQ_OFFLOAD=y +CONFIG_ZTEST_FATAL_HOOK=y +CONFIG_ZTEST_ASSERT_HOOK=y diff --git a/tests/ztest/error_hook/src/main.c b/tests/ztest/error_hook/src/main.c new file mode 100644 index 00000000000..66e9faa7088 --- /dev/null +++ b/tests/ztest/error_hook/src/main.c @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2020 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACKSIZE) +#define THREAD_TEST_PRIORITY 5 + +static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE); +static struct k_thread tdata; + +static ZTEST_BMEM int case_type; + +/* A semaphore using inside irq_offload */ +extern struct k_sem offload_sem; + +/* test case type */ +enum { + ZTEST_CATCH_FATAL_ACCESS_NULL, + ZTEST_CATCH_FATAL_ILLEAGAL_INSTRUCTION, + ZTEST_CATCH_FATAL_DIVIDE_ZERO, + ZTEST_CATCH_FATAL_K_PANIC, + ZTEST_CATCH_FATAL_K_OOPS, + ZTEST_CATCH_FATAL_IN_ISR, + ZTEST_CATCH_ASSERT_FAIL, + ZTEST_CATCH_ASSERT_IN_ISR, + ZTEST_CATCH_USER_FATAL_Z_OOPS, + ZTEST_ERROR_MAX +} error_case_type; + + +static void trigger_assert_fail(void *a) +{ + /* trigger an assert fail condition */ + __ASSERT(a != NULL, "parameter a should not be NULL!"); +} + +static void trigger_fault_illeagl_instuction(void) +{ + void *a = NULL; + + /* execute an illeagal instructions */ + ((void(*)(void))&a)(); +} + +static void trigger_fault_access_null(void) +{ + void *a = NULL; + + /* access a null of address */ + int b = *((int *)a); + + printk("b is %d\n", b); +} + +static void trigger_fault_divide_zero(void) +{ + int a = 1; + int b = 0; + + /* divde zero */ + a = a / b; + printk("a is %d\n", a); +} + +static void trigger_fault_oops(void) +{ + k_oops(); +} + +static void trigger_fault_panic(void) +{ + k_panic(); +} + +static void release_offload_sem(void) +{ + /* Semaphore used inside irq_offload need to be + * released after assert or fault happened. + */ + k_sem_give(&offload_sem); +} + +/* This is the fatal error hook that allow you to do actions after + * fatal error happened. This is optional, you can choose to define + * this yourself. If not, it will use the default one. + */ +void ztest_post_fatal_error_hook(unsigned int reason, + const z_arch_esf_t *pEsf) +{ + switch (case_type) { + case ZTEST_CATCH_FATAL_ACCESS_NULL: + case ZTEST_CATCH_FATAL_ILLEAGAL_INSTRUCTION: + case ZTEST_CATCH_FATAL_DIVIDE_ZERO: + case ZTEST_CATCH_FATAL_K_PANIC: + case ZTEST_CATCH_FATAL_K_OOPS: + case ZTEST_CATCH_USER_FATAL_Z_OOPS: + zassert_true(true, NULL); + break; + + /* Unfortunately, the case of trigger a fatal error + * inside ISR context still cannot be dealed with, + * So please don't use it this way. + */ + case ZTEST_CATCH_FATAL_IN_ISR: + zassert_true(false, NULL); + break; + default: + zassert_true(false, NULL); + break; + } +} + +/* This is the assert fail post hook that allow you to do actions after + * assert fail happened. This is optional, you can choose to define + * this yourself. If not, it will use the default one. + */ +void ztest_post_assert_fail_hook(void) +{ + switch (case_type) { + case ZTEST_CATCH_ASSERT_FAIL: + ztest_test_pass(); + break; + case ZTEST_CATCH_ASSERT_IN_ISR: + release_offload_sem(); + ztest_test_pass(); + break; + + default: + ztest_test_fail(); + break; + } +} + +static void tThread_entry(void *p1, void *p2, void *p3) +{ + int sub_type = *(int *)p1; + + printk("case type is %d\n", case_type); + + ztest_set_fault_valid(false); + + switch (sub_type) { + case ZTEST_CATCH_FATAL_ACCESS_NULL: + ztest_set_fault_valid(true); + trigger_fault_access_null(); + break; + case ZTEST_CATCH_FATAL_ILLEAGAL_INSTRUCTION: + ztest_set_fault_valid(true); + trigger_fault_illeagl_instuction(); + break; + case ZTEST_CATCH_FATAL_DIVIDE_ZERO: + ztest_set_fault_valid(true); + trigger_fault_divide_zero(); + break; + case ZTEST_CATCH_FATAL_K_PANIC: + ztest_set_fault_valid(true); + trigger_fault_panic(); + break; + case ZTEST_CATCH_FATAL_K_OOPS: + ztest_set_fault_valid(true); + trigger_fault_oops(); + break; + + default: + break; + } + + /* should not reach here */ + ztest_test_fail(); +} + +static int run_trigger_thread(int i) +{ + int ret; + uint32_t perm = K_INHERIT_PERMS; + + case_type = i; + + if (_is_user_context()) { + perm = perm | K_USER; + } + + k_tid_t tid = k_thread_create(&tdata, tstack, STACK_SIZE, + (k_thread_entry_t)tThread_entry, + (void *)&case_type, NULL, NULL, + K_PRIO_PREEMPT(THREAD_TEST_PRIORITY), + perm, K_NO_WAIT); + + ret = k_thread_join(tid, K_FOREVER); + + return ret; +} + +/** + * @brief Test if a fatal error can be catched + * + * @details Valid a fatal error we triggered in thread context works. + * If the fatal error happened and the program enter assert_post_handler, + * that means fatal error triggered as expected. + */ +void test_catch_fatal_error(void) +{ +#if defined(CONFIG_ARCH_HAS_USERSPACE) + run_trigger_thread(ZTEST_CATCH_FATAL_ACCESS_NULL); + run_trigger_thread(ZTEST_CATCH_FATAL_ILLEAGAL_INSTRUCTION); + run_trigger_thread(ZTEST_CATCH_FATAL_DIVIDE_ZERO); +#endif + run_trigger_thread(ZTEST_CATCH_FATAL_K_PANIC); + run_trigger_thread(ZTEST_CATCH_FATAL_K_OOPS); +} + +/** + * @brief Test if catching an assert works + * + * @details Valid the assert in thread context works or not. If the assert + * fail happened and the program enter assert_post_handler, that means + * assert works as expected. + */ +void test_catch_assert_fail(void) +{ + case_type = ZTEST_CATCH_ASSERT_FAIL; + + ztest_set_assert_valid(false); + + ztest_set_assert_valid(true); + trigger_assert_fail(NULL); + + ztest_test_fail(); +} + +/* a handler using by irq_offload */ +static void tIsr_assert(const void *p) +{ + ztest_set_assert_valid(true); + trigger_assert_fail(NULL); +} + +/** + * @brief Test if an assert fail works in ISR context + * + * @details Valid the assert in ISR context works or not. If the assert + * fail happened and the program enter assert_post_handler, that means + * assert works as expected. + */ +void test_catch_assert_in_isr(void) +{ + case_type = ZTEST_CATCH_ASSERT_IN_ISR; + irq_offload(tIsr_assert, NULL); +} + + +#if defined(CONFIG_USERSPACE) +static void trigger_z_oops(void *a) +{ + Z_OOPS(*((bool *)a)); +} + +/** + * @brief Test if a z_oops can be catched + * + * @details Valid a z_oops we triggered in thread context works. + * If the z_oops happened and the program enter our handler, + * that means z_oops triggered as expected. This test only for + * userspace. + */ +void test_catch_z_oops(void) +{ + case_type = ZTEST_CATCH_USER_FATAL_Z_OOPS; + + ztest_set_fault_valid(true); + trigger_z_oops((void *)false); +} +#endif + + +void test_main(void) +{ + +#if defined(CONFIG_USERSPACE) + k_thread_access_grant(k_current_get(), &tdata, &tstack); + + ztest_test_suite(error_hook_tests, + ztest_user_unit_test(test_catch_assert_fail), + ztest_user_unit_test(test_catch_fatal_error), + ztest_unit_test(test_catch_assert_in_isr), + ztest_user_unit_test(test_catch_z_oops) + ); + ztest_run_test_suite(error_hook_tests); +#else + ztest_test_suite(error_hook_tests, + ztest_unit_test(test_catch_fatal_error), + ztest_unit_test(test_catch_assert_fail), + ztest_unit_test(test_catch_assert_in_isr) + ); + ztest_run_test_suite(error_hook_tests); +#endif +} diff --git a/tests/ztest/error_hook/testcase.yaml b/tests/ztest/error_hook/testcase.yaml new file mode 100644 index 00000000000..90410cab09e --- /dev/null +++ b/tests/ztest/error_hook/testcase.yaml @@ -0,0 +1,9 @@ +tests: + testing.ztest.error_hook: + filter: CONFIG_ARCH_HAS_USERSPACE + tags: test_framework userspace ignore_faults + + testing.ztest.error_hook.no_userspace: + extra_args: CONF_FILE=prj_no_userspace.conf + filter: not CONFIG_ARCH_HAS_USERSPACE + tags: test_framework ignore_faults