Files
RTOS/tools/analysis/stack_usage_analyzer.py
T
root ca13734bf0 Add full automotive RTOS project
Add kernel (Cortex-M0/M3/M4, Tricore, S32K, RISC-V ports), drivers,
middleware (CAN stack, diagnostics, safety), applications, board
support, build/test tooling, and documentation.
2026-08-23 03:35:29 -04:00

205 lines
6.9 KiB
Python
Executable File

#!/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()