#!/usr/bin/env python3 """ @file stack_usage_analyzer.py @brief Stack usage analyzer for Automotive RTOS """ import os import re import sys import argparse from collections import defaultdict from pathlib import Path class StackUsageAnalyzer: """Analyze stack usage from map files and source code""" def __init__(self, project_root): self.project_root = Path(project_root) self.stack_usage = {} self.function_sizes = {} self.call_graph = defaultdict(list) def parse_map_file(self, map_file): """Parse linker map file for symbol information""" print(f"Parsing map file: {map_file}") symbol_pattern = re.compile( r'^\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+(\w+)\s+(\w+)$' ) with open(map_file, 'r') as f: for line in f: match = symbol_pattern.match(line) if match: address = int(match.group(1), 16) size = int(match.group(2), 16) section = match.group(3) name = match.group(4) if section in ['.text', '.data', '.bss']: self.function_sizes[name] = { 'size': size, 'section': section, 'address': address } def analyze_source_files(self): """Analyze source files for stack allocation""" print("Analyzing source files for stack usage...") source_patterns = [ self.project_root / 'kernel' / 'src' / '**' / '*.c', self.project_root / 'drivers' / 'src' / '**' / '*.c', self.project_root / 'middleware' / '**' / 'src' / '**' / '*.c', self.project_root / 'applications' / '**' / 'src' / '**' / '*.c', ] stack_var_pattern = re.compile( r'\b(?:uint8_t|uint16_t|uint32_t|int8_t|int16_t|int32_t|' r'char|short|int|long|float|double|struct|union)\s+' r'(\w+)\s*\[(\d+)\]' ) for pattern in source_patterns: for source_file in self.project_root.glob(str(pattern)): self._analyze_source_file(source_file, stack_var_pattern) def _analyze_source_file(self, source_file, pattern): """Analyze individual source file""" try: with open(source_file, 'r') as f: content = f.read() # Find all stack variables for match in pattern.finditer(content): var_name = match.group(1) array_size = int(match.group(2)) # Estimate variable size var_size = self._estimate_variable_size(match.group(0)) if var_size > 0: print(f" {source_file.name}: {var_name}[{array_size}] = {var_size} bytes") except Exception as e: print(f" Warning: Could not analyze {source_file}: {e}") def _estimate_variable_size(self, declaration): """Estimate variable size from declaration""" size_map = { 'uint8_t': 1, 'int8_t': 1, 'char': 1, 'uint16_t': 2, 'int16_t': 2, 'short': 2, 'uint32_t': 4, 'int32_t': 4, 'int': 4, 'float': 4, 'uint64_t': 8, 'int64_t': 8, 'double': 8, 'long': 8, } for type_name, size in size_map.items(): if type_name in declaration: # Find array size array_match = re.search(r'\[(\d+)\]', declaration) if array_match: return size * int(array_match.group(1)) return size return 0 def analyze_task_stacks(self): """Analyze task stack configurations""" print("\nAnalyzing task stack configurations...") task_config_file = self.project_root / 'config' / 'task_config.h' if not task_config_file.exists(): print(" Task configuration file not found") return with open(task_config_file, 'r') as f: content = f.read() # Find stack size definitions stack_pattern = re.compile( r'#define\s+TASK_STACK_(\w+)\s+(\d+)' ) total_stack = 0 task_count = 0 print("\n Task Stack Allocations:") print(" " + "-" * 40) for match in stack_pattern.finditer(content): task_name = match.group(1) stack_size = int(match.group(2)) total_stack += stack_size task_count += 1 print(f" {task_name:30s} {stack_size:8d} bytes") print(" " + "-" * 40) print(f" Total: {total_stack} bytes across {task_count} tasks") def generate_report(self, output_file): """Generate stack usage report""" print(f"\nGenerating report: {output_file}") with open(output_file, 'w') as f: f.write("=" * 80 + "\n") f.write("Stack Usage Analysis Report\n") f.write("=" * 80 + "\n\n") # Function sizes f.write("Function Sizes:\n") f.write("-" * 40 + "\n") f.write(f"{'Function':30s} {'Size':>8s} {'Section':10s}\n") f.write("-" * 40 + "\n") for name, info in sorted(self.function_sizes.items(), key=lambda x: x[1]['size'], reverse=True): f.write(f"{name:30s} {info['size']:8d} {info['section']:10s}\n") f.write("\n\n") # Recommendations f.write("Recommendations:\n") f.write("-" * 40 + "\n") f.write("1. Review functions with large stack allocations\n") f.write("2. Consider using static allocation for large buffers\n") f.write("3. Monitor stack usage during runtime\n") f.write("4. Ensure stack overflow detection is enabled\n") def analyze(self, map_file=None): """Run complete analysis""" print("=" * 60) print("Stack Usage Analyzer") print("=" * 60) # Parse map file if provided if map_file and os.path.exists(map_file): self.parse_map_file(map_file) # Analyze source files self.analyze_source_files() # Analyze task stacks self.analyze_task_stacks() # Generate report report_file = self.project_root / 'build' / 'stack_usage_report.txt' report_file.parent.mkdir(parents=True, exist_ok=True) self.generate_report(report_file) print(f"\nReport saved to: {report_file}") def main(): parser = argparse.ArgumentParser(description='Stack usage analyzer') parser.add_argument('--project', default='.', help='Project root directory') parser.add_argument('--map-file', help='Linker map file') parser.add_argument('--output', help='Output report file') args = parser.parse_args() analyzer = StackUsageAnalyzer(args.project) analyzer.analyze(args.map_file) if __name__ == '__main__': main()