diff options
Diffstat (limited to 'extra/stack_analyzer/stack_analyzer.py')
-rwxr-xr-x | extra/stack_analyzer/stack_analyzer.py | 65 |
1 files changed, 50 insertions, 15 deletions
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 <host_cmd_motion_sense+0x1d2>" 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(): |