summaryrefslogtreecommitdiff
path: root/extra
diff options
context:
space:
mode:
Diffstat (limited to 'extra')
-rwxr-xr-xextra/stack_analyzer/stack_analyzer.py81
-rwxr-xr-xextra/stack_analyzer/stack_analyzer_unittest.py70
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, '')