From 476f045fcc62641ca81d7ae5beab3d1122a10376 Mon Sep 17 00:00:00 2001 From: shrutikbalwan Date: Sat, 19 Sep 2026 20:45:16 +0530 Subject: [PATCH] Handle POSIX thread marker failures and add regression tests --- portable/ThirdParty/GCC/Posix/port.c | 28 +++- .../ThirdParty/GCC/Posix/tests/CMakeLists.txt | 33 ++++ .../GCC/Posix/tests/FreeRTOSConfig.h | 28 ++++ portable/ThirdParty/GCC/Posix/tests/README.md | 23 +++ .../GCC/Posix/tests/scheduler_smoke.c | 24 +++ .../GCC/Posix/tests/thread_marker.c | 145 ++++++++++++++++++ 6 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 portable/ThirdParty/GCC/Posix/tests/CMakeLists.txt create mode 100644 portable/ThirdParty/GCC/Posix/tests/FreeRTOSConfig.h create mode 100644 portable/ThirdParty/GCC/Posix/tests/README.md create mode 100644 portable/ThirdParty/GCC/Posix/tests/scheduler_smoke.c create mode 100644 portable/ThirdParty/GCC/Posix/tests/thread_marker.c diff --git a/portable/ThirdParty/GCC/Posix/port.c b/portable/ThirdParty/GCC/Posix/port.c index a1b1ca8c7cf..13fc932ac59 100644 --- a/portable/ThirdParty/GCC/Posix/port.c +++ b/portable/ThirdParty/GCC/Posix/port.c @@ -129,6 +129,8 @@ static void prvInitThreadKey( void ); static void prvMarkAsFreeRTOSThread( void ); static BaseType_t prvIsFreeRTOSThread( void ); static void prvDestroyThreadKey( void ); +static void prvFatalError( const char * pcCall, + int iErrno ) __attribute__( ( __noreturn__ ) ); /*-----------------------------------------------------------*/ static void prvThreadKeyDestructor( void * pvData ) @@ -139,7 +141,13 @@ static void prvThreadKeyDestructor( void * pvData ) static void prvInitThreadKey( void ) { - pthread_key_create( &xThreadKey, prvThreadKeyDestructor ); + int iRet = pthread_key_create( &xThreadKey, prvThreadKeyDestructor ); + + if( iRet != 0 ) + { + prvFatalError( "pthread_key_create", iRet ); + } + /* Destroy xThreadKey when the process exits. */ atexit( prvDestroyThreadKey ); } @@ -148,15 +156,26 @@ static void prvInitThreadKey( void ) static void prvMarkAsFreeRTOSThread( void ) { uint8_t * pucThreadData = NULL; + int iRet; ( void ) pthread_once( &hThreadKeyOnce, prvInitThreadKey ); pucThreadData = malloc( 1 ); - configASSERT( pucThreadData != NULL ); + + if( pucThreadData == NULL ) + { + prvFatalError( "malloc", ENOMEM ); + } *pucThreadData = 1; - pthread_setspecific( xThreadKey, pucThreadData ); + iRet = pthread_setspecific( xThreadKey, pucThreadData ); + + if( iRet != 0 ) + { + free( pucThreadData ); + prvFatalError( "pthread_setspecific", iRet ); + } } /*-----------------------------------------------------------*/ @@ -184,9 +203,6 @@ static void prvDestroyThreadKey( void ) } /*-----------------------------------------------------------*/ -static void prvFatalError( const char * pcCall, - int iErrno ) __attribute__( ( __noreturn__ ) ); - void prvFatalError( const char * pcCall, int iErrno ) { diff --git a/portable/ThirdParty/GCC/Posix/tests/CMakeLists.txt b/portable/ThirdParty/GCC/Posix/tests/CMakeLists.txt new file mode 100644 index 00000000000..daa7f38ab2d --- /dev/null +++ b/portable/ThirdParty/GCC/Posix/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.15) +project(posix_port_tests C) +enable_testing() +find_package(Threads REQUIRED) + +get_filename_component(KERNEL_ROOT "../../../../.." ABSOLUTE) +add_library(freertos_config INTERFACE) +target_include_directories(freertos_config INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}") +set(FREERTOS_PORT GCC_POSIX CACHE STRING "" FORCE) +set(FREERTOS_HEAP 4 CACHE STRING "" FORCE) +add_subdirectory("${KERNEL_ROOT}" kernel) + +foreach(assert_enabled 0 1) + set(target thread_marker_${assert_enabled}) + add_executable(${target} thread_marker.c) + target_include_directories(${target} PRIVATE . .. "${KERNEL_ROOT}/include") + target_compile_definitions(${target} PRIVATE TEST_ASSERT_ENABLED=${assert_enabled}) + target_compile_options(${target} PRIVATE -Wall -Wextra -Werror -ffunction-sections -fdata-sections) + target_link_libraries(${target} PRIVATE Threads::Threads) + target_link_options(${target} PRIVATE -Wl,--gc-sections + -Wl,--wrap=pthread_key_create -Wl,--wrap=pthread_setspecific + -Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=abort) + foreach(scenario success key_create key_query allocation setspecific) + add_test(NAME ${target}_${scenario} COMMAND ${target} ${scenario}) + set_tests_properties(${target}_${scenario} PROPERTIES TIMEOUT 10) + endforeach() +endforeach() + +add_executable(scheduler_smoke scheduler_smoke.c) +target_link_libraries(scheduler_smoke PRIVATE freertos_kernel) +target_compile_options(scheduler_smoke PRIVATE -Wall -Wextra -Werror) +add_test(NAME scheduler_smoke COMMAND scheduler_smoke) +set_tests_properties(scheduler_smoke PROPERTIES TIMEOUT 10) diff --git a/portable/ThirdParty/GCC/Posix/tests/FreeRTOSConfig.h b/portable/ThirdParty/GCC/Posix/tests/FreeRTOSConfig.h new file mode 100644 index 00000000000..887b31b4b78 --- /dev/null +++ b/portable/ThirdParty/GCC/Posix/tests/FreeRTOSConfig.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include + +#define configUSE_PREEMPTION 1 +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configTICK_RATE_HZ 100 +#define configMAX_PRIORITIES 4 +#define configMINIMAL_STACK_SIZE 4096 +#define configTOTAL_HEAP_SIZE ( 256 * 1024 ) +#define configMAX_TASK_NAME_LEN 16 +#define configUSE_16_BIT_TICKS 0 +#define configUSE_MUTEXES 1 +#define configUSE_TIMERS 0 +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 + +#if TEST_ASSERT_ENABLED + #define configASSERT( x ) do { if( !( x ) ) { abort(); } } while( 0 ) +#endif + +#endif /* ifndef FREERTOS_CONFIG_H */ diff --git a/portable/ThirdParty/GCC/Posix/tests/README.md b/portable/ThirdParty/GCC/Posix/tests/README.md new file mode 100644 index 00000000000..9509889d05f --- /dev/null +++ b/portable/ThirdParty/GCC/Posix/tests/README.md @@ -0,0 +1,23 @@ +# POSIX thread-marker regression tests + +Run on Linux with GCC, CMake, and a GNU-compatible linker: + +```sh +cmake -S portable/ThirdParty/GCC/Posix/tests -B build/posix-tests +cmake --build build/posix-tests +ctest --test-dir build/posix-tests --output-on-failure +``` + +The failure-injection executable includes the actual port implementation to +reach its private helpers. Linker wrappers inject key-creation, allocation, and +TLS-storage failures. Each failure must terminate via the fatal handler; failed +TLS storage must first free the allocated marker. Tests run with assertions +both enabled and disabled. The success case checks thread identity, isolation +from the calling thread, and automatic destructor cleanup after pthread exit. +The separate smoke test links the full kernel and runs a real scheduled task +through a tick delay and scheduler shutdown. + +For sanitizer validation, configure another build directory with +`-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer"`. +These tests are specific to the POSIX simulator; they do not validate hardware +ports. The kernel's main CMock suite lives in the parent FreeRTOS repository. diff --git a/portable/ThirdParty/GCC/Posix/tests/scheduler_smoke.c b/portable/ThirdParty/GCC/Posix/tests/scheduler_smoke.c new file mode 100644 index 00000000000..5a0445cbe7a --- /dev/null +++ b/portable/ThirdParty/GCC/Posix/tests/scheduler_smoke.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: MIT */ +#include "FreeRTOS.h" +#include "task.h" + +static volatile int iTaskRan; + +static void prvTask( void * pvUnused ) +{ + ( void ) pvUnused; + vTaskDelay( 2 ); + iTaskRan = 1; + vTaskEndScheduler(); +} + +int main( void ) +{ + if( xTaskCreate( prvTask, "smoke", configMINIMAL_STACK_SIZE, NULL, 1, NULL ) != pdPASS ) + { + return EXIT_FAILURE; + } + + vTaskStartScheduler(); + return iTaskRan ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/portable/ThirdParty/GCC/Posix/tests/thread_marker.c b/portable/ThirdParty/GCC/Posix/tests/thread_marker.c new file mode 100644 index 00000000000..02446be3850 --- /dev/null +++ b/portable/ThirdParty/GCC/Posix/tests/thread_marker.c @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: MIT */ + +/* Include the implementation to exercise its private TLS helpers unchanged. + * Unused scheduler sections are discarded by the linker. */ +#include "../port.c" + +static const char * pcScenario; +static void * pvMarker; +static int iAllocations; +static int iFrees; +static int iStores; + +extern void * __real_malloc( size_t xSize ); +extern void __real_free( void * pvPointer ); +extern int __real_pthread_key_create( pthread_key_t * pxKey, + void ( * pxDestructor )( void * ) ); +extern int __real_pthread_setspecific( pthread_key_t xKey, + const void * pvValue ); + +int __wrap_pthread_key_create( pthread_key_t * pxKey, + void ( * pxDestructor )( void * ) ) +{ + if( ( strcmp( pcScenario, "key_create" ) == 0 ) || ( strcmp( pcScenario, "key_query" ) == 0 ) ) + { + return EAGAIN; + } + + return __real_pthread_key_create( pxKey, pxDestructor ); +} + +void * __wrap_malloc( size_t xSize ) +{ + if( xSize == 1 ) + { + iAllocations++; + + if( strcmp( pcScenario, "allocation" ) == 0 ) + { + return NULL; + } + + pvMarker = __real_malloc( xSize ); + return pvMarker; + } + + return __real_malloc( xSize ); +} + +void __wrap_free( void * pvPointer ) +{ + if( ( pvPointer != NULL ) && ( pvPointer == pvMarker ) ) + { + iFrees++; + } + + __real_free( pvPointer ); +} + +int __wrap_pthread_setspecific( pthread_key_t xKey, + const void * pvValue ) +{ + iStores++; + + if( strcmp( pcScenario, "setspecific" ) == 0 ) + { + return ENOMEM; + } + + return __real_pthread_setspecific( xKey, pvValue ); +} + +void __wrap_abort( void ) +{ + int iPassed = 0; + + if( ( strcmp( pcScenario, "key_create" ) == 0 ) || ( strcmp( pcScenario, "key_query" ) == 0 ) ) + { + iPassed = ( iAllocations == 0 ) && ( iStores == 0 ); + } + else if( strcmp( pcScenario, "allocation" ) == 0 ) + { + iPassed = ( iAllocations == 1 ) && ( iStores == 0 ); + } + else if( strcmp( pcScenario, "setspecific" ) == 0 ) + { + iPassed = ( iAllocations == 1 ) && ( iStores == 1 ) && ( iFrees == 1 ); + } + + _Exit( iPassed ? EXIT_SUCCESS : EXIT_FAILURE ); +} + +static void * prvMarkedThread( void * pvUnused ) +{ + ( void ) pvUnused; + + if( prvIsFreeRTOSThread() != pdFALSE ) + { + return ( void * ) 1; + } + + prvMarkAsFreeRTOSThread(); + return ( void * ) ( intptr_t ) ( prvIsFreeRTOSThread() != pdTRUE ); +} + +int main( int argc, + char ** argv ) +{ + pthread_t xThread; + void * pvResult; + + if( argc != 2 ) + { + return EXIT_FAILURE; + } + + pcScenario = argv[ 1 ]; + + if( strcmp( pcScenario, "success" ) == 0 ) + { + if( pthread_create( &xThread, NULL, prvMarkedThread, NULL ) != 0 ) + { + return EXIT_FAILURE; + } + + if( pthread_join( xThread, &pvResult ) != 0 ) + { + return EXIT_FAILURE; + } + + return ( ( pvResult == NULL ) && ( iAllocations == 1 ) && ( iFrees == 1 ) && + ( prvIsFreeRTOSThread() == pdFALSE ) ) ? EXIT_SUCCESS : EXIT_FAILURE; + } + + if( strcmp( pcScenario, "key_query" ) == 0 ) + { + ( void ) prvIsFreeRTOSThread(); + } + else + { + prvMarkAsFreeRTOSThread(); + } + + fprintf( stderr, "Continued after injected %s failure\n", pcScenario ); + return EXIT_FAILURE; +}