diff options
Diffstat (limited to 'cpukit/libmisc/stackchk')
-rw-r--r-- | cpukit/libmisc/stackchk/README | 53 | ||||
-rw-r--r-- | cpukit/libmisc/stackchk/check.c | 515 | ||||
-rw-r--r-- | cpukit/libmisc/stackchk/internal.h | 95 | ||||
-rw-r--r-- | cpukit/libmisc/stackchk/stackchk.h | 135 |
4 files changed, 798 insertions, 0 deletions
diff --git a/cpukit/libmisc/stackchk/README b/cpukit/libmisc/stackchk/README new file mode 100644 index 0000000000..5421a77434 --- /dev/null +++ b/cpukit/libmisc/stackchk/README @@ -0,0 +1,53 @@ +# +# $Id$ +# + +Introduction +============ + +This directory contains a stack bounds checker. It provides two +primary features: + + + check for stack overflow at each context switch + + provides an educated guess at each task's stack usage + +Enabling +======== + +Add the stack checker extension to the initial user extension set. +If using confdefs.h to build your configuration table, this is +as simple as adding -DSTACK_CHECK_ON to the gcc command line which +compiles the file defining the configuration table. In the RTEMS +test suites and samples, this is always init.c + +Background +========== + +The stack overflow check at context switch works by looking for +a 16 byte pattern at the logical end of the stack to be corrupted. +The "guesser" assumes that the entire stack was prefilled with a known +pattern and assumes that the pattern is still in place if the memory +has not been used as a stack. + +Both of these can be fooled by pushing large holes onto the stack +and not writing to them... or (much more unlikely) writing the +magic patterns into memory. + +This code has not been extensively tested. It is provided as a tool +for RTEMS users to catch the most common mistake in multitasking +systems ... too little stack space. Suggestions and comments are appreciated. + +NOTES: + +1. Stack usage information is questionable on CPUs which push + large holes on stack. + +2. The stack checker has a tendency to generate a fault when + trying to print the helpful diagnostic message. If it comes + out, congratulations. If not, then the variable Stack_check_Blown_task + contains a pointer to the TCB of the offending task. This + is usually enough to go on. + +FUTURE: + +1. Determine how/if gcc will generate stack probe calls and support that. diff --git a/cpukit/libmisc/stackchk/check.c b/cpukit/libmisc/stackchk/check.c new file mode 100644 index 0000000000..d2c0b1001b --- /dev/null +++ b/cpukit/libmisc/stackchk/check.c @@ -0,0 +1,515 @@ +/* + * Stack Overflow Check User Extension Set + * + * NOTE: This extension set automatically determines at + * initialization time whether the stack for this + * CPU grows up or down and installs the correct + * extension routines for that direction. + * + * COPYRIGHT (c) 1989-2010. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <inttypes.h> + +/* + * The stack dump information may be printed by a "fatal" extension. + * Fatal extensions only get called via rtems_fatal_error_occurred() + * and not when rtems_shutdown_executive() is called. + * When that happens, this #define should be deleted and all the code + * it marks. + */ +#define DONT_USE_FATAL_EXTENSION + +#include <string.h> +#include <stdlib.h> + +#include <rtems/bspIo.h> +#include <rtems/stackchk.h> +#include "internal.h" + +/* + * Variable to indicate when the stack checker has been initialized. + */ +static int Stack_check_Initialized = 0; + +/* + * The "magic pattern" used to mark the end of the stack. + */ +Stack_check_Control Stack_check_Pattern; + +/* + * Helper function to report if the actual stack pointer is in range. + * + * NOTE: This uses a GCC specific method. + */ +static inline bool Stack_check_Frame_pointer_in_range( + Stack_Control *the_stack +) +{ + #if defined(__GNUC__) + void *sp = __builtin_frame_address(0); + + if ( sp < the_stack->area ) { + return false; + } + if ( sp > (the_stack->area + the_stack->size) ) { + return false; + } + #else + #error "How do I check stack bounds on a non-GNU compiler?" + #endif + return true; +} + +/* + * Where the pattern goes in the stack area is dependent upon + * whether the stack grow to the high or low area of the memory. + */ +#if (CPU_STACK_GROWS_UP == TRUE) + #define Stack_check_Get_pattern( _the_stack ) \ + ((char *)(_the_stack)->area + \ + (_the_stack)->size - sizeof( Stack_check_Control ) ) + + #define Stack_check_Calculate_used( _low, _size, _high_water ) \ + ((char *)(_high_water) - (char *)(_low)) + + #define Stack_check_usable_stack_start(_the_stack) \ + ((_the_stack)->area) + +#else + /* + * We need this magic offset because during a task delete the task stack will + * be freed before we enter the task switch extension which checks the stack. + * The task stack free operation will write the next and previous pointers + * for the free list into this area. + */ + #define Stack_check_Get_pattern( _the_stack ) \ + ((char *)(_the_stack)->area + sizeof(Heap_Block) - HEAP_BLOCK_HEADER_SIZE) + + #define Stack_check_Calculate_used( _low, _size, _high_water) \ + ( ((char *)(_low) + (_size)) - (char *)(_high_water) ) + + #define Stack_check_usable_stack_start(_the_stack) \ + ((char *)(_the_stack)->area + sizeof(Stack_check_Control)) + +#endif + +/* + * Obtain a properly typed pointer to the area to check. + */ +#define Stack_check_Get_pattern_area( _the_stack ) \ + (Stack_check_Control *) Stack_check_Get_pattern( _the_stack ) + +/* + * The assumption is that if the pattern gets overwritten, the task + * is too close. This defines the usable stack memory. + */ +#define Stack_check_usable_stack_size(_the_stack) \ + ((_the_stack)->size - sizeof(Stack_check_Control)) + +#if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + /* + * Did RTEMS allocate the interrupt stack? If so, put it in + * Stack_Control format. + */ + Stack_Control Stack_check_Interrupt_stack; +#endif + +/* + * Fill an entire stack area with BYTE_PATTERN. This will be used + * to check for amount of actual stack used. + */ +#define Stack_check_Dope_stack(_stack) \ + memset((_stack)->area, BYTE_PATTERN, (_stack)->size) + +/* + * Stack_check_Initialize + */ +void Stack_check_Initialize( void ) +{ + int i; + uint32_t *p; + static uint32_t pattern[ 4 ] = { + 0xFEEDF00D, 0x0BAD0D06, /* FEED FOOD to BAD DOG */ + 0xDEADF00D, 0x600D0D06 /* DEAD FOOD but GOOD DOG */ + }; + + if ( Stack_check_Initialized ) + return; + + /* + * Dope the pattern and fill areas + */ + p = Stack_check_Pattern.pattern; + for ( i = 0; i < PATTERN_SIZE_WORDS; i++ ) { + p[i] = pattern[ i%4 ]; + } + + /* + * If appropriate, setup the interrupt stack for high water testing + * also. + */ + #if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + if (_CPU_Interrupt_stack_low && _CPU_Interrupt_stack_high) { + Stack_check_Interrupt_stack.area = _CPU_Interrupt_stack_low; + Stack_check_Interrupt_stack.size = (char *) _CPU_Interrupt_stack_high - + (char *) _CPU_Interrupt_stack_low; + Stack_check_Dope_stack(&Stack_check_Interrupt_stack); + } + #endif + + Stack_check_Initialized = 1; +} + +/* + * rtems_stack_checker_create_extension + */ +bool rtems_stack_checker_create_extension( + Thread_Control *running __attribute__((unused)), + Thread_Control *the_thread +) +{ + Stack_check_Initialize(); + + if (the_thread) + Stack_check_Dope_stack(&the_thread->Start.Initial_stack); + + return true; +} + +/* + * rtems_stack_checker_Begin_extension + */ +void rtems_stack_checker_begin_extension( + Thread_Control *the_thread +) +{ + Stack_check_Control *the_pattern; + + if ( the_thread->Object.id == 0 ) /* skip system tasks */ + return; + + the_pattern = Stack_check_Get_pattern_area(&the_thread->Start.Initial_stack); + + *the_pattern = Stack_check_Pattern; +} + +/* + * Stack_check_report_blown_task + * + * Report a blown stack. Needs to be a separate routine + * so that interrupt handlers can use this too. + * + * NOTE: The system is in a questionable state... we may not get + * the following message out. + */ +void Stack_check_report_blown_task( + Thread_Control *running, + bool pattern_ok +) RTEMS_COMPILER_NO_RETURN_ATTRIBUTE; + +void Stack_check_report_blown_task(Thread_Control *running, bool pattern_ok) +{ + Stack_Control *stack = &running->Start.Initial_stack; + void *pattern_area = Stack_check_Get_pattern(stack); + char name[32]; + + printk("BLOWN STACK!!!\n"); + printk("task control block: 0x%08" PRIxPTR "\n", running); + printk("task ID: 0x%08lx\n", (unsigned long) running->Object.id); + printk( + "task name: 0x%08" PRIx32 "\n", + running->Object.name.name_u32 + ); + printk( + "task name string: %s\n", + rtems_object_get_name(running->Object.id, sizeof(name), name) + ); + printk( + "task stack area (%lu Bytes): 0x%08" PRIxPTR " .. 0x%08" PRIxPTR "\n", + (unsigned long) stack->size, + stack->area, + ((char *) stack->area + stack->size) + ); + if (!pattern_ok) { + printk( + "damaged pattern area (%lu Bytes): 0x%08" PRIxPTR " .. 0x%08" PRIxPTR "\n", + (unsigned long) PATTERN_SIZE_BYTES, + pattern_area, + (pattern_area + PATTERN_SIZE_BYTES) + ); + } + + #if defined(RTEMS_MULTIPROCESSING) + if (rtems_configuration_get_user_multiprocessing_table()) { + printk( + "node: 0x%08" PRIxPTR "\n", + rtems_configuration_get_user_multiprocessing_table()->node + ); + } + #endif + + rtems_fatal_error_occurred(0x81); +} + +/* + * rtems_stack_checker_switch_extension + */ +void rtems_stack_checker_switch_extension( + Thread_Control *running __attribute__((unused)), + Thread_Control *heir __attribute__((unused)) +) +{ + Stack_Control *the_stack = &running->Start.Initial_stack; + void *pattern; + bool sp_ok; + bool pattern_ok = true; + + pattern = Stack_check_Get_pattern_area(the_stack); + + /* + * Check for an out of bounds stack pointer or an overwrite + */ + sp_ok = Stack_check_Frame_pointer_in_range( the_stack ); + + pattern_ok = (!memcmp( pattern, + (void *) Stack_check_Pattern.pattern, PATTERN_SIZE_BYTES)); + + if ( !sp_ok || !pattern_ok ) { + Stack_check_report_blown_task( running, pattern_ok ); + } +} + +/* + * Check if blown + */ +bool rtems_stack_checker_is_blown( void ) +{ + Stack_Control *the_stack = &_Thread_Executing->Start.Initial_stack; + bool sp_ok; + bool pattern_ok = true; + + /* + * Check for an out of bounds stack pointer + */ + + sp_ok = Stack_check_Frame_pointer_in_range( the_stack ); + + /* + * The stack checker must be initialized before the pattern is there + * to check. + */ + if ( Stack_check_Initialized ) { + pattern_ok = (!memcmp( + Stack_check_Get_pattern(the_stack), + (void *) Stack_check_Pattern.pattern, + PATTERN_SIZE_BYTES + )); + } + + + /* + * Let's report as much as we can. + */ + if ( !sp_ok || !pattern_ok ) { + Stack_check_report_blown_task( _Thread_Executing, pattern_ok ); + /* DOES NOT RETURN */ + } + + /* + * The Stack Pointer and the Pattern Area are OK so return false. + */ + return false; +} + +/* + * Stack_check_find_high_water_mark + */ +static inline void *Stack_check_find_high_water_mark( + const void *s, + size_t n +) +{ + const uint32_t *base, *ebase; + uint32_t length; + + base = s; + length = n/4; + + #if ( CPU_STACK_GROWS_UP == TRUE ) + /* + * start at higher memory and find first word that does not + * match pattern + */ + + base += length - 1; + for (ebase = s; base > ebase; base--) + if (*base != U32_PATTERN) + return (void *) base; + #else + /* + * start at lower memory and find first word that does not + * match pattern + */ + + base += PATTERN_SIZE_WORDS; + for (ebase = base + length; base < ebase; base++) + if (*base != U32_PATTERN) + return (void *) base; + #endif + + return (void *)0; +} + +/* + * Stack_check_Dump_threads_usage + * + * Try to print out how much stack was actually used by the task. + */ +static void *print_context; +static rtems_printk_plugin_t print_handler; + +void Stack_check_Dump_threads_usage( + Thread_Control *the_thread +) +{ + uint32_t size, used; + void *low; + void *high_water_mark; + void *current; + Stack_Control *stack; + char name[5]; + + /* + * The pointer passed in for the_thread is guaranteed to be non-NULL from + * rtems_iterate_over_all_threads() so no need to check it here. + */ + + /* + * Obtain interrupt stack information + */ + #if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + if (the_thread == (Thread_Control *) -1) { + if (!Stack_check_Interrupt_stack.area) + return; + stack = &Stack_check_Interrupt_stack; + the_thread = 0; + current = 0; + } else + #endif + { + stack = &the_thread->Start.Initial_stack; + current = (void *)_CPU_Context_Get_SP( &the_thread->Registers ); + } + + low = Stack_check_usable_stack_start(stack); + size = Stack_check_usable_stack_size(stack); + + high_water_mark = Stack_check_find_high_water_mark(low, size); + + if ( high_water_mark ) + used = Stack_check_Calculate_used( low, size, high_water_mark ); + else + used = 0; + + + #if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + if ( the_thread ) + #endif + { + (*print_handler)( + print_context, + "0x%08" PRIx32 " %4s", + the_thread->Object.id, + rtems_object_get_name( the_thread->Object.id, sizeof(name), name ) + ); + } + #if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + else { + (*print_handler)( print_context, "0x%08" PRIx32 " INTR", ~0 ); + } + #endif + + (*print_handler)( + print_context, + " %010p - %010p %010p %8" PRId32 " ", + stack->area, + stack->area + stack->size - 1, + current, + size + ); + + if (Stack_check_Initialized == 0) { + (*print_handler)( print_context, "Unavailable\n" ); + } else { + (*print_handler)( print_context, "%8" PRId32 "\n", used ); + } + + +} + +/* + * rtems_stack_checker_fatal_extension + */ +#ifndef DONT_USE_FATAL_EXTENSION + void rtems_stack_checker_fatal_extension( + Internal_errors_Source source, + bool is_internal, + uint32_t status + ) + { + if (status == 0) + rtems_stack_checker_report_usage(); + } +#endif + +/*PAGE + * + * rtems_stack_checker_report_usage + */ + +void rtems_stack_checker_report_usage_with_plugin( + void *context, + rtems_printk_plugin_t print +) +{ + if ( !print ) + return; + + print_context = context; + print_handler = print; + + (*print)( context, "Stack usage by thread\n"); + (*print)( context, +" ID NAME LOW HIGH CURRENT AVAILABLE USED\n" + ); + + /* iterate over all threads and dump the usage */ + rtems_iterate_over_all_threads( Stack_check_Dump_threads_usage ); + + #if (CPU_ALLOCATE_INTERRUPT_STACK == TRUE) + /* dump interrupt stack info if any */ + Stack_check_Dump_threads_usage((Thread_Control *) -1); + #endif + + print_context = NULL; + print_handler = NULL; +} + +void rtems_stack_checker_report_usage( void ) +{ + rtems_stack_checker_report_usage_with_plugin( NULL, printk_plugin ); +} diff --git a/cpukit/libmisc/stackchk/internal.h b/cpukit/libmisc/stackchk/internal.h new file mode 100644 index 0000000000..a16a61e30e --- /dev/null +++ b/cpukit/libmisc/stackchk/internal.h @@ -0,0 +1,95 @@ +/* internal.h + * + * This include file contains internal information + * for the RTEMS stack checker. + * + * COPYRIGHT (c) 1989-2006. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + */ + +#ifndef __INTERNAL_STACK_CHECK_h +#define __INTERNAL_STACK_CHECK_h + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This structure is used to fill in and compare the "end of stack" + * marker pattern. + * pattern area must be a multiple of 4 words. + */ + +#ifdef CPU_STACK_CHECK_SIZE +#define PATTERN_SIZE_WORDS (((CPU_STACK_CHECK_SIZE / 4) + 3) & ~0x3) +#else +#define PATTERN_SIZE_WORDS (4) +#endif + +#define PATTERN_SIZE_BYTES (PATTERN_SIZE_WORDS * sizeof(uint32_t)) + +typedef struct { + uint32_t pattern[ PATTERN_SIZE_WORDS ]; +} Stack_check_Control; + +/* + * The pattern used to fill the entire stack. + */ + +#define BYTE_PATTERN 0xA5 +#define U32_PATTERN 0xA5A5A5A5 + +/* + * rtems_stack_checker_create_extension + */ + +bool rtems_stack_checker_create_extension( + Thread_Control *running, + Thread_Control *the_thread +); + +/* + * rtems_stack_checker_begin_extension + */ + +void rtems_stack_checker_begin_extension( + Thread_Control *the_thread +); + +/* + * rtems_stack_checker_switch_extension + */ + +void rtems_stack_checker_switch_extension( + Thread_Control *running, + Thread_Control *heir +); + +/* + * rtems_stack_checker_fatal_extension + */ + +void rtems_stack_checker_fatal_extension( + Internal_errors_Source source, + bool is_internal, + uint32_t status +); + +/* + * rtems_stack_checker_report_usage + */ + +void rtems_stack_checker_report_usage( void ); + +#ifdef __cplusplus +} +#endif + +#endif +/* end of include file */ diff --git a/cpukit/libmisc/stackchk/stackchk.h b/cpukit/libmisc/stackchk/stackchk.h new file mode 100644 index 0000000000..02bbac3a45 --- /dev/null +++ b/cpukit/libmisc/stackchk/stackchk.h @@ -0,0 +1,135 @@ +/** @file rtems/stackchk.h + * + * This include file contains information necessary to utilize + * and install the stack checker mechanism. + */ + +/* + * COPYRIGHT (c) 1989-2007. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + */ + +#ifndef _RTEMS_STACKCHK_H +#define _RTEMS_STACKCHK_H + +#include <stdbool.h> /* bool */ + +#include <rtems/score/percpu.h> /* Thread_Control */ +#include <rtems/bspIo.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Has Current Task Blown Its Stack + * + * This method is used to determine if the current stack pointer + * of the currently executing task is within bounds. + * + * @return This method returns true if the currently executing task + * has blown its stack. + * + */ +bool rtems_stack_checker_is_blown( void ); + +/** @brief Print Stack Usage Report + * + * This method prints a stack usage report for the curently executing + * task. + * + * @note It uses printk to print the report. + */ +void rtems_stack_checker_report_usage( void ); + +/** @brief Print Stack Usage Report + * + * This method prints a stack usage report for the curently executing + * task. + * + * @param[in] context is the context to pass to the print handler + * @param[in] print is the print handler + * + * @note It uses the caller's routine to print the report. + */ +void rtems_stack_checker_report_usage_with_plugin( + void *context, + rtems_printk_plugin_t print +); + +/************************************************************* + ************************************************************* + ** Prototyped only so the user extension can be installed ** + ************************************************************* + *************************************************************/ + +/** @brief Stack Checker Task Create Extension + * + * This method is the task create extension for the stack checker. + * + * @param[in] running points to the currently executing task + * @param[in] the_thread points to the newly created task + * + * @note If this this the first task created, the stack checker + * will automatically intialize itself. + */ +bool rtems_stack_checker_create_extension( + Thread_Control *running, + Thread_Control *the_thread +); + +/** @brief Stack Checker Task Begin Extension + * + * This method is the task begin extension for the stack checker. + * + * @param[in] the_thread points to task starting to execute + * + * @note This is called from the internal method _Thread_Handler. + */ +void rtems_stack_checker_begin_extension( + Thread_Control *the_thread +); + +/** @brief Stack Checker Task Context Switch Extension + * + * This method is the task context switch extension for the stack checker. + * + * @param[in] running points to the currently executing task which + * is being context switched out + * @param[in] running points to the heir task which we are switching to + * + * @note This is called from the internal method _Thread_Dispatch. + */ +void rtems_stack_checker_switch_extension( + Thread_Control *running, + Thread_Control *heir +); + +/** @brief Stack Checker Extension Set Definition + * + * This macro defines the user extension handler set for the stack + * checker. This macro is normally only used by confdefs.h. + */ +#define RTEMS_STACK_CHECKER_EXTENSION \ +{ \ + rtems_stack_checker_create_extension, /* rtems_task_create */ \ + 0, /* rtems_task_start */ \ + 0, /* rtems_task_restart */ \ + 0, /* rtems_task_delete */ \ + rtems_stack_checker_switch_extension, /* task_switch */ \ + rtems_stack_checker_begin_extension, /* task_begin */ \ + 0, /* task_exitted */ \ + 0 /* rtems_stack_checker_fatal_extension */, /* fatal */ \ +} + +#ifdef __cplusplus +} +#endif + +#endif +/* end of include file */ |