lldb-disasm.py revision d7e532921a938378cceea96a60d4235abe719a1f
1afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen#!/usr/bin/env python 2afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 3afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen""" 4afd19042fa577feabe14c1b97dd5653e3af567d6Johnny ChenRun lldb to disassemble all the available functions for an executable image. 5afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 6afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen""" 7afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 8afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chenimport os 9afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chenimport sys 10afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chenfrom optparse import OptionParser 11afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 12afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chendef setupSysPath(): 13afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen """ 142efc7c6c4e791ed64adf037e4908b598d1f0ec8bJohnny Chen Add LLDB.framework/Resources/Python and the test dir to the sys.path. 15afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen """ 16afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # Get the directory containing the current script. 17afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen scriptPath = sys.path[0] 18afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen if not scriptPath.endswith(os.path.join('utils', 'test')): 19afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print "This script expects to reside in lldb's utils/test directory." 20afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.exit(-1) 21afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 22afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # This is our base name component. 23afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen base = os.path.abspath(os.path.join(scriptPath, os.pardir, os.pardir)) 24afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 25afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # This is for the goodies in the test directory under base. 26afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.path.append(os.path.join(base,'test')) 27afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 28afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # These are for xcode build directories. 29afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen xcode3_build_dir = ['build'] 30afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen xcode4_build_dir = ['build', 'lldb', 'Build', 'Products'] 31afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dbg = ['Debug'] 32afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen rel = ['Release'] 33afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen bai = ['BuildAndIntegration'] 34afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen python_resource_dir = ['LLDB.framework', 'Resources', 'Python'] 35afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 36afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dbgPath = os.path.join(base, *(xcode3_build_dir + dbg + python_resource_dir)) 37afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dbgPath2 = os.path.join(base, *(xcode4_build_dir + dbg + python_resource_dir)) 38afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen relPath = os.path.join(base, *(xcode3_build_dir + rel + python_resource_dir)) 39afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen relPath2 = os.path.join(base, *(xcode4_build_dir + rel + python_resource_dir)) 40afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen baiPath = os.path.join(base, *(xcode3_build_dir + bai + python_resource_dir)) 41afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen baiPath2 = os.path.join(base, *(xcode4_build_dir + bai + python_resource_dir)) 42afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 43afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = None 44afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen if os.path.isfile(os.path.join(dbgPath, 'lldb.py')): 45afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = dbgPath 46afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen elif os.path.isfile(os.path.join(dbgPath2, 'lldb.py')): 47afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = dbgPath2 48afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen elif os.path.isfile(os.path.join(relPath, 'lldb.py')): 49afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = relPath 50afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen elif os.path.isfile(os.path.join(relPath2, 'lldb.py')): 51afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = relPath2 52afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen elif os.path.isfile(os.path.join(baiPath, 'lldb.py')): 53afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = baiPath 54afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen elif os.path.isfile(os.path.join(baiPath2, 'lldb.py')): 55afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldbPath = baiPath2 56afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 57afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen if not lldbPath: 58afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print 'This script requires lldb.py to be in either ' + dbgPath + ',', 59afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print relPath + ', or ' + baiPath 60afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.exit(-1) 61afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 62afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # This is to locate the lldb.py module. Insert it right after sys.path[0]. 63afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.path[1:1] = [lldbPath] 64afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print "sys.path:", sys.path 65afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 66afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 679aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chendef run_command(ci, cmd, res, echoInput=True, echoOutput=True): 689aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen if echoInput: 699aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "run command:", cmd 70afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen ci.HandleCommand(cmd, res) 71afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen if res.Succeeded(): 729aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen if echoOutput: 739aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "run_command output:", res.GetOutput() 74afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen else: 759aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen if echoOutput: 769aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "run command failed!" 779aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "run_command error:", res.GetError() 78afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 79d7e532921a938378cceea96a60d4235abe719a1fJohnny Chendef do_lldb_disassembly(lldb_commands, exe, disassemble_options, num_symbols, symbols_to_disassemble, quiet_disassembly): 807df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen import lldb, atexit, re 81afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 82afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # Create the debugger instance now. 83afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dbg = lldb.SBDebugger.Create() 84234ebabe6218127a39cbe98dbad7b53f06907becJohnny Chen if not dbg: 85afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen raise Exception('Invalid debugger instance') 86afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 87afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # Register an exit callback. 88afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen atexit.register(lambda: lldb.SBDebugger.Terminate()) 89afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 90afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # We want our debugger to be synchronous. 91afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dbg.SetAsync(False) 92afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 93afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # Get the command interpreter from the debugger. 94afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen ci = dbg.GetCommandInterpreter() 95afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen if not ci: 96afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen raise Exception('Could not get the command interpreter') 97afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 98afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # And the associated result object. 99afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen res = lldb.SBCommandReturnObject() 100afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 101afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # See if there any extra command(s) to execute before we issue the file command. 102afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen for cmd in lldb_commands: 103afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen run_command(ci, cmd, res) 104afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 105563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen # Now issue the file command. 106563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen run_command(ci, 'file %s' % exe, res) 107563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen 1087df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen # Create a target. 109563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen #target = dbg.CreateTarget(exe) 110563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen target = dbg.GetSelectedTarget() 1117df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen stream = lldb.SBStream() 1127df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen 113563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen def IsCodeType(symbol): 114563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen """Check whether an SBSymbol represents code.""" 115563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen return symbol.GetType() == lldb.eSymbolTypeCode 116563ce6abb47005e3829e5d8fb33c4ed45bda6430Johnny Chen 1177df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen # Define a generator for the symbols to disassemble. 118d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen def symbol_iter(num, symbols, target, verbose): 1197df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen # If we specify the symbols to disassemble, ignore symbol table dump. 1207df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen if symbols: 1217df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen for i in range(len(symbols)): 1227df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen print "symbol:", symbols[i] 1237df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen yield symbols[i] 1247df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen else: 1257df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen limited = True if num != -1 else False 1267df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen if limited: 1277df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen count = 0 1287df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen stream = lldb.SBStream() 129a3cd9479fb6ffca90d335cb4ca802f4686dd0e10Johnny Chen for m in target.module_iter(): 1307df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen print "module:", m 131a3cd9479fb6ffca90d335cb4ca802f4686dd0e10Johnny Chen for s in m: 1327df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen if limited and count >= num: 1337df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen return 1347df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen print "symbol:", s.GetName() 1357df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen if IsCodeType(s): 1367df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen if limited: 1377df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen count = count + 1 138d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen if verbose: 139d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen print "returning symbol:", s.GetName() 1407df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen yield s.GetName() 141d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen if verbose: 142d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen print "start address:", s.GetStartAddress() 143d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen print "end address:", s.GetEndAddress() 144d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen s.GetDescription(stream) 145d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen print "symbol description:", stream.GetData() 146d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen stream.Clear() 1477df6fc423f1922ba0700d38e990d8422493184c2Johnny Chen 148337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen # Disassembly time. 149d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen for symbol in symbol_iter(num_symbols, symbols_to_disassemble, target, not quiet_disassembly): 150337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen cmd = "disassemble %s '%s'" % (disassemble_options, symbol) 151d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen run_command(ci, cmd, res, True, not quiet_disassembly) 1529aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen 153afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 154afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chendef main(): 155afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # This is to set up the Python path to include the pexpect-2.4 dir. 156afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # Remember to update this when/if things change. 157afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen scriptPath = sys.path[0] 158afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) 159afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 160afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen parser = OptionParser(usage="""\ 161afd19042fa577feabe14c1b97dd5653e3af567d6Johnny ChenRun lldb to disassemble all the available functions for an executable image. 162afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 163afd19042fa577feabe14c1b97dd5653e3af567d6Johnny ChenUsage: %prog [options] 164afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen""") 165afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen parser.add_option('-C', '--lldb-command', 166afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen type='string', action='append', metavar='COMMAND', 167afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen default=[], dest='lldb_commands', 168afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen help='Command(s) lldb executes after starting up (can be empty)') 169afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen parser.add_option('-e', '--executable', 170afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen type='string', action='store', 171afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen dest='executable', 1729aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen help="""Mandatory: the executable to do disassembly on.""") 1739aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen parser.add_option('-o', '--options', 1749aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen type='string', action='store', 1759aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen dest='disassemble_options', 1769aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen help="""Mandatory: the options passed to lldb's 'disassemble' command.""") 1779aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen parser.add_option('-n', '--num-symbols', 178337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen type='int', action='store', default=-1, 1799aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen dest='num_symbols', 1809aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen help="""The number of symbols to disassemble, if specified.""") 181d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen parser.add_option('-q', '--quiet-disassembly', 182d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen action='store_true', default=False, 183d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen dest='quiet_disassembly', 184d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""") 185337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen parser.add_option('-s', '--symbol', 186337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen type='string', action='append', metavar='SYMBOL', default=[], 187337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen dest='symbols_to_disassemble', 188337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen help="""The symbol(s) to invoke lldb's 'disassemble' command on, if specified.""") 189337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen 190afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen opts, args = parser.parse_args() 191afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 192afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen lldb_commands = opts.lldb_commands 193afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 1949aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen if not opts.executable or not opts.disassemble_options: 195afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen parser.print_help() 196afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen sys.exit(1) 1979aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen 198afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen executable = opts.executable 1999aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen disassemble_options = opts.disassemble_options 200337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen num_symbols = opts.num_symbols 201d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen quiet_disassembly = opts.quiet_disassembly 202337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen symbols_to_disassemble = opts.symbols_to_disassemble 203afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 204afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen # We have parsed the options. 205afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print "lldb commands:", lldb_commands 206afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen print "executable:", executable 2079aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "disassemble options:", disassemble_options 2089aaceb1bed6a4f83389c76b1cb7a71f8d22db547Johnny Chen print "num of symbols to disassemble:", num_symbols 209d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen print "quiet disassembly output:", quiet_disassembly 210337836bbe11dd3543b6c6e236fa161d072181328Johnny Chen print "symbols to disassemble:", symbols_to_disassemble 211afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 212afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen setupSysPath() 213d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen do_lldb_disassembly(lldb_commands, executable, disassemble_options, 214d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen num_symbols, 215d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen symbols_to_disassemble, 216d7e532921a938378cceea96a60d4235abe719a1fJohnny Chen quiet_disassembly) 217afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen 218afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chenif __name__ == '__main__': 219afd19042fa577feabe14c1b97dd5653e3af567d6Johnny Chen main() 220