summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChe-yu Wu <cheyuw@google.com>2017-08-18 16:08:51 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-08-29 04:45:51 -0700
commitaf32f8918e119022f5a02e930edcd6e4de75bee1 (patch)
tree4215b2821a998effce4cc0d80848f06d740950a7
parent4fc9cf4c116d06153f7ab4a02533d4b5406248d3 (diff)
downloadchrome-ec-af32f8918e119022f5a02e930edcd6e4de75bee1.tar.gz
extra/stack_analyzer: Eliminate annotated indirect calls.
Indirect calls can be eliminated by adding corresponding annotations. 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 \ --annotation=./extra/stack_analyzer/example_annotation.yaml \ ./build/elm/RW/ec.RW.elf make BOARD=elm SECTION=RW \ ANNOTATION=./extra/stack_analyzer/example_annotation.yaml \ analyzestack Change-Id: I18c317f9c6478b5b431ee04d882453791df27891 Signed-off-by: Che-yu Wu <cheyuw@google.com> Reviewed-on: https://chromium-review.googlesource.com/631082 Reviewed-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--extra/stack_analyzer/README.md39
-rw-r--r--extra/stack_analyzer/example_annotation.yaml5
-rwxr-xr-xextra/stack_analyzer/stack_analyzer.py246
-rwxr-xr-xextra/stack_analyzer/stack_analyzer_unittest.py105
4 files changed, 289 insertions, 106 deletions
diff --git a/extra/stack_analyzer/README.md b/extra/stack_analyzer/README.md
index 73e980e610..0933959249 100644
--- a/extra/stack_analyzer/README.md
+++ b/extra/stack_analyzer/README.md
@@ -25,22 +25,22 @@ For each task, it will output the result like below,
Task: PD_C1, Max size: 1156 (932 + 224), Allocated size: 640
Call Trace:
pd_task (160) [common/usb_pd_protocol.c:1644] 1008a6e8
- -> pd_task [common/usb_pd_protocol.c:1808] 1008ac8a
- - handle_request [common/usb_pd_protocol.c:1191]
- - handle_data_request [common/usb_pd_protocol.c:798]
- -> pd_task [common/usb_pd_protocol.c:2672] 1008c222
+ -> pd_task[common/usb_pd_protocol.c:1808] 1008ac8a
+ - handle_request[common/usb_pd_protocol.c:1191]
+ - handle_data_request[common/usb_pd_protocol.c:798]
+ -> pd_task[common/usb_pd_protocol.c:2672] 1008c222
-> [annotation]
pd_send_request_msg.lto_priv.263 (56) [common/usb_pd_protocol.c:653] 1009a0b4
- -> pd_send_request_msg.lto_priv.263 [common/usb_pd_protocol.c:712] 1009a22e0
+ -> pd_send_request_msg.lto_priv.263[common/usb_pd_protocol.c:712] 1009a22e0
```
The `pd_task` uses 160 bytes on the stack and calls `pd_send_request_msg.lto_priv.263`.
The callsites to the next function will be shown like below,
```
--> pd_task [common/usb_pd_protocol.c:1808] 1008ac8a
- - handle_request [common/usb_pd_protocol.c:1191]
- - handle_data_request [common/usb_pd_protocol.c:798]
--> pd_task [common/usb_pd_protocol.c:2672] 1008c222
+-> pd_task[common/usb_pd_protocol.c:1808] 1008ac8a
+ - handle_request[common/usb_pd_protocol.c:1191]
+ - handle_data_request[common/usb_pd_protocol.c:798]
+-> pd_task[common/usb_pd_protocol.c:2672] 1008c222
```
This means one callsite to the next function is at `usb_pd_protocol.c:798`,
but it is inlined to the current function and you can follow the trace:
@@ -49,3 +49,24 @@ find the callsite. The second callsite is at `usb_pd_protocol.c:2672`. And the
third one is added by annotation.
The unresolved indirect callsites have the similar format to the above.
+
+Annotating Indirect Call
+------------------------
+
+To annotate an indirect call like this,
+```
+Unresolved indirect callsites:
+ pd_transmit
+ -> pd_transmit[common/usb_pd_protocol.c:407] 802c9c8
+ - tcpm_transmit[driver/tcpm/tcpm.h:142]
+```
+It is an indirect call in the `tcpm_transmit`, which is inlined to the `pd_transmit`.
+
+You can add a annotation like the below to eliminate it.
+```
+add:
+ tcpm_transmit[driver/tcpm/tcpm.h:142]:
+ - anx74xx_tcpm_transmit
+```
+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).
diff --git a/extra/stack_analyzer/example_annotation.yaml b/extra/stack_analyzer/example_annotation.yaml
index f4726d55c4..906342e762 100644
--- a/extra/stack_analyzer/example_annotation.yaml
+++ b/extra/stack_analyzer/example_annotation.yaml
@@ -10,6 +10,11 @@ add:
motion_lid_calc[common/motion_lid.c]:
- get_range[driver/accel_kionix.c]
+ # The full signature (function name[path:line number]) can be used to
+ # eliminate the indirect call (see README.md).
+ tcpm_transmit[driver/tcpm/tcpm.h:142]:
+ - anx74xx_tcpm_transmit
+
# Remove some call paths.
remove:
# Remove all callsites pointing to panic_assert_fail.
diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py
index 2d84ccec60..58677eac80 100755
--- a/extra/stack_analyzer/stack_analyzer.py
+++ b/extra/stack_analyzer/stack_analyzer.py
@@ -287,6 +287,7 @@ class ArmAnalyzer(object):
INDIRECT_CALL_OPERAND_RE = re.compile(r'^r\d+|sb|sl|fp|ip|sp|pc$')
# TODO(cheyuw): Handle conditional versions of following
# instructions.
+ # TODO(cheyuw): Handle other kinds of pc modifying instructions (e.g. mov pc).
LDR_OPCODE_RE = re.compile(r'^ldr(\.\w)?$')
# Example: "pc, [sp], #4"
LDR_PC_OPERAND_RE = re.compile(r'^pc, \[([^\]]+)\]')
@@ -378,9 +379,17 @@ class StackAnalyzer(object):
Analyze: Run the stack analysis.
"""
+ C_FUNCTION_NAME = r'_A-Za-z0-9'
+
+ # Assume there is no ":" in the path.
# Example: "driver/accel_kionix.c:321 (discriminator 3)"
ADDRTOLINE_RE = re.compile(
r'^(?P<path>[^:]+):(?P<linenum>\d+)(\s+\(discriminator\s+\d+\))?$')
+ # To eliminate the suffix appended by compilers, try to extract the
+ # C function name from the prefix of symbol name.
+ # Example: "SHA256_transform.constprop.28"
+ FUNCTION_PREFIX_NAME_RE = re.compile(
+ r'^(?P<name>[{0}]+)([^{0}].*)?$'.format(C_FUNCTION_NAME))
# Errors of annotation resolving.
ANNOTATION_ERROR_INVALID = 'invalid signature'
@@ -451,7 +460,7 @@ class StackAnalyzer(object):
# Assume the output is always well-formed.
assert result is not None
line_infos.append((function_name.strip(),
- result.group('path').strip(),
+ os.path.realpath(result.group('path').strip()),
int(result.group('linenum'))))
self.address_to_line_cache[cache_key] = line_infos
@@ -622,7 +631,7 @@ class StackAnalyzer(object):
return function_map
- def MappingAnnotation(self, function_map, signature_set):
+ def MapAnnotation(self, function_map, signature_set):
"""Map annotation signatures to functions.
Args:
@@ -630,18 +639,8 @@ class StackAnalyzer(object):
signature_set: Set of annotation signatures.
Returns:
- Map of signatures to functions, set of signatures which can't be resolved.
+ Map of signatures to functions, map of signatures which can't be resolved.
"""
- C_FUNCTION_NAME = r'_A-Za-z0-9'
- # To eliminate the suffix appended by compilers, try to extract the
- # C function name from the prefix of symbol name.
- # Example: "SHA256_transform.constprop.28"
- prefix_name_regex = re.compile(
- r'^(?P<name>[{0}]+)([^{0}].*)?$'.format(C_FUNCTION_NAME))
- # Example: "get_range[driver/accel_kionix.c]"
- annotation_signature_regex = re.compile(
- r'^(?P<name>[{}]+)(\[(?P<path>.+)\])?$'.format(C_FUNCTION_NAME))
-
# Build the symbol map indexed by symbol name. If there are multiple symbols
# with the same name, add them into a set. (e.g. symbols of static function
# with the same name)
@@ -649,7 +648,7 @@ class StackAnalyzer(object):
for symbol in self.symbols:
if symbol.symtype == 'F':
# Function symbol.
- result = prefix_name_regex.match(symbol.name)
+ result = self.FUNCTION_PREFIX_NAME_RE.match(symbol.name)
if result is not None:
function = function_map.get(symbol.address)
# Ignore the symbol not in disassembly.
@@ -660,20 +659,14 @@ class StackAnalyzer(object):
# Build the signature map indexed by annotation signature.
signature_map = {}
- failed_sigs = set()
+ sig_error_map = {}
symbol_path_map = {}
for sig in signature_set:
- result = annotation_signature_regex.match(sig)
- if result is None:
- failed_sigs.add((sig, self.ANNOTATION_ERROR_INVALID))
- continue
-
- name = result.group('name').strip()
- path = result.group('path')
+ (name, path, _) = sig
functions = symbol_map.get(name)
if functions is None:
- failed_sigs.add((sig, self.ANNOTATION_ERROR_NOTFOUND))
+ sig_error_map[sig] = self.ANNOTATION_ERROR_NOTFOUND
continue
if name not in symbol_path_map:
@@ -691,7 +684,7 @@ class StackAnalyzer(object):
# symbol path). Assume they are the same copies and do the same
# annotation operations of them because we don't know which copy is
# indicated by the users.
- group_map[os.path.realpath(symbol_path)].append(function)
+ group_map[symbol_path].append(function)
symbol_path_map[name] = group_map
@@ -702,24 +695,93 @@ class StackAnalyzer(object):
if path is None:
if len(group_map) > 1:
# There is ambiguity but the path isn't specified.
- failed_sigs.add((sig, self.ANNOTATION_ERROR_AMBIGUOUS))
+ sig_error_map[sig] = self.ANNOTATION_ERROR_AMBIGUOUS
continue
# No path signature but all symbol signatures of functions are same.
# Assume they are the same functions, so there is no ambiguity.
(function_group,) = group_map.values()
else:
- function_group = group_map.get(os.path.realpath(path.strip()))
+ function_group = group_map.get(path)
if function_group is None:
- failed_sigs.add((sig, self.ANNOTATION_ERROR_NOTFOUND))
+ sig_error_map[sig] = self.ANNOTATION_ERROR_NOTFOUND
continue
# The function_group is a list of all the same functions (according to
# our assumption) which should be annotated together.
signature_map[sig] = function_group
- return (signature_map, failed_sigs)
+ return (signature_map, sig_error_map)
+
+ def LoadAnnotation(self):
+ """Load annotation rules.
+
+ Returns:
+ Map of add rules, set of remove rules, set of text signatures which can't
+ be parsed.
+ """
+ # Assume there is no ":" in the path.
+ # Example: "get_range.lto.2501[driver/accel_kionix.c:327]"
+ annotation_signature_regex = re.compile(
+ r'^(?P<name>[^\[]+)(\[(?P<path>[^:]+)(:(?P<linenum>\d+))?\])?$')
+
+ def NormalizeSignature(signature_text):
+ """Parse and normalize the annotation signature.
+
+ Args:
+ signature_text: Text of the annotation signature.
+
+ Returns:
+ (function name, path, line number) of the signature. The path and line
+ number can be None if not exist. None if failed to parse.
+ """
+ result = annotation_signature_regex.match(signature_text.strip())
+ if result is None:
+ return None
+
+ name_result = self.FUNCTION_PREFIX_NAME_RE.match(
+ result.group('name').strip())
+ if name_result is None:
+ return None
+
+ path = result.group('path')
+ if path is not None:
+ path = os.path.realpath(path.strip())
+
+ linenum = result.group('linenum')
+ if linenum is not None:
+ linenum = int(linenum.strip())
+
+ return (name_result.group('name').strip(), path, linenum)
+
+ add_rules = collections.defaultdict(set)
+ remove_rules = set()
+ invalid_sigtxts = set()
+
+ if 'add' in self.annotation and self.annotation['add'] is not None:
+ for src_sigtxt, dst_sigtxts in self.annotation['add'].items():
+ src_sig = NormalizeSignature(src_sigtxt)
+ if src_sig is None:
+ invalid_sigtxts.add(src_sigtxt)
+ continue
+
+ for dst_sigtxt in dst_sigtxts:
+ 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 remove_sigtxt in self.annotation['remove']:
+ remove_sig = NormalizeSignature(remove_sigtxt)
+ if remove_sig is None:
+ invalid_sigtxts.add(remove_sigtxt)
+ else:
+ remove_rules.add(remove_sig)
+
+ return (add_rules, remove_rules, invalid_sigtxts)
def ResolveAnnotation(self, function_map):
"""Resolve annotation.
@@ -728,32 +790,64 @@ class StackAnalyzer(object):
function_map: Function map.
Returns:
- Set of added call edges, set of invalid paths, set of annotation
- signatures which can't be resolved.
+ Set of added call edges, set of invalid paths, set of eliminated
+ callsite addresses, set of annotation signatures which can't be resolved.
"""
- # Collect annotation signatures.
- annotation_add_map = self.annotation.get('add', {})
- annotation_remove_list = self.annotation.get('remove', [])
+ (add_rules, remove_rules, invalid_sigtxts) = self.LoadAnnotation()
- signature_set = set(annotation_remove_list)
- for src_sig, dst_sigs in annotation_add_map.items():
+ signature_set = set(remove_rules)
+ for src_sig, dst_sigs in add_rules.items():
signature_set.add(src_sig)
signature_set.update(dst_sigs)
- signature_set = {sig.strip() for sig in signature_set}
-
# Map signatures to functions.
- (signature_map, failed_sigs) = self.MappingAnnotation(function_map,
- signature_set)
+ (signature_map, sig_error_map) = self.MapAnnotation(function_map,
+ signature_set)
+
+ # Build the indirect callsite map indexed by callsite signature.
+ indirect_map = collections.defaultdict(set)
+ for function in function_map.values():
+ for callsite in function.callsites:
+ if callsite.target is not None:
+ continue
+
+ # Found an indirect callsite.
+ line_info = self.AddressToLine(callsite.address)[0]
+ if line_info is None:
+ continue
+
+ (name, path, linenum) = line_info
+ result = self.FUNCTION_PREFIX_NAME_RE.match(name)
+ if result is None:
+ continue
+
+ indirect_map[(result.group('name').strip(), path, linenum)].add(
+ (function, callsite.address))
# Generate the annotation sets.
add_set = set()
remove_set = set()
-
- for src_sig, dst_sigs in annotation_add_map.items():
- src_funcs = signature_map.get(src_sig)
- if src_funcs is None:
- continue
+ eliminated_addrs = set()
+
+ for src_sig, dst_sigs in add_rules.items():
+ src_funcs = set(signature_map.get(src_sig, []))
+ # Try to match the source signature to the indirect callsites. Even if it
+ # can't be found in disassembly.
+ indirect_calls = indirect_map.get(src_sig)
+ if indirect_calls is not None:
+ for function, callsite_address in indirect_calls:
+ # Add the caller of the indirect callsite to the source functions.
+ src_funcs.add(function)
+ # Assume each callsite can be represented by a unique address.
+ eliminated_addrs.add(callsite_address)
+
+ if src_sig in sig_error_map:
+ # Assume the error is always the not found error. Since the signature
+ # found in indirect callsite map must be a full signature, it can't
+ # happen the ambiguous error.
+ assert sig_error_map[src_sig] == self.ANNOTATION_ERROR_NOTFOUND
+ # Found in inline stack, remove the not found error.
+ del sig_error_map[src_sig]
for dst_sig in dst_sigs:
dst_funcs = signature_map.get(dst_sig)
@@ -766,24 +860,45 @@ class StackAnalyzer(object):
for dst_func in dst_funcs:
add_set.add((src_func, dst_func))
- for remove_sig in annotation_remove_list:
+ for remove_sig in remove_rules:
remove_funcs = signature_map.get(remove_sig)
if remove_funcs is not None:
# Add all the same functions.
remove_set.update(remove_funcs)
- return add_set, remove_set, failed_sigs
+ failed_sigtxts = set()
+ for sigtxt in invalid_sigtxts:
+ failed_sigtxts.add((sigtxt, self.ANNOTATION_ERROR_INVALID))
+
+ # Translate the tupled failed signatures to text signatures.
+ for sig, error in sig_error_map.items():
+ (name, path, linenum) = sig
+ bracket_text = ''
+ if path is not None:
+ path = os.path.relpath(path)
+ if linenum is None:
+ bracket_text = '[{}]'.format(path)
+ else:
+ bracket_text = '[{}:{}]'.format(path, linenum)
+
+ failed_sigtxts.add((name + bracket_text, error))
+
+ return (add_set, remove_set, eliminated_addrs, failed_sigtxts)
- def PreprocessCallGraph(self, function_map, add_set, remove_set):
- """Preprocess the callgraph.
+ def PreprocessAnnotation(self, function_map, add_set, remove_set,
+ eliminated_addrs):
+ """Preprocess the annotation and callgraph.
- It will add the missing call edges, and remove simple invalid paths (the
- paths only have one vertex) from the function_map.
+ Add the missing call edges, and remove simple invalid paths (the paths only
+ have one vertex) from the function_map.
+
+ Eliminate the annotated indirect callsites.
Args:
function_map: Function map.
add_set: Set of missing call edges.
remove_set: Set of invalid paths.
+ eliminated_addrs: Set of eliminated callsite addresses.
"""
for src_func, dst_func in add_set:
# TODO(cheyuw): Support tailing call annotation.
@@ -793,13 +908,18 @@ class StackAnalyzer(object):
for function in function_map.values():
cleaned_callsites = []
for callsite in function.callsites:
- if callsite.callee not in remove_set:
- cleaned_callsites.append(callsite)
+ if callsite.callee in remove_set:
+ continue
+
+ if callsite.target is None and callsite.address in eliminated_addrs:
+ continue
+
+ cleaned_callsites.append(callsite)
function.callsites = cleaned_callsites
def AnalyzeCallGraph(self, function_map):
- """Analyze call graph.
+ """Analyze callgraph.
It will update the max stack size and path for each function.
@@ -807,7 +927,7 @@ class StackAnalyzer(object):
function_map: Function map.
Returns:
- SCC groups of the call graph.
+ SCC groups of the callgraph.
"""
def BuildSCC(function):
"""Tarjan's strongly connected components algorithm.
@@ -912,7 +1032,9 @@ class StackAnalyzer(object):
else:
(function_name, path, linenum) = line_info
- line_texts.append('{} [{}:{}]'.format(function_name, path, linenum))
+ line_texts.append('{}[{}:{}]'.format(function_name,
+ os.path.relpath(path),
+ linenum))
print('{}-> {} {:x}'.format(prefix, line_texts[0], address))
for depth, line_text in enumerate(line_texts[1:]):
@@ -929,8 +1051,11 @@ class StackAnalyzer(object):
raise StackAnalyzerError('Failed to run objdump.')
function_map = self.AnalyzeDisassembly(disasm_text)
- (add_set, remove_set, failed_sigs) = self.ResolveAnnotation(function_map)
- self.PreprocessCallGraph(function_map, add_set, remove_set)
+ result = self.ResolveAnnotation(function_map)
+ (add_set, remove_set, eliminated_addrs, failed_sigtxts) = result
+ self.PreprocessAnnotation(function_map,
+ add_set, remove_set,
+ eliminated_addrs)
cycle_groups = self.AnalyzeCallGraph(function_map)
# Print the results of task-aware stack analysis.
@@ -954,7 +1079,7 @@ class StackAnalyzer(object):
output = ' {} ({}) [{}:{}] {:x}'.format(curr_func.name,
curr_func.stack_frame,
- path,
+ os.path.relpath(path),
linenum,
curr_func.address)
if len(cycle_groups[curr_func.cycle_index]) > 0:
@@ -988,8 +1113,8 @@ class StackAnalyzer(object):
PrintInlineStack(address, ' ')
print('Unresolved annotation signatures:')
- for sig, error in failed_sigs:
- print(' {}: {}'.format(sig, error))
+ for sigtxt, error in failed_sigtxts:
+ print(' {}: {}'.format(sigtxt, error))
def ParseArgs():
@@ -1112,6 +1237,7 @@ def main():
except IOError:
raise StackAnalyzerError('Failed to open annotation file.')
+ # TODO(cheyuw): Do complete annotation format verification.
if not isinstance(annotation, dict):
raise StackAnalyzerError('Invalid annotation file.')
diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py
index 0f62e1a3be..ce12b83552 100755
--- a/extra/stack_analyzer/stack_analyzer_unittest.py
+++ b/extra/stack_analyzer/stack_analyzer_unittest.py
@@ -8,6 +8,7 @@
from __future__ import print_function
import mock
+import os
import subprocess
import unittest
@@ -213,6 +214,18 @@ class StackAnalyzerTest(unittest.TestCase):
self.assertEqual(tasklist, expect_rw_tasklist)
def testResolveAnnotation(self):
+ self.analyzer.annotation = {}
+ (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
+ self.assertEqual(add_rules, {})
+ self.assertEqual(remove_rules, set())
+ self.assertEqual(invalid_sigtxts, set())
+
+ self.analyzer.annotation = {'add': None, 'remove': None}
+ (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
+ self.assertEqual(add_rules, {})
+ self.assertEqual(remove_rules, set())
+ self.assertEqual(invalid_sigtxts, set())
+
funcs = {
0x1000: sa.Function(0x1000, 'hook_task', 0, []),
0x2000: sa.Function(0x2000, 'console_task', 0, []),
@@ -221,50 +234,61 @@ class StackAnalyzerTest(unittest.TestCase):
0x13000: sa.Function(0x13000, 'inlined_mul', 0, []),
0x13100: sa.Function(0x13100, 'inlined_mul', 0, []),
}
+ funcs[0x1000].callsites = [
+ sa.Callsite(0x1002, None, False, None)]
# Set address_to_line_cache to fake the results of addr2line.
self.analyzer.address_to_line_cache = {
- (0x1000, False): [('hook_task', 'a.c', 10)],
- (0x2000, False): [('console_task', 'b.c', 20)],
- (0x4000, False): [('toudhpad_calc', './a.c', 20)],
- (0x5000, False): [('touchpad_calc.constprop.42', 'b.c', 40)],
- (0x12000, False): [('trackpad_range', 't.c', 10)],
- (0x13000, False): [('inlined_mul', 'x.c', 12)],
- (0x13100, False): [('inlined_mul', 'x.c', 12)],
+ (0x1000, False): [('hook_task', os.path.abspath('a.c'), 10)],
+ (0x1002, False): [('toot_calc', os.path.abspath('t.c'), 1234)],
+ (0x2000, False): [('console_task', os.path.abspath('b.c'), 20)],
+ (0x4000, False): [('toudhpad_calc', os.path.abspath('a.c'), 20)],
+ (0x5000, False): [
+ ('touchpad_calc.constprop.42', os.path.abspath('b.c'), 40)],
+ (0x12000, False): [('trackpad_range', os.path.abspath('t.c'), 10)],
+ (0x13000, False): [('inlined_mul', os.path.abspath('x.c'), 12)],
+ (0x13100, False): [('inlined_mul', os.path.abspath('x.c'), 12)],
}
self.analyzer.annotation = {
'add': {
- 'hook_task': ['touchpad_calc[a.c]', 'hook_task'],
+ 'hook_task.lto.573': ['touchpad_calc.lto.2501[a.c]'],
'console_task': ['touchpad_calc[b.c]', 'inlined_mul_alias'],
'hook_task[q.c]': ['hook_task'],
'inlined_mul[x.c]': ['inlined_mul'],
+ 'toot_calc[t.c:1234]': ['hook_task'],
},
'remove': {
- 'touchpad?calc',
+ 'touchpad?calc[',
'touchpad_calc',
'touchpad_calc[a.c]',
'task_unk[a.c]',
- 'touchpad_calc[../a.c]',
+ 'touchpad_calc[x/a.c]',
'trackpad_range',
'inlined_mul',
},
}
- signature_set = set(self.analyzer.annotation['remove'])
- for src_sig, dst_sigs in self.analyzer.annotation['add'].items():
+
+ (add_rules, remove_rules, invalid_sigtxts) = self.analyzer.LoadAnnotation()
+ self.assertEqual(invalid_sigtxts, {'touchpad?calc['})
+
+ signature_set = set(remove_rules)
+ for src_sig, dst_sigs in add_rules.items():
signature_set.add(src_sig)
signature_set.update(dst_sigs)
- (signature_map, failed_sigs) = self.analyzer.MappingAnnotation(
- funcs, signature_set)
- (add_set, remove_set, failed_sigs) = self.analyzer.ResolveAnnotation(funcs)
+ (signature_map, failed_sigs) = self.analyzer.MapAnnotation(funcs,
+ signature_set)
+ result = self.analyzer.ResolveAnnotation(funcs)
+ (add_set, remove_set, eliminated_addrs, failed_sigs) = result
expect_signature_map = {
- 'hook_task': {funcs[0x1000]},
- 'touchpad_calc[a.c]': {funcs[0x4000]},
- 'touchpad_calc[b.c]': {funcs[0x5000]},
- 'console_task': {funcs[0x2000]},
- 'inlined_mul_alias': {funcs[0x13100]},
- 'inlined_mul[x.c]': {funcs[0x13000], funcs[0x13100]},
- 'inlined_mul': {funcs[0x13000], funcs[0x13100]},
+ ('hook_task', None, None): {funcs[0x1000]},
+ ('touchpad_calc', os.path.abspath('a.c'), None): {funcs[0x4000]},
+ ('touchpad_calc', os.path.abspath('b.c'), None): {funcs[0x5000]},
+ ('console_task', None, None): {funcs[0x2000]},
+ ('inlined_mul_alias', None, None): {funcs[0x13100]},
+ ('inlined_mul', os.path.abspath('x.c'), None): {funcs[0x13000],
+ funcs[0x13100]},
+ ('inlined_mul', None, None): {funcs[0x13000], funcs[0x13100]},
}
self.assertEqual(len(signature_map), len(expect_signature_map))
for sig, funclist in signature_map.items():
@@ -285,16 +309,17 @@ class StackAnalyzerTest(unittest.TestCase):
funcs[0x13000],
funcs[0x13100]
})
+ self.assertEqual(eliminated_addrs, {0x1002})
self.assertEqual(failed_sigs, {
- ('touchpad?calc', sa.StackAnalyzer.ANNOTATION_ERROR_INVALID),
+ ('touchpad?calc[', sa.StackAnalyzer.ANNOTATION_ERROR_INVALID),
('touchpad_calc', sa.StackAnalyzer.ANNOTATION_ERROR_AMBIGUOUS),
('hook_task[q.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND),
('task_unk[a.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND),
- ('touchpad_calc[../a.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND),
+ ('touchpad_calc[x/a.c]', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND),
('trackpad_range', sa.StackAnalyzer.ANNOTATION_ERROR_NOTFOUND),
})
- def testPreprocessCallGraph(self):
+ def testPreprocessAnnotation(self):
funcs = {
0x1000: sa.Function(0x1000, 'hook_task', 0, []),
0x2000: sa.Function(0x2000, 'console_task', 0, []),
@@ -303,11 +328,17 @@ class StackAnalyzerTest(unittest.TestCase):
funcs[0x1000].callsites = [
sa.Callsite(0x1002, 0x1000, False, funcs[0x1000])]
funcs[0x2000].callsites = [
- sa.Callsite(0x2002, 0x1000, False, funcs[0x1000])]
+ sa.Callsite(0x2002, 0x1000, False, funcs[0x1000]),
+ sa.Callsite(0x2006, None, True, None),
+ ]
add_set = {(funcs[0x2000], funcs[0x4000]), (funcs[0x4000], funcs[0x1000])}
remove_set = {funcs[0x1000]}
+ eliminated_addrs = {0x2006}
- self.analyzer.PreprocessCallGraph(funcs, add_set, remove_set)
+ self.analyzer.PreprocessAnnotation(funcs,
+ add_set,
+ remove_set,
+ eliminated_addrs)
expect_funcs = {
0x1000: sa.Function(0x1000, 'hook_task', 0, []),
@@ -408,30 +439,30 @@ class StackAnalyzerTest(unittest.TestCase):
@mock.patch('subprocess.check_output')
def testAddressToLine(self, checkoutput_mock):
- checkoutput_mock.return_value = 'fake_func\ntest.c:1'
+ checkoutput_mock.return_value = 'fake_func\n/test.c:1'
self.assertEqual(self.analyzer.AddressToLine(0x1234),
- [('fake_func', 'test.c', 1)])
+ [('fake_func', '/test.c', 1)])
checkoutput_mock.assert_called_once_with(
['addr2line', '-f', '-e', './ec.RW.elf', '1234'])
checkoutput_mock.reset_mock()
- checkoutput_mock.return_value = 'fake_func\na.c:1\nbake_func\nb.c:2\n'
+ checkoutput_mock.return_value = 'fake_func\n/a.c:1\nbake_func\n/b.c:2\n'
self.assertEqual(self.analyzer.AddressToLine(0x1234, True),
- [('fake_func', 'a.c', 1), ('bake_func', 'b.c', 2)])
+ [('fake_func', '/a.c', 1), ('bake_func', '/b.c', 2)])
checkoutput_mock.assert_called_once_with(
['addr2line', '-f', '-e', './ec.RW.elf', '1234', '-i'])
checkoutput_mock.reset_mock()
- checkoutput_mock.return_value = 'fake_func\ntest.c:1 (discriminator 128)'
+ checkoutput_mock.return_value = 'fake_func\n/test.c:1 (discriminator 128)'
self.assertEqual(self.analyzer.AddressToLine(0x12345),
- [('fake_func', 'test.c', 1)])
+ [('fake_func', '/test.c', 1)])
checkoutput_mock.assert_called_once_with(
['addr2line', '-f', '-e', './ec.RW.elf', '12345'])
checkoutput_mock.reset_mock()
- checkoutput_mock.return_value = '??\n:?\nbake_func\nb.c:2\n'
+ checkoutput_mock.return_value = '??\n:?\nbake_func\n/b.c:2\n'
self.assertEqual(self.analyzer.AddressToLine(0x123456),
- [None, ('bake_func', 'b.c', 2)])
+ [None, ('bake_func', '/b.c', 2)])
checkoutput_mock.assert_called_once_with(
['addr2line', '-f', '-e', './ec.RW.elf', '123456'])
checkoutput_mock.reset_mock()
@@ -481,11 +512,11 @@ class StackAnalyzerTest(unittest.TestCase):
'Task: CONSOLE, Max size: 240 (16 + 224), Allocated size: 460'),
mock.call('Call Trace:'),
mock.call(' console_task (8) [??:0] 2000'),
- mock.call(' -> ?? [??:0] 2002'),
+ mock.call(' -> ??[??:0] 2002'),
mock.call(' hook_task (8) [??:0] 1000'),
mock.call('Unresolved indirect callsites:'),
mock.call(' console_task'),
- mock.call(' -> ?? [??:0] 200a'),
+ mock.call(' -> ??[??:0] 200a'),
mock.call('Unresolved annotation signatures:'),
mock.call(' fake_func: function is not found'),
])