diff options
Diffstat (limited to 'extra')
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer.py | 81 | ||||
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer_unittest.py | 70 |
2 files changed, 103 insertions, 48 deletions
diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py index 0461e2983a..df7b7c8932 100755 --- a/extra/stack_analyzer/stack_analyzer.py +++ b/extra/stack_analyzer/stack_analyzer.py @@ -6,17 +6,23 @@ """Statically analyze stack usage of EC firmware. Example: - extra/stack_analyzer/stack_analyzer.py ./build/elm/RW/ec.RW.elf \ - ./build/elm/RW/ec.RW.taskinfo + extra/stack_analyzer/stack_analyzer.py \ + --export_taskinfo ./build/elm/util/export_taskinfo.so \ + --section RW \ + ./build/elm/RW/ec.RW.elf + """ from __future__ import print_function import argparse +import ctypes import re import subprocess +SECTION_RO = 'RO' +SECTION_RW = 'RW' # TODO(cheyuw): This should depend on the CPU and build options. # The size of extra stack frame needed by interrupts. (on cortex-m with FPU) INTERRUPT_EXTRA_STACK_FRAME = 224 @@ -26,6 +32,17 @@ class StackAnalyzerError(Exception): """Exception class for stack analyzer utility.""" +class TaskInfo(ctypes.Structure): + """Taskinfo ctypes structure. + + The structure definition is corresponding to the "struct taskinfo" + in "util/export_taskinfo.so.c". + """ + _fields_ = [('name', ctypes.c_char_p), + ('routine', ctypes.c_char_p), + ('stack_size', ctypes.c_uint32)] + + class Task(object): """Task information. @@ -640,14 +657,14 @@ class StackAnalyzer(object): cycle_groups = self.AnalyzeCallGraph(function_map) # Print the results of task-aware stack analysis. - # TODO(cheyuw): Resolve and show the allocated task size. for task in self.tasklist: routine_func = function_map[task.routine_address] - print('Task: {}, Max size: {} ({} + {})'.format( + print('Task: {}, Max size: {} ({} + {}), Allocated size: {}'.format( task.name, routine_func.stack_max_usage + INTERRUPT_EXTRA_STACK_FRAME, routine_func.stack_max_usage, - INTERRUPT_EXTRA_STACK_FRAME)) + INTERRUPT_EXTRA_STACK_FRAME, + task.stack_max_size)) print('Call Trace:') curr_func = routine_func @@ -673,24 +690,25 @@ def ParseArgs(): """ parser = argparse.ArgumentParser(description="EC firmware stack analyzer.") parser.add_argument('elf_path', help="the path of EC firmware ELF") - parser.add_argument('taskinfo_path', - help="the path of EC taskinfo generated by Makefile") + parser.add_argument('--export_taskinfo', required=True, + help="the path of export_taskinfo.so utility") + parser.add_argument('--section', required=True, help='the section.', + choices=[SECTION_RO, SECTION_RW]) parser.add_argument('--objdump', default='objdump', help='the path of objdump') parser.add_argument('--addr2line', default='addr2line', help='the path of addr2line') - # TODO(cheyuw): Add an option for dumping stack usage of all - # functions. + # TODO(cheyuw): Add an option for dumping stack usage of all functions. return parser.parse_args() -def ParseSymbolFile(symbol_text): - """Parse the content of the symbol file. +def ParseSymbolText(symbol_text): + """Parse the content of the symbol text. Args: - symbol_text: Text of the symbol file. + symbol_text: Text of the symbols. Returns: symbols: Symbol list. @@ -718,21 +736,31 @@ def ParseSymbolFile(symbol_text): return symbols -def ParseTasklistFile(taskinfo_text, symbols): - """Parse the task information generated by Makefile. +def LoadTasklist(section, export_taskinfo, symbols): + """Load the task information. Args: - taskinfo_text: Text of the taskinfo file. + section: Section (RO | RW). + export_taskinfo: Handle of export_taskinfo.so. symbols: Symbol list. Returns: tasklist: Task list. """ - # Example: ("HOOKS",hook_task,LARGER_TASK_STACK_SIZE) ("USB_CHG_P0", ... - results = re.findall(r'\("([^"]+)", ([^,]+), ([^\)]+)\)', taskinfo_text) + + TaskInfoPointer = ctypes.POINTER(TaskInfo) + taskinfos = TaskInfoPointer() + if section == SECTION_RO: + get_taskinfos_func = export_taskinfo.get_ro_taskinfos + else: + get_taskinfos_func = export_taskinfo.get_rw_taskinfos + + taskinfo_num = get_taskinfos_func(ctypes.pointer(taskinfos)) + tasklist = [] - for name, routine_name, stack_max_size in results: - tasklist.append(Task(name, routine_name, stack_max_size)) + for index in range(taskinfo_num): + taskinfo = taskinfos[index] + tasklist.append(Task(taskinfo.name, taskinfo.routine, taskinfo.stack_size)) # Resolve routine address for each task. It's more efficient to resolve all # routine addresses of tasks together. @@ -759,7 +787,7 @@ def main(): try: options = ParseArgs() - # Generate and parse the symbol file. + # Generate and parse the symbols. try: symbol_text = subprocess.check_output([options.objdump, '-t', @@ -769,16 +797,15 @@ def main(): except OSError: raise StackAnalyzerError('Failed to run objdump.') - symbols = ParseSymbolFile(symbol_text) + symbols = ParseSymbolText(symbol_text) - # Parse the taskinfo file. + # Load the tasklist. try: - with open(options.taskinfo_path, 'r') as taskinfo_file: - taskinfo_text = taskinfo_file.read() - tasklist = ParseTasklistFile(taskinfo_text, symbols) + export_taskinfo = ctypes.CDLL(options.export_taskinfo) + except OSError: + raise StackAnalyzerError('Failed to load export_taskinfo.') - except IOError: - raise StackAnalyzerError('Failed to open taskinfo.') + tasklist = LoadTasklist(options.section, export_taskinfo, symbols) analyzer = StackAnalyzer(options, symbols, tasklist) analyzer.Analyze() diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py index f4cbe9aadd..ff8ec840f3 100755 --- a/extra/stack_analyzer/stack_analyzer_unittest.py +++ b/extra/stack_analyzer/stack_analyzer_unittest.py @@ -97,15 +97,16 @@ class StackAnalyzerTest(unittest.TestCase): sa.Symbol(0x2000, 'F', 0x51C, 'console_task'), sa.Symbol(0x3200, 'O', 0x124, '__just_data'), sa.Symbol(0x4000, 'F', 0x11C, 'touchpad_calc')] - tasklist = [sa.Task('HOOKS', 'hook_task', '2048', 0x1000), - sa.Task('CONSOLE', 'console_task', 'STACK_SIZE', 0x2000)] + tasklist = [sa.Task('HOOKS', 'hook_task', 2048, 0x1000), + sa.Task('CONSOLE', 'console_task', 460, 0x2000)] options = mock.MagicMock(elf_path='./ec.RW.elf', - taskinfo_path='./ec.RW.taskinfo', + export_taskinfo='none', + section='RW', objdump='objdump', addr2line='addr2line') self.analyzer = sa.StackAnalyzer(options, symbols, tasklist) - def testParseSymbolFile(self): + def testParseSymbolText(self): symbol_text = ( '0 g F .text e8 Foo\n' '0000dead w F .text 000000e8 .hidden Bar\n' @@ -113,7 +114,7 @@ class StackAnalyzerTest(unittest.TestCase): 'deadbee g O .rodata 00000008 __Hooo_ooo\n' 'deadbee g .rodata 00000000 __foo_doo_coo_end\n' ) - symbols = sa.ParseSymbolFile(symbol_text) + symbols = sa.ParseSymbolText(symbol_text) expect_symbols = [sa.Symbol(0x0, 'F', 0xe8, 'Foo'), sa.Symbol(0xdead, 'F', 0xe8, 'Bar'), sa.Symbol(0xdeadbeef, 'O', 0x4, 'Woooo'), @@ -121,19 +122,42 @@ class StackAnalyzerTest(unittest.TestCase): sa.Symbol(0xdeadbee, 'O', 0x0, '__foo_doo_coo_end')] self.assertEqual(symbols, expect_symbols) - def testParseTasklist(self): - taskinfo_text = ( - '("HOOKS", hook_task, 2048) ' - '("WOOKS", hook_task, 4096) ' - '("CONSOLE", console_task, STACK_SIZE)' - ) - tasklist = sa.ParseTasklistFile(taskinfo_text, self.analyzer.symbols) - expect_tasklist = [ - sa.Task('HOOKS', 'hook_task', '2048', 0x1000), - sa.Task('WOOKS', 'hook_task', '4096', 0x1000), - sa.Task('CONSOLE', 'console_task', 'STACK_SIZE', 0x2000), + def testLoadTasklist(self): + def tasklist_to_taskinfos(pointer, tasklist): + taskinfos = [] + for task in tasklist: + taskinfos.append(sa.TaskInfo(name=task.name, + routine=task.routine_name, + stack_size=task.stack_max_size)) + + TaskInfoArray = sa.TaskInfo * len(taskinfos) + pointer.contents.contents = TaskInfoArray(*taskinfos) + return len(taskinfos) + + def ro_taskinfos(pointer): + return tasklist_to_taskinfos(pointer, expect_ro_tasklist) + + def rw_taskinfos(pointer): + return tasklist_to_taskinfos(pointer, expect_rw_tasklist) + + expect_ro_tasklist = [ + sa.Task('HOOKS', 'hook_task', 2048, 0x1000), ] - self.assertEqual(tasklist, expect_tasklist) + + expect_rw_tasklist = [ + sa.Task('HOOKS', 'hook_task', 2048, 0x1000), + sa.Task('WOOKS', 'hook_task', 4096, 0x1000), + sa.Task('CONSOLE', 'console_task', 460, 0x2000), + ] + + export_taskinfo = mock.MagicMock( + get_ro_taskinfos=mock.MagicMock(side_effect=ro_taskinfos), + get_rw_taskinfos=mock.MagicMock(side_effect=rw_taskinfos)) + + tasklist = sa.LoadTasklist('RO', export_taskinfo, self.analyzer.symbols) + self.assertEqual(tasklist, expect_ro_tasklist) + tasklist = sa.LoadTasklist('RW', export_taskinfo, self.analyzer.symbols) + self.assertEqual(tasklist, expect_rw_tasklist) def testAnalyzeDisassembly(self): disasm_text = ( @@ -259,10 +283,12 @@ class StackAnalyzerTest(unittest.TestCase): checkoutput_mock.side_effect = [disasm_text, '?', '?', '?'] self.analyzer.Analyze() print_mock.assert_has_calls([ - mock.call('Task: HOOKS, Max size: 224 (0 + 224)'), + mock.call( + 'Task: HOOKS, Max size: 224 (0 + 224), Allocated size: 2048'), mock.call('Call Trace:'), mock.call('\thook_task (0) 1000 [?]'), - mock.call('Task: CONSOLE, Max size: 232 (8 + 224)'), + mock.call( + 'Task: CONSOLE, Max size: 232 (8 + 224), Allocated size: 460'), mock.call('Call Trace:'), mock.call('\tconsole_task (8) 2000 [?]'), ]) @@ -285,14 +311,16 @@ class StackAnalyzerTest(unittest.TestCase): '2000 g F .text 0000051c .hidden console_task\n') parseargs_mock.return_value = mock.MagicMock(elf_path='./ec.RW.elf', - taskinfo_path='', + export_taskinfo='none', + section='RW', objdump='objdump', addr2line='addr2line') with mock.patch('__builtin__.print') as print_mock: checkoutput_mock.return_value = symbol_text sa.main() - print_mock.assert_called_once_with('Error: Failed to open taskinfo.') + print_mock.assert_called_once_with( + 'Error: Failed to load export_taskinfo.') with mock.patch('__builtin__.print') as print_mock: checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '') |