Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions common/src/tx_thread_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
/* DESCRIPTION */
/* */
/* This function creates a thread and places it on the list of created */
/* threads. */
/* threads. Stack alignment preserves the full port-defined address. */
/* */
/* INPUT */
/* */
Expand Down Expand Up @@ -130,12 +130,11 @@ ULONG stack_fill_value;
overflow conditions during run-time. */
stack_size = ((stack_size/(sizeof(ULONG))) * (sizeof(ULONG))) - (sizeof(ULONG));

/* Ensure the starting stack address is evenly aligned. */
#ifdef TX_MISRA_ENABLE
new_stack_start = TX_POINTER_TO_ULONG_CONVERT(stack_start);
#else
new_stack_start = TX_POINTER_TO_ALIGN_TYPE_CONVERT(stack_start);
#endif /* TX_MISRA_ENABLE */
/* Ensure the starting stack address is evenly aligned. The port-defined
ALIGN_TYPE preserves all pointer bits, even when ULONG is narrower.
MISRA C:2012/2023 Rule 11.6 (C:2004 Rule 11.3) deviation: these
pointer/integer conversions are required for address alignment. */
new_stack_start = (ALIGN_TYPE) ((VOID *) stack_start);
updated_stack_start = (((new_stack_start) + ((sizeof(ULONG)) - ((ULONG) 1)) ) & (~((sizeof(ULONG)) - ((ULONG) 1))));

/* Determine if the starting stack address is different. */
Expand All @@ -147,11 +146,7 @@ ULONG stack_fill_value;
}

/* Update the starting stack pointer. */
#ifdef TX_MISRA_ENABLE
stack_start = TX_ULONG_TO_POINTER_CONVERT(updated_stack_start);
#else
stack_start = TX_ALIGN_TYPE_TO_POINTER_CONVERT(updated_stack_start);
#endif /* TX_MISRA_ENABLE */
stack_start = (VOID *) ((ALIGN_TYPE) updated_stack_start);
#endif

/* Prepare the thread control block prior to placing it on the created
Expand Down
20 changes: 20 additions & 0 deletions test/tx/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
##############################################################################
# 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
##############################################################################

# Portions of this file were generated with AI assistance.

cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
cmake_policy(SET CMP0054 NEW)
cmake_policy(SET CMP0057 NEW)
Expand Down Expand Up @@ -147,3 +159,11 @@ else()
-Waggregate-return
-Wfloat-equal)
endif()

# Preserve native32 host coverage and also exercise the Linux pointer-width
# mismatch with a native64 kernel, including MISRA plus stack checking.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND THREADX_ARCH STREQUAL "linux" AND
THREADX_TOOLCHAIN STREQUAL "gnu" AND
CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
add_subdirectory(thread_stack_alignment)
endif()
89 changes: 89 additions & 0 deletions test/tx/cmake/thread_stack_alignment/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
##############################################################################
# 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 Codex (gpt-6.1-sol).
# 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
##############################################################################

cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(thread_stack_alignment_test LANGUAGES C)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$" OR
NOT CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang)$")
message(FATAL_ERROR "Stack alignment regression requires native x86_64 Linux/GNU port widths")
endif()

# The host suite normally builds with -m32 and configuration-specific TX_ macros.
# Keep that coverage intact, but compile this directory independently at native64
# width: the defect requires ALIGN_TYPE to be wider than ULONG. Clear only this
# directory's inherited options/definitions, then select the tested features.
set_property(DIRECTORY PROPERTY COMPILE_OPTIONS "")
set_property(DIRECTORY PROPERTY LINK_OPTIONS "")
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS "")
add_compile_options(-m64 -Wall -Wextra -ffunction-sections -fdata-sections)
add_link_options(-m64 -Wl,--gc-sections)

enable_testing()
get_filename_component(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../.." ABSOLUTE)
set(TEST_SOURCE_DIR ${REPO_ROOT}/test/tx/thread_stack_alignment)

# Enforce warning-free compilation for the new tests and changed service.
# Unchanged MISRA/kernel sources keep their existing native64 diagnostics.
set_source_files_properties(
${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_test.c
${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_schedule_test.c
${REPO_ROOT}/common/src/tx_thread_create.c
PROPERTIES COMPILE_OPTIONS -Werror)

foreach(mode misra control)
set(test_target tx_stack_alignment_${mode})
add_executable(${test_target}
${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_test.c
${REPO_ROOT}/common/src/tx_thread_create.c
${REPO_ROOT}/common/src/tx_misra.c)
target_include_directories(${test_target} PRIVATE
${REPO_ROOT}/common/inc ${REPO_ROOT}/ports/linux/gnu/inc)
target_compile_definitions(${test_target} PRIVATE TX_ENABLE_STACK_CHECKING)
if(mode STREQUAL "misra")
target_compile_definitions(${test_target} PRIVATE TX_MISRA_ENABLE)
endif()
add_test(NAME thread_stack_alignment_native64_${mode} COMMAND ${test_target})
set_tests_properties(thread_stack_alignment_native64_${mode}
PROPERTIES TIMEOUT 20 LABELS "native64;stack_alignment")
endforeach()

# Use the existing source lists to build a separate real Linux kernel. This
# target also works when the parent already owns the normal native32 threadx.
set(PROJECT_NAME tx_stack_alignment_linux)
add_library(${PROJECT_NAME} STATIC)
add_subdirectory(${REPO_ROOT}/common common_native64)
add_subdirectory(${REPO_ROOT}/ports/linux/gnu linux_native64)
target_compile_definitions(${PROJECT_NAME} PUBLIC
TX_MISRA_ENABLE TX_ENABLE_STACK_CHECKING TX_TIMER_PROCESS_IN_ISR)
target_link_libraries(${PROJECT_NAME} PUBLIC pthread)

add_executable(tx_stack_alignment_schedule
${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_schedule_test.c)
target_link_libraries(tx_stack_alignment_schedule PRIVATE tx_stack_alignment_linux)
target_link_options(tx_stack_alignment_schedule PRIVATE -Wl,--wrap=_tx_thread_stack_build)

# Each process owns one kernel instance, with a fresh worker and observer.
foreach(offset RANGE 0 3)
add_test(NAME thread_stack_alignment_native64_schedule_${offset}
COMMAND tx_stack_alignment_schedule ${offset})
set_tests_properties(thread_stack_alignment_native64_schedule_${offset}
PROPERTIES TIMEOUT 20 RUN_SERIAL TRUE LABELS "native64;stack_alignment")
endforeach()
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/***************************************************************************
* 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 Codex (gpt-6.1-sol).
* 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
**************************************************************************/

/* Exercise actual Linux thread creation and scheduling across sleep/wake.
Check metadata before the real port builder can touch an invalid address. */
#include "tx_api.h"
#include <stdio.h>

#define STACK_BYTES 8192U
#define PADDING_BYTES 32U
#define SENTINEL 0xA5U

static TX_THREAD worker;
static TX_THREAD observer;
static ALIGN_TYPE worker_storage[(STACK_BYTES + 2U * PADDING_BYTES) / sizeof(ALIGN_TYPE)];
static ALIGN_TYPE observer_storage[(STACK_BYTES + 2U * PADDING_BYTES) / sizeof(ALIGN_TYPE)];
static UCHAR *worker_input;
static UCHAR *observer_input;
static UINT offset;
static volatile UINT visits;

VOID __real__tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*entry)(VOID));

/* An assertion exits cleanly on the original tree, before stack dereference or
creation of either application pthread. The CTest timeout catches hangs. */
static VOID require(UINT condition, const CHAR *message)
{
if (condition == 0U)
{
printf("FAIL: %s\n", message);
exit(1);
}
}

/* Validate full-width metadata before delegating to the genuine Linux builder. */
VOID __wrap__tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*entry)(VOID))
{
UCHAR *input = (thread_ptr == &worker) ? worker_input : observer_input;
ULONG remainder = (ULONG) ((ALIGN_TYPE) input % sizeof(ULONG));
ULONG adjustment = (remainder == 0U) ? 0U : (ULONG) sizeof(ULONG) - remainder;
UCHAR *expected_start = input + adjustment;
ULONG expected_size = STACK_BYTES - (ULONG) sizeof(ULONG);

require((thread_ptr == &worker) || (thread_ptr == &observer), "unexpected thread build");
if (adjustment != 0U)
{
expected_size -= (ULONG) sizeof(ULONG);
}
printf("port build: input=%p start=%p end=%p size=%u\n", (VOID *) input,
thread_ptr -> tx_thread_stack_start, thread_ptr -> tx_thread_stack_end,
(UINT) thread_ptr -> tx_thread_stack_size);
require(thread_ptr -> tx_thread_stack_start == expected_start, "stack start retains high bits");
require(thread_ptr -> tx_thread_stack_size == expected_size, "stack guard reservations");
require(thread_ptr -> tx_thread_stack_end == expected_start + expected_size - 1U,
"stack end remains in supplied storage");
__real__tx_thread_stack_build(thread_ptr, entry);
require((ALIGN_TYPE) thread_ptr -> tx_thread_stack_ptr >= (ALIGN_TYPE) expected_start,
"initial stack pointer lower bound");
require((ALIGN_TYPE) thread_ptr -> tx_thread_stack_ptr <= (ALIGN_TYPE) (expected_start + expected_size - 1U),
"initial stack pointer upper bound");
}

/* Check padding after the real builder and after the worker has run. */
static VOID check_padding(UCHAR *bytes, UINT start_offset)
{
UINT i;

for (i = 0U; i < sizeof(worker_storage); i++)
{
if ((i < PADDING_BYTES + start_offset) || (i >= PADDING_BYTES + start_offset + STACK_BYTES))
{
require(bytes[i] == SENTINEL, "caller padding survives creation/scheduling");
}
}
}

/* The worker must run twice, separated by an actual timer suspension. */
static VOID worker_entry(ULONG input)
{
require(input == 17U, "scalar worker input");
require(tx_thread_identify() == &worker, "worker was scheduled");
visits++;
require(tx_thread_sleep(1U) == TX_SUCCESS, "worker sleep/wake");
visits++;
}

/* A lower-priority observer gives the worker time to complete after waking. */
static VOID observer_entry(ULONG input)
{
require(input == 23U, "scalar observer input");
require(tx_thread_sleep(3U) == TX_SUCCESS, "observer sleep/wake");
require(visits == 2U, "worker ran across sleep/wake");
require(worker.tx_thread_state == TX_COMPLETED, "worker completed");
check_padding((UCHAR *) worker_storage, offset);
check_padding((UCHAR *) observer_storage, 0U);
puts("PASS: high-address stack survived real creation and scheduling");
exit(0);
}

/* Timer processing in ISR mode avoids an unrelated timer-thread stack. */
VOID tx_application_define(VOID *unused)
{
UINT status;

(void) unused;
status = tx_thread_create(&worker, "alignment worker", worker_entry, 17U,
worker_input, STACK_BYTES, 15U, 15U, TX_NO_TIME_SLICE, TX_DONT_START);
require(status == TX_SUCCESS, "worker create");
require(worker.tx_thread_stack_highest_ptr == worker.tx_thread_stack_ptr, "worker highest pointer");
status = tx_thread_create(&observer, "alignment observer", observer_entry, 23U,
observer_input, STACK_BYTES, 16U, 16U, TX_NO_TIME_SLICE, TX_DONT_START);
require(status == TX_SUCCESS, "observer create");
require(observer.tx_thread_stack_highest_ptr == observer.tx_thread_stack_ptr, "observer highest pointer");
require((worker.tx_thread_state == TX_SUSPENDED) && (observer.tx_thread_state == TX_SUSPENDED),
"created threads suspended before resume");
check_padding((UCHAR *) worker_storage, offset);
check_padding((UCHAR *) observer_storage, 0U);
require(tx_thread_resume(&worker) == TX_SUCCESS, "worker resume");
require(tx_thread_resume(&observer) == TX_SUCCESS, "observer resume");
}

/* Require the genuine 4/8/8 Linux mismatch and actual high-address storage. */
INT main(INT argc, CHAR **argv)
{
setvbuf(stdout, TX_NULL, _IONBF, 0);
require(argc == 2, "one alignment offset required");
require((argv[1][0] >= '0') && (argv[1][0] <= '3') && (argv[1][1] == '\0'), "valid offset");
offset = (UINT) (argv[1][0] - '0');
worker_input = (UCHAR *) worker_storage + PADDING_BYTES + offset;
observer_input = (UCHAR *) observer_storage + PADDING_BYTES;
printf("sizeof(ULONG)=%zu sizeof(ALIGN_TYPE)=%zu sizeof(void*)=%zu offset=%u\n",
sizeof(ULONG), sizeof(ALIGN_TYPE), sizeof(VOID *), offset);
require((sizeof(ULONG) == 4U) && (sizeof(ALIGN_TYPE) == 8U) && (sizeof(VOID *) == 8U),
"native64 Linux port widths");
require(((ALIGN_TYPE) worker_input > (ALIGN_TYPE) ((ULONG) ~0U)) &&
((ALIGN_TYPE) observer_input > (ALIGN_TYPE) ((ULONG) ~0U)), "high-address stacks");
memset(worker_storage, SENTINEL, sizeof(worker_storage));
memset(observer_storage, SENTINEL, sizeof(observer_storage));
tx_kernel_enter();
require(0U, "kernel unexpectedly returned");
return(1);
}
Loading
Loading