1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
|
/***************************************************************************
* 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 drives the MISRA compliance shim.
A MISRA build replaces the kernel's pointer conversions, its user timer
derivation and its thread stack check with calls to functions that do the
same work behind a prototype. The test drives each of them through the macro
the kernel itself uses, so the shim and the in-line form the other builds
compile are asserted to agree.
The stack check is the one with logic in it. It scores seven conditions:
whether there is a thread to check at all, whether its control block is still
a thread, whether its stack pointer has gone below the lowest recorded,
whether either of the two fill patterns that fence the stack has been
overwritten, whether the lowest recorded stack pointer has dropped below the
stack, and whether the word under that pointer is still fill. The test gives
a scratch thread a fault in turn and checks it with the fault in place, so
every condition is taken both ways. Every fault is written into the scratch
thread's own control block or into the words the test laid out around its
stack; the kernel is not modified to produce any of them.
The scratch thread is created and never started, and the stack it is given is
a buffer of the test's own with a fence on each side, so no faulted state is
ever visible to the scheduler. That matters more here than it does on a
single processor: TX_DISABLE on SMP is _tx_thread_smp_protect, and the check
releases it around the stack error handler and the stack analysis, so those
two run with the other cores free. Nothing but this test refers to the
scratch thread, so a fault planted before a check is still there when the
check reads it, whatever else runs in between.
One of the conversions belongs to the trace component rather than the kernel
proper, and no kernel source uses the macro that reaches it, so it is driven
here alongside the others. */
#include <stdio.h>
#include "tx_api.h"
#define TX_SOURCE_CODE
#include "tx_trace.h"
/* The scratch thread's stack, fenced on both sides. The check reads the word
immediately above the stack area and, when the lowest recorded stack pointer
is dropped below the stack, the word immediately below it, so both have to
belong to the test rather than to whatever happens to be laid out next. */
#define SHIM_FENCE_WORDS 4
#define SHIM_STACK_WORDS (TEST_STACK_SIZE_PRINTF / sizeof(ULONG))
static ULONG shim_stack_area[SHIM_FENCE_WORDS + SHIM_STACK_WORDS + SHIM_FENCE_WORDS];
#define SHIM_STACK_START (&shim_stack_area[SHIM_FENCE_WORDS])
static unsigned long thread_0_counter = 0;
static TX_THREAD thread_0;
/* The thread the stack check is driven against. It is created without being
started, so nothing ever runs on the stack the test lays out for it. */
static TX_THREAD thread_1;
static TX_TIMER timer_0;
#ifdef TX_ENABLE_EVENT_TRACE
/* A trace buffer entry for the one conversion whose macro no kernel source
uses. Nothing reads it; the conversion only takes its address. */
static TX_TRACE_BUFFER_ENTRY trace_entry;
#endif
/* Define thread prototypes. */
static void thread_0_entry(ULONG thread_input);
static void thread_1_entry(ULONG thread_input);
/* Prototype for test control return. */
void test_control_return(UINT status);
/* Define the timer entry, which never runs: the timer exists so that the user
timer derivation has a real control block to recover. */
static void timer_0_entry(ULONG timer_input)
{
}
#ifdef TX_MISRA_ENABLE
/* Fill the scratch stack and both of its fences with the pattern the check
reads as untouched, so that every condition below is decided by the fault the
test plants and by nothing else. */
static void shim_stack_reset(TX_THREAD *thread_ptr)
{
UINT index;
for (index = 0; index < (SHIM_FENCE_WORDS + SHIM_STACK_WORDS + SHIM_FENCE_WORDS); index++)
{
shim_stack_area[index] = TX_STACK_FILL;
}
/* Start from the thread's own stack pointer, which is where an untouched
thread's lowest recorded pointer sits. */
thread_ptr -> tx_thread_stack_highest_ptr = thread_ptr -> tx_thread_stack_ptr;
}
#endif
/* Define what the initial system looks like. */
#ifdef CTEST
void test_application_define(void *first_unused_memory)
#else
void threadx_misra_shim_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;
/* 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, 1,
pointer, TEST_STACK_SIZE_PRINTF,
16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
pointer = pointer + TEST_STACK_SIZE_PRINTF;
/* Check for status. */
if (status != TX_SUCCESS)
{
printf("Running MISRA Shim Test............................................ ERROR #1\n");
test_control_return(1);
}
/* Create the thread the stack check is driven against, on the test's own
fenced stack area and without starting it. */
status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
(VOID *) SHIM_STACK_START, SHIM_STACK_WORDS * sizeof(ULONG),
20, 20, TX_NO_TIME_SLICE, TX_DONT_START);
/* Check for status. */
if (status != TX_SUCCESS)
{
printf("Running MISRA Shim Test............................................ ERROR #2\n");
test_control_return(1);
}
/* Create the timer the user timer derivation recovers. */
status = tx_timer_create(&timer_0, "timer 0", timer_0_entry, 0,
100, 100, TX_NO_ACTIVATE);
/* Check for status. */
if (status != TX_SUCCESS)
{
printf("Running MISRA Shim Test............................................ ERROR #3\n");
test_control_return(1);
}
}
/* Define the test thread. */
static void thread_0_entry(ULONG thread_input)
{
TX_TIMER *recovered_timer;
TX_THREAD *recovered_thread;
UCHAR *recovered_name;
VOID *converted;
CHAR name[] = "thread 0";
#ifdef TX_MISRA_ENABLE
TX_THREAD *thread_ptr;
ULONG *stack_start;
VOID *scratch_highest;
ULONG saved_id;
#endif
/* Increment thread 0 counter. */
thread_0_counter++;
/* Inform user of success getting to this test. */
printf("Running MISRA Shim Test............................................ ");
/* A ULONG taken back to a pointer is the pointer it came from. */
converted = TX_ULONG_TO_POINTER_CONVERT(TX_POINTER_TO_ULONG_CONVERT(&thread_0));
if (converted != (VOID *) &thread_0)
{
printf("ERROR #4\n");
test_control_return(1);
}
/* A void pointer taken to a thread pointer is the thread it came from. */
recovered_thread = TX_VOID_TO_THREAD_POINTER_CONVERT((VOID *) &thread_0);
if (recovered_thread != &thread_0)
{
printf("ERROR #5\n");
test_control_return(1);
}
/* A character pointer taken to an unsigned one still addresses the same
string. */
recovered_name = TX_CHAR_TO_UCHAR_POINTER_CONVERT(name);
if ((recovered_name == TX_NULL) || (*recovered_name != (UCHAR) 't'))
{
printf("ERROR #6\n");
test_control_return(1);
}
/* The internal timer block a timer expiration is handed is derived back to
the application timer that contains it. */
TX_USER_TIMER_POINTER_GET(&(timer_0.tx_timer_internal), recovered_timer)
if (recovered_timer != &timer_0)
{
printf("ERROR #7\n");
test_control_return(1);
}
#ifdef TX_ENABLE_EVENT_TRACE
/* A trace buffer entry taken to an unsigned character pointer still
addresses the entry. */
if (TX_ENTRY_TO_UCHAR_POINTER_CONVERT(&trace_entry) != (UCHAR *) &trace_entry)
{
printf("ERROR #8\n");
test_control_return(1);
}
#endif
#ifdef TX_MISRA_ENABLE
thread_ptr = &thread_1;
stack_start = (ULONG *) thread_ptr -> tx_thread_stack_start;
/* No thread to check at all. The kernel reaches the check through
TX_THREAD_STACK_CHECK, which hands it the thread's own lowest recorded
stack pointer; with no thread there is no such field, so this one case is
driven through the shim directly with a pointer of the test's own. */
scratch_highest = (VOID *) stack_start;
_tx_misra_thread_stack_check(TX_NULL, &scratch_highest);
/* A control block that is no longer a thread. The check reads the
identifier and does nothing else. */
shim_stack_reset(thread_ptr);
saved_id = thread_ptr -> tx_thread_id;
thread_ptr -> tx_thread_id = ((ULONG) 0);
TX_THREAD_STACK_CHECK(thread_ptr)
thread_ptr -> tx_thread_id = saved_id;
/* An untouched thread: the stack is where it has always been and both
fences are intact. */
shim_stack_reset(thread_ptr);
TX_THREAD_STACK_CHECK(thread_ptr)
/* The lowest recorded stack pointer is put above the thread's own stack
pointer, so the check finds the stack deeper than it has recorded and
lowers its mark. */
shim_stack_reset(thread_ptr);
thread_ptr -> tx_thread_stack_highest_ptr = thread_ptr -> tx_thread_stack_end;
TX_THREAD_STACK_CHECK(thread_ptr)
if (thread_ptr -> tx_thread_stack_highest_ptr != thread_ptr -> tx_thread_stack_ptr)
{
printf("ERROR #9\n");
test_control_return(1);
}
/* The fence below the stack is overwritten, which is an underflow as far as
the check is concerned. */
shim_stack_reset(thread_ptr);
*stack_start = ~TX_STACK_FILL;
TX_THREAD_STACK_CHECK(thread_ptr)
/* The fence above the stack is overwritten, which is an overflow. The fence
below is left intact, so the check reaches the second condition of the
three before it reports. */
shim_stack_reset(thread_ptr);
*((ULONG *) (((UCHAR *) thread_ptr -> tx_thread_stack_end) + 1)) = ~TX_STACK_FILL;
TX_THREAD_STACK_CHECK(thread_ptr)
/* The lowest recorded stack pointer is dropped below the stack itself, with
both fences intact so that the check reaches the third condition. The word
under that pointer is left as fill, so no stack analysis follows. */
shim_stack_reset(thread_ptr);
thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start - 1);
TX_THREAD_STACK_CHECK(thread_ptr)
/* The word under the lowest recorded stack pointer is made used, so the
check runs the stack analysis with both fences intact and the pointer
inside the stack. */
shim_stack_reset(thread_ptr);
*(stack_start + 1) = ~TX_STACK_FILL;
thread_ptr -> tx_thread_stack_highest_ptr = (VOID *) (stack_start + 2);
TX_THREAD_STACK_CHECK(thread_ptr)
/* The analysis walks up from the bottom of the stack to the first used word
and records it. */
if (thread_ptr -> tx_thread_stack_highest_ptr != (VOID *) (stack_start + 1))
{
printf("ERROR #10\n");
test_control_return(1);
}
/* Leave the scratch thread with nothing planted in it. */
shim_stack_reset(thread_ptr);
#endif
printf("SUCCESS!\n");
test_control_return(0);
}
/* The scratch thread never runs. */
static void thread_1_entry(ULONG thread_input)
{
while (1)
{
tx_thread_suspend(&thread_1);
}
}
|