summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChe-yu Wu <cheyuw@google.com>2017-08-14 11:46:29 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-08-15 00:25:20 -0700
commit64ecddfd861002cb5cff04b22211d23cf5fbcb5d (patch)
treedf0868c2ebd61189da143727f68f0f21f4ae2028
parentd2a06c36b13d62ef47e625067a4b7747bcf1a8f1 (diff)
downloadchrome-ec-64ecddfd861002cb5cff04b22211d23cf5fbcb5d.tar.gz
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>
-rw-r--r--Makefile.rules23
-rwxr-xr-xextra/stack_analyzer/stack_analyzer.py81
-rwxr-xr-xextra/stack_analyzer/stack_analyzer_unittest.py70
-rw-r--r--include/task_filter.h6
-rw-r--r--util/build.mk11
-rw-r--r--util/export_taskinfo.c43
6 files changed, 166 insertions, 68 deletions
diff --git a/Makefile.rules b/Makefile.rules
index 3be6a64582..4256f921ee 100644
--- a/Makefile.rules
+++ b/Makefile.rules
@@ -94,10 +94,11 @@ cmd_sharedlib_elf = $(CC) $(libsharedobjs_deps) \
-Wl,-T,common/ec.$(SHOBJLIB).ld $(LDFLAGS) \
-o $(out)/$(SHOBJLIB)/$(SHOBJLIB).elf \
-Wl,-Map,$(out)/$(SHOBJLIB)/$(SHOBJLIB).map
-cmd_taskinfo = $(CPP) -P -DSECTION_IS_$(3) \
- -I$(BDIR) -DBOARD_$(UC_BOARD) -D_MAKEFILE_DUMP_INFO \
- -imacros $(PROJECT).tasklist include/task_filter.h \
- -o $@
+cmd_c_to_taskinfo = $(BUILDCC) \
+ $(filter-out -DSECTION_IS_$(BLD),$(BUILD_CFLAGS)) -DSECTION_IS_$(3) \
+ -MMD -MF $@.d -c $< -flto -o $@
+cmd_link_taskinfo = $(BUILDCC) $(BUILD_CFLAGS) --shared -fPIC $^ \
+ $(BUILD_LDFLAGS) -flto -o $@
# commands for RSA signature: rwsig does not need to sign the whole image
# (it signs the RW part separately). usbpd1 type needs to sign the final image.
@@ -427,13 +428,6 @@ $(npcx-flash-fw-bin):
-Wl,-Map,$(out)/$(npcx-flash-fw).map
-@ $(OBJCOPY) -O binary $(out)/$(npcx-flash-fw).elf $@
-# Update taskinfo when the ec.tasklist is modified.
-$(out)/RO/%.taskinfo: $(BDIR)/$(PROJECT).tasklist
- $(call quiet,taskinfo,TSKINFO,RO)
-
-$(out)/RW/%.taskinfo: $(BDIR)/$(PROJECT).tasklist
- $(call quiet,taskinfo,TSKINFO,RW)
-
.PHONY: xrefs
xrefs: $(call targ_if_prog,etags,$(out)/TAGS) \
$(call targ_if_prog,ctags,$(out)/tags)
@@ -547,19 +541,20 @@ newsizes:
# them first is because elf dependencies will cause the elf files be rebuilt for
# updating date, which shouldn't happen when analyzing the existing firmwares.
.PHONY: analyzestack
-analyzestack: $(out)/RO/ec.RO.taskinfo $(out)/RW/ec.RW.taskinfo
+analyzestack: $(out)/util/export_taskinfo.so
@if [ "$(SECTION)" != "RO" ] && [ "$(SECTION)" != "RW" ]; then \
echo "Please specify SECTION=RO or RW. The default is RW."; \
SECTION="RW"; \
fi; \
ELF=$(out)/$$SECTION/ec.$$SECTION.elf; \
- TASKLIST=$(out)/$$SECTION/ec.$$SECTION.taskinfo; \
+ EXPORT_TASKINFO=$(out)/util/export_taskinfo.so; \
if [ ! -f "$$ELF" ]; then \
echo "Some files are missing. Are they built?"; \
exit 1; \
fi; \
extra/stack_analyzer/stack_analyzer.py --objdump "$(OBJDUMP)" \
- --addr2line "$(ADDR2LINE)" "$$ELF" "$$TASKLIST"
+ --addr2line "$(ADDR2LINE)" --section "$$SECTION" \
+ --export_taskinfo "$$EXPORT_TASKINFO" "$$ELF"
.SECONDARY:
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, '')
diff --git a/include/task_filter.h b/include/task_filter.h
index 2840a01e13..af80194e7f 100644
--- a/include/task_filter.h
+++ b/include/task_filter.h
@@ -46,11 +46,5 @@
CONFIG_TASK_LIST CONFIG_TEST_TASK_LIST CONFIG_CTS_TASK_LIST
#endif
-/* If included directly from Makefile, dump details of task list. */
-#ifdef _MAKEFILE_DUMP_INFO
-#define TASK(n, r, d, s) (#n, r, s)
-CONFIG_TASK_LIST CONFIG_TEST_TASK_LIST CONFIG_CTS_TASK_LIST
-#endif
-
#endif /* __CROS_EC_TASK_FILTER_H */
diff --git a/util/build.mk b/util/build.mk
index 8708c0eb89..95ded5724b 100644
--- a/util/build.mk
+++ b/util/build.mk
@@ -9,6 +9,7 @@
host-util-bin=ectool lbplay stm32mon ec_sb_firmware_update lbcc \
ec_parse_panicinfo
build-util-bin=ec_uartd iteflash
+build-util-art+=util/export_taskinfo.so
ifeq ($(CHIP),npcx)
build-util-bin+=ecst
endif
@@ -37,3 +38,13 @@ $(out)/util/usb_pd_policy.o: board/$(BOARD)/usb_pd_policy.c
$(call quiet,c_to_vif,BUILDCC)
deps += $(out)/util/usb_pd_policy.o.d
endif # CONFIG_USB_POWER_DELIVERY
+
+$(out)/util/export_taskinfo.so: $(out)/util/export_taskinfo_ro.o \
+ $(out)/util/export_taskinfo_rw.o
+ $(call quiet,link_taskinfo,BUILDLD)
+
+$(out)/util/export_taskinfo_ro.o: util/export_taskinfo.c
+ $(call quiet,c_to_taskinfo,BUILDCC,RO)
+
+$(out)/util/export_taskinfo_rw.o: util/export_taskinfo.c
+ $(call quiet,c_to_taskinfo,BUILDCC,RW)
diff --git a/util/export_taskinfo.c b/util/export_taskinfo.c
new file mode 100644
index 0000000000..b6b9bea7b8
--- /dev/null
+++ b/util/export_taskinfo.c
@@ -0,0 +1,43 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The cmd_c_to_taskinfo will compile this file with different
+ * section definitions to export different tasklists.
+ */
+
+#include <stdint.h>
+
+#include "config.h"
+#include "task_id.h"
+
+#ifdef SECTION_IS_RO
+#define GET_TASKINFOS_FUNC get_ro_taskinfos
+#elif defined(SECTION_IS_RW)
+#define GET_TASKINFOS_FUNC get_rw_taskinfos
+#else
+#error "Current section (RO/RW) is not defined."
+#endif
+
+struct taskinfo {
+ char *name;
+ char *routine;
+ uint32_t stack_size;
+};
+
+#define TASK(n, r, d, s) { \
+ .name = #n, \
+ .routine = #r, \
+ .stack_size = s, \
+},
+static const struct taskinfo const taskinfos[] = {
+ CONFIG_TASK_LIST
+};
+#undef TASK
+
+uint32_t GET_TASKINFOS_FUNC(const struct taskinfo **infos)
+{
+ *infos = taskinfos;
+ /* Calculate the number of tasks */
+ return sizeof(taskinfos) / sizeof(*taskinfos);
+}