def getRuntimeTraceOfLoadedFiles(logger, path): """ Returns the files loaded when executing a binary. """ # This will make a crazy amount of work, # pylint: disable=I0021,too-many-branches,too-many-locals,too-many-statements if not os.path.exists(path): # TODO: Have a logger package passed. logger.sysexit("Error, cannot find %r (%r)." % (path, os.path.abspath(path))) result = [] if os.name == "posix": if sys.platform == "darwin" or sys.platform.startswith("freebsd"): if not isExecutableCommand("dtruss"): sys.exit( """\ Error, needs 'dtruss' on your system to scan used libraries.""" ) if not isExecutableCommand("sudo"): sys.exit( """\ Error, needs 'sudo' on your system to scan used libraries.""" ) args = ("sudo", "dtruss", "-t", "open", path) else: if not isExecutableCommand("strace"): sys.exit( """\ Error, needs 'strace' on your system to scan used libraries.""" ) args = ( "strace", "-e", "file", "-s4096", # Some paths are truncated in output otherwise os.path.abspath(path), ) # Ensure executable is not polluted with third party stuff, # tests may fail otherwise due to unexpected libs being loaded with withEnvironmentVarOverriden("LD_PRELOAD", None): tracing_command = args[0] if args[0] != "sudo" else args[1] process = subprocess.Popen( args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) _stdout_strace, stderr_strace = process.communicate() exit_strace = process.returncode if exit_strace != 0: if str is not bytes: stderr_strace = stderr_strace.decode("utf8") logger.warning(stderr_strace) logger.sysexit("Failed to run %r." % tracing_command) with open(path + ".strace", "wb") as f: f.write(stderr_strace) for line in stderr_strace.split(b"\n"): if process.returncode != 0: logger.my_print(line) if not line: continue # Don't consider files not found. The "site" module checks lots # of things. if b"ENOENT" in line: continue if line.startswith(b"stat(") and b"S_IFDIR" in line: continue # Allow stats on the python binary, and stuff pointing to the # standard library, just not uses of it. It will search there # for stuff. if ( line.startswith(b"lstat(") or line.startswith(b"stat(") or line.startswith(b"readlink(") ): filename = line[line.find(b"(") + 2 : line.find(b", ") - 1] # At least Python3.7 considers the default Python3 path. if filename == b"/usr/bin/python3": continue if filename in ( b"/usr/bin/python3." + version for version in (b"5", b"6", b"7", b"8", b"9") ): continue binary_path = _python_executable if str is not bytes: binary_path = binary_path.encode("utf-8") found = False while binary_path: if filename == binary_path: found = True break if binary_path == os.path.dirname(binary_path): break binary_path = os.path.dirname(binary_path) if filename == os.path.join( binary_path, b"python" + ( "%d%d" % (_python_version[0], _python_version[1]) ).encode("utf8"), ): found = True continue if found: continue result.extend( os.path.abspath(match) for match in re.findall(b'"(.*?)(?:\\\\0)?"', line) ) if sys.version.startswith("3"): result = [s.decode("utf-8") for s in result] elif os.name == "nt": command = ( getDependsExePath(), "-c", # Console mode "-ot%s" % path + ".depends", "-f1", "-pb", "-pa1", # Turn on all profiling options. "-ps1", # Simulate ShellExecute with app dirs in PATH. "-pp1", # Do not long DllMain calls. "-po1", # Log DllMain call for all other messages. "-ph1", # Hook the process. "-pl1", # Log LoadLibrary calls. "-pt1", # Thread information. "-pe1", # First chance exceptions. "-pg1", # Log GetProcAddress calls. "-pf1", # Use full paths. "-pc1", # Profile child processes. path, ) subprocess.call(command) inside = False for line in getFileContentByLine(path + ".depends"): if "| Module Dependency Tree |" in line: inside = True continue if not inside: continue if "| Module List |" in line: break if "]" not in line: continue # Skip missing DLLs, apparently not needed anyway. if "?" in line[: line.find("]")]: continue dll_filename = line[line.find("]") + 2 :].rstrip() dll_filename = os.path.normcase(dll_filename) assert os.path.isfile(dll_filename), repr(dll_filename) # The executable itself is of course exempted. if dll_filename == os.path.normcase(os.path.abspath(path)): continue result.append(dll_filename) os.unlink(path + ".depends") result = list(sorted(set(result))) return result
def getRuntimeTraceOfLoadedFiles(path, trace_error=True): """ Returns the files loaded when executing a binary. """ # This will make a crazy amount of work, # pylint: disable=I0021,too-many-branches,too-many-locals,too-many-statements result = [] if os.name == "posix": if sys.platform == "darwin" or sys.platform.startswith("freebsd"): if not isExecutableCommand("dtruss"): sys.exit("""\ Error, needs 'dtruss' on your system to scan used libraries.""") if not isExecutableCommand("sudo"): sys.exit("""\ Error, needs 'sudo' on your system to scan used libraries.""") args = ("sudo", "dtruss", "-t", "open", path) else: if not isExecutableCommand("strace"): sys.exit("""\ Error, needs 'strace' on your system to scan used libraries.""") args = ( "strace", "-e", "file", "-s4096", # Some paths are truncated otherwise. path, ) # Ensure executable is not polluted with third party stuff, # tests may fail otherwise due to unexpected libs being loaded with withEnvironmentVarOverriden("LD_PRELOAD", None): process = subprocess.Popen(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _stdout_strace, stderr_strace = process.communicate() exit_strace = process.returncode if exit_strace != 0: if str is not bytes: stderr_strace = stderr_strace.decode("utf8") my_print(stderr_strace, file=sys.stderr) sys.exit("Failed to run strace.") with open(path + ".strace", "wb") as f: f.write(stderr_strace) for line in stderr_strace.split(b"\n"): if process.returncode != 0 and trace_error: my_print(line) if not line: continue # Don't consider files not found. The "site" module checks lots # of things. if b"ENOENT" in line: continue if line.startswith(b"stat(") and b"S_IFDIR" in line: continue # Allow stats on the python binary, and stuff pointing to the # standard library, just not uses of it. It will search there # for stuff. if (line.startswith(b"lstat(") or line.startswith(b"stat(") or line.startswith(b"readlink(")): filename = line[line.find(b"(") + 2:line.find(b", ") - 1] # At least Python3.7 considers the default Python3 path. if filename == b"/usr/bin/python3": continue if filename in (b"/usr/bin/python3." + version for version in (b"5", b"6", b"7")): continue binary_path = _python_executable if str is not bytes: binary_path = binary_path.encode("utf-8") found = False while binary_path: if filename == binary_path: found = True break if binary_path == os.path.dirname(binary_path): break binary_path = os.path.dirname(binary_path) if filename == os.path.join( binary_path, b"python" + _python_version[:3].encode("utf8")): found = True continue if found: continue result.extend( os.path.abspath(match) for match in re.findall(b'"(.*?)(?:\\\\0)?"', line)) if sys.version.startswith("3"): result = [s.decode("utf-8") for s in result] elif os.name == "nt": subprocess.call(( getDependsExePath(), "-c", "-ot%s" % path + ".depends", "-f1", "-pa1", "-ps1", "-pp0", "-pl1", path, )) inside = False for line in getFileContentByLine(path + ".depends"): if "| Module Dependency Tree |" in line: inside = True continue if not inside: continue if "| Module List |" in line: break if "]" not in line: continue # Skip missing DLLs, apparently not needed anyway. if "?" in line[:line.find("]")]: continue dll_filename = line[line.find("]") + 2:-1] assert os.path.isfile(dll_filename), dll_filename # The executable itself is of course exempted. if os.path.normcase(dll_filename) == os.path.normcase( os.path.abspath(path)): continue dll_filename = os.path.normcase(dll_filename) result.append(dll_filename) os.unlink(path + ".depends") result = list(sorted(set(result))) return result