From 4f21ee309c63db2084dd0c0122c5311636a3627e Mon Sep 17 00:00:00 2001 From: Che-yu Wu Date: Fri, 18 Aug 2017 09:01:24 +0800 Subject: extra/stack_analyzer: Show indirect calls. Show the indirect calls found in disassembly. 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: Ib82e68e0bc6c4be56ab679c297f256cbfe94bbb7 Signed-off-by: Che-yu Wu Reviewed-on: https://chromium-review.googlesource.com/628048 Reviewed-by: Nicolas Boichat --- extra/stack_analyzer/README.md | 2 + extra/stack_analyzer/stack_analyzer.py | 65 +++++++++++++++++++------ extra/stack_analyzer/stack_analyzer_unittest.py | 12 ++++- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/extra/stack_analyzer/README.md b/extra/stack_analyzer/README.md index 9bb37656f3..73e980e610 100644 --- a/extra/stack_analyzer/README.md +++ b/extra/stack_analyzer/README.md @@ -47,3 +47,5 @@ but it is inlined to the current function and you can follow the trace: `usb_pd_protocol.c:1808 -> usb_pd_protocol.c:1191 -> usb_pd_protocol.c:798` to 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. diff --git a/extra/stack_analyzer/stack_analyzer.py b/extra/stack_analyzer/stack_analyzer.py index 4b10bbfe2d..2d84ccec60 100755 --- a/extra/stack_analyzer/stack_analyzer.py +++ b/extra/stack_analyzer/stack_analyzer.py @@ -136,7 +136,7 @@ class Callsite(object): Attributes: address: Address of callsite location. None if it is unknown. - target: Callee address. + target: Callee address. None if it is unknown. is_tail: A bool indicates that it is a tailing call. callee: Resolved callee function. None if it hasn't been resolved. """ @@ -145,12 +145,14 @@ class Callsite(object): """Constructor. Args: - address: Address of callsite location. - target: Callee address. + address: Address of callsite location. None if it is unknown. + target: Callee address. None if it is unknown. is_tail: A bool indicates that it is a tailing call. (function jump to another function without restoring the stack frame) callee: Resolved callee function. """ + # It makes no sense that both address and target are unknown. + assert not (address is None and target is None) self.address = address self.target = target self.is_tail = is_tail @@ -281,8 +283,13 @@ class ArmAnalyzer(object): CBZ_CBNZ_OPCODE_RE = re.compile(r'^(cbz|cbnz)(\.\w)?$') # Example: "r0, 1009bcbe " CBZ_CBNZ_OPERAND_RE = re.compile(r'^[^,]+,\s+{}$'.format(IMM_ADDRESS_RE)) + # Ignore lr register because it's for return. + INDIRECT_CALL_OPERAND_RE = re.compile(r'^r\d+|sb|sl|fp|ip|sp|pc$') # TODO(cheyuw): Handle conditional versions of following # instructions. + LDR_OPCODE_RE = re.compile(r'^ldr(\.\w)?$') + # Example: "pc, [sp], #4" + LDR_PC_OPERAND_RE = re.compile(r'^pc, \[([^\]]+)\]') # TODO(cheyuw): Handle other kinds of stm instructions. PUSH_OPCODE_RE = re.compile(r'^push$') STM_OPCODE_RE = re.compile(r'^stmdb$') @@ -298,7 +305,7 @@ class ArmAnalyzer(object): instructions: Instruction list. Returns: - (stack_frame, callsites): Size of stack frame and callsite list. + (stack_frame, callsites): Size of stack frame, callsite list. """ stack_frame = 0 callsites = [] @@ -307,22 +314,38 @@ class ArmAnalyzer(object): is_call_opcode = self.CALL_OPCODE_RE.match(opcode) is not None 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: + is_tail = is_jump_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: + if result is None: + # Failed to match immediate address, maybe it is an indirect call. + # CBZ and CBNZ can't be indirect calls. + if (not is_cbz_cbnz_opcode and + self.INDIRECT_CALL_OPERAND_RE.match(operand_text) is not None): + # Found an indirect call. + callsites.append(Callsite(address, None, is_tail)) + + else: 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)) + # Maybe it is a callsite. + callsites.append(Callsite(address, target_address, is_tail)) + + elif self.LDR_OPCODE_RE.match(opcode) is not None: + result = self.LDR_PC_OPERAND_RE.match(operand_text) + if result is not None: + # Ignore "ldr pc, [sp], xx" because it's usually a return. + if result.group(1) != 'sp': + # Found an indirect call. + callsites.append(Callsite(address, None, True)) elif self.PUSH_OPCODE_RE.match(opcode) is not None: # Example: "{r4, r5, r6, r7, lr}" @@ -593,8 +616,9 @@ class StackAnalyzer(object): # Resolve callees of functions. for function in function_map.values(): for callsite in function.callsites: - # Remain the callee as None if we can't resolve it. - callsite.callee = function_map.get(callsite.target) + if callsite.target is not None: + # Remain the callee as None if we can't resolve it. + callsite.callee = function_map.get(callsite.target) return function_map @@ -951,10 +975,21 @@ class StackAnalyzer(object): curr_func = succ_func - if len(failed_sigs) > 0: - print('Failed to resolve some annotation signatures:') - for sig, error in failed_sigs: - print(' {}: {}'.format(sig, error)) + print('Unresolved indirect callsites:') + for function in function_map.values(): + indirect_callsites = [] + for callsite in function.callsites: + if callsite.target is None: + indirect_callsites.append(callsite.address) + + if len(indirect_callsites) > 0: + print(' {}'.format(function.name)) + for address in indirect_callsites: + PrintInlineStack(address, ' ') + + print('Unresolved annotation signatures:') + for sig, error in failed_sigs: + print(' {}: {}'.format(sig, error)) def ParseArgs(): diff --git a/extra/stack_analyzer/stack_analyzer_unittest.py b/extra/stack_analyzer/stack_analyzer_unittest.py index 03144fda32..0f62e1a3be 100755 --- a/extra/stack_analyzer/stack_analyzer_unittest.py +++ b/extra/stack_analyzer/stack_analyzer_unittest.py @@ -123,12 +123,16 @@ class ArmAnalyzerTest(unittest.TestCase): (0x2c, 'stmdb', 'sp!, {r4}'), (0x30, 'stmdb', 'sp, {r4}'), (0x34, 'bx.n', '10 '), + (0x36, 'bx.n', 'r3'), + (0x38, 'ldr', 'pc, [r10]'), ] (size, callsites) = analyzer.AnalyzeFunction(symbol, instructions) self.assertEqual(size, 72) expect_callsites = [sa.Callsite(0x1e, 0xdeadbeef, False), sa.Callsite(0x22, 0x0, False), - sa.Callsite(0x34, 0x10, True)] + sa.Callsite(0x34, 0x10, True), + sa.Callsite(0x36, None, True), + sa.Callsite(0x38, None, True)] self.assertEqual(callsites, expect_callsites) @@ -459,6 +463,7 @@ class StackAnalyzerTest(unittest.TestCase): ' 2000: b508\t\tpush {r3, lr}\n' ' 2002: f00e fcc5\tbl 1000 \n' ' 2006: f00e bd3b\tb.w 53968 \n' + ' 200a: 1234 5678\tb.w sl\n' ) addrtoline_mock.return_value = [('??', '??', 0)] @@ -478,7 +483,10 @@ class StackAnalyzerTest(unittest.TestCase): mock.call(' console_task (8) [??:0] 2000'), mock.call(' -> ?? [??:0] 2002'), mock.call(' hook_task (8) [??:0] 1000'), - mock.call('Failed to resolve some annotation signatures:'), + mock.call('Unresolved indirect callsites:'), + mock.call(' console_task'), + mock.call(' -> ?? [??:0] 200a'), + mock.call('Unresolved annotation signatures:'), mock.call(' fake_func: function is not found'), ]) -- cgit v1.2.1