summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--extra/stack_analyzer/README.md28
-rwxr-xr-xextra/stack_analyzer/stack_analyzer.py128
-rwxr-xr-xextra/stack_analyzer/stack_analyzer_unittest.py45
3 files changed, 187 insertions, 14 deletions
diff --git a/extra/stack_analyzer/README.md b/extra/stack_analyzer/README.md
index a13b3b66db..bead6d10b4 100644
--- a/extra/stack_analyzer/README.md
+++ b/extra/stack_analyzer/README.md
@@ -72,3 +72,31 @@ add:
```
The source `tcpm_transmit[driver/tcpm/tcpm.h:142]` must be a full signature (function_name[path:line number]).
So the resolver can know which indirect call you want to annotate and eliminate (even if it is inlined).
+
+Annotating arrays (hooks, console commands, host commands)
+----------------------------------------------------------
+
+When a callsite calls a number of functions based on values from an constant
+array (in `.rodata` section), one can use the following syntax:
+
+```
+ hook_task[common/hooks.c:197]:
+ - { name: __deferred_funcs, stride: 4, offset: 0 }
+ - { name: __hooks_second, stride: 8, offset: 0 }
+ - { name: __hooks_tick, stride: 8, offset: 0 }
+```
+
+Where `name` is the symbol name for the start of the array (the end of the array
+is `<name>_end`), stride is the array element size, and offset is the offset of
+the function pointer in the structure. For example, above, `__deferred_funcs` is
+a simple array of function pointers, while `__hooks_tick` is an array of
+`struct hook_data` (size 8, pointer at offset 0):
+
+```
+struct hook_data {
+ /* Hook processing routine. */
+ void (*routine)(void);
+ /* Priority; low numbers = higher priority. */
+ int priority;
+};
+```
diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py
index a839515b9e..329e4faa6b 100755
--- a/extra/stack_analyzer/stack_analyzer.py
+++ b/extra/stack_analyzer/stack_analyzer.py
@@ -387,17 +387,20 @@ class StackAnalyzer(object):
ANNOTATION_ERROR_NOTFOUND = 'function is not found'
ANNOTATION_ERROR_AMBIGUOUS = 'signature is ambiguous'
- def __init__(self, options, symbols, tasklist, annotation):
+ def __init__(self, options, symbols, rodata, tasklist, annotation):
"""Constructor.
Args:
options: Namespace from argparse.parse_args().
symbols: Symbol list.
+ rodata: Content of .rodata section (offset, data)
tasklist: Task list.
annotation: Annotation config.
"""
self.options = options
self.symbols = symbols
+ self.rodata_offset = rodata[0]
+ self.rodata = rodata[1]
self.tasklist = tasklist
self.annotation = annotation
self.address_to_line_cache = {}
@@ -746,6 +749,57 @@ class StackAnalyzer(object):
return (name_result.group('name').strip(), path, linenum)
+ def ExpandArray(dic):
+ """Parse and expand a symbol array
+
+ Args:
+ dic: Dictionary for the array annotation
+
+ Returns:
+ array of (symbol name, None, None).
+ """
+ # TODO(drinkcat): This function is quite inefficient, as it goes through
+ # the symbol table multiple times.
+
+ begin_name = dic['name']
+ end_name = dic['name'] + "_end"
+ offset = dic['offset'] if 'offset' in dic else 0
+ stride = dic['stride']
+
+ begin_address = None
+ end_address = None
+
+ for symbol in self.symbols:
+ if (symbol.name == begin_name):
+ begin_address = symbol.address
+ if (symbol.name == end_name):
+ end_address = symbol.address
+
+ if (not begin_address or not end_address):
+ return None
+
+ output = []
+ # TODO(drinkcat): This is inefficient as we go from address to symbol
+ # object then to symbol name, and later on we'll go back from symbol name
+ # to symbol object.
+ for addr in range(begin_address+offset, end_address, stride):
+ # TODO(drinkcat): Not all architectures need to drop the first bit.
+ val = self.rodata[(addr-self.rodata_offset)/4] & 0xfffffffe
+ name = None
+ for symbol in self.symbols:
+ if (symbol.address == val):
+ result = self.FUNCTION_PREFIX_NAME_RE.match(symbol.name)
+ name = result.group('name')
+ break
+
+ if not name:
+ raise StackAnalyzerError('Cannot find function for address %s.',
+ hex(val))
+
+ output.append((name, None, None))
+
+ return output
+
add_rules = collections.defaultdict(set)
remove_rules = list()
invalid_sigtxts = set()
@@ -758,11 +812,18 @@ class StackAnalyzer(object):
continue
for dst_sigtxt in dst_sigtxts:
- dst_sig = NormalizeSignature(dst_sigtxt)
- if dst_sig is None:
- invalid_sigtxts.add(dst_sigtxt)
+ if isinstance(dst_sigtxt, dict):
+ dst_sig = ExpandArray(dst_sigtxt)
+ if dst_sig is None:
+ invalid_sigtxts.add(str(dst_sigtxt))
+ else:
+ add_rules[src_sig].update(dst_sig)
else:
- add_rules[src_sig].add(dst_sig)
+ dst_sig = NormalizeSignature(dst_sigtxt)
+ if dst_sig is None:
+ invalid_sigtxts.add(dst_sigtxt)
+ else:
+ add_rules[src_sig].add(dst_sig)
if 'remove' in self.annotation and self.annotation['remove'] is not None:
for sigtxt_path in self.annotation['remove']:
@@ -1385,6 +1446,54 @@ def ParseSymbolText(symbol_text):
return symbols
+def ParseRoDataText(rodata_text):
+ """Parse the content of rodata
+
+ Args:
+ symbol_text: Text of the rodata dump.
+
+ Returns:
+ symbols: Symbol list.
+ """
+ # Examples: 8018ab0 00040048 00010000 10020000 4b8e0108 ...H........K...
+ # 100a7294 00000000 00000000 01000000 ............
+
+ base_offset = None
+ offset = None
+ rodata = []
+ for line in rodata_text.splitlines():
+ line = line.strip()
+ space = line.find(' ')
+ if space < 0:
+ continue
+ try:
+ address = int(line[0:space], 16)
+ except ValueError:
+ continue
+
+ if not base_offset:
+ base_offset = address
+ offset = address
+ elif address != offset:
+ raise StackAnalyzerError('objdump of rodata not contiguous.')
+
+ for i in range(0, 4):
+ num = line[(space + 1 + i*9):(space + 9 + i*9)]
+ if len(num.strip()) > 0:
+ val = int(num, 16)
+ else:
+ val = 0
+ # TODO(drinkcat): Not all platforms are necessarily big-endian
+ rodata.append((val & 0x000000ff) << 24 |
+ (val & 0x0000ff00) << 8 |
+ (val & 0x00ff0000) >> 8 |
+ (val & 0xff000000) >> 24)
+
+ offset = offset + 4*4
+
+ return (base_offset, rodata)
+
+
def LoadTasklist(section, export_taskinfo, symbols):
"""Load the task information.
@@ -1465,12 +1574,17 @@ def main():
symbol_text = subprocess.check_output([options.objdump,
'-t',
options.elf_path])
+ rodata_text = subprocess.check_output([options.objdump,
+ '-s',
+ '-j', '.rodata',
+ options.elf_path])
except subprocess.CalledProcessError:
- raise StackAnalyzerError('objdump failed to dump symbol table.')
+ raise StackAnalyzerError('objdump failed to dump symbol table or rodata.')
except OSError:
raise StackAnalyzerError('Failed to run objdump.')
symbols = ParseSymbolText(symbol_text)
+ rodata = ParseRoDataText(rodata_text)
# Load the tasklist.
try:
@@ -1480,7 +1594,7 @@ def main():
tasklist = LoadTasklist(options.section, export_taskinfo, symbols)
- analyzer = StackAnalyzer(options, symbols, tasklist, annotation)
+ analyzer = StackAnalyzer(options, symbols, rodata, tasklist, annotation)
analyzer.Analyze()
except StackAnalyzerError as e:
print('Error: {}'.format(e))
diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py
index 1fe4944844..d159b2e14a 100755
--- a/extra/stack_analyzer/stack_analyzer_unittest.py
+++ b/extra/stack_analyzer/stack_analyzer_unittest.py
@@ -149,16 +149,22 @@ class StackAnalyzerTest(unittest.TestCase):
sa.Symbol(0x12000, 'F', 0x13C, 'trackpad_range'),
sa.Symbol(0x13000, 'F', 0x200, 'inlined_mul'),
sa.Symbol(0x13100, 'F', 0x200, 'inlined_mul'),
- sa.Symbol(0x13100, 'F', 0x200, 'inlined_mul_alias')]
+ sa.Symbol(0x13100, 'F', 0x200, 'inlined_mul_alias'),
+ sa.Symbol(0x20000, 'O', 0x0, '__array'),
+ sa.Symbol(0x20010, 'O', 0x0, '__array_end'),
+ ]
tasklist = [sa.Task('HOOKS', 'hook_task', 2048, 0x1000),
sa.Task('CONSOLE', 'console_task', 460, 0x2000)]
+ # Array at 0x20000 that contains pointers to hook_task and console_task,
+ # with stride=8, offset=4
+ rodata = (0x20000, [ 0xDEAD1000, 0x00001000, 0xDEAD2000, 0x00002000 ])
options = mock.MagicMock(elf_path='./ec.RW.elf',
export_taskinfo='fake',
section='RW',
objdump='objdump',
addr2line='addr2line',
annotation=None)
- self.analyzer = sa.StackAnalyzer(options, symbols, tasklist, {})
+ self.analyzer = sa.StackAnalyzer(options, symbols, rodata, tasklist, {})
def testParseSymbolText(self):
symbol_text = (
@@ -176,6 +182,17 @@ class StackAnalyzerTest(unittest.TestCase):
sa.Symbol(0xdeadbee, 'O', 0x0, '__foo_doo_coo_end')]
self.assertEqual(symbols, expect_symbols)
+ def testParseRoData(self):
+ rodata_text = (
+ '\n'
+ 'Contents of section .rodata:\n'
+ ' 20000 dead1000 00100000 dead2000 00200000 He..f.He..s.\n'
+ )
+ rodata = sa.ParseRoDataText(rodata_text)
+ expect_rodata = (0x20000,
+ [ 0x0010adde, 0x00001000, 0x0020adde, 0x00002000 ])
+ self.assertEqual(rodata, expect_rodata)
+
def testLoadTasklist(self):
def tasklist_to_taskinfos(pointer, tasklist):
taskinfos = []
@@ -235,16 +252,26 @@ class StackAnalyzerTest(unittest.TestCase):
}
(add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
self.assertEqual(add_rules, {})
- self.assertEqual(remove_rules, [
+ self.assertEqual(list.sort(remove_rules), list.sort([
[('a', None, None), ('1', None, None), ('x', None, None)],
[('a', None, None), ('0', None, None), ('x', None, None)],
[('a', None, None), ('2', None, None), ('x', None, None)],
[('b', os.path.abspath('x'), 3), ('1', None, None), ('x', None, None)],
[('b', os.path.abspath('x'), 3), ('0', None, None), ('x', None, None)],
[('b', os.path.abspath('x'), 3), ('2', None, None), ('x', None, None)],
- ])
+ ]))
self.assertEqual(invalid_sigtxts, {'['})
+ self.analyzer.annotation = {
+ 'add': {
+ 'touchpad_calc': [ dict(name='__array', stride=8, offset=4) ],
+ }
+ }
+ (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
+ self.assertEqual(add_rules, {
+ ('touchpad_calc', None, None):
+ set([('console_task', None, None), ('hook_task', None, None)])})
+
funcs = {
0x1000: sa.Function(0x1000, 'hook_task', 0, []),
0x2000: sa.Function(0x2000, 'console_task', 0, []),
@@ -288,7 +315,6 @@ class StackAnalyzerTest(unittest.TestCase):
['inlined_mul', 'inlined_mul_alias', 'console_task'],
],
}
-
(add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
self.assertEqual(invalid_sigtxts, {'touchpad?calc['})
@@ -631,6 +657,11 @@ class StackAnalyzerTest(unittest.TestCase):
def testMain(self, parseargs_mock, checkoutput_mock):
symbol_text = ('1000 g F .text 0000015c .hidden hook_task\n'
'2000 g F .text 0000051c .hidden console_task\n')
+ rodata_text = (
+ '\n'
+ 'Contents of section .rodata:\n'
+ ' 20000 dead1000 00100000 dead2000 00200000 He..f.He..s.\n'
+ )
args = mock.MagicMock(elf_path='./ec.RW.elf',
export_taskinfo='fake',
@@ -675,7 +706,7 @@ class StackAnalyzerTest(unittest.TestCase):
args.annotation = None
with mock.patch('__builtin__.print') as print_mock:
- checkoutput_mock.return_value = symbol_text
+ checkoutput_mock.side_effect = [symbol_text, rodata_text]
sa.main()
print_mock.assert_called_once_with(
'Error: Failed to load export_taskinfo.')
@@ -684,7 +715,7 @@ class StackAnalyzerTest(unittest.TestCase):
checkoutput_mock.side_effect = subprocess.CalledProcessError(1, '')
sa.main()
print_mock.assert_called_once_with(
- 'Error: objdump failed to dump symbol table.')
+ 'Error: objdump failed to dump symbol table or rodata.')
with mock.patch('__builtin__.print') as print_mock:
checkoutput_mock.side_effect = OSError()