""" Android dalvik.system APIs implemented in Python. dalvik.system package - DexClassLoader, PathClassLoader, DexFile, VMRuntime, VMDebug, etc. Desktop stub implementation. """ from __future__ import annotations from .._core import Stub, AndroidOnlyError import sys as _sys import os as _os from typing import List, Optional __all__ = [ "DexClassLoader", "PathClassLoader", "BaseDexClassLoader", "DexFile", "InMemoryDexClassLoader", "VMRuntime", "VMDebug", "CloseGuard", "PotentialDeadlockError", "BlockGuard", ] # ============================================================ # BaseDexClassLoader # ============================================================ class BaseDexClassLoader(Stub): """ dalvik.system.BaseDexClassLoader A class loader that loads classes from DEX files. """ def __init__(self, dex_path: str = "", optimized_dir: str = None, library_search_path: str = None, parent: Any = None): super().__init__() self._dex_path = dex_path self._optimized_dir = optimized_dir self._library_search_path = library_search_path self._parent = parent or type.__subclasses__ self._loaded_classes = set() self._assertion_status = {} self._default_assertion_status = False def loadClass(self, name: str, resolve: bool = True) -> Optional[type]: """Loads a class by name.""" # Try to find the class in the Python path parts = name.split(".") module_name = ".".join(parts[:-1]) class_name = parts[-1] try: if module_name: module = __import__(module_name, fromlist=[class_name]) cls = getattr(module, class_name, None) else: cls = globals().get(class_name) or __builtins__.get(class_name) if cls: self._loaded_classes.add(name) return cls return None except (ImportError, AttributeError): return None def findClass(self, name: str) -> Optional[type]: """Finds a class by name.""" return self.loadClass(name) def findResource(self, name: str) -> Optional[str]: """Finds a resource by name.""" path = _os.path.join(self._dex_path or "", name) if _os.path.exists(path): return path return None def findResources(self, name: str) -> List[str]: """Finds all resources by name.""" result = [] r = self.findResource(name) if r: result.append(r) return result def getParent(self): return self._parent def getPackage(self, name: str): """Gets a package by name.""" return None def setClassAssertionStatus(self, class_name: str, enabled: bool): self._assertion_status[class_name] = enabled def setDefaultAssertionStatus(self, enabled: bool): self._default_assertion_status = enabled def setPackageAssertionStatus(self, package_name: str, enabled: bool): pass def clearAssertionStatus(self): self._assertion_status = {} self._default_assertion_status = False def toString(self) -> str: return f"BaseDexClassLoader@{hex(id(self))}[dex={self._dex_path}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # DexClassLoader # ============================================================ class DexClassLoader(BaseDexClassLoader): """ dalvik.system.DexClassLoader A class loader that loads classes from .jar and .apk files. """ def __init__(self, dex_path: str = "", optimized_dir: str = None, library_search_path: str = None, parent: Any = None): super().__init__(dex_path, optimized_dir, library_search_path, parent) def loadClass(self, name: str, resolve: bool = True) -> Optional[type]: return super().loadClass(name, resolve) def findClass(self, name: str) -> Optional[type]: return super().findClass(name) def getParent(self): return super().getParent() def getResourceAsStream(self, name: str): """Returns an input stream for reading a resource.""" path = self.findResource(name) if path: return open(path, 'rb') return None def getResources(self, name: str): """Returns an enumeration of URLs for a resource.""" return iter(self.findResources(name)) def toString(self) -> str: return f"DexClassLoader@{hex(id(self))}[dex={self._dex_path}]" # ============================================================ # PathClassLoader # ============================================================ class PathClassLoader(BaseDexClassLoader): """ dalvik.system.PathClassLoader A class loader for loading classes from the file system. """ def __init__(self, dex_path: str = "", parent: Any = None, library_search_path: str = None): super().__init__(dex_path, None, library_search_path, parent) def loadClass(self, name: str, resolve: bool = True) -> Optional[type]: return super().loadClass(name, resolve) def findClass(self, name: str) -> Optional[type]: return super().findClass(name) def getParent(self): return super().getParent() def toString(self) -> str: return f"PathClassLoader@{hex(id(self))}[dex={self._dex_path}]" # ============================================================ # InMemoryDexClassLoader # ============================================================ class InMemoryDexClassLoader(BaseDexClassLoader): """ dalvik.system.InMemoryDexClassLoader A class loader that loads classes from in-memory DEX buffers. """ def __init__(self, dex_buffers: List, parent: Any = None): super().__init__("", None, None, parent) self._dex_buffers = dex_buffers def loadClass(self, name: str, resolve: bool = True) -> Optional[type]: return super().loadClass(name, resolve) def findClass(self, name: str) -> Optional[type]: return super().findClass(name) def toString(self) -> str: return f"InMemoryDexClassLoader@{hex(id(self))}[buffers={len(self._dex_buffers)}]" # ============================================================ # DexFile # ============================================================ class DexFile(Stub): """ dalvik.system.DexFile Represents a single DEX file. """ def __init__(self, file_name: str = None): super().__init__() self._file_name = file_name self._closed = False @staticmethod def loadDex(source_path: str, output_path: str, flags: int = 0) -> "DexFile": return DexFile(source_path) def loadClass(self, name: str, loader: Any = None) -> Optional[type]: """Loads a class from this DEX file.""" parts = name.split(".") try: module = __import__(".".join(parts[:-1]), fromlist=[parts[-1]]) return getattr(module, parts[-1], None) except (ImportError, AttributeError): return None def loadClassBinaryName(self, name: str, loader: Any = None) -> Optional[type]: """Loads a class using binary (internal) name format.""" java_name = name.replace("/", ".") return self.loadClass(java_name, loader) def getName(self) -> str: return self._file_name or "" def close(self): self._closed = True def entries(self): """Returns an enumeration of class names.""" return iter([]) def getEntryCount(self) -> int: return 0 @staticmethod def isDexOptNeeded(source_path: str) -> bool: return False def toString(self) -> str: return f"DexFile@{hex(id(self))}[file={self._file_name}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # VMRuntime # ============================================================ class VMRuntime(Stub): """ dalvik.system.VMRuntime Provides access to Dalvik/ART VM runtime features. """ _runtime = None def __init__(self): super().__init__() self._target_sdk_version = 0 self._jit_compiler_filter = "speed" self._large_heap_class = None self._large_heap_size = 0 self._native_allocation_count = 0 self._disabled_compat_changes = [] @staticmethod def getRuntime() -> "VMRuntime": if VMRuntime._runtime is None: VMRuntime._runtime = VMRuntime() return VMRuntime._runtime def is64Bit(self) -> bool: import struct return struct.calcsize("P") == 8 def setTargetSdkVersion(self, version: int): self._target_sdk_version = version def getTargetSdkVersion(self) -> int: return self._target_sdk_version def startJitCompilation(self): pass def endJitCompilation(self): pass def concurrentGC(self, enable: bool): pass def enableGcLogging(self, enable: bool): pass def vmInstructionSet(self) -> str: import platform machine = platform.machine() if machine in ("x86_64", "amd64"): return "x86_64" elif machine in ("i386", "i686"): return "x86" elif machine in ("aarch64", "arm64"): return "arm64" elif machine.startswith("arm"): return "arm" return machine def isVm64(self) -> bool: return self.is64Bit() def getExprOpts(self) -> str: return "" def setExprOpts(self, opts: str): pass def registerAppInfo(self, app_name: str, app_data_dir: str): pass def getDetachThreadTimeout(self) -> int: return 0 def setDetachThreadTimeout(self, timeout_ms: int): pass def setDisabledCompatChanges(self, changes: List[int]): self._disabled_compat_changes = list(changes) def isBootClassPathOnDisk(self) -> bool: return True def isJavaDebuggable(self) -> bool: return False def getJitCompilerFilter(self) -> str: return self._jit_compiler_filter def setJitCompilerFilter(self, filter_: str): self._jit_compiler_filter = filter_ def getLargeHeapClass(self) -> str: return self._large_heap_class def setLargeHeapClass(self, class_name: str): self._large_heap_class = class_name def getLargeHeapSize(self) -> int: return self._large_heap_size def setLargeHeapSize(self, size: int): self._large_heap_size = size def registerNativeAllocation(self, bytes_: int): self._native_allocation_count += bytes_ def registerNativeFree(self, bytes_: int): self._native_allocation_count -= bytes_ def toString(self) -> str: return f"VMRuntime@{hex(id(self))}[64bit={self.is64Bit()}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # VMDebug # ============================================================ class VMDebug(Stub): """ dalvik.system.VMDebug Provides debug features for the VM. """ TRACE_COUNT_ALLOCS: int = 1 TRACE_COUNT_ALLOCS_SYSTEM_ONLY: int = 2 TRACE_COUNT_ALLOCS_ALL: int = 3 def __init__(self): super().__init__() self._alloc_count = 0 self._alloc_size = 0 self._freed_count = 0 self._freed_size = 0 @staticmethod def startCountingAllocations(): pass @staticmethod def stopCountingAllocations(): pass @staticmethod def getGlobalAllocCount() -> int: return 0 @staticmethod def getGlobalAllocSize() -> int: return 0 @staticmethod def getGlobalFreedCount() -> int: return 0 @staticmethod def getGlobalFreedSize() -> int: return 0 @staticmethod def getLoadedClasses() -> int: return 0 @staticmethod def printLoadedClasses(count: int): pass @staticmethod def isDebuggerConnected() -> bool: return False @staticmethod def isDebuggingEnabled() -> bool: return False @staticmethod def resetAllocCount(): pass @staticmethod def getThreadAllocCount() -> int: return 0 @staticmethod def getThreadAllocSize() -> int: return 0 @staticmethod def threadCpuTimeNanos() -> int: import time return int(time.time() * 1e9) @staticmethod def startEmulatorTracing(): pass @staticmethod def stopEmulatorTracing(): pass @staticmethod def startMethodTracing(filename: str = None, buffer_size: int = 0, flags: int = 0, sampling_interval: int = 0): pass @staticmethod def stopMethodTracing(): pass @staticmethod def getMethodTracingMode() -> int: return 0 @staticmethod def getRuntimeStat(stat_name: str) -> str: return "" @staticmethod def getRuntimeStatValue(stat_name: str, value: int) -> int: return 0 @staticmethod def resolveClassLinkageError(cls: type): pass @staticmethod def crash(): raise RuntimeError("VMDebug.crash() called") @staticmethod def dumpReferenceTables(): pass @staticmethod def setAllocTrackerStackDepth(depth: int): pass @staticmethod def attachAgent(agent: str, options: str = None, class_loader: Any = None): pass @staticmethod def startAllocCounting(): pass @staticmethod def stopAllocCounting(): pass @staticmethod def getGlobalExternalAllocCount() -> int: return 0 @staticmethod def getGlobalExternalAllocSize() -> int: return 0 @staticmethod def getGlobalExternalFreedCount() -> int: return 0 @staticmethod def getGlobalExternalFreedSize() -> int: return 0 @staticmethod def setAllocationLimit(limit: int): pass @staticmethod def setGlobalCpuLimit(limit: int): pass @staticmethod def setThreadCpuLimit(limit: int): pass @staticmethod def setMethodTracingPriority(priority: int): pass def toString(self) -> str: return f"VMDebug@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # CloseGuard # ============================================================ class CloseGuard(Stub): """ dalvik.system.CloseGuard Reports when resources are not closed properly. """ REPORTER = None def __init__(self): super().__init__() self._open = False self._reported = False @staticmethod def get() -> "CloseGuard": return CloseGuard() def open(self, closer: str): """Marks this guard as open.""" self._open = True def close(self): """Marks this guard as closed.""" self._open = False def warnIfOpen(self): """Warns if the guard is still open.""" if self._open and not self._reported: self._reported = True @staticmethod def setEnabled(enabled: bool): pass @staticmethod def getReporter(): return CloseGuard.REPORTER @staticmethod def setReporter(reporter): CloseGuard.REPORTER = reporter def toString(self) -> str: return f"CloseGuard@{hex(id(self))}[open={self._open}]" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF # ============================================================ # PotentialDeadlockError # ============================================================ class PotentialDeadlockError(Exception, Stub): """ dalvik.system.PotentialDeadlockError Thrown when a potential deadlock is detected. """ def __init__(self, message: str = None): super().__init__(message or "Potential deadlock detected") self._message = message def toString(self) -> str: return f"PotentialDeadlockError: {self._message}" def equals(self, other) -> bool: return isinstance(other, PotentialDeadlockError) and self._message == other._message def hashCode(self) -> int: return hash(self._message) & 0x7FFFFFFF # ============================================================ # BlockGuard # ============================================================ class BlockGuard(Stub): """ dalvik.system.BlockGuard Mechanism to detect and prevent blocking operations on the main thread. """ LAX_POLICY = None class Policy(Stub): """ dalvik.system.BlockGuard.Policy Interface for block guard policies. """ def onReadFromDisk(self): pass def onWriteToDisk(self): pass def onNetwork(self): pass def onExplicitSuspend(self): pass def getPolicyMask(self) -> int: return 0 def onFileSystemAccess(self): pass def onUnbufferedIO(self): pass def toString(self) -> str: return "BlockGuard.Policy" def equals(self, other) -> bool: return isinstance(other, BlockGuard.Policy) def hashCode(self) -> int: return hash("BlockGuard.Policy") & 0x7FFFFFFF class Operational(Policy): """ dalvik.system.BlockGuard.Operational Operational policy for block guard. """ def toString(self) -> str: return "BlockGuard.Operational" def equals(self, other) -> bool: return isinstance(other, BlockGuard.Operational) def hashCode(self) -> int: return hash("BlockGuard.Operational") & 0x7FFFFFFF _delegate = None def __init__(self): super().__init__() @staticmethod def setDelegate(delegate): BlockGuard._delegate = delegate @staticmethod def isOperational() -> bool: return BlockGuard._delegate is not None @staticmethod def initDelegate(): """Initializes the delegate as a BlockGuard.Operational.""" BlockGuard._delegate = BlockGuard.Operational() @staticmethod def getThreadPolicy(): """Returns the policy for the current thread.""" return BlockGuard.LAX_POLICY or BlockGuard.Policy() @staticmethod def setThreadPolicy(policy: Policy): pass def toString(self) -> str: return f"BlockGuard@{hex(id(self))}" def equals(self, other) -> bool: return self is other def hashCode(self) -> int: return hash(self) & 0x7FFFFFFF BlockGuard.LAX_POLICY = BlockGuard.Policy()