/*************************************************************************** * Copyright (c) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /* This test drives the thread stack check through each of its outcomes. TX_THREAD_STACK_CHECK sits on ten sites across the scheduler and scores seven conditions at each of them: whether there is a thread to check at all, whether its control block is still a thread, whether its stack pointer has gone below the lowest it has been, whether either of the two fill patterns that fence the stack has been overwritten, whether the lowest recorded stack pointer has dropped below the stack itself, and whether the word below that pointer is still fill. Ordinary use only ever takes the clean arm of the last five. The test gives each thread a fault in turn and then drives every site with the fault in place, so each site sees the faulted arm as well. Every fault is written into the thread's own control block or into the fence words around its stack, and is undone before the next one; the kernel is not modified to produce any of them. It also covers the two parts of the stack machinery that no fault reaches: the realignment a create performs when it is handed an odd stack address, and the error handler's MISRA build, where there is no application handler to call and the function exists only to keep the argument referenced. */ #include #include "tx_api.h" #define TX_SOURCE_CODE #include "tx_thread.h" /* The priorities the test runs at. */ #define TEST_PRIORITY 16 #define PEER_PRIORITY 16 #define PREEMPT_PRIORITY 10 #define LOW_PRIORITY 20 /* Each thread's stack is preceded by a fence of this many words, which the test sets to the thread's own fill value. The fault that drops the lowest recorded stack pointer below the stack needs the word under that pointer to still read as fill, or the stack analysis that follows would search a range whose ends are the wrong way round. */ #define STACK_FENCE_WORDS 4 #define SCRATCH_STACK_SIZE TEST_STACK_SIZE_PRINTF /* The faults, applied to every thread in turn and then undone. */ #define FAULT_NONE 0 #define FAULT_HIGHEST_ABOVE_STACK 1 #define FAULT_END_PATTERN 2 #define FAULT_HIGHEST_BELOW_STACK 3 #define FAULT_WORD_BELOW_HIGHEST 4 #define FAULT_START_PATTERN 5 #define FAULTS 6 static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; static unsigned long thread_1_counter = 0; static TX_THREAD thread_1; static unsigned long thread_2_counter = 0; static TX_THREAD thread_2; static TX_THREAD thread_3; /* The thread the create fault is applied to, which is created and deleted inside the test rather than at definition time. */ static TX_THREAD thread_4; static CHAR *thread_4_stack; /* A thread that is left terminated. Terminating it again is a service that ends in a preemption check and touches nothing else. */ static TX_THREAD thread_5; static TX_THREAD *faulted_threads[4]; static VOID *saved_highest[4]; static ULONG saved_end_pattern[4]; static ULONG saved_low_word[4]; static ULONG saved_start_pattern[4]; static ULONG stack_error_count = 0; /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); static void thread_1_entry(ULONG thread_input); static void thread_2_entry(ULONG thread_input); static void thread_3_entry(ULONG thread_input); static void thread_4_entry(ULONG thread_input); static void thread_5_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); /* The value the stack check reads as an intact fill pattern for this thread. With random number stack filling it is the thread's own value rather than the constant, so the fences the test writes have to be read from the control block exactly as the check reads them. */ static ULONG stack_fill_value(TX_THREAD *thread_ptr) { ULONG value; #if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) value = thread_ptr -> tx_thread_stack_fill_value; #else value = ((ULONG) 0xEFEFEFEFUL); #endif return(value); } /* Record that the kernel reported a stack error. */ static void stack_error_handler(TX_THREAD *thread_ptr) { stack_error_count++; } /* Apply one fault to every thread the sites under test look at. */ static void apply_fault(UINT fault) { TX_INTERRUPT_SAVE_AREA UINT index; TX_THREAD *thread_ptr; ULONG *stack_start; ULONG fill; TX_DISABLE for (index = 0; index < 4; index++) { thread_ptr = faulted_threads[index]; stack_start = (ULONG *) thread_ptr -> tx_thread_stack_start; fill = stack_fill_value(thread_ptr); saved_highest[index] = thread_ptr -> tx_thread_stack_highest_ptr; saved_end_pattern[index] = *((ULONG *) (((UCHAR *) thread_ptr -> tx_thread_stack_end) + 1)); saved_low_word[index] = *(stack_start + 1); saved_start_pattern[index] = *stack_start; switch (fault) { case FAULT_HIGHEST_ABOVE_STACK: /* The lowest recorded stack pointer is put above the thread's own stack pointer, so the check finds the stack deeper than it has recorded and lowers its mark. */ thread_ptr -> tx_thread_stack_highest_ptr = thread_ptr -> tx_thread_stack_end; break; case FAULT_END_PATTERN: /* The fence at the top of the stack is overwritten, which is an overflow as far as the check is concerned. */ *((ULONG *) (((UCHAR *) thread_ptr -> tx_thread_stack_end) + 1)) = ~fill; break; case FAULT_HIGHEST_BELOW_STACK: /* The lowest recorded stack pointer is dropped below the stack. The fence under the stack is set to fill first, so the word the check reads under that pointer is intact and no stack analysis follows. */ *(stack_start - 1) = fill; *(stack_start - 2) = fill; thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start - 1); break; case FAULT_WORD_BELOW_HIGHEST: /* The word under the lowest recorded stack pointer is made used, so the check runs the stack analysis with both fences intact. */ *(stack_start + 1) = ~fill; thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start + 2); break; case FAULT_START_PATTERN: /* The fence at the bottom of the stack is overwritten and the lowest recorded stack pointer is put just above it. The stack analysis that follows leaves the pointer where it is, so this is the one fault a site does not repair on its way through and the sites behind it see it too. */ *stack_start = ~fill; thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start + 1); break; default: break; } } TX_RESTORE } /* Undo whichever fault is in place. */ static void clear_fault(void) { TX_INTERRUPT_SAVE_AREA UINT index; TX_THREAD *thread_ptr; TX_DISABLE for (index = 0; index < 4; index++) { thread_ptr = faulted_threads[index]; thread_ptr -> tx_thread_stack_highest_ptr = saved_highest[index]; *((ULONG *) (((UCHAR *) thread_ptr -> tx_thread_stack_end) + 1)) = saved_end_pattern[index]; *(((ULONG *) thread_ptr -> tx_thread_stack_start) + 1) = saved_low_word[index]; *((ULONG *) thread_ptr -> tx_thread_stack_start) = saved_start_pattern[index]; } TX_RESTORE } /* The steps that, between them, reach every site the stack check sits on. The fault is applied and undone around each one, because the first site to see a thread with a fault in its recorded stack pointer repairs that pointer on the way through. */ #define SITE_STEP_RESUME_PEER 0 #define SITE_STEP_RELINQUISH 1 #define SITE_STEP_TIME_SLICE 2 #define SITE_STEP_SUSPEND_ROTATED 3 #define SITE_STEP_RESUME_ROTATED 4 #define SITE_STEP_RESUME_PREEMPTING 5 #define SITE_STEP_PREEMPT_CHECK 6 #define SITE_STEP_PRIORITY_CHANGE 7 #define SITE_STEP_PREEMPTION_CHANGE 8 #define SITE_STEP_SUSPEND_PEER 9 #define SITE_STEPS 10 /* Drive one of them. */ static UINT drive_site(UINT step, UINT fault) { UINT status = TX_SUCCESS; UINT old_threshold; UINT old_priority; switch (step) { case SITE_STEP_RESUME_PEER: /* A peer at the test thread's own priority, so that the relinquish and the time slice below have somewhere to hand the processor to. */ status = tx_thread_resume(&thread_1); break; case SITE_STEP_RELINQUISH: /* The relinquish checks the running thread on the way in and the thread it hands over to on the way out. */ tx_thread_relinquish(); break; case SITE_STEP_TIME_SLICE: /* The time slice check does the same across a rotation. */ _tx_thread_time_slice(); break; case SITE_STEP_SUSPEND_ROTATED: /* A rotation leaves the peer as the thread to execute while the test thread is still the one running, which is the only way a suspend of a third thread ends with a thread to check that is neither of them. The rotation checks the peer on its way through and repairs it, so the fault is put back before the suspend that is under test. Preemption is held off across all of it, because a tick arriving between the rotation and the suspend would act on the rotation and hand the peer the processor, and the peer repairs itself on its way back. */ /* The third thread is left suspended by whichever of the steps below ran last, so it is put back on the ready list here rather than assumed to be on it. */ status = tx_thread_resume(&thread_3); if ((status != TX_SUCCESS) && (status != TX_RESUME_ERROR)) { break; } status = TX_SUCCESS; _tx_thread_preempt_disable++; _tx_thread_time_slice(); clear_fault(); apply_fault(fault); status = tx_thread_suspend(&thread_3); _tx_thread_preempt_disable--; break; case SITE_STEP_RESUME_ROTATED: /* The same shape, for the resume. */ _tx_thread_time_slice(); clear_fault(); apply_fault(fault); status = tx_thread_resume(&thread_3); break; case SITE_STEP_PREEMPT_CHECK: /* The preemption check looks at the thread that is going to run, which the rotation has made the peer. A terminate of an already terminated thread ends in one and touches nothing else. */ _tx_thread_time_slice(); clear_fault(); apply_fault(fault); status = tx_thread_terminate(&thread_5); break; case SITE_STEP_RESUME_PREEMPTING: /* A resume that preempts checks the thread it makes ready and then the thread that is going to run; the suspend that gives the processor back checks the thread that is giving it up. */ status = tx_thread_resume(&thread_2); break; case SITE_STEP_PRIORITY_CHANGE: /* Raising a ready thread above the running one leaves the preemption check with a thread to check and a reason to return to the system. */ status = tx_thread_priority_change(&thread_3, PREEMPT_PRIORITY, &old_priority); if (status == TX_SUCCESS) { status = tx_thread_priority_change(&thread_3, LOW_PRIORITY, &old_priority); } break; case SITE_STEP_PREEMPTION_CHANGE: status = tx_thread_preemption_change(&thread_1, PEER_PRIORITY, &old_threshold); break; default: /* Leave the peer suspended, so the next pass starts where this one did. */ status = tx_thread_suspend(&thread_1); break; } return(status); } /* Drive the sites with a control block whose thread identifier is not a thread's. The check treats such a block as nothing to check, so every site has to see one. The identifier is put back as soon as the site under test has run, and the threads that run while it is wrong use the internal entry points, because the error checking shell rejects the block outright. */ static UINT drive_bad_thread_id(void) { TX_INTERRUPT_SAVE_AREA UINT status; status = tx_thread_resume(&thread_1); if (status != TX_SUCCESS) { return(status); } /* The relinquish, which checks the running thread and then the thread it hands over to. */ thread_0.tx_thread_id = TX_CLEAR_ID; thread_1.tx_thread_id = TX_CLEAR_ID; tx_thread_relinquish(); thread_0.tx_thread_id = TX_THREAD_ID; thread_1.tx_thread_id = TX_THREAD_ID; /* The time slice check, which does the same across a rotation. It schedules nothing itself, so the whole thing is done with interrupts held off. */ TX_DISABLE thread_0.tx_thread_id = TX_CLEAR_ID; thread_1.tx_thread_id = TX_CLEAR_ID; _tx_thread_time_slice(); thread_0.tx_thread_id = TX_THREAD_ID; thread_1.tx_thread_id = TX_THREAD_ID; TX_RESTORE /* The preemption check, which looks at the thread the rotation left as the one to execute. */ _tx_thread_time_slice(); thread_1.tx_thread_id = TX_CLEAR_ID; status = tx_thread_terminate(&thread_5); thread_1.tx_thread_id = TX_THREAD_ID; if (status != TX_SUCCESS) { return(status); } /* A resume that preempts, which checks the thread it makes ready and then the thread that is going to run. The published entry point rejects the block before the resume is reached, so the internal one is used. */ thread_2.tx_thread_id = TX_CLEAR_ID; status = _tx_thread_resume(&thread_2); thread_2.tx_thread_id = TX_THREAD_ID; if (status != TX_SUCCESS) { return(status); } return(tx_thread_suspend(&thread_1)); } /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_thread_stack_check_application_define(void *first_unused_memory) #endif { UINT status; CHAR *pointer; /* Put first available memory address into a character pointer. Each stack is preceded by a fence the test writes itself and followed by a word of slack, so that writing the pattern just past a stack's end cannot reach the next thread's. */ pointer = (CHAR *) first_unused_memory; pointer = pointer + (STACK_FENCE_WORDS * sizeof(ULONG)); status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, TEST_PRIORITY, TEST_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #1\n"); test_control_return(1); } pointer = pointer + (STACK_FENCE_WORDS * sizeof(ULONG)); status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, pointer, SCRATCH_STACK_SIZE, PEER_PRIORITY, PEER_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + SCRATCH_STACK_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #2\n"); test_control_return(1); } pointer = pointer + (STACK_FENCE_WORDS * sizeof(ULONG)); status = tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2, pointer, SCRATCH_STACK_SIZE, PREEMPT_PRIORITY, PREEMPT_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + SCRATCH_STACK_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #3\n"); test_control_return(1); } pointer = pointer + (STACK_FENCE_WORDS * sizeof(ULONG)); status = tx_thread_create(&thread_3, "thread 3", thread_3_entry, 3, pointer, SCRATCH_STACK_SIZE, LOW_PRIORITY, LOW_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START); pointer = pointer + SCRATCH_STACK_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #4\n"); test_control_return(1); } /* Hold the stack the create fault is applied to. The odd address the test hands the create has to sit inside memory the thread may use, so the stack starts a word in. */ thread_4_stack = pointer + 1; pointer = pointer + SCRATCH_STACK_SIZE + (2 * (ULONG) sizeof(ULONG)); pointer = pointer + (STACK_FENCE_WORDS * sizeof(ULONG)); status = tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5, pointer, SCRATCH_STACK_SIZE, LOW_PRIORITY, LOW_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + SCRATCH_STACK_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #11\n"); test_control_return(1); } status = tx_thread_terminate(&thread_5); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Stack Check Test.................................... ERROR #12\n"); test_control_return(1); } faulted_threads[0] = &thread_0; faulted_threads[1] = &thread_1; faulted_threads[2] = &thread_2; faulted_threads[3] = &thread_3; /* Register the stack error handler, so that the faults below are reported rather than ignored. */ status = tx_thread_stack_error_notify(stack_error_handler); #ifdef TX_ENABLE_STACK_CHECKING if (status != TX_SUCCESS) #else if (status != TX_FEATURE_NOT_ENABLED) #endif { printf("Running Thread Stack Check Test.................................... ERROR #5\n"); test_control_return(1); } } /* The peer hands the processor straight back, through the internal entry point so that it keeps working while the test faults its control block. */ static void thread_1_entry(ULONG thread_input) { while (1) { thread_1_counter++; _tx_thread_relinquish(); } } /* The preempting thread gives the processor straight back. */ static void thread_2_entry(ULONG thread_input) { while (1) { thread_2_counter++; _tx_thread_suspend(&thread_2); } } /* The low priority thread stays ready and never runs. */ static void thread_3_entry(ULONG thread_input) { while (1) { _tx_thread_suspend(&thread_3); } } /* The thread created on an odd stack address never runs. */ static void thread_4_entry(ULONG thread_input) { while (1) { _tx_thread_suspend(&thread_4); } } /* Neither does the terminated one. */ static void thread_5_entry(ULONG thread_input) { while (1) { _tx_thread_suspend(&thread_5); } } /* Define the test thread. */ static void thread_0_entry(ULONG thread_input) { UINT status; UINT fault; UINT step; /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running Thread Stack Check Test.................................... "); /* Walk every site the stack check sits on, once with no fault in place and then once per fault. */ for (fault = 0; fault < FAULTS; fault++) { for (step = 0; step < SITE_STEPS; step++) { apply_fault(fault); status = drive_site(step, fault); clear_fault(); if (status != TX_SUCCESS) { printf("ERROR #6\n"); test_control_return(1); } } } status = drive_bad_thread_id(); if (status != TX_SUCCESS) { printf("ERROR #7\n"); test_control_return(1); } #ifdef TX_ENABLE_STACK_CHECKING /* Most of the faults are errors the kernel reports. */ if (stack_error_count == 0) { printf("ERROR #13\n"); test_control_return(1); } #endif /* A create handed an odd stack address rounds the address up and takes another word off the size to stay inside the memory it was given. */ status = tx_thread_create(&thread_4, "thread 4", thread_4_entry, 4, thread_4_stack, SCRATCH_STACK_SIZE, LOW_PRIORITY, LOW_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); if (status != TX_SUCCESS) { printf("ERROR #8\n"); test_control_return(1); } status = tx_thread_terminate(&thread_4); if (status != TX_SUCCESS) { printf("ERROR #9\n"); test_control_return(1); } status = tx_thread_delete(&thread_4); if (status != TX_SUCCESS) { printf("ERROR #10\n"); test_control_return(1); } #if defined(TX_MISRA_ENABLE) || defined(TX_ENABLE_STACK_CHECKING) /* The error handler is reached from the stack check above in the builds that have one to call. In the MISRA build there is no handler and the function exists only to keep its argument referenced, which nothing in the kernel calls it to do. Both arms are driven here. */ _tx_thread_stack_error_handler(&thread_0); _tx_thread_stack_error_handler(TX_NULL); #endif /* Success! */ printf("SUCCESS!\n"); test_control_return(0); }