find_runtime_symbols.py revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved. 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file. 5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)"""Find symbols in a binary corresponding to given runtime virtual addresses. 6c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 7c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)Note that source file names are treated as symbols in this script while they 8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)are actually not. 9c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)""" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from static_symbols import StaticSymbolsInFile 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from proc_maps import ProcMaps 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 19c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)try: 20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) from collections import OrderedDict # pylint: disable=E0611 21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)except ImportError: 22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) BASE_PATH = os.path.dirname(os.path.abspath(__file__)) 23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) SIMPLEJSON_PATH = os.path.join(BASE_PATH, os.pardir, os.pardir, 'third_party') 24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sys.path.insert(0, SIMPLEJSON_PATH) 25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) from simplejson import OrderedDict 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)FUNCTION_SYMBOLS = 0 29c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)SOURCEFILE_SYMBOLS = 1 30c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)TYPEINFO_SYMBOLS = 2 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 32c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)_MAPS_FILENAME = 'maps' 33c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)_FILES_FILENAME = 'files.json' 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class RuntimeSymbolsInProcess(object): 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self): 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._maps = None 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._static_symbols_in_filse = {} 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def find_procedure(self, runtime_address): 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for vma in self._maps.iter(ProcMaps.executable): 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if vma.begin <= runtime_address < vma.end: 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_symbols = self._static_symbols_in_filse.get(vma.name) 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if static_symbols: 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return static_symbols.find_procedure_by_runtime_address( 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) runtime_address, vma) 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) def find_sourcefile(self, runtime_address): 53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) for vma in self._maps.iter(ProcMaps.executable): 54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if vma.begin <= runtime_address < vma.end: 55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) static_symbols = self._static_symbols_in_filse.get(vma.name) 56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if static_symbols: 57c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return static_symbols.find_sourcefile_by_runtime_address( 58c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) runtime_address, vma) 59c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 60c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return None 61c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return None 62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def find_typeinfo(self, runtime_address): 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for vma in self._maps.iter(ProcMaps.constants): 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if vma.begin <= runtime_address < vma.end: 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_symbols = self._static_symbols_in_filse.get(vma.name) 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if static_symbols: 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return static_symbols.find_typeinfo_by_runtime_address( 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) runtime_address, vma) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @staticmethod 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def load(prepared_data_dir): 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) symbols_in_process = RuntimeSymbolsInProcess() 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) with open(os.path.join(prepared_data_dir, _MAPS_FILENAME), mode='r') as f: 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) symbols_in_process._maps = ProcMaps.load(f) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) with open(os.path.join(prepared_data_dir, _FILES_FILENAME), mode='r') as f: 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) files = json.load(f) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # pylint: disable=W0212 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for vma in symbols_in_process._maps.iter(ProcMaps.executable_and_constants): 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_entry = files.get(vma.name) 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not file_entry: 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_symbols = StaticSymbolsInFile(vma.name) 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) nm_entry = file_entry.get('nm') 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if nm_entry and nm_entry['format'] == 'bsd': 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) with open(os.path.join(prepared_data_dir, nm_entry['file']), 'r') as f: 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_symbols.load_nm_bsd(f, nm_entry['mangled']) 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) readelf_entry = file_entry.get('readelf-e') 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if readelf_entry: 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) with open(os.path.join(prepared_data_dir, readelf_entry['file']), 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'r') as f: 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) static_symbols.load_readelf_ew(f) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 102c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) decodedline_file_entry = file_entry.get('readelf-debug-decodedline-file') 103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if decodedline_file_entry: 104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) with open(os.path.join(prepared_data_dir, 105c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) decodedline_file_entry['file']), 'r') as f: 106c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) static_symbols.load_readelf_debug_decodedline_file(f) 107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) symbols_in_process._static_symbols_in_filse[vma.name] = static_symbols 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return symbols_in_process 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _find_runtime_function_symbols(symbols_in_process, addresses): 114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result = OrderedDict() 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for address in addresses: 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if isinstance(address, basestring): 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) address = int(address, 16) 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) found = symbols_in_process.find_procedure(address) 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if found: 120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = found.name 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = '0x%016x' % address 123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return result 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _find_runtime_sourcefile_symbols(symbols_in_process, addresses): 127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result = OrderedDict() 128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) for address in addresses: 129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if isinstance(address, basestring): 130c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) address = int(address, 16) 131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) found = symbols_in_process.find_sourcefile(address) 132c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if found: 133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = found 134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = '' 136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return result 137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 138c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 139c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def _find_runtime_typeinfo_symbols(symbols_in_process, addresses): 140c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result = OrderedDict() 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for address in addresses: 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if isinstance(address, basestring): 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) address = int(address, 16) 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if address == 0: 145c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = 'no typeinfo' 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) found = symbols_in_process.find_typeinfo(address) 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if found: 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if found.startswith('typeinfo for '): 150c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = found[13:] 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 152c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = found 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 154c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) result[address] = '0x%016x' % address 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return result 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 158c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)_INTERNAL_FINDERS = { 159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) FUNCTION_SYMBOLS: _find_runtime_function_symbols, 160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) SOURCEFILE_SYMBOLS: _find_runtime_sourcefile_symbols, 161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) TYPEINFO_SYMBOLS: _find_runtime_typeinfo_symbols, 162c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def find_runtime_symbols(symbol_type, symbols_in_process, addresses): 166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return _INTERNAL_FINDERS[symbol_type](symbols_in_process, addresses) 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main(): 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # FIX: Accept only .pre data 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if len(sys.argv) < 2: 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.stderr.write("""Usage: 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)%s /path/to/prepared_data_dir/ < addresses.txt 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)""" % sys.argv[0]) 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return 1 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) log = logging.getLogger('find_runtime_symbols') 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) log.setLevel(logging.WARN) 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) handler = logging.StreamHandler() 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) handler.setLevel(logging.WARN) 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) formatter = logging.Formatter('%(message)s') 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) handler.setFormatter(formatter) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) log.addHandler(handler) 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) prepared_data_dir = sys.argv[1] 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not os.path.exists(prepared_data_dir): 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) log.warn("Nothing found: %s" % prepared_data_dir) 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return 1 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not os.path.isdir(prepared_data_dir): 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) log.warn("Not a directory: %s" % prepared_data_dir) 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return 1 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) symbols_in_process = RuntimeSymbolsInProcess.load(prepared_data_dir) 194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) symbols_dict = find_runtime_symbols(FUNCTION_SYMBOLS, 195c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) symbols_in_process, 196c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sys.stdin) 197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) for address, symbol in symbols_dict: 198c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if symbol: 199c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) print '%016x %s' % (address, symbol) 200c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 201c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) print '%016x' % address 202c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 203c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return 0 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__': 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.exit(main()) 208