diff options
author | Che-yu Wu <cheyuw@google.com> | 2017-08-17 17:12:03 +0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-08-22 05:08:48 -0700 |
commit | 00e022272938d3c3f8b5d60f11c960d9da5546ac (patch) | |
tree | 94948ade8c96c4c566fde04c7ef2177bf1a36fbe /extra | |
parent | ef09835e1941a2388e15b410791bed6ff5d84339 (diff) | |
download | chrome-ec-00e022272938d3c3f8b5d60f11c960d9da5546ac.tar.gz |
extra/stack_analyzer: Fix cbz/cbnz and addr2line parsing.
Fix the cbz/cbnz operands parsing.
Parse the discriminator output of addr2line.
BUG=none
BRANCH=none
TEST=extra/stack_analyzer/stack_analyzer_unittest.py
Change-Id: Iade1c14db0dc63fa65ef0f5df778b4f4f1e4f802
Signed-off-by: Che-yu Wu <cheyuw@google.com>
Reviewed-on: https://chromium-review.googlesource.com/625498
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
Diffstat (limited to 'extra')
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer.py | 60 | ||||
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer_unittest.py | 28 |
2 files changed, 50 insertions, 38 deletions
diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py index 5d97d15ef1..8593821221 100755 --- a/extra/stack_analyzer/stack_analyzer.py +++ b/extra/stack_analyzer/stack_analyzer.py @@ -267,16 +267,20 @@ class ArmAnalyzer(object): CONDITION_CODES = ['', 'eq', 'ne', 'cs', 'hs', 'cc', 'lo', 'mi', 'pl', 'vs', 'vc', 'hi', 'ls', 'ge', 'lt', 'gt', 'le'] CONDITION_CODES_RE = '({})'.format('|'.join(CONDITION_CODES)) + # Assume there is no function name containing ">". + IMM_ADDRESS_RE = r'([0-9A-Fa-f]+)\s+<([^>]+)>' # Fuzzy regular expressions for instruction and operand parsing. # Branch instructions. JUMP_OPCODE_RE = re.compile( - r'^(b{0}|bx{0}|cbz|cbnz)(\.\w)?$'.format(CONDITION_CODES_RE)) + r'^(b{0}|bx{0})(\.\w)?$'.format(CONDITION_CODES_RE)) # Call instructions. CALL_OPCODE_RE = re.compile( r'^(bl{0}|blx{0})(\.\w)?$'.format(CONDITION_CODES_RE)) - # Assume there is no function name containing ">". - CALL_OPERAND_RE = re.compile(r'^([0-9A-Fa-f]+)\s+<([^>]+)>$') + CALL_OPERAND_RE = re.compile(r'^{}$'.format(IMM_ADDRESS_RE)) + CBZ_CBNZ_OPCODE_RE = re.compile(r'^(cbz|cbnz)(\.\w)?$') + # Example: "r0, 1009bcbe <host_cmd_motion_sense+0x1d2>" + CBZ_CBNZ_OPERAND_RE = re.compile(r'^[^,]+,\s+{}$'.format(IMM_ADDRESS_RE)) # TODO(cheyuw): Handle conditional versions of following # instructions. # TODO(cheyuw): Handle other kinds of stm instructions. @@ -296,40 +300,29 @@ class ArmAnalyzer(object): Returns: (stack_frame, callsites): Size of stack frame and callsite list. """ - def DetectCallsite(operand_text): - """Check if the instruction is a callsite. - - Args: - operand_text: Text of instruction operands. - - Returns: - target_address: Target address. None if it isn't a callsite. - """ - result = self.CALL_OPERAND_RE.match(operand_text) - if result is None: - return None - - target_address = int(result.group(1), 16) - - if (function_symbol.size > 0 and - function_symbol.address < target_address < - (function_symbol.address + function_symbol.size)): - # Filter out the in-function target (branches and in-function calls, - # which are actually branches). - return None - - return target_address - stack_frame = 0 callsites = [] for address, opcode, operand_text in instructions: is_jump_opcode = self.JUMP_OPCODE_RE.match(opcode) is not None is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None - if is_jump_opcode or is_call_opcode: - target_address = DetectCallsite(operand_text) - if target_address is not None: - # Maybe it's a callsite. - callsites.append(Callsite(address, target_address, is_jump_opcode)) + is_cbz_cbnz_opcode = self.CBZ_CBNZ_OPCODE_RE.match(opcode) is not None + if is_jump_opcode or is_call_opcode or is_cbz_cbnz_opcode: + if is_cbz_cbnz_opcode: + result = self.CBZ_CBNZ_OPERAND_RE.match(operand_text) + else: + result = self.CALL_OPERAND_RE.match(operand_text) + + if result is not None: + target_address = int(result.group(1), 16) + # Filter out the in-function target (branches and in-function calls, + # which are actually branches). + if not (function_symbol.size > 0 and + function_symbol.address < target_address < + (function_symbol.address + function_symbol.size)): + # Maybe it's a callsite. + callsites.append(Callsite(address, + target_address, + is_jump_opcode or is_cbz_cbnz_opcode)) elif self.PUSH_OPCODE_RE.match(opcode) is not None: # Example: "{r4, r5, r6, r7, lr}" @@ -596,7 +589,8 @@ class StackAnalyzer(object): annotation_signature_regex = re.compile( r'^(?P<name>[{}]+)(\[(?P<path>.+)\])?$'.format(C_FUNCTION_NAME)) # Example: driver/accel_kionix.c:321 and ??:0 - addrtoline_regex = re.compile(r'^(?P<path>.+):\d+$') + addrtoline_regex = re.compile( + r'^(?P<path>[^:]+):\d+(\s+\(discriminator\s+\d+\))?$') # 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 diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py index 2c0a4ae8f1..e011a670c4 100755 --- a/extra/stack_analyzer/stack_analyzer_unittest.py +++ b/extra/stack_analyzer/stack_analyzer_unittest.py @@ -57,7 +57,7 @@ class ArmAnalyzerTest(unittest.TestCase): return rets def testInstructionMatching(self): - jump_list = self.AppendConditionCode(['b', 'bx']) + ['cbz', 'cbnz'] + jump_list = self.AppendConditionCode(['b', 'bx']) jump_list += (list(opcode + '.n' for opcode in jump_list) + list(opcode + '.w' for opcode in jump_list)) for opcode in jump_list: @@ -66,6 +66,12 @@ class ArmAnalyzerTest(unittest.TestCase): self.assertIsNone(sa.ArmAnalyzer.JUMP_OPCODE_RE.match('bl')) self.assertIsNone(sa.ArmAnalyzer.JUMP_OPCODE_RE.match('blx')) + cbz_list = ['cbz', 'cbnz', 'cbz.n', 'cbnz.n', 'cbz.w', 'cbnz.w'] + for opcode in cbz_list: + self.assertIsNotNone(sa.ArmAnalyzer.CBZ_CBNZ_OPCODE_RE.match(opcode)) + + self.assertIsNone(sa.ArmAnalyzer.CBZ_CBNZ_OPCODE_RE.match('cbn')) + call_list = self.AppendConditionCode(['bl', 'blx']) call_list += list(opcode + '.n' for opcode in call_list) for opcode in call_list: @@ -78,6 +84,11 @@ class ArmAnalyzerTest(unittest.TestCase): self.assertEqual(result.group(1), '53f90') self.assertEqual(result.group(2), 'get_time+0x18') + result = sa.ArmAnalyzer.CBZ_CBNZ_OPERAND_RE.match('r6, 53f90 <get+0x0>') + self.assertIsNotNone(result) + self.assertEqual(result.group(1), '53f90') + self.assertEqual(result.group(2), 'get+0x0') + self.assertIsNotNone(sa.ArmAnalyzer.PUSH_OPCODE_RE.match('push')) self.assertIsNone(sa.ArmAnalyzer.PUSH_OPCODE_RE.match('pushal')) self.assertIsNotNone(sa.ArmAnalyzer.STM_OPCODE_RE.match('stmdb')) @@ -313,7 +324,8 @@ class StackAnalyzerTest(unittest.TestCase): '00001000 <hook_task>:\n' ' 1000: dead beef\tfake\n' ' 1004: 4770\t\tbx lr\n' - ' 1006: 00015cfc\t.word 0x00015cfc\n' + ' 1006: b113\tcbz r3, 100929de <flash_command_write>\n' + ' 1008: 00015cfc\t.word 0x00015cfc\n' '00002000 <console_task>:\n' ' 2000: b508\t\tpush {r3, lr} ; malformed comments,; r0, r1 \n' ' 2002: f00e fcc5\tbl 1000 <hook_task>\n' @@ -324,7 +336,8 @@ class StackAnalyzerTest(unittest.TestCase): '00010000 <look_task>:' ) function_map = self.analyzer.AnalyzeDisassembly(disasm_text) - func_hook_task = sa.Function(0x1000, 'hook_task', 0, []) + func_hook_task = sa.Function(0x1000, 'hook_task', 0, [ + sa.Callsite(0x1006, 0x100929de, True, None)]) expect_funcmap = { 0x1000: func_hook_task, 0x2000: sa.Function(0x2000, 'console_task', 8, @@ -391,8 +404,13 @@ class StackAnalyzerTest(unittest.TestCase): @mock.patch('subprocess.check_output') def testAddressToLine(self, checkoutput_mock): - checkoutput_mock.return_value = 'test.c [1]' - self.assertEqual(self.analyzer.AddressToLine(0x1234), 'test.c [1]') + checkoutput_mock.return_value = 'test.c:1' + self.assertEqual(self.analyzer.AddressToLine(0x1234), 'test.c:1') + checkoutput_mock.assert_called_once_with( + ['addr2line', '-e', './ec.RW.elf', '1234']) + + checkoutput_mock.return_value = 'test.c:1 (discriminator 1289031)' + self.assertEqual(self.analyzer.AddressToLine(0x1234), 'test.c:1') checkoutput_mock.assert_called_once_with( ['addr2line', '-e', './ec.RW.elf', '1234']) |