/*************************************************************************** * 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 writes a later trace event over a byte allocate's own entry. The byte allocate records a trace event on the way in, saves the entry's time stamp, and comes back to fill in the address it returned once it has one -- but only if the time stamp says nothing has overwritten the entry meanwhile. Ordinary use never takes the other arm. The service holds the protection across the whole of it except for the search, and nothing in the kernel writes a trace event inside that gap, so the entry the allocate saved is always still its own by the time it looks again. The gap is reachable from the test side, because the Linux SMP port already calls out to the harness inside it: TX_BYTE_ALLOCATE_EXTENSION runs between the restore and the disable that bracket the search, and exists for exactly this kind of coverage work. The extension gained a second clause, guarded by its own flag, that calls the routine below. The restore that opens the gap is a real release on SMP. TX_RESTORE is _tx_thread_smp_unprotect, which gives the protection structure up once the nesting count reaches zero and the preempt disable flag is clear, and _tx_byte_allocate raises neither before it gets there. So the hook below runs outside the protection and may take it again for an insert of its own. Writing an event is not on its own enough. With the byte pool event class enabled the allocate's own insert advances the buffer, so a later event lands on the next entry and leaves the saved one alone. With the class filtered the insert writes nothing and the buffer stays parked on the saved entry, so the next event written by anything lands exactly on it. Filtering the class is what makes the entry overwritable; the port hook is what puts an event in the window. Neither closes the arm without the other. The kernel is not modified and no symbol is replaced: the allocate runs its own code throughout, and the only thing the test supplies is an event at a moment the service already offers to the harness. The arm is reachable in one configuration only, and not for want of trying in the other. Under TX_MISRA_ENABLE the Linux SMP port takes its trace time stamp from _tx_misra_time_stamp_get(), whose body is `return(0);`, so every entry in the buffer carries a time stamp of zero and the allocate's comparison reads 0 == 0 whatever is written over the entry. No event, no interrupt and no other core can make it false there. The overwrite itself is still driven in that configuration -- the hook runs and the entry is written -- but only the configuration with a real time source can assert the consequence. */ #include #include "tx_api.h" #define TX_SOURCE_CODE #include "tx_thread.h" #include "tx_trace.h" #define TEST_PRIORITY 16 /* The pool the allocate under test comes from, and the size taken from it. */ #define POOL_SIZE 2048 #define ALLOCATION_SIZE 100 /* The trace buffer. Its size does not matter to this test -- the entry that gets overwritten is the one the buffer is parked on, not one reached by wrapping -- but the object registry has to hold the objects the test creates. */ #define TRACE_BUFFER_SIZE 8192 #define TRACE_REGISTRY_ENTRIES 32 static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; static TX_BYTE_POOL pool_0; static UCHAR trace_buffer[TRACE_BUFFER_SIZE]; /* How many times the port hook called the routine below. */ static volatile ULONG overwrite_count = 0; /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); /* Called from TX_BYTE_ALLOCATE_EXTENSION, inside the byte allocate's own window between the release and the next protection, with the trace buffer parked on the entry the allocate saved. The user event written here therefore lands on that entry and gives it a new time stamp. The harness declares this weakly and does nothing with it; this is the one test that gives it a body. */ void overwrite_byte_allocate_trace_entry(void) { overwrite_count++; #ifdef TX_ENABLE_EVENT_TRACE tx_trace_user_event_insert(TX_TRACE_USER_EVENT_START, 0, 0, 0, 0); #endif } static void thread_0_entry(ULONG thread_input) { UINT status; CHAR *memory_ptr; #ifdef TX_ENABLE_EVENT_TRACE TX_TRACE_BUFFER_ENTRY *saved_entry; ULONG saved_time_stamp; #endif /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running Byte Pool Trace Overwrite Test............................ "); #ifdef TX_ENABLE_EVENT_TRACE status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), TRACE_REGISTRY_ENTRIES); if (status != TX_SUCCESS) { printf("ERROR #2\n"); test_control_return(1); } /* Filter the byte pool events, so that the allocate's own insert writes nothing and leaves the buffer parked where it is. */ status = tx_trace_event_filter(TX_TRACE_BYTE_POOL_EVENTS); if (status != TX_SUCCESS) { printf("ERROR #3\n"); test_control_return(1); } /* The entry the allocate is about to save, and what it holds now. The allocate reads the same entry back after the search and compares. */ saved_entry = _tx_trace_buffer_current_ptr; saved_time_stamp = saved_entry -> tx_trace_buffer_entry_time_stamp; /* Arm the port hook for the next allocate. */ threadx_byte_allocate_trace_test = ((TEST_FLAG) 1); #endif status = tx_byte_allocate(&pool_0, (VOID **) &memory_ptr, ALLOCATION_SIZE, TX_NO_WAIT); if (status != TX_SUCCESS) { printf("ERROR #4\n"); test_control_return(1); } #ifdef TX_ENABLE_EVENT_TRACE /* The hook ran, inside the allocate. */ if (overwrite_count != ((ULONG) 1)) { printf("ERROR #5\n"); test_control_return(1); } /* The entry the allocate saved was written over, whichever configuration this is: the identifier is the user event's rather than the allocate's. The identifier carries the writing core in its top byte, so only the event half of it is compared. */ if ((saved_entry -> tx_trace_buffer_entry_event_id & ((ULONG) 0x00FFFFFF)) != ((ULONG) TX_TRACE_USER_EVENT_START)) { printf("ERROR #6\n"); test_control_return(1); } #ifndef TX_MISRA_ENABLE /* With a real time source the entry now reads differently from what the allocate recorded, which is what its own check compares against. */ if (saved_entry -> tx_trace_buffer_entry_time_stamp == saved_time_stamp) { printf("ERROR #7\n"); test_control_return(1); } /* So the allocate left the entry alone rather than writing its address into it, which is the arm under test. */ if (saved_entry -> tx_trace_buffer_entry_information_field_2 == TX_POINTER_TO_ULONG_CONVERT(memory_ptr)) { printf("ERROR #8\n"); test_control_return(1); } #endif /* Put the event class back and leave tracing off, so nothing after this test inherits either. */ status = tx_trace_event_unfilter(TX_TRACE_BYTE_POOL_EVENTS); if (status != TX_SUCCESS) { printf("ERROR #9\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #10\n"); test_control_return(1); } #endif status = tx_byte_release(memory_ptr); if (status != TX_SUCCESS) { printf("ERROR #11\n"); test_control_return(1); } /* Success! */ printf("SUCCESS!\n"); test_control_return(0); } /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_byte_pool_trace_overwrite_application_define(void *first_unused_memory) #endif { UINT status; CHAR *pointer; /* Put first available memory address into a character pointer. */ pointer = (CHAR *) first_unused_memory; 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 Byte Pool Trace Overwrite Test............................ ERROR #1\n"); test_control_return(1); } status = tx_byte_pool_create(&pool_0, "pool 0", pointer, POOL_SIZE); pointer = pointer + POOL_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Byte Pool Trace Overwrite Test............................ ERROR #1\n"); test_control_return(1); } }