#!/usr/bin/python2.7 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import itertools import os import re import subprocess import sys class Error(Exception): """Module error class.""" class UnknownArchitectureError(Error): """Raised if an architecture can not be handled.""" def GetCrossTool(tool): chost = os.getenv('CHOST', '') if chost: return chost + '-' + tool return tool def SymbolMap(object_filename, sort_numerically=True): """Run nm tool to list symbols from object file.""" cmd = [GetCrossTool('nm')] if sort_numerically: cmd.append('-n') cmd.append(object_filename), out = subprocess.check_output(cmd) for line in out.splitlines(): cols = line.split() if len(cols) == 2: addr = None symbol_type, symbol_name = cols elif len(cols) == 3: addr, symbol_type, symbol_name = cols else: raise Error('Unexpected number of columns') yield addr, symbol_type, symbol_name def Disassemble(object_filename, start_address, stop_address): """Disassemble a portion of an object file using objdump.""" return subprocess.check_output(( GetCrossTool('objdump'), '-d', '--no-show-raw-insn', '--start-address', '0x'+start_address, '--stop-address', '0x'+stop_address, object_filename)) ASSEMBLY_RE = re.compile( r'^ +(?P
[0-9A-Fa-f]+):\t(?P\S+)\s+(?P.*)$') X86_CONDITIONAL_BRANCH_INSTRUCTIONS = set([ 'jo', # opcode: 0x70 'jno', # 0x71 'jb', 'jnae', 'jc', # 0x72 'jnb', 'jae', 'jnc', # 0x73 'jz', 'je' # 0x74 'jnz', 'jne', # 0x75 'jbe', 'jna', # 0x76 'jnbe', 'ja', # 0x77 'js', # 0x78 'jns', # 0x79 'jp', 'jpe', # 0x7a 'jnp', 'jpo', # 0x7b 'jl', 'jnge', # 0x7c 'jnl', 'jge', # 0x7d 'jle', 'jng', # 0x7e 'jnle', 'jg', # 0x7f 'loopnz', 'loopne', # 0xe0 'loopz', 'loope', # 0xe1 'loop', # 0xe2 'jcxz', 'jecxz', 'jrcxz',]) # 0xe3 def IsBranch_x86(mnemonic): return mnemonic in X86_CONDITIONAL_BRANCH_INSTRUCTIONS ARM_BRANCH_INSTRUCTIONS = [ 'b', 'bl', 'blx', 'bx', 'bxj', 'cbz', 'cbnz'] ARM_CONDITIONS = [ 'eq', 'ne', 'cs', 'hs', 'cc', 'lo', 'mi', 'pl', 'vs', 'vc', 'hi', 'ls', 'ge', 'lt', 'gt', 'le', 'al'] ARM_ALL_BRANCH_INSTRUCTIONS = set( instr + cond for instr, cond in itertools.product(ARM_BRANCH_INSTRUCTIONS, ARM_CONDITIONS)) def IsBranch_arm(mnemonic): if '.' in mnemonic: mnemonic, width = mnemonic.split('.', 1) return mnemonic in ARM_ALL_BRANCH_INSTRUCTIONS X86_ARCH_CHOST_RE = re.compile(r'^(x86|i[2346]86)') def ChooseIsBranchForChost(chost): if X86_ARCH_CHOST_RE.match(chost): return IsBranch_x86 if chost.startswith('arm'): return IsBranch_arm raise UnknownArchitectureError(chost) def _FindLoopBranches(disassembly, is_branch): for line in disassembly.splitlines(): m = ASSEMBLY_RE.match(line) if not m: continue address, mnemonic, operands = m.group('address', 'mnemonic', 'operands') if is_branch(mnemonic): target_address, target_label = operands.split() yield address, target_address def FindLoopBranches(disassembly): chost = os.getenv('CHOST', '') is_branch = ChooseIsBranchForChost(chost) return _FindLoopBranches(disassembly, is_branch) def main(): object_filename = sys.argv[1] for addr, symbol_type, symbol_name in SymbolMap(object_filename): if symbol_name == 'the_loop_start': loop_start = addr if symbol_name == 'the_loop_end': loop_end = addr disassembly = Disassemble(object_filename, loop_start, loop_end) try: for source, target in FindLoopBranches(disassembly): print source, target except UnknownArchitectureError as e: print >> sys.stderr, 'Unknown architecture for chost ' + e.args[0] if __name__ == '__main__': main()