diff options
-rw-r--r-- | Makefile.rules | 23 | ||||
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer.py | 81 | ||||
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer_unittest.py | 70 | ||||
-rw-r--r-- | include/task_filter.h | 6 | ||||
-rw-r--r-- | util/build.mk | 11 | ||||
-rw-r--r-- | util/export_taskinfo.c | 43 |
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); +} |