From 5fc10746a8106007449f936b8fc2452e5bc773cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 10 Sep 2026 08:14:20 -0400 Subject: [PATCH] Made the stack analyze binary search require several consecutive fill words, so unwritten holes in a used stack no longer cause the highest stack pointer to be under-reported The binary search in _tx_thread_stack_analyze() accepted a probe location as unused as soon as a single word still held TX_STACK_FILL. A word inside an otherwise used region that simply was never written - the padding of a partially initialized local array, for example - therefore made the search move away from the real boundary and report far less stack usage than the thread had actually consumed, which in turn kept the stack guard from firing. The probe now walks down from the candidate location and requires TX_THREAD_STACK_ANALYZE_FILL_WORDS consecutive fill words before it treats the location as unused, stopping early at the lowest location already known to hold the fill pattern. The new macro defaults to eight words and can be overridden in tx_port.h; setting it to one restores the previous behavior. Holes shorter than the configured run no longer mislead the search, and the result is unchanged for stacks that contain no such holes. Assisted-by: Copilot (Opus 5) --- common/inc/tx_api.h | 13 ++++++ common/src/tx_thread_stack_analyze.c | 50 +++++++++++++++++++++++- common_smp/inc/tx_api.h | 13 ++++++ common_smp/src/tx_thread_stack_analyze.c | 50 +++++++++++++++++++++++- 4 files changed, 122 insertions(+), 4 deletions(-) diff --git a/common/inc/tx_api.h b/common/inc/tx_api.h index 321e70a9b..bf9de8565 100644 --- a/common/inc/tx_api.h +++ b/common/inc/tx_api.h @@ -1843,6 +1843,19 @@ UINT _tx_trace_interrupt_control(UINT new_posture); #endif +/* Define the number of consecutive stack fill pattern words the thread stack analyze function must + observe, working towards the lowest address of the stack, before it accepts a probe location as + unused. A value of one restores the original behavior, where a single word that happens to still + hold the fill pattern - an unwritten hole inside an otherwise used region, such as the padding of + a partially initialized local array - can make the binary search stop early and under-report the + stack usage. Larger values make the result more accurate at the cost of a few additional reads + per binary search iteration. This can be re-defined in tx_port.h. */ + +#ifndef TX_THREAD_STACK_ANALYZE_FILL_WORDS +#define TX_THREAD_STACK_ANALYZE_FILL_WORDS ((ULONG) 8) +#endif + + /* Add a default macro that can be re-defined in tx_port.h to add processing to the initialize kernel enter function. By default, this is simply defined as whitespace. */ diff --git a/common/src/tx_thread_stack_analyze.c b/common/src/tx_thread_stack_analyze.c index 080d00522..e3023f13d 100644 --- a/common/src/tx_thread_stack_analyze.c +++ b/common/src/tx_thread_stack_analyze.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -70,6 +72,9 @@ TX_INTERRUPT_SAVE_AREA ULONG *stack_ptr; ULONG *stack_lowest; ULONG *stack_highest; +ULONG *probe_ptr; +ULONG probe_count; +UINT fill_present; ULONG size; @@ -110,8 +115,49 @@ ULONG size; size = (ULONG) (TX_ULONG_POINTER_DIF(stack_highest, stack_lowest))/((ULONG) 2); stack_ptr = TX_ULONG_POINTER_ADD(stack_lowest, size); - /* Determine if the pattern is still there. */ - if (*stack_ptr != TX_STACK_FILL) + /* Determine if the pattern is still there. To avoid stopping on an + unwritten hole inside an otherwise used region, require several + consecutive fill words, working towards the lowest address. The scan + stops at the lowest known fill location, since everything at or below + that point is already known to hold the fill pattern. */ + fill_present = TX_TRUE; + probe_ptr = stack_ptr; + probe_count = TX_THREAD_STACK_ANALYZE_FILL_WORDS; + while (probe_count != ((ULONG) 0)) + { + + /* Determine if this word still holds the fill pattern. */ + if (*probe_ptr != TX_STACK_FILL) + { + + /* No, the probe location is in use. */ + fill_present = TX_FALSE; + probe_count = ((ULONG) 0); + } + else + { + + /* Yes, account for this word. */ + probe_count--; + + /* Determine if the lowest known fill location has been reached. */ + if (probe_ptr <= stack_lowest) + { + + /* Yes, nothing further to check. */ + probe_count = ((ULONG) 0); + } + else + { + + /* Position to the previous word in the stack. */ + probe_ptr = TX_ULONG_POINTER_SUB(probe_ptr, 1); + } + } + } + + /* Determine if the probe location is in use. */ + if (fill_present == TX_FALSE) { /* Update the stack highest, since we need to look in the upper half now. */ diff --git a/common_smp/inc/tx_api.h b/common_smp/inc/tx_api.h index 7d0ccbe54..679bfe679 100644 --- a/common_smp/inc/tx_api.h +++ b/common_smp/inc/tx_api.h @@ -1853,6 +1853,19 @@ UINT _tx_trace_interrupt_control(UINT new_posture); #endif +/* Define the number of consecutive stack fill pattern words the thread stack analyze function must + observe, working towards the lowest address of the stack, before it accepts a probe location as + unused. A value of one restores the original behavior, where a single word that happens to still + hold the fill pattern - an unwritten hole inside an otherwise used region, such as the padding of + a partially initialized local array - can make the binary search stop early and under-report the + stack usage. Larger values make the result more accurate at the cost of a few additional reads + per binary search iteration. This can be re-defined in tx_port.h. */ + +#ifndef TX_THREAD_STACK_ANALYZE_FILL_WORDS +#define TX_THREAD_STACK_ANALYZE_FILL_WORDS ((ULONG) 8) +#endif + + /* Add a default macro that can be re-defined in tx_port.h to add processing to the initialize kernel enter function. By default, this is simply defined as whitespace. */ diff --git a/common_smp/src/tx_thread_stack_analyze.c b/common_smp/src/tx_thread_stack_analyze.c index 080d00522..e3023f13d 100644 --- a/common_smp/src/tx_thread_stack_analyze.c +++ b/common_smp/src/tx_thread_stack_analyze.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Some portions generated by Copilot (Opus 5). + /**************************************************************************/ /**************************************************************************/ @@ -70,6 +72,9 @@ TX_INTERRUPT_SAVE_AREA ULONG *stack_ptr; ULONG *stack_lowest; ULONG *stack_highest; +ULONG *probe_ptr; +ULONG probe_count; +UINT fill_present; ULONG size; @@ -110,8 +115,49 @@ ULONG size; size = (ULONG) (TX_ULONG_POINTER_DIF(stack_highest, stack_lowest))/((ULONG) 2); stack_ptr = TX_ULONG_POINTER_ADD(stack_lowest, size); - /* Determine if the pattern is still there. */ - if (*stack_ptr != TX_STACK_FILL) + /* Determine if the pattern is still there. To avoid stopping on an + unwritten hole inside an otherwise used region, require several + consecutive fill words, working towards the lowest address. The scan + stops at the lowest known fill location, since everything at or below + that point is already known to hold the fill pattern. */ + fill_present = TX_TRUE; + probe_ptr = stack_ptr; + probe_count = TX_THREAD_STACK_ANALYZE_FILL_WORDS; + while (probe_count != ((ULONG) 0)) + { + + /* Determine if this word still holds the fill pattern. */ + if (*probe_ptr != TX_STACK_FILL) + { + + /* No, the probe location is in use. */ + fill_present = TX_FALSE; + probe_count = ((ULONG) 0); + } + else + { + + /* Yes, account for this word. */ + probe_count--; + + /* Determine if the lowest known fill location has been reached. */ + if (probe_ptr <= stack_lowest) + { + + /* Yes, nothing further to check. */ + probe_count = ((ULONG) 0); + } + else + { + + /* Position to the previous word in the stack. */ + probe_ptr = TX_ULONG_POINTER_SUB(probe_ptr, 1); + } + } + } + + /* Determine if the probe location is in use. */ + if (fill_present == TX_FALSE) { /* Update the stack highest, since we need to look in the upper half now. */