ec: Add a task information library for the stack analyzer.

Add a shared library to export task information.
Modified the stack analyzer to get information from the shared library.
Show allocated stack sizes of tasks in the stack analyzer.

To get the all task information (including the allocated stack size),
A shared library is added and compiled with the board to export all
configurations of the tasklist.

BUG=chromium:648840
BRANCH=none
TEST=extra/stack_analyzer/stack_analyzer_unittest.py
     make BOARD=elm && extra/stack_analyzer/stack_analyzer.py \
         --objdump=arm-none-eabi-objdump \
         --addr2line=arm-none-eabi-addr2line \
         --export_taskinfo=./build/elm/util/export_taskinfo.so \
         --section=RW \
         ./build/elm/RW/ec.RW.elf
     make BOARD=${BOARD} SECTION=${SECTION} analyzestack

Change-Id: I72f01424142bb0a99c6776a55735557308e2cab6
Signed-off-by: Che-yu Wu <cheyuw@google.com>
Reviewed-on: https://chromium-review.googlesource.com/611693
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
This commit is contained in:
Che-yu Wu
2017-08-14 11:46:29 +08:00
committed by chrome-bot
parent d2a06c36b1
commit 64ecddfd86
6 changed files with 166 additions and 68 deletions

View File

@@ -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()