mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-12 19:04:59 +00:00
stack_analyzer: Add new syntax for function pointer arrays
Makes it far simpler to support hooks, console commands, host commands. BRANCH=poppy,fizz BUG=chromium:648840 TEST=Add new array annotation, run stack_analyzer Change-Id: I8ed074ba5534661ed59f4f713bb4ba194e712f4e Signed-off-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/966042 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
This commit is contained in:
committed by
chrome-bot
parent
324a7ab083
commit
0cbb4b6f9d
@@ -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;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user