Restored the random stack fill value cleared during thread creation (#732)

Fixes #723

With `TX_ENABLE_STACK_CHECKING` and `TX_ENABLE_RANDOM_NUMBER_STACK_FILLING` both
enabled, `_tx_thread_create` picked a random byte, stored it in
`tx_thread_stack_fill_value`, filled the stack with it -- and then cleared the
whole control block with `TX_MEMSET`. The stack held the pattern while the
control block claimed zero, so `TX_THREAD_STACK_CHECK` and
`_tx_thread_stack_analyze` compared against the wrong value for the entire life
of the thread.

The value is now computed into a local and written back after the clear. The
clear stays where it is, because the module manager's error checking walks the
created list before the control block may be touched.

Fixed in `common/src/tx_thread_create.c`, `common_smp/src/tx_thread_create.c`
and `txm_module_manager_thread_create.c`, where it additionally left a user-mode
module thread's kernel stack filled with zeros.

`threadx_thread_stack_fill_value_test` creates sixteen unstarted threads and
checks each control block against the pattern in its stack, tolerating a random
zero byte without letting that hide the defect. Added to both suites: `ERROR #3`
on `dev` in `stack_checking_rand_fill_build`, green with the fix. Five tx
configurations at 104 tests, five SMP at 117, and all three sources clean under
`-Wall -Wextra` across every combination of the four stack-filling switches.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
This commit is contained in:
Frédéric Desbiens
2026-09-15 16:24:58 -04:00
committed by GitHub
parent 201cc04609
commit 30b3d22adc
9 changed files with 453 additions and 15 deletions
+1
View File
@@ -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
+2
View File
@@ -255,6 +255,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 *);
@@ -403,6 +404,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,
@@ -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 <stdio.h>
#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);
}
+1
View File
@@ -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
+2
View File
@@ -232,6 +232,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 *);
@@ -352,6 +353,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,
@@ -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 <stdio.h>
#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);
}