From a091e4d13eae4dfecc74f65a184460b11ce673bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Fri, 11 Sep 2026 06:45:47 -0400 Subject: [PATCH] Restored the random stack fill value in the thread control block after it is cleared during thread creation, so stack checking now looks for the pattern that is actually in the stack When TX_ENABLE_STACK_CHECKING and TX_ENABLE_RANDOM_NUMBER_STACK_FILLING were both enabled, _tx_thread_create picked a random byte, replicated it into the four bytes of tx_thread_stack_fill_value, and filled the stack with it through the TX_STACK_FILL macro. It then cleared the whole control block with TX_MEMSET, which reset that field to zero. The stack held the random pattern while the control block claimed the pattern was zero, so TX_THREAD_STACK_CHECK and _tx_thread_stack_analyze compared the stack against the wrong value for the entire life of the thread. The random value is now computed into a local variable and stored in the control block again after the block has been cleared. The same defect was present in the SMP kernel and in the module manager, where it additionally left the kernel stack of a user mode module thread filled with zeros rather than with the random pattern. A regression test creates several threads that are never started and checks that the fill value recorded in each control block is the pattern present in the stack. It fails on the previous code in the stack_checking_rand_fill_build configuration and passes in all five configurations of both the tx and the SMP suites. Fixes #723 Assisted-by: Copilot (Opus 5) --- common/src/tx_thread_create.c | 28 ++- .../src/txm_module_manager_thread_create.c | 26 ++- common_smp/src/tx_thread_create.c | 28 ++- test/smp/cmake/regression/CMakeLists.txt | 1 + test/smp/regression/testcontrol.c | 2 + .../threadx_thread_stack_fill_value_test.c | 190 ++++++++++++++++++ test/tx/cmake/regression/CMakeLists.txt | 1 + test/tx/regression/testcontrol.c | 2 + .../threadx_thread_stack_fill_value_test.c | 190 ++++++++++++++++++ 9 files changed, 453 insertions(+), 15 deletions(-) create mode 100644 test/smp/regression/threadx_thread_stack_fill_value_test.c create mode 100644 test/tx/regression/threadx_thread_stack_fill_value_test.c diff --git a/common/src/tx_thread_create.c b/common/src/tx_thread_create.c index 026b0ada5..37b4d3e2d 100644 --- a/common/src/tx_thread_create.c +++ b/common/src/tx_thread_create.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -93,18 +95,26 @@ UCHAR *temp_ptr; ALIGN_TYPE new_stack_start; ALIGN_TYPE updated_stack_start; #endif +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) +ULONG stack_fill_value; +#endif #ifndef TX_DISABLE_STACK_FILLING #if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) /* Initialize the stack fill value to a 8-bit random value. */ - thread_ptr -> tx_thread_stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; + stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; /* Duplicate the random value in each of the 4 bytes of the stack fill value. */ - thread_ptr -> tx_thread_stack_fill_value = thread_ptr -> tx_thread_stack_fill_value | - (thread_ptr -> tx_thread_stack_fill_value << 8) | - (thread_ptr -> tx_thread_stack_fill_value << 16) | - (thread_ptr -> tx_thread_stack_fill_value << 24); + stack_fill_value = stack_fill_value | + (stack_fill_value << 8) | + (stack_fill_value << 16) | + (stack_fill_value << 24); + + /* Store the fill value in the control block so that the stack fill below picks it up + through the TX_STACK_FILL macro. The control block is cleared further down in this + function, so the value is stored again once that has been done. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; #endif /* Set the thread stack to a pattern prior to creating the initial @@ -150,6 +160,14 @@ ALIGN_TYPE updated_stack_start; /* Initialize thread control block to all zeros. */ TX_MEMSET(thread_ptr, 0, (sizeof(TX_THREAD))); +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) + + /* Clearing the control block reset the stack fill value, so store the value that was + actually used to fill the stack again. Otherwise the stack checking and stack analyze + routines would look for a pattern that is not the one present in the stack. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; +#endif + /* Place the supplied parameters into the thread's control block. */ thread_ptr -> tx_thread_name = name_ptr; thread_ptr -> tx_thread_entry = entry_function; diff --git a/common_modules/module_manager/src/txm_module_manager_thread_create.c b/common_modules/module_manager/src/txm_module_manager_thread_create.c index a178cc35a..fdb0e768a 100644 --- a/common_modules/module_manager/src/txm_module_manager_thread_create.c +++ b/common_modules/module_manager/src/txm_module_manager_thread_create.c @@ -106,6 +106,9 @@ UCHAR *temp_ptr; ALIGN_TYPE new_stack_start; ALIGN_TYPE updated_stack_start; #endif +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) +ULONG stack_fill_value; +#endif TXM_MODULE_THREAD_ENTRY_INFO *thread_entry_info; VOID *stack_end; ULONG i; @@ -263,13 +266,18 @@ ULONG i; #if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) /* Initialize the stack fill value to a 8-bit random value. */ - thread_ptr -> tx_thread_stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; + stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; /* Duplicate the random value in each of the 4 bytes of the stack fill value. */ - thread_ptr -> tx_thread_stack_fill_value = thread_ptr -> tx_thread_stack_fill_value | - (thread_ptr -> tx_thread_stack_fill_value << 8) | - (thread_ptr -> tx_thread_stack_fill_value << 16) | - (thread_ptr -> tx_thread_stack_fill_value << 24); + stack_fill_value = stack_fill_value | + (stack_fill_value << 8) | + (stack_fill_value << 16) | + (stack_fill_value << 24); + + /* Store the fill value in the control block so that the stack fill below picks it up + through the TX_STACK_FILL macro. The control block is cleared further down in this + function, so the value is stored again once that has been done. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; #endif /* Set the thread stack to a pattern prior to creating the initial @@ -311,6 +319,14 @@ ULONG i; /* Initialize thread control block to all zeros. */ TX_MEMSET(thread_ptr, 0, sizeof(TX_THREAD)); +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) + + /* Clearing the control block reset the stack fill value, so store the value that was + actually used to fill the stack again. Otherwise the stack checking and stack analyze + routines would look for a pattern that is not the one present in the stack. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; +#endif + /* Note that TX_ENABLE_STACK_CHECKING is not supported for module threads. A user mode module thread owns two stacks and the scheduler swaps tx_thread_stack_start, tx_thread_stack_end and tx_thread_stack_size over to the kernel stack whenever the diff --git a/common_smp/src/tx_thread_create.c b/common_smp/src/tx_thread_create.c index 28a1dae1b..05019ab8b 100644 --- a/common_smp/src/tx_thread_create.c +++ b/common_smp/src/tx_thread_create.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -93,19 +95,27 @@ UCHAR *temp_ptr; ALIGN_TYPE new_stack_start; ALIGN_TYPE updated_stack_start; #endif +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) +ULONG stack_fill_value; +#endif #ifndef TX_DISABLE_STACK_FILLING #if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) /* Initialize the stack fill value to a 8-bit random value. */ - thread_ptr -> tx_thread_stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; + stack_fill_value = ((ULONG) TX_RAND()) & 0xFFUL; /* Duplicate the random value in each of the 4 bytes of the stack fill value. */ - thread_ptr -> tx_thread_stack_fill_value = thread_ptr -> tx_thread_stack_fill_value | - (thread_ptr -> tx_thread_stack_fill_value << 8) | - (thread_ptr -> tx_thread_stack_fill_value << 16) | - (thread_ptr -> tx_thread_stack_fill_value << 24); + stack_fill_value = stack_fill_value | + (stack_fill_value << 8) | + (stack_fill_value << 16) | + (stack_fill_value << 24); + + /* Store the fill value in the control block so that the stack fill below picks it up + through the TX_STACK_FILL macro. The control block is cleared further down in this + function, so the value is stored again once that has been done. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; #endif /* Set the thread stack to a pattern prior to creating the initial @@ -143,6 +153,14 @@ ALIGN_TYPE updated_stack_start; /* Initialize thread control block to all zeros. */ TX_MEMSET(thread_ptr, 0, sizeof(TX_THREAD)); +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) && !defined(TX_DISABLE_STACK_FILLING) + + /* Clearing the control block reset the stack fill value, so store the value that was + actually used to fill the stack again. Otherwise the stack checking and stack analyze + routines would look for a pattern that is not the one present in the stack. */ + thread_ptr -> tx_thread_stack_fill_value = stack_fill_value; +#endif + /* Place the supplied parameters into the thread's control block. */ thread_ptr -> tx_thread_name = name_ptr; thread_ptr -> tx_thread_entry = entry_function; diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index dd1d48a30..771fb44dd 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -114,6 +114,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_thread_sleep_for_100ticks_test.c ${SOURCE_DIR}/threadx_thread_sleep_terminate_test.c ${SOURCE_DIR}/threadx_thread_stack_checking_test.c + ${SOURCE_DIR}/threadx_thread_stack_fill_value_test.c ${SOURCE_DIR}/threadx_thread_terminate_delete_test.c ${SOURCE_DIR}/threadx_thread_exit_callback_transition_test.c ${SOURCE_DIR}/threadx_smp_thread_exit_callback_remote_test.c diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index 59d8e69a7..fc907d27b 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -253,6 +253,7 @@ void threadx_thread_create_preemption_threshold_application_define(void *); void threadx_thread_information_application_define(void *); void threadx_thread_reset_application_define(void *); void threadx_thread_stack_checking_application_define(void *); +void threadx_thread_stack_fill_value_application_define(void *); void threadx_time_get_set_application_define(void *); @@ -401,6 +402,7 @@ TEST_ENTRY test_control_tests[] = threadx_thread_information_application_define, threadx_thread_reset_application_define, threadx_thread_stack_checking_application_define, + threadx_thread_stack_fill_value_application_define, threadx_time_get_set_application_define, diff --git a/test/smp/regression/threadx_thread_stack_fill_value_test.c b/test/smp/regression/threadx_thread_stack_fill_value_test.c new file mode 100644 index 000000000..896c0c7f7 --- /dev/null +++ b/test/smp/regression/threadx_thread_stack_fill_value_test.c @@ -0,0 +1,190 @@ +/***************************************************************************/ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + +// Some portions generated by Copilot (Opus 5). + +/* This test checks that the stack fill value recorded in a thread control block is the + value that was actually written into that thread's stack. The two used to disagree + when TX_ENABLE_RANDOM_NUMBER_STACK_FILLING was enabled, because the control block was + cleared after the random value had been chosen and the stack had been filled with it, + which left the stack checking and stack analyze routines looking for a pattern that is + not the one present in the stack. */ + +#include +#include "tx_api.h" + + +/* Number of probe threads created. A random fill value can legitimately come out as zero, + which hides the defect for that one thread, so several threads are created and at least + one of them is required to have produced a non-zero pattern. */ + +#define TEST_PROBE_THREADS 16 + + +static unsigned long thread_0_counter = 0; +static TX_THREAD thread_0; + +static TX_THREAD probe_thread; +static ULONG probe_stack[TX_MINIMUM_STACK / sizeof(ULONG)]; + + +/* Define task prototypes. */ + +static void thread_0_entry(ULONG task_input); + + +/* Prototype for test control return. */ + +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_thread_stack_fill_value_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; + + pointer = (CHAR *) first_unused_memory; + + /* Put system definition stuff in here, e.g. thread creates and other assorted + create information. */ + + status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("Running Thread Stack Fill Value Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* Define the test threads. */ + +static void thread_0_entry(ULONG thread_input) +{ + +UINT status; +UINT i; +ULONG stack_pattern; +UINT non_zero_seen = TX_FALSE; + + + /* Increment thread 0 counter. */ + thread_0_counter++; + + /* Inform user of success getting to this test. */ + printf("Running Thread Stack Fill Value Test................................ "); + + for (i = 0; i < TEST_PROBE_THREADS; i++) + { + + /* Create a thread that is never started, so that its stack still holds nothing but + the fill pattern and the initial stack frame built at the top of the stack. */ + status = tx_thread_create(&probe_thread, "probe thread", thread_0_entry, 0, + probe_stack, sizeof(probe_stack), + 17, 17, TX_NO_TIME_SLICE, TX_DONT_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #2\n"); + test_control_return(1); + } + + /* Pick up the pattern the create service wrote into the unused part of the stack. */ + stack_pattern = *(((ULONG *) probe_thread.tx_thread_stack_start) + 4); + +#ifndef TX_DISABLE_STACK_FILLING +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) + + /* The control block must hold the value that was written into the stack. */ + if (probe_thread.tx_thread_stack_fill_value != stack_pattern) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + /* The random value is duplicated into all four bytes of the fill value. */ + if ((stack_pattern & 0xFFUL) != + ((stack_pattern >> 24) & 0xFFUL)) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + if (stack_pattern != ((ULONG) 0)) + { + + non_zero_seen = TX_TRUE; + } +#else + + /* Without random filling, the pattern is the fixed one. */ + if (stack_pattern != ((ULONG) 0xEFEFEFEFUL)) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + non_zero_seen = TX_TRUE; +#endif +#else + + /* Stack filling is disabled, so there is nothing to compare. */ + non_zero_seen = TX_TRUE; +#endif + + /* Terminate and delete the probe thread so that the next pass starts from a clean + slate. A thread created with TX_DONT_START is suspended rather than completed, + so it must be terminated before it can be deleted. */ + status = tx_thread_terminate(&probe_thread); + + if (status == TX_SUCCESS) + { + + status = tx_thread_delete(&probe_thread); + } + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + } + + /* A run in which every fill value came out as zero would prove nothing. */ + if (non_zero_seen != TX_TRUE) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + /* Success! */ + printf("SUCCESS!\n"); + test_control_return(0); +} diff --git a/test/tx/cmake/regression/CMakeLists.txt b/test/tx/cmake/regression/CMakeLists.txt index 1479a93e0..6b94a8576 100644 --- a/test/tx/cmake/regression/CMakeLists.txt +++ b/test/tx/cmake/regression/CMakeLists.txt @@ -97,6 +97,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_thread_sleep_for_100ticks_test.c ${SOURCE_DIR}/threadx_thread_sleep_terminate_test.c ${SOURCE_DIR}/threadx_thread_stack_checking_test.c + ${SOURCE_DIR}/threadx_thread_stack_fill_value_test.c ${SOURCE_DIR}/threadx_thread_terminate_delete_test.c ${SOURCE_DIR}/threadx_thread_exit_callback_transition_test.c ${SOURCE_DIR}/threadx_thread_time_slice_change_test.c diff --git a/test/tx/regression/testcontrol.c b/test/tx/regression/testcontrol.c index 00e31d9b0..1bdcdb715 100644 --- a/test/tx/regression/testcontrol.c +++ b/test/tx/regression/testcontrol.c @@ -230,6 +230,7 @@ void threadx_thread_create_preemption_threshold_application_define(void *); void threadx_thread_information_application_define(void *); void threadx_thread_reset_application_define(void *); void threadx_thread_stack_checking_application_define(void *); +void threadx_thread_stack_fill_value_application_define(void *); void threadx_time_get_set_application_define(void *); @@ -350,6 +351,7 @@ TEST_ENTRY test_control_tests[] = threadx_thread_information_application_define, threadx_thread_reset_application_define, threadx_thread_stack_checking_application_define, + threadx_thread_stack_fill_value_application_define, threadx_time_get_set_application_define, diff --git a/test/tx/regression/threadx_thread_stack_fill_value_test.c b/test/tx/regression/threadx_thread_stack_fill_value_test.c new file mode 100644 index 000000000..896c0c7f7 --- /dev/null +++ b/test/tx/regression/threadx_thread_stack_fill_value_test.c @@ -0,0 +1,190 @@ +/***************************************************************************/ +/* 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. */ +/* */ +/* SPDX-License-Identifier: MIT */ +/***************************************************************************/ + +// Some portions generated by Copilot (Opus 5). + +/* This test checks that the stack fill value recorded in a thread control block is the + value that was actually written into that thread's stack. The two used to disagree + when TX_ENABLE_RANDOM_NUMBER_STACK_FILLING was enabled, because the control block was + cleared after the random value had been chosen and the stack had been filled with it, + which left the stack checking and stack analyze routines looking for a pattern that is + not the one present in the stack. */ + +#include +#include "tx_api.h" + + +/* Number of probe threads created. A random fill value can legitimately come out as zero, + which hides the defect for that one thread, so several threads are created and at least + one of them is required to have produced a non-zero pattern. */ + +#define TEST_PROBE_THREADS 16 + + +static unsigned long thread_0_counter = 0; +static TX_THREAD thread_0; + +static TX_THREAD probe_thread; +static ULONG probe_stack[TX_MINIMUM_STACK / sizeof(ULONG)]; + + +/* Define task prototypes. */ + +static void thread_0_entry(ULONG task_input); + + +/* Prototype for test control return. */ + +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_thread_stack_fill_value_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; + + pointer = (CHAR *) first_unused_memory; + + /* Put system definition stuff in here, e.g. thread creates and other assorted + create information. */ + + status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("Running Thread Stack Fill Value Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* Define the test threads. */ + +static void thread_0_entry(ULONG thread_input) +{ + +UINT status; +UINT i; +ULONG stack_pattern; +UINT non_zero_seen = TX_FALSE; + + + /* Increment thread 0 counter. */ + thread_0_counter++; + + /* Inform user of success getting to this test. */ + printf("Running Thread Stack Fill Value Test................................ "); + + for (i = 0; i < TEST_PROBE_THREADS; i++) + { + + /* Create a thread that is never started, so that its stack still holds nothing but + the fill pattern and the initial stack frame built at the top of the stack. */ + status = tx_thread_create(&probe_thread, "probe thread", thread_0_entry, 0, + probe_stack, sizeof(probe_stack), + 17, 17, TX_NO_TIME_SLICE, TX_DONT_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #2\n"); + test_control_return(1); + } + + /* Pick up the pattern the create service wrote into the unused part of the stack. */ + stack_pattern = *(((ULONG *) probe_thread.tx_thread_stack_start) + 4); + +#ifndef TX_DISABLE_STACK_FILLING +#if defined(TX_ENABLE_RANDOM_NUMBER_STACK_FILLING) && defined(TX_ENABLE_STACK_CHECKING) + + /* The control block must hold the value that was written into the stack. */ + if (probe_thread.tx_thread_stack_fill_value != stack_pattern) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + /* The random value is duplicated into all four bytes of the fill value. */ + if ((stack_pattern & 0xFFUL) != + ((stack_pattern >> 24) & 0xFFUL)) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + if (stack_pattern != ((ULONG) 0)) + { + + non_zero_seen = TX_TRUE; + } +#else + + /* Without random filling, the pattern is the fixed one. */ + if (stack_pattern != ((ULONG) 0xEFEFEFEFUL)) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + non_zero_seen = TX_TRUE; +#endif +#else + + /* Stack filling is disabled, so there is nothing to compare. */ + non_zero_seen = TX_TRUE; +#endif + + /* Terminate and delete the probe thread so that the next pass starts from a clean + slate. A thread created with TX_DONT_START is suspended rather than completed, + so it must be terminated before it can be deleted. */ + status = tx_thread_terminate(&probe_thread); + + if (status == TX_SUCCESS) + { + + status = tx_thread_delete(&probe_thread); + } + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + } + + /* A run in which every fill value came out as zero would prove nothing. */ + if (non_zero_seen != TX_TRUE) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + /* Success! */ + printf("SUCCESS!\n"); + test_control_return(0); +}