def main(asm, show_bytes, header, files): """Disassembles a Python bytecode file. We handle bytecode for virtually every release of Python and some releases of PyPy. The version of Python in the bytecode doesn't have to be the same version as the Python interpreter used to run this program. For example, you can disassemble Python 3.6.9 bytecode from Python 2.7.15 and vice versa. """ if not (2.7 <= PYTHON_VERSION <= 3.9): if 2.4 <= PYTHON_VERSION <= 2.6: sys.stderr.write( "This code works on 2.7..3.8. code that works for %s can be found in the python-2.4 branch\n" % PYTHON_VERSION) sys.exit(1) sys.stderr.write("This works on Python version 2.7..3.8; have %s.\n" % PYTHON_VERSION) sys.exit(2) for path in files: # Some sanity checks if not osp.exists(path): sys.stderr.write("File name: '%s' doesn't exist\n" % path) continue elif not osp.isfile(path): sys.stderr.write("File name: '%s' isn't a file\n" % path) continue elif osp.getsize(path) < 50 and not path.endswith(".py"): sys.stderr.write( "File name: '%s (%d bytes)' is too short to be a valid pyc file\n" % (path, osp.getsize(path))) continue disassemble_file(path, sys.stdout, asm, header, show_bytes) return
def main(format, files): """Disassembles a Python bytecode file. We handle bytecode for virtually every release of Python and some releases of PyPy. The version of Python in the bytecode doesn't have to be the same version as the Python interpreter used to run this program. For example, you can disassemble Python 3.6.9 bytecode from Python 2.7.15 and vice versa. """ if not ((2, 7) <= PYTHON_VERSION_TRIPLE < (3, 12)): mess = "This code works on 3.6 to 3.12." if (2, 4) <= PYTHON_VERSION_TRIPLE <= (2, 7): mess += " Code that works for %s can be found in the python-2.4 branch\n" elif (3, 1) <= PYTHON_VERSION_TRIPLE <= (3, 2): mess += " Code that works for %s can be found in the python-3.1 branch\n" elif (3, 3) <= PYTHON_VERSION_TRIPLE <= (3, 5): mess += " Code that works for %s can be found in the python-3.3 branch\n" sys.stderr.write(mess % PYTHON_VERSION_STR) sys.exit(2) for path in files: # Some sanity checks if not osp.exists(path): sys.stderr.write("File name: '%s' doesn't exist\n" % path) continue elif not osp.isfile(path): sys.stderr.write("File name: '%s' isn't a file\n" % path) continue elif osp.getsize(path) < 50 and not path.endswith(".py"): sys.stderr.write( "File name: '%s (%d bytes)' is too short to be a valid pyc file\n" % (path, osp.getsize(path))) continue disassemble_file(path, sys.stdout, format) return
def main(conversion_type, input_pyc, output_pyc): """Convert Python bytecode from one version to another. INPUT_PYC contains the input bytecode path name OUTPUT_PYC contians the output bytecode path name if supplied The --conversion type option specifies what conversion to do. Note: there are a very limited set of conversions currently supported. Help out and write more!""" shortname = osp.basename(input_pyc) if shortname.endswith(".pyc"): shortname = shortname[:-4] src_version = conversion_to_version(conversion_type, is_dest=False) dest_version = conversion_to_version(conversion_type, is_dest=True) if output_pyc is None: output_pyc = "%s-%s.pyc" % (shortname, dest_version) if conversion_type in UPWARD_COMPATABLE: copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version) return temp_asm = NamedTemporaryFile("w", suffix=".pyasm", prefix=shortname, delete=False) (filename, co, version, timestamp, magic_int) = disassemble_file(input_pyc, temp_asm, asm_format=True) temp_asm.close() assert version == float( src_version ), "Need Python %s bytecode; got bytecode for version %s" % (src_version, version) asm = asm_file(temp_asm.name) new_asm = transform_asm(asm, conversion_type, src_version, dest_version) os.unlink(temp_asm.name) write_pycfile(output_pyc, new_asm)
def findlabels(): my_dir = osp.dirname(osp.abspath(__file__)) # Python 2.7 code code = bug708901.__code__ # FIXME: Consider loading from a file like below to remove # dependence on running interpreter if PYTHON_VERSION_TRIPLE[:2] == (2, 7): # 19: 0 SETUP_LOOP 23 (to 26) # 3 LOAD_GLOBAL 0 (range) # 6 LOAD_CONST 1 (1) # # 20: 9 LOAD_CONST 2 (10) # 12 CALL_FUNCTION 2 (2 positional, 0 keyword pair) # 15 GET_ITER # >> 16 FOR_ITER 6 (to 25) # 19 STORE_FAST 0 (res) # # 21: 22 JUMP_ABSOLUTE 16 (to 16) # >> 25 POP_BLOCK # >> 26 LOAD_CONST 0 (None) # 29 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) expected = { 3: [0], 6: [3], 9: [6], 12: [9], 15: [12], 16: [15, 22], 19: [16], 22: [19], 25: [16], 26: [0, 25], 29: [26] } assert expected == offset_map offsets = opcode_27.get_jump_targets(code, opcode_27) assert offsets == [26, 25, 16] test_pyc = my_dir + '/../test/bytecode_2.7/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size, _) = load_module(test_pyc) code = co.co_consts[0] offsets = opcode_27.get_jump_targets(code, opcode_27) assert [10] == offsets # from xdis import disassemble_file # print('\n') # disassemble_file(test_pyc) # 2: 0 LOAD_FAST 0 (a) # 3 POP_JUMP_IF_FALSE 10 (to 10) # # 3: 6 LOAD_CONST 1 (5) # 9 RETURN_VALUE # # 5: >> 10 LOAD_CONST 2 (6) # 13 RETURN_VALUE # 14 LOAD_CONST 0 (None) # 17 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) # print(offset_map) expect = {3: [0], 6: [3], 9: [6], 10: [3], 13: [10], 17: [14]} assert expect == offset_map # Python 3.6 code wordcode # ------------------------ test_pyc = my_dir + '/../test/bytecode_3.6/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size, _) = load_module(test_pyc) code = co.co_consts[0] # 2: 0 LOAD_FAST 0 (a) # 2 POP_JUMP_IF_FALSE 8 (to 8) # # 3: 4 LOAD_CONST 1 (5) # 6 RETURN_VALUE # # 5: 8 LOAD_CONST 2 (6) # 10 RETURN_VALUE # 12 LOAD_CONST 0 (None) # 14 RETURN_VALUE offsets = opcode_36.get_jump_targets(code, opcode_36) assert offsets == [8] from xdis import disassemble_file print('\n') disassemble_file(test_pyc) offset_map = opcode_36.get_jump_target_maps(code, opcode_36) expect = {2: [0], 4: [2], 6: [4], 8: [2], 10: [8], 14: [12]} assert expect == offset_map
def do_tests(src_dir, obj_patterns, target_dir, opts): def file_matches(files, root, basenames, patterns): files.extend([ os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat) ]) files = [] # Change directories so use relative rather than # absolute paths. This speeds up things, and allows # main() to write to a relative-path destination. cwd = os.getcwd() os.chdir(src_dir) if opts['do_compile']: compiled_version = opts['compiled_version'] if compiled_version and PYTHON_VERSION != compiled_version: sys.stderr.write("Not compiling: desired Python version is %s " "but we are running %s\n" % (compiled_version, PYTHON_VERSION)) else: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass pass for root, dirs, basenames in os.walk('.'): # Turn root into a relative path dirname = root[2:] # 2 = len('.') + 1 file_matches(files, dirname, basenames, obj_patterns) if not files: sys.stderr.write( "Didn't come up with any files to test! Try with --compile?\n") exit(1) os.chdir(cwd) files.sort() if opts['start_with']: try: start_with = files.index(opts['start_with']) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: pass output = open(os.devnull, "w") # output = sys.stdout print(time.ctime()) print('Source directory: ', src_dir) cwd = os.getcwd() os.chdir(src_dir) try: for infile in files: disassemble_file(infile, output) if opts['do_verify']: pass # print("Need to do something here to verify %s" % infile) # msg = verify.verify_file(infile, outfile) # if failed_files != 0: # exit(2) # elif failed_verify != 0: # exit(3) except (KeyboardInterrupt, OSError): print() exit(1) os.chdir(cwd)
def do_tests( src_dir, patterns, target_dir, start_with=None, do_verify=False, max_files=800, do_compile=False, verbose=False, ): def visitor(files, dirname, names): files.extend([ os.path.normpath(os.path.join(dirname, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) def file_matches(files, root, basenames, patterns): files.extend([ os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat) ]) files = [] if do_compile: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass cwd = os.getcwd() os.chdir(src_dir) if PYTHON3: for root, dirname, names in os.walk(os.curdir): files.extend([ os.path.normpath(os.path.join(root, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) pass pass else: os.path.walk(os.curdir, visitor, files) files.sort() if start_with: try: start_with = files.index(start_with) files = files[start_with:] print(">>> starting with file", files[0]) except ValueError: pass if len(files) > max_files: files = [file for file in files if not "site-packages" in file] files = [file for file in files if not "test" in file] if len(files) > max_files: files = files[:max_files] pass elif len(files) == 0: print("No files found\n") os.chdir(cwd) return output = open(os.devnull, "w") # output = sys.stdout start_time = time.time() print(time.ctime()) for i, bc_file in enumerate(files): if verbose: print(os.path.join(src_dir, bc_file)) if src_dir.find("simple_source") >= 0: continue if sys.version_info >= (3, 4, 0) and bc_file.endswith(".py"): check_bc_file = check_object_path(bc_file) if not osp.exists(check_bc_file): basename = osp.basename(bc_file)[0:-3] new_bc_file = tempfile.mkstemp(prefix=basename + "-", suffix=".pyc", text=False)[1] py_compile.compile(bc_file, cfile=new_bc_file, doraise=True) bc_file = new_bc_file bc_filename, co, version, ts, magic, _, _, _ = disassemble_file( bc_file, output) if do_verify: file = co.co_filename verify_file(file, bc_filename) if i % 100 == 0 and i > 0: print("Processed %d files" % (i)) print("Processed %d files, total" % (i + 1)) print(time.ctime()) elapsed_time = time.time() - start_time print("%g seconds" % elapsed_time) os.chdir(cwd)