From 8527e5f830ecafd687e0fdb071c59e9d9e152f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Tue, 13 May 2025 09:41:20 +0200 Subject: [PATCH] [WIP] Auto-generate from .gir --- frida/__init__.py | 173 - frida/_frida/__init__.pyi | 877 --- frida/_frida/extension.c | 6124 ----------------- frida/_frida/extension.version | 7 - frida/_frida/meson.build | 21 - frida/core.py | 1822 ----- .../py.typed => frida_bindgen/__init__.py} | 0 frida/frida_bindgen/__main__.py | 4 + frida/frida_bindgen/assets/codegen_helpers.c | 1970 ++++++ frida/frida_bindgen/assets/codegen_helpers.ts | 100 + .../frida_bindgen/assets/codegen_prototypes.h | 78 + frida/frida_bindgen/assets/codegen_types.h | 57 + .../assets/customization_facade.exports | 13 + .../assets/customization_facade.ts | 157 + .../assets/customization_helpers.imports | 2 + .../assets/customization_helpers.ts | 396 ++ frida/frida_bindgen/cli.py | 96 + frida/frida_bindgen/codegen.py | 2233 ++++++ frida/frida_bindgen/customization.py | 924 +++ frida/frida_bindgen/loader.py | 60 + frida/frida_bindgen/model.py | 1357 ++++ frida/meson.build | 88 +- meson.build | 12 +- 23 files changed, 7538 insertions(+), 9033 deletions(-) delete mode 100644 frida/__init__.py delete mode 100644 frida/_frida/__init__.pyi delete mode 100644 frida/_frida/extension.c delete mode 100644 frida/_frida/extension.version delete mode 100644 frida/_frida/meson.build delete mode 100644 frida/core.py rename frida/{_frida/py.typed => frida_bindgen/__init__.py} (100%) create mode 100644 frida/frida_bindgen/__main__.py create mode 100644 frida/frida_bindgen/assets/codegen_helpers.c create mode 100644 frida/frida_bindgen/assets/codegen_helpers.ts create mode 100644 frida/frida_bindgen/assets/codegen_prototypes.h create mode 100644 frida/frida_bindgen/assets/codegen_types.h create mode 100644 frida/frida_bindgen/assets/customization_facade.exports create mode 100644 frida/frida_bindgen/assets/customization_facade.ts create mode 100644 frida/frida_bindgen/assets/customization_helpers.imports create mode 100644 frida/frida_bindgen/assets/customization_helpers.ts create mode 100644 frida/frida_bindgen/cli.py create mode 100644 frida/frida_bindgen/codegen.py create mode 100644 frida/frida_bindgen/customization.py create mode 100644 frida/frida_bindgen/loader.py create mode 100644 frida/frida_bindgen/model.py diff --git a/frida/__init__.py b/frida/__init__.py deleted file mode 100644 index 483a51b..0000000 --- a/frida/__init__.py +++ /dev/null @@ -1,173 +0,0 @@ -from typing import Any, Callable, Dict, List, Optional, Tuple, Union - -try: - from . import _frida -except Exception as ex: - print("") - print("***") - if str(ex).startswith("No module named "): - print("Frida native extension not found") - print("Please check your PYTHONPATH.") - else: - print(f"Failed to load the Frida native extension: {ex}") - print("Please ensure that the extension was compiled correctly") - print("***") - print("") - raise ex -from . import core - -__version__: str = _frida.__version__ - -get_device_manager = core.get_device_manager -Relay = _frida.Relay -PortalService = core.PortalService -EndpointParameters = core.EndpointParameters -Compiler = core.Compiler -PackageManager = core.PackageManager -FileMonitor = _frida.FileMonitor -Cancellable = core.Cancellable - -ServerNotRunningError = _frida.ServerNotRunningError -ExecutableNotFoundError = _frida.ExecutableNotFoundError -ExecutableNotSupportedError = _frida.ExecutableNotSupportedError -ProcessNotFoundError = _frida.ProcessNotFoundError -ProcessNotRespondingError = _frida.ProcessNotRespondingError -InvalidArgumentError = _frida.InvalidArgumentError -InvalidOperationError = _frida.InvalidOperationError -PermissionDeniedError = _frida.PermissionDeniedError -AddressInUseError = _frida.AddressInUseError -TimedOutError = _frida.TimedOutError -NotSupportedError = _frida.NotSupportedError -ProtocolError = _frida.ProtocolError -TransportError = _frida.TransportError -OperationCancelledError = _frida.OperationCancelledError - - -def query_system_parameters() -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system - """ - - return get_local_device().query_system_parameters() - - -def spawn( - program: Union[str, List[Union[str, bytes]], Tuple[Union[str, bytes]]], - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, -) -> int: - """ - Spawn a process into an attachable state - """ - - return get_local_device().spawn(program=program, argv=argv, envp=envp, env=env, cwd=cwd, stdio=stdio, **kwargs) - - -def resume(target: core.ProcessTarget) -> None: - """ - Resume a process from the attachable state - :param target: the PID or name of the process - """ - - get_local_device().resume(target) - - -def kill(target: core.ProcessTarget) -> None: - """ - Kill a process - :param target: the PID or name of the process - """ - - get_local_device().kill(target) - - -def attach( - target: core.ProcessTarget, realm: Optional[str] = None, persist_timeout: Optional[int] = None -) -> core.Session: - """ - Attach to a process - :param target: the PID or name of the process - """ - - return get_local_device().attach(target, realm=realm, persist_timeout=persist_timeout) - - -def inject_library_file(target: core.ProcessTarget, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a process. - :param target: the PID or name of the process - """ - - return get_local_device().inject_library_file(target, path, entrypoint, data) - - -def inject_library_blob(target: core.ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a process - :param target: the PID or name of the process - """ - - return get_local_device().inject_library_blob(target, blob, entrypoint, data) - - -def get_local_device() -> core.Device: - """ - Get the local device - """ - - return get_device_manager().get_local_device() - - -def get_remote_device() -> core.Device: - """ - Get the first remote device in the devices list - """ - - return get_device_manager().get_remote_device() - - -def get_usb_device(timeout: int = 0) -> core.Device: - """ - Get the first device connected over USB in the devices list - """ - - return get_device_manager().get_usb_device(timeout) - - -def get_device(id: Optional[str], timeout: int = 0) -> core.Device: - """ - Get a device by its id - """ - - return get_device_manager().get_device(id, timeout) - - -def get_device_matching(predicate: Callable[[core.Device], bool], timeout: int = 0) -> core.Device: - """ - Get device matching predicate. - :param predicate: a function to filter the devices - :param timeout: operation timeout in seconds - """ - - return get_device_manager().get_device_matching(predicate, timeout) - - -def enumerate_devices() -> List[core.Device]: - """ - Enumerate all the devices from the device manager - """ - - return get_device_manager().enumerate_devices() - - -@core.cancellable -def shutdown() -> None: - """ - Shutdown the main device manager - """ - - get_device_manager()._impl.close() diff --git a/frida/_frida/__init__.pyi b/frida/_frida/__init__.pyi deleted file mode 100644 index 86cefc5..0000000 --- a/frida/_frida/__init__.pyi +++ /dev/null @@ -1,877 +0,0 @@ -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union - -# Exceptions -class AddressInUseError(Exception): ... -class ExecutableNotFoundError(Exception): ... -class ExecutableNotSupportedError(Exception): ... -class ServerNotRunningError(Exception): ... -class TimedOutError(Exception): ... -class TransportError(Exception): ... -class ProcessNotFoundError(Exception): ... -class ProcessNotRespondingError(Exception): ... -class ProtocolError(Exception): ... -class InvalidArgumentError(Exception): ... -class InvalidOperationError(Exception): ... -class NotSupportedError(Exception): ... -class OperationCancelledError(Exception): ... -class PermissionDeniedError(Exception): ... - -class Object: - def __init__(self, *args: Any, **kwargs: Any) -> None: ... - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler. - """ - ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler. - """ - ... - -class Application(Object): - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def name(self) -> str: - """ - Human-readable application name. - """ - ... - - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID, or 0 if not running. - """ - ... - -class Bus(Object): - def attach(self) -> None: - """ - Attach to the bus. - """ - ... - - def post(self, message: str, data: Optional[Union[bytes, str]]) -> None: - """ - Post a JSON-encoded message to the bus. - """ - ... - -class Cancellable(Object): - def cancel(self) -> None: - """ - Set cancellable to cancelled. - """ - ... - - def connect(self, callback: Callable[..., Any]) -> int: - """ - Register notification callback. - """ - ... - - def disconnect(self, handler_id: int) -> None: - """ - Unregister notification callback. - """ - ... - - @classmethod - def get_current(cls) -> "Cancellable": - """ - Get the top cancellable from the stack. - """ - ... - - def get_fd(self) -> int: - """ - Get file descriptor for integrating with an event loop. - """ - ... - - def is_cancelled(self) -> bool: - """ - Query whether cancellable has been cancelled. - """ - ... - - def pop_current(self) -> None: - """ - Pop cancellable off the cancellable stack. - """ - ... - - def push_current(self) -> None: - """ - Push cancellable onto the cancellable stack. - """ - ... - - def raise_if_cancelled(self) -> None: - """ - Raise an exception if cancelled. - """ - ... - - def release_fd(self) -> None: - """ - Release a resource previously allocated by get_fd(). - """ - ... - -class Child(Object): - @property - def argv(self) -> List[str]: - """ - Argument vector. - """ - ... - - @property - def envp(self) -> Dict[str, str]: - """ - Environment vector. - """ - ... - - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def origin(self) -> str: - """ - Origin. - """ - ... - - @property - def parent_pid(self) -> int: - """ - Parent Process ID. - """ - ... - - @property - def path(self) -> str: - """ - Path of executable. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - -class Crash(Object): - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - @property - def process_name(self) -> str: - """ - Process name. - """ - ... - - @property - def report(self) -> str: - """ - Human-readable crash report. - """ - ... - - @property - def summary(self) -> str: - """ - Human-readable crash summary. - """ - ... - -class Device(Object): - @property - def id(self) -> Optional[str]: - """ - Device ID. - """ - ... - - @property - def name(self) -> Optional[str]: - """ - Human-readable device name. - """ - ... - - @property - def icon(self) -> Optional[Any]: - """ - Icon. - """ - ... - - @property - def type(self) -> Optional[str]: - """ - Device type. One of: local, remote, usb. - """ - ... - - @property - def bus(self) -> Optional[Bus]: - """ - Message bus. - """ - ... - - def attach(self, pid: int, realm: Optional[str] = None, persist_timeout: Optional[int] = None) -> "Session": - """ - Attach to a PID. - """ - ... - - def disable_spawn_gating(self) -> None: - """ - Disable spawn gating. - """ - ... - - def enable_spawn_gating(self) -> None: - """ - Enable spawn gating. - """ - ... - - def enumerate_applications( - self, identifiers: Optional[Sequence[str]] = None, scope: Optional[str] = None - ) -> List[Application]: - """ - Enumerate applications. - """ - ... - - def enumerate_pending_children(self) -> List[Child]: - """ - Enumerate pending children. - """ - ... - - def enumerate_pending_spawn(self) -> List["Spawn"]: - """ - Enumerate pending spawn. - """ - ... - - def enumerate_processes(self, pids: Optional[Sequence[int]] = None, scope: Optional[str] = None) -> List[Process]: - """ - Enumerate processes. - """ - ... - - def get_frontmost_application(self, scope: Optional[str] = None) -> Optional[Application]: - """ - Get details about the frontmost application. - """ - ... - - def inject_library_blob(self, pid: int, blob_buffer: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a PID. - """ - ... - - def inject_library_file(self, pid: int, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a PID. - """ - ... - - def input(self, pid: int, data: bytes) -> None: - """ - Input data on stdin of a spawned process. - """ - ... - - def is_lost(self) -> bool: - """ - Query whether the device has been lost. - """ - ... - - def kill(self, pid: int) -> None: - """ - Kill a PID. - """ - ... - - def open_channel(self, address: str) -> "IOStream": - """ - Open a device-specific communication channel. - """ - ... - - def open_service(self, address: str) -> Service: - """ - Open a device-specific service. - """ - ... - - def unpair(self) -> None: - """ - Unpair device. - """ - ... - - def query_system_parameters(self) -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system. - """ - ... - - def resume(self, pid: int) -> None: - """ - Resume a process from the attachable state. - """ - ... - - def spawn( - self, - program: str, - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, - ) -> int: - """ - Spawn a process into an attachable state. - """ - ... - -class DeviceManager(Object): - def add_remote_device( - self, - address: str, - certificate: Optional[str] = None, - origin: Optional[str] = None, - token: Optional[str] = None, - keepalive_interval: Optional[int] = None, - ) -> Device: - """ - Add a remote device. - """ - ... - - def close(self) -> None: - """ - Close the device manager. - """ - ... - - def enumerate_devices(self) -> List[Device]: - """ - Enumerate devices. - """ - ... - - def get_device_matching(self, predicate: Callable[[Device], bool], timeout: int) -> Device: - """ - Get device matching predicate. - """ - ... - - def remove_remote_device(self, address: str) -> None: - """ - Remove a remote device. - """ - ... - -class EndpointParameters(Object): ... - -class FileMonitor(Object): - def disable(self) -> None: - """ - Disable the file monitor. - """ - ... - - def enable(self) -> None: - """ - Enable the file monitor. - """ - ... - -class IOStream(Object): - def close(self) -> None: - """ - Close the stream. - """ - ... - - def is_closed(self) -> bool: - """ - Query whether the stream is closed. - """ - ... - - def read(self, size: int) -> bytes: - """ - Read up to the specified number of bytes from the stream. - """ - ... - - def read_all(self, size: int) -> bytes: - """ - Read exactly the specified number of bytes from the stream. - """ - ... - - def write(self, data: bytes) -> int: - """ - Write as much as possible of the provided data to the stream. - """ - ... - - def write_all(self, data: bytes) -> None: - """ - Write all of the provided data to the stream. - """ - ... - -class PortalMembership(Object): - def terminate(self) -> None: - """ - Terminate the membership. - """ - ... - -class PortalService(Object): - @property - def device(self) -> Device: - """ - Device for in-process control. - """ - ... - - def broadcast(self, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Broadcast a message to all control channels. - """ - ... - - def enumerate_tags(self, connection_id: int) -> List[str]: - """ - Enumerate tags of a specific connection. - """ - ... - - def kick(self, connection_id: int) -> None: - """ - Kick out a specific connection. - """ - ... - - def narrowcast(self, tag: str, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to control channels with a specific tag. - """ - ... - - def post(self, connection_id: int, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to a specific control channel. - """ - ... - - def start(self) -> None: - """ - Start listening for incoming connections. - """ - ... - - def stop(self) -> None: - """ - Stop listening for incoming connections, and kick any connected clients. - """ - ... - - def tag(self, connection_id: int, tag: str) -> None: - """ - Tag a specific control channel. - """ - ... - - def untag(self, connection_id: int, tag: str) -> None: - """ - Untag a specific control channel. - """ - ... - -class Process(Object): - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - @property - def name(self) -> str: - """ - Human-readable process name. - """ - ... - - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - -class Relay(Object): - def __init__(self, address: str, username: str, password: str, kind: str) -> None: ... - @property - def address(self) -> str: - """ - Network address or address:port of the TURN server. - """ - ... - - @property - def kind(self) -> str: - """ - Relay kind. One of: turn-udp, turn-tcp, turn-tls. - """ - ... - - @property - def password(self) -> str: - """ - The TURN password to use for the allocate request. - """ - ... - - @property - def username(self) -> str: - """ - The TURN username to use for the allocate request. - """ - ... - -class Script(Object): - def eternalize(self) -> None: - """ - Eternalize the script. - """ - ... - - def is_destroyed(self) -> bool: - """ - Query whether the script has been destroyed. - """ - ... - - def load(self) -> None: - """ - Load the script. - """ - ... - - def post(self, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a JSON-encoded message to the script. - """ - ... - - def unload(self) -> None: - """ - Unload the script. - """ - ... - - def enable_debugger(self, port: Optional[int]) -> None: - """ - Enable the Node.js compatible script debugger - """ - ... - - def disable_debugger(self) -> None: - """ - Disable the Node.js compatible script debugger - """ - ... - -class Service(Object): - def activate(self) -> None: - """ - Activate the service. - """ - ... - - def cancel(self) -> None: - """ - Cancel the service. - """ - ... - - def request(self, parameters: Any) -> Any: - """ - Perform a request. - """ - ... - -class Session(Object): - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - def compile_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> bytes: - """ - Compile script source code to bytecode. - """ - ... - - def create_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> Script: - """ - Create a new script. - """ - ... - - def create_script_from_bytes( - self, data: bytes, name: Optional[str] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script from bytecode. - """ - ... - - def snapshot_script(self, embed_script: str, warmup_script: Optional[str], runtime: Optional[str] = None) -> bytes: - """ - Evaluate script and snapshot the resulting VM state - """ - ... - - def detach(self) -> None: - """ - Detach session from the process. - """ - ... - - def disable_child_gating(self) -> None: - """ - Disable child gating. - """ - ... - - def enable_child_gating(self) -> None: - """ - Enable child gating. - """ - ... - - def is_detached(self) -> bool: - """ - Query whether the session is detached. - """ - ... - - def join_portal( - self, address: str, certificate: Optional[str] = None, token: Optional[str] = None, acl: Optional[Any] = None - ) -> PortalMembership: - """ - Join a portal. - """ - ... - - def resume(self) -> None: - """ - Resume session after network error. - """ - ... - - def setup_peer_connection( - self, stun_server: Optional[str] = None, relays: Optional[Sequence[Relay]] = None - ) -> None: - """ - Set up a peer connection with the target process. - """ - ... - -class Spawn(Object): - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - -class Compiler(Object): - def build( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[str] = None, - bundle_format: Optional[str] = None, - type_check: Optional[str] = None, - source_maps: Optional[str] = None, - compression: Optional[str] = None, - platform: Optional[str] = None, - externals: Optional[Sequence[str]] = None, - ) -> str: - """ - Build an agent. - """ - ... - - def watch( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[str] = None, - bundle_format: Optional[str] = None, - type_check: Optional[str] = None, - source_maps: Optional[str] = None, - compression: Optional[str] = None, - platform: Optional[str] = None, - externals: Optional[Sequence[str]] = None, - ) -> None: - """ - Continuously build an agent. - """ - ... - -class PackageManager(Object): - @property - def registry(self) -> str: - """ - The registry being used. - """ - ... - - @registry.setter - def registry(self, value: str) -> None: - """ - Change the registry to use. - """ - ... - - def search( - self, - query: str, - offset: Optional[int] = None, - limit: Optional[int] = None, - ) -> PackageSearchResult: - """ - Search for packages to install. - """ - ... - - def install( - self, - project_root: Optional[str] = None, - role: Optional[str] = None, - specs: Optional[Sequence[str]] = None, - omits: Optional[Sequence[str]] = None, - ) -> PackageInstallResult: - """ - Install one or more packages. - """ - ... - -class Package(Object): - @property - def name(self) -> str: - """ - Package name. - """ - ... - - @property - def version(self) -> str: - """ - Package version. - """ - ... - - @property - def description(self) -> Optional[str]: - """ - Package description. - """ - ... - - @property - def url(self) -> Optional[str]: - """ - Package URL. - """ - ... - -class PackageSearchResult(Object): - @property - def packages(self) -> List[Package]: - """ - Batch of matching packages. - """ - ... - - @property - def total(self) -> int: - """ - Total matching packages. - """ - ... - -class PackageInstallResult(Object): - @property - def packages(self) -> List[Package]: - """ - The toplevel packages that are installed. - """ - ... - -__version__: str diff --git a/frida/_frida/extension.c b/frida/_frida/extension.c deleted file mode 100644 index cc7a291..0000000 --- a/frida/_frida/extension.c +++ /dev/null @@ -1,6124 +0,0 @@ -/* - * Copyright (C) 2013-2023 Ole André Vadla Ravnås - * Copyright (C) 2024 Håvard Sørbø - * - * Licence: wxWindows Library Licence, Version 3.1 - */ - -#include -#include - -#ifdef _MSC_VER -# pragma warning (push) -# pragma warning (disable: 4115) -# pragma warning (disable: 4211) -#endif -#ifdef _POSIX_C_SOURCE -# undef _POSIX_C_SOURCE -#endif - -#define PY_SSIZE_T_CLEAN - -/* - * Don't propagate _DEBUG state to pyconfig as it incorrectly attempts to load - * debug libraries that don't normally ship with Python (e.g. 2.x). Debuggers - * wishing to spelunk the Python core can override this workaround by defining - * _FRIDA_ENABLE_PYDEBUG. - */ -#if defined (_DEBUG) && !defined (_FRIDA_ENABLE_PYDEBUG) -# undef _DEBUG -# include -# define _DEBUG -#else -# include -#endif - -#include -#include -#include -#ifdef _MSC_VER -# pragma warning (pop) -#endif -#ifdef __APPLE__ -# include -# if TARGET_OS_OSX -# include -# endif -#endif - -#define PYFRIDA_TYPE(name) \ - (&_PYFRIDA_TYPE_VAR (name, type)) -#define PYFRIDA_TYPE_OBJECT(name) \ - PYFRIDA_TYPE (name)->object -#define _PYFRIDA_TYPE_VAR(name, var) \ - G_PASTE (G_PASTE (G_PASTE (Py, name), _), var) -#define PYFRIDA_DEFINE_BASETYPE(pyname, cname, init_func, destroy_func, ...) \ - _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ - _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE); \ - static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ - { \ - .parent = NULL, \ - .object = NULL, \ - .init_from_handle = (PyGObjectInitFromHandleFunc) init_func, \ - .destroy = destroy_func, \ - } -#define PYFRIDA_DEFINE_TYPE(pyname, cname, parent_cname, init_func, destroy_func, ...) \ - _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ - _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT); \ - static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ - { \ - .parent = PYFRIDA_TYPE (parent_cname), \ - .object = NULL, \ - .init_from_handle = (PyGObjectInitFromHandleFunc) init_func, \ - .destroy = destroy_func, \ - } -#define PYFRIDA_REGISTER_TYPE(cname, gtype) \ - G_BEGIN_DECLS \ - { \ - PyGObjectType * t = PYFRIDA_TYPE (cname); \ - t->object = PyType_FromSpecWithBases (&_PYFRIDA_TYPE_VAR (cname, spec), \ - (t->parent != NULL) ? PyTuple_Pack (1, t->parent->object) : NULL); \ - PyGObject_register_type (gtype, t); \ - Py_IncRef (t->object); \ - PyModule_AddObject (module, G_STRINGIFY (cname), t->object); \ - } \ - G_END_DECLS -#define _PYFRIDA_DEFINE_TYPE_SPEC(cname, pyname, type_flags) \ - static PyType_Spec _PYFRIDA_TYPE_VAR (cname, spec) = \ - { \ - .name = pyname, \ - .basicsize = sizeof (G_PASTE (Py, cname)), \ - .itemsize = 0, \ - .flags = type_flags, \ - .slots = _PYFRIDA_TYPE_VAR (cname, slots), \ - } -#define _PYFRIDA_DEFINE_TYPE_SLOTS(cname, ...) \ - static PyType_Slot _PYFRIDA_TYPE_VAR (cname, slots)[] = \ - { \ - __VA_ARGS__ \ - { 0 }, \ - } - -#define PY_GOBJECT(o) ((PyGObject *) (o)) -#define PY_GOBJECT_HANDLE(o) (PY_GOBJECT (o)->handle) -#define PY_GOBJECT_SIGNAL_CLOSURE(o) ((PyGObjectSignalClosure *) (o)) - -#define PyFrida_RETURN_NONE \ - G_STMT_START \ - { \ - Py_IncRef (Py_None); \ - return Py_None; \ - } \ - G_STMT_END - -static struct PyModuleDef PyFrida_moduledef = { PyModuleDef_HEAD_INIT, "_frida", "Frida", -1, NULL, }; - -static volatile gint toplevel_objects_alive = 0; - -static PyObject * inspect_getargspec; -static PyObject * inspect_ismethod; - -static PyObject * datetime_constructor; - -static initproc PyGObject_tp_init; -static destructor PyGObject_tp_dealloc; -static GHashTable * pygobject_type_spec_by_type; -static GHashTable * frida_exception_by_error_code; -static PyObject * cancelled_exception; - -typedef struct _PyGObject PyGObject; -typedef struct _PyGObjectType PyGObjectType; -typedef struct _PyGObjectSignalClosure PyGObjectSignalClosure; -typedef struct _PyDeviceManager PyDeviceManager; -typedef struct _PyDevice PyDevice; -typedef struct _PyApplication PyApplication; -typedef struct _PyProcess PyProcess; -typedef struct _PySpawn PySpawn; -typedef struct _PyChild PyChild; -typedef struct _PyCrash PyCrash; -typedef struct _PyBus PyBus; -typedef struct _PyService PyService; -typedef struct _PySession PySession; -typedef struct _PyScript PyScript; -typedef struct _PyRelay PyRelay; -typedef struct _PyPortalMembership PyPortalMembership; -typedef struct _PyPortalService PyPortalService; -typedef struct _PyEndpointParameters PyEndpointParameters; -typedef struct _PyCompiler PyCompiler; -typedef struct _PyPackageManager PyPackageManager; -typedef struct _PyPackage PyPackage; -typedef struct _PyPackageSearchResult PyPackageSearchResult; -typedef struct _PyPackageInstallResult PyPackageInstallResult; -typedef struct _PyFileMonitor PyFileMonitor; -typedef struct _PyIOStream PyIOStream; -typedef struct _PyCancellable PyCancellable; - -#define FRIDA_TYPE_PYTHON_AUTHENTICATION_SERVICE (frida_python_authentication_service_get_type ()) -G_DECLARE_FINAL_TYPE (FridaPythonAuthenticationService, frida_python_authentication_service, FRIDA, PYTHON_AUTHENTICATION_SERVICE, GObject) - -typedef void (* PyGObjectInitFromHandleFunc) (PyObject * self, gpointer handle); - -struct _PyGObject -{ - PyObject_HEAD - - gpointer handle; - const PyGObjectType * type; - - GSList * signal_closures; -}; - -struct _PyGObjectType -{ - PyGObjectType * parent; - PyObject * object; - PyGObjectInitFromHandleFunc init_from_handle; - GDestroyNotify destroy; -}; - -struct _PyGObjectSignalClosure -{ - GClosure parent; - guint signal_id; - guint max_arg_count; -}; - -struct _PyDeviceManager -{ - PyGObject parent; -}; - -struct _PyDevice -{ - PyGObject parent; - PyObject * id; - PyObject * name; - PyObject * icon; - PyObject * type; - PyObject * bus; -}; - -struct _PyApplication -{ - PyGObject parent; - PyObject * identifier; - PyObject * name; - guint pid; - PyObject * parameters; -}; - -struct _PyProcess -{ - PyGObject parent; - guint pid; - PyObject * name; - PyObject * parameters; -}; - -struct _PySpawn -{ - PyGObject parent; - guint pid; - PyObject * identifier; -}; - -struct _PyChild -{ - PyGObject parent; - guint pid; - guint parent_pid; - PyObject * origin; - PyObject * identifier; - PyObject * path; - PyObject * argv; - PyObject * envp; -}; - -struct _PyCrash -{ - PyGObject parent; - guint pid; - PyObject * process_name; - PyObject * summary; - PyObject * report; - PyObject * parameters; -}; - -struct _PyBus -{ - PyGObject parent; -}; - -struct _PyService -{ - PyGObject parent; -}; - -struct _PySession -{ - PyGObject parent; - guint pid; -}; - -struct _PyScript -{ - PyGObject parent; -}; - -struct _PyRelay -{ - PyGObject parent; - PyObject * address; - PyObject * username; - PyObject * password; - PyObject * kind; -}; - -struct _PyPortalMembership -{ - PyGObject parent; -}; - -struct _PyPortalService -{ - PyGObject parent; - PyObject * device; -}; - -struct _PyEndpointParameters -{ - PyGObject parent; -}; - -struct _FridaPythonAuthenticationService -{ - GObject parent; - PyObject * callback; - GThreadPool * pool; -}; - -struct _PyCompiler -{ - PyGObject parent; -}; - -struct _PyPackageManager -{ - PyGObject parent; -}; - -struct _PyPackage -{ - PyGObject parent; - PyObject * name; - PyObject * version; - PyObject * description; - PyObject * url; -}; - -struct _PyPackageSearchResult -{ - PyGObject parent; - PyObject * packages; - guint total; -}; - -struct _PyPackageInstallResult -{ - PyGObject parent; - PyObject * packages; -}; - -struct _PyFileMonitor -{ - PyGObject parent; -}; - -struct _PyIOStream -{ - PyGObject parent; - GInputStream * input; - GOutputStream * output; -}; - -struct _PyCancellable -{ - PyGObject parent; -}; - -static PyObject * PyGObject_new_take_handle (gpointer handle, const PyGObjectType * type); -static PyObject * PyGObject_try_get_from_handle (gpointer handle); -static int PyGObject_init (PyGObject * self); -static void PyGObject_dealloc (PyGObject * self); -static void PyGObject_take_handle (PyGObject * self, gpointer handle, const PyGObjectType * type); -static gpointer PyGObject_steal_handle (PyGObject * self); -static PyObject * PyGObject_on (PyGObject * self, PyObject * args); -static PyObject * PyGObject_off (PyGObject * self, PyObject * args); -static gint PyGObject_compare_signal_closure_callback (PyGObjectSignalClosure * closure, PyObject * callback); -static gboolean PyGObject_parse_signal_method_args (PyObject * args, GType instance_type, guint * signal_id, PyObject ** callback); -static const gchar * PyGObject_class_name_from_c (const gchar * cname); -static GClosure * PyGObject_make_closure_for_signal (guint signal_id, PyObject * callback, guint max_arg_count); -static void PyGObjectSignalClosure_finalize (PyObject * callback); -static void PyGObjectSignalClosure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, - gpointer invocation_hint, gpointer marshal_data); -static PyObject * PyGObjectSignalClosure_marshal_params (const GValue * params, guint params_length); -static PyObject * PyGObject_marshal_value (const GValue * value); -static PyObject * PyGObject_marshal_string (const gchar * str); -static gboolean PyGObject_unmarshal_string (PyObject * value, gchar ** str); -static PyObject * PyGObject_marshal_datetime (const gchar * iso8601_text); -static PyObject * PyGObject_marshal_strv (gchar * const * strv, gint length); -static gboolean PyGObject_unmarshal_strv (PyObject * value, gchar *** strv, gint * length); -static PyObject * PyGObject_marshal_envp (gchar * const * envp, gint length); -static gboolean PyGObject_unmarshal_envp (PyObject * value, gchar *** envp, gint * length); -static PyObject * PyGObject_marshal_enum (gint value, GType type); -static gboolean PyGObject_unmarshal_enum (const gchar * str, GType type, gpointer value); -static PyObject * PyGObject_marshal_bytes (GBytes * bytes); -static PyObject * PyGObject_marshal_bytes_non_nullable (GBytes * bytes); -static PyObject * PyGObject_marshal_variant (GVariant * variant); -static PyObject * PyGObject_marshal_variant_byte_array (GVariant * variant); -static PyObject * PyGObject_marshal_variant_dict (GVariant * variant); -static PyObject * PyGObject_marshal_variant_array (GVariant * variant); -static gboolean PyGObject_unmarshal_variant (PyObject * value, GVariant ** variant); -static gboolean PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, GVariant ** variant); -static gboolean PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, GVariant ** variant); -static PyObject * PyGObject_marshal_parameters_dict (GHashTable * dict); -static PyObject * PyGObject_marshal_socket_address (GSocketAddress * address); -static gboolean PyGObject_unmarshal_certificate (const gchar * str, GTlsCertificate ** certificate); -static PyObject * PyGObject_marshal_object (gpointer handle, GType type); - -static int PyDeviceManager_init (PyDeviceManager * self, PyObject * args, PyObject * kwds); -static void PyDeviceManager_dealloc (PyDeviceManager * self); -static PyObject * PyDeviceManager_close (PyDeviceManager * self); -static PyObject * PyDeviceManager_get_device_matching (PyDeviceManager * self, PyObject * args); -static gboolean PyDeviceManager_is_matching_device (FridaDevice * device, PyObject * predicate); -static PyObject * PyDeviceManager_enumerate_devices (PyDeviceManager * self); -static PyObject * PyDeviceManager_add_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw); -static PyObject * PyDeviceManager_remove_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw); -static FridaRemoteDeviceOptions * PyDeviceManager_parse_remote_device_options (const gchar * certificate_value, const gchar * origin, - const gchar * token, gint keepalive_interval); - -static PyObject * PyDevice_new_take_handle (FridaDevice * handle); -static int PyDevice_init (PyDevice * self, PyObject * args, PyObject * kw); -static void PyDevice_init_from_handle (PyDevice * self, FridaDevice * handle); -static void PyDevice_dealloc (PyDevice * self); -static PyObject * PyDevice_repr (PyDevice * self); -static PyObject * PyDevice_is_lost (PyDevice * self); -static PyObject * PyDevice_query_system_parameters (PyDevice * self); -static PyObject * PyDevice_get_frontmost_application (PyDevice * self, PyObject * args, PyObject * kw); -static PyObject * PyDevice_enumerate_applications (PyDevice * self, PyObject * args, PyObject * kw); -static FridaApplicationQueryOptions * PyDevice_parse_application_query_options (PyObject * identifiers_value, const gchar * scope_value); -static PyObject * PyDevice_enumerate_processes (PyDevice * self, PyObject * args, PyObject * kw); -static FridaProcessQueryOptions * PyDevice_parse_process_query_options (PyObject * pids_value, const gchar * scope_value); -static PyObject * PyDevice_enable_spawn_gating (PyDevice * self); -static PyObject * PyDevice_disable_spawn_gating (PyDevice * self); -static PyObject * PyDevice_enumerate_pending_spawn (PyDevice * self); -static PyObject * PyDevice_enumerate_pending_children (PyDevice * self); -static PyObject * PyDevice_spawn (PyDevice * self, PyObject * args, PyObject * kw); -static PyObject * PyDevice_input (PyDevice * self, PyObject * args); -static PyObject * PyDevice_resume (PyDevice * self, PyObject * args); -static PyObject * PyDevice_kill (PyDevice * self, PyObject * args); -static PyObject * PyDevice_attach (PyDevice * self, PyObject * args, PyObject * kw); -static FridaSessionOptions * PyDevice_parse_session_options (const gchar * realm_value, guint persist_timeout); -static PyObject * PyDevice_inject_library_file (PyDevice * self, PyObject * args); -static PyObject * PyDevice_inject_library_blob (PyDevice * self, PyObject * args); -static PyObject * PyDevice_open_channel (PyDevice * self, PyObject * args); -static PyObject * PyDevice_open_service (PyDevice * self, PyObject * args); -static PyObject * PyDevice_unpair (PyDevice * self); - -static PyObject * PyApplication_new_take_handle (FridaApplication * handle); -static int PyApplication_init (PyApplication * self, PyObject * args, PyObject * kw); -static void PyApplication_init_from_handle (PyApplication * self, FridaApplication * handle); -static void PyApplication_dealloc (PyApplication * self); -static PyObject * PyApplication_repr (PyApplication * self); -static PyObject * PyApplication_marshal_parameters_dict (GHashTable * dict); - -static PyObject * PyProcess_new_take_handle (FridaProcess * handle); -static int PyProcess_init (PyProcess * self, PyObject * args, PyObject * kw); -static void PyProcess_init_from_handle (PyProcess * self, FridaProcess * handle); -static void PyProcess_dealloc (PyProcess * self); -static PyObject * PyProcess_repr (PyProcess * self); -static PyObject * PyProcess_marshal_parameters_dict (GHashTable * dict); - -static PyObject * PySpawn_new_take_handle (FridaSpawn * handle); -static int PySpawn_init (PySpawn * self, PyObject * args, PyObject * kw); -static void PySpawn_init_from_handle (PySpawn * self, FridaSpawn * handle); -static void PySpawn_dealloc (PySpawn * self); -static PyObject * PySpawn_repr (PySpawn * self); - -static PyObject * PyChild_new_take_handle (FridaChild * handle); -static int PyChild_init (PyChild * self, PyObject * args, PyObject * kw); -static void PyChild_init_from_handle (PyChild * self, FridaChild * handle); -static void PyChild_dealloc (PyChild * self); -static PyObject * PyChild_repr (PyChild * self); - -static int PyCrash_init (PyCrash * self, PyObject * args, PyObject * kw); -static void PyCrash_init_from_handle (PyCrash * self, FridaCrash * handle); -static void PyCrash_dealloc (PyCrash * self); -static PyObject * PyCrash_repr (PyCrash * self); - -static PyObject * PyBus_new_take_handle (FridaBus * handle); -static PyObject * PyBus_attach (PySession * self); -static PyObject * PyBus_post (PyScript * self, PyObject * args, PyObject * kw); - -static PyObject * PyService_new_take_handle (FridaService * handle); -static PyObject * PyService_activate (PyService * self); -static PyObject * PyService_cancel (PyService * self); -static PyObject * PyService_request (PyService * self, PyObject * args); - -static PyObject * PySession_new_take_handle (FridaSession * handle); -static int PySession_init (PySession * self, PyObject * args, PyObject * kw); -static void PySession_init_from_handle (PySession * self, FridaSession * handle); -static PyObject * PySession_repr (PySession * self); -static PyObject * PySession_is_detached (PySession * self); -static PyObject * PySession_detach (PySession * self); -static PyObject * PySession_resume (PySession * self); -static PyObject * PySession_enable_child_gating (PySession * self); -static PyObject * PySession_disable_child_gating (PySession * self); -static PyObject * PySession_create_script (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_compile_script (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); -static FridaScriptOptions * PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, - const gchar * runtime_value); -static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); -static FridaSnapshotOptions * PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value); -static PyObject * PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw); -static FridaPeerOptions * PySession_parse_peer_options (const gchar * stun_server, PyObject * relays); -static PyObject * PySession_join_portal (PySession * self, PyObject * args, PyObject * kw); -static FridaPortalOptions * PySession_parse_portal_options (const gchar * certificate_value, const gchar * token, PyObject * acl_value); - -static PyObject * PyScript_new_take_handle (FridaScript * handle); -static PyObject * PyScript_is_destroyed (PyScript * self); -static PyObject * PyScript_load (PyScript * self); -static PyObject * PyScript_unload (PyScript * self); -static PyObject * PyScript_eternalize (PyScript * self); -static PyObject * PyScript_post (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyScript_enable_debugger (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyScript_disable_debugger (PyScript * self); - -static int PyRelay_init (PyRelay * self, PyObject * args, PyObject * kw); -static void PyRelay_init_from_handle (PyRelay * self, FridaRelay * handle); -static void PyRelay_dealloc (PyRelay * self); -static PyObject * PyRelay_repr (PyRelay * self); - -static PyObject * PyPortalMembership_new_take_handle (FridaPortalMembership * handle); -static PyObject * PyPortalMembership_terminate (PyPortalMembership * self); - -static int PyPortalService_init (PyPortalService * self, PyObject * args, PyObject * kw); -static void PyPortalService_init_from_handle (PyPortalService * self, FridaPortalService * handle); -static void PyPortalService_dealloc (PyPortalService * self); -static PyObject * PyPortalService_start (PyPortalService * self); -static PyObject * PyPortalService_stop (PyPortalService * self); -static PyObject * PyPortalService_kick (PyScript * self, PyObject * args); -static PyObject * PyPortalService_post (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_narrowcast (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_broadcast (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_enumerate_tags (PyScript * self, PyObject * args); -static PyObject * PyPortalService_tag (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_untag (PyScript * self, PyObject * args, PyObject * kw); - -static int PyEndpointParameters_init (PyEndpointParameters * self, PyObject * args, PyObject * kw); - -static FridaPythonAuthenticationService * frida_python_authentication_service_new (PyObject * callback); -static void frida_python_authentication_service_iface_init (gpointer g_iface, gpointer iface_data); -static void frida_python_authentication_service_dispose (GObject * object); -static void frida_python_authentication_service_authenticate (FridaAuthenticationService * service, const gchar * token, - GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data); -static gchar * frida_python_authentication_service_authenticate_finish (FridaAuthenticationService * service, GAsyncResult * result, - GError ** error); -static void frida_python_authentication_service_do_authenticate (GTask * task, FridaPythonAuthenticationService * self); - -static int PyCompiler_init (PyCompiler * self, PyObject * args, PyObject * kw); -static void PyCompiler_dealloc (PyCompiler * self); -static PyObject * PyCompiler_build (PyCompiler * self, PyObject * args, PyObject * kw); -static PyObject * PyCompiler_watch (PyCompiler * self, PyObject * args, PyObject * kw); -static gboolean PyCompiler_set_options (FridaCompilerOptions * options, const gchar * project_root_value, const gchar * output_format_value, - const gchar * bundle_format_value, const gchar * type_check_value, const gchar * source_maps_value, const gchar * compression_value, - const gchar * platform_value, PyObject * externals_value); - -static int PyPackageManager_init (PyPackageManager * self, PyObject * args, PyObject * kw); -static void PyPackageManager_dealloc (PyPackageManager * self); -static PyObject * PyPackageManager_repr (PyPackageManager * self); -static PyObject * PyPackageManager_get_registry (PyPackageManager * self, void * closure); -static int PyPackageManager_set_registry (PyPackageManager * self, PyObject * val, void * closure); -static PyObject * PyPackageManager_search (PyPackageManager * self, PyObject * args, PyObject * kw); -static PyObject * PyPackageManager_install (PyPackageManager * self, PyObject * args, PyObject * kw); -static FridaPackageInstallOptions * PyPackageManager_parse_install_options (const gchar * project_root, const char * role_value, - PyObject * specs_value, PyObject * omits_value); - -static PyObject * PyPackage_new_take_handle (FridaPackage * handle); -static int PyPackage_init (PyPackage * self, PyObject * args, PyObject * kw); -static void PyPackage_init_from_handle (PyPackage * self, FridaPackage * handle); -static void PyPackage_dealloc (PyPackage * self); -static PyObject * PyPackage_repr (PyPackage * self); - -static PyObject * PyPackageSearchResult_new_take_handle (FridaPackageSearchResult * handle); -static int PyPackageSearchResult_init (PyPackageSearchResult * self, PyObject * args, PyObject * kw); -static void PyPackageSearchResult_init_from_handle (PyPackageSearchResult * self, FridaPackageSearchResult * handle); -static void PyPackageSearchResult_dealloc (PyPackageSearchResult * self); -static PyObject * PyPackageSearchResult_repr (PyPackageSearchResult * self); - -static PyObject * PyPackageInstallResult_new_take_handle (FridaPackageInstallResult * handle); -static int PyPackageInstallResult_init (PyPackageInstallResult * self, PyObject * args, PyObject * kw); -static void PyPackageInstallResult_init_from_handle (PyPackageInstallResult * self, FridaPackageInstallResult * handle); -static void PyPackageInstallResult_dealloc (PyPackageInstallResult * self); -static PyObject * PyPackageInstallResult_repr (PyPackageInstallResult * self); - -static int PyFileMonitor_init (PyFileMonitor * self, PyObject * args, PyObject * kw); -static void PyFileMonitor_dealloc (PyFileMonitor * self); -static PyObject * PyFileMonitor_enable (PyFileMonitor * self); -static PyObject * PyFileMonitor_disable (PyFileMonitor * self); - -static PyObject * PyIOStream_new_take_handle (GIOStream * handle); -static int PyIOStream_init (PyIOStream * self, PyObject * args, PyObject * kw); -static void PyIOStream_init_from_handle (PyIOStream * self, GIOStream * handle); -static PyObject * PyIOStream_repr (PyIOStream * self); -static PyObject * PyIOStream_is_closed (PyIOStream * self); -static PyObject * PyIOStream_close (PyIOStream * self); -static PyObject * PyIOStream_read (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_read_all (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_write (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_write_all (PyIOStream * self, PyObject * args); - -static int PyCancellable_init (PyCancellable * self, PyObject * args, PyObject * kw); -static PyObject * PyCancellable_repr (PyCancellable * self); -static PyObject * PyCancellable_is_cancelled (PyCancellable * self); -static PyObject * PyCancellable_raise_if_cancelled (PyCancellable * self); -static PyObject * PyCancellable_get_fd (PyCancellable * self); -static PyObject * PyCancellable_release_fd (PyCancellable * self); -static PyObject * PyCancellable_get_current (PyCancellable * self); -static PyObject * PyCancellable_push_current (PyCancellable * self); -static PyObject * PyCancellable_pop_current (PyCancellable * self); -static PyObject * PyCancellable_connect (PyCancellable * self, PyObject * args); -static PyObject * PyCancellable_disconnect (PyCancellable * self, PyObject * args); -static void PyCancellable_on_cancelled (GCancellable * cancellable, PyObject * callback); -static void PyCancellable_destroy_callback (PyObject * callback); -static PyObject * PyCancellable_cancel (PyCancellable * self); - -static PyObject * PyFrida_raise (GError * error); -static gchar * PyFrida_repr (PyObject * obj); -static guint PyFrida_get_max_argument_count (PyObject * callable); - -static PyMethodDef PyGObject_methods[] = -{ - { "on", (PyCFunction) PyGObject_on, METH_VARARGS, "Add a signal handler." }, - { "off", (PyCFunction) PyGObject_off, METH_VARARGS, "Remove a signal handler." }, - { NULL } -}; - -static PyMethodDef PyDeviceManager_methods[] = -{ - { "close", (PyCFunction) PyDeviceManager_close, METH_NOARGS, "Close the device manager." }, - { "get_device_matching", (PyCFunction) PyDeviceManager_get_device_matching, METH_VARARGS, "Get device matching predicate." }, - { "enumerate_devices", (PyCFunction) PyDeviceManager_enumerate_devices, METH_NOARGS, "Enumerate devices." }, - { "add_remote_device", (PyCFunction) PyDeviceManager_add_remote_device, METH_VARARGS | METH_KEYWORDS, "Add a remote device." }, - { "remove_remote_device", (PyCFunction) PyDeviceManager_remove_remote_device, METH_VARARGS | METH_KEYWORDS, "Remove a remote device." }, - { NULL } -}; - -static PyMethodDef PyDevice_methods[] = -{ - { "is_lost", (PyCFunction) PyDevice_is_lost, METH_NOARGS, "Query whether the device has been lost." }, - { "query_system_parameters", (PyCFunction) PyDevice_query_system_parameters, METH_NOARGS, "Returns a dictionary of information about the host system." }, - { "get_frontmost_application", (PyCFunction) PyDevice_get_frontmost_application, METH_VARARGS | METH_KEYWORDS, "Get details about the frontmost application." }, - { "enumerate_applications", (PyCFunction) PyDevice_enumerate_applications, METH_VARARGS | METH_KEYWORDS, "Enumerate applications." }, - { "enumerate_processes", (PyCFunction) PyDevice_enumerate_processes, METH_VARARGS | METH_KEYWORDS, "Enumerate processes." }, - { "enable_spawn_gating", (PyCFunction) PyDevice_enable_spawn_gating, METH_NOARGS, "Enable spawn gating." }, - { "disable_spawn_gating", (PyCFunction) PyDevice_disable_spawn_gating, METH_NOARGS, "Disable spawn gating." }, - { "enumerate_pending_spawn", (PyCFunction) PyDevice_enumerate_pending_spawn, METH_NOARGS, "Enumerate pending spawn." }, - { "enumerate_pending_children", (PyCFunction) PyDevice_enumerate_pending_children, METH_NOARGS, "Enumerate pending children." }, - { "spawn", (PyCFunction) PyDevice_spawn, METH_VARARGS | METH_KEYWORDS, "Spawn a process into an attachable state." }, - { "input", (PyCFunction) PyDevice_input, METH_VARARGS, "Input data on stdin of a spawned process." }, - { "resume", (PyCFunction) PyDevice_resume, METH_VARARGS, "Resume a process from the attachable state." }, - { "kill", (PyCFunction) PyDevice_kill, METH_VARARGS, "Kill a PID." }, - { "attach", (PyCFunction) PyDevice_attach, METH_VARARGS | METH_KEYWORDS, "Attach to a PID." }, - { "inject_library_file", (PyCFunction) PyDevice_inject_library_file, METH_VARARGS, "Inject a library file to a PID." }, - { "inject_library_blob", (PyCFunction) PyDevice_inject_library_blob, METH_VARARGS, "Inject a library blob to a PID." }, - { "open_channel", (PyCFunction) PyDevice_open_channel, METH_VARARGS, "Open a device-specific communication channel." }, - { "open_service", (PyCFunction) PyDevice_open_service, METH_VARARGS, "Open a device-specific service." }, - { "unpair", (PyCFunction) PyDevice_unpair, METH_NOARGS, "Unpair device." }, - { NULL } -}; - -static PyMemberDef PyDevice_members[] = -{ - { "id", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, id), READONLY, "Device ID." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, name), READONLY, "Human-readable device name." }, - { "icon", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, icon), READONLY, "Icon." }, - { "type", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, type), READONLY, "Device type. One of: local, remote, usb." }, - { "bus", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, bus), READONLY, "Message bus." }, - { NULL } -}; - -static PyMemberDef PyApplication_members[] = -{ - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, identifier), READONLY, "Application identifier." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, name), READONLY, "Human-readable application name." }, - { "pid", T_UINT, G_STRUCT_OFFSET (PyApplication, pid), READONLY, "Process ID, or 0 if not running." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMemberDef PyProcess_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyProcess, pid), READONLY, "Process ID." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyProcess, name), READONLY, "Human-readable process name." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyProcess, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMemberDef PySpawn_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PySpawn, pid), READONLY, "Process ID." }, - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PySpawn, identifier), READONLY, "Application identifier." }, - { NULL } -}; - -static PyMemberDef PyChild_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyChild, pid), READONLY, "Process ID." }, - { "parent_pid", T_UINT, G_STRUCT_OFFSET (PyChild, parent_pid), READONLY, "Parent Process ID." }, - { "origin", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, origin), READONLY, "Origin." }, - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, identifier), READONLY, "Application identifier." }, - { "path", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, path), READONLY, "Path of executable." }, - { "argv", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, argv), READONLY, "Argument vector." }, - { "envp", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, envp), READONLY, "Environment vector." }, - { NULL } -}; - -static PyMemberDef PyCrash_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyCrash, pid), READONLY, "Process ID." }, - { "process_name", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, process_name), READONLY, "Process name." }, - { "summary", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, summary), READONLY, "Human-readable crash summary." }, - { "report", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, report), READONLY, "Human-readable crash report." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMethodDef PyBus_methods[] = -{ - { "attach", (PyCFunction) PyBus_attach, METH_NOARGS, "Attach to the bus." }, - { "post", (PyCFunction) PyBus_post, METH_VARARGS | METH_KEYWORDS, "Post a JSON-encoded message to the bus." }, - { NULL } -}; - -static PyMethodDef PyService_methods[] = -{ - { "activate", (PyCFunction) PyService_activate, METH_NOARGS, "Activate the service." }, - { "cancel", (PyCFunction) PyService_cancel, METH_NOARGS, "Cancel the service." }, - { "request", (PyCFunction) PyService_request, METH_VARARGS, "Perform a request." }, - { NULL } -}; - -static PyMethodDef PySession_methods[] = -{ - { "is_detached", (PyCFunction) PySession_is_detached, METH_NOARGS, "Query whether the session is detached." }, - { "detach", (PyCFunction) PySession_detach, METH_NOARGS, "Detach session from the process." }, - { "resume", (PyCFunction) PySession_resume, METH_NOARGS, "Resume session after network error." }, - { "enable_child_gating", (PyCFunction) PySession_enable_child_gating, METH_NOARGS, "Enable child gating." }, - { "disable_child_gating", (PyCFunction) PySession_disable_child_gating, METH_NOARGS, "Disable child gating." }, - { "create_script", (PyCFunction) PySession_create_script, METH_VARARGS | METH_KEYWORDS, "Create a new script." }, - { "create_script_from_bytes", (PyCFunction) PySession_create_script_from_bytes, METH_VARARGS | METH_KEYWORDS, "Create a new script from bytecode." }, - { "compile_script", (PyCFunction) PySession_compile_script, METH_VARARGS | METH_KEYWORDS, "Compile script source code to bytecode." }, - { "snapshot_script", (PyCFunction) PySession_snapshot_script, METH_VARARGS | METH_KEYWORDS, "Evaluate script and snapshot the resulting VM state." }, - { "setup_peer_connection", (PyCFunction) PySession_setup_peer_connection, METH_VARARGS | METH_KEYWORDS, "Set up a peer connection with the target process." }, - { "join_portal", (PyCFunction) PySession_join_portal, METH_VARARGS | METH_KEYWORDS, "Join a portal." }, - { NULL } -}; - -static PyMemberDef PySession_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PySession, pid), READONLY, "Process ID." }, - { NULL } -}; - -static PyMethodDef PyScript_methods[] = -{ - { "is_destroyed", (PyCFunction) PyScript_is_destroyed, METH_NOARGS, "Query whether the script has been destroyed." }, - { "load", (PyCFunction) PyScript_load, METH_NOARGS, "Load the script." }, - { "unload", (PyCFunction) PyScript_unload, METH_NOARGS, "Unload the script." }, - { "eternalize", (PyCFunction) PyScript_eternalize, METH_NOARGS, "Eternalize the script." }, - { "post", (PyCFunction) PyScript_post, METH_VARARGS | METH_KEYWORDS, "Post a JSON-encoded message to the script." }, - { "enable_debugger", (PyCFunction) PyScript_enable_debugger, METH_VARARGS | METH_KEYWORDS, "Enable the Node.js compatible script debugger." }, - { "disable_debugger", (PyCFunction) PyScript_disable_debugger, METH_NOARGS, "Disable the Node.js compatible script debugger." }, - { NULL } -}; - -static PyMemberDef PyRelay_members[] = -{ - { "address", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, address), READONLY, "Network address or address:port of the TURN server." }, - { "username", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, username), READONLY, "The TURN username to use for the allocate request." }, - { "password", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, password), READONLY, "The TURN password to use for the allocate request." }, - { "kind", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, kind), READONLY, "Relay kind. One of: turn-udp, turn-tcp, turn-tls." }, - { NULL } -}; - -static PyMethodDef PyPortalMembership_methods[] = -{ - { "terminate", (PyCFunction) PyPortalMembership_terminate, METH_NOARGS, "Terminate the membership." }, - { NULL } -}; - -static PyMethodDef PyPortalService_methods[] = -{ - { "start", (PyCFunction) PyPortalService_start, METH_NOARGS, "Start listening for incoming connections." }, - { "stop", (PyCFunction) PyPortalService_stop, METH_NOARGS, "Stop listening for incoming connections, and kick any connected clients." }, - { "kick", (PyCFunction) PyPortalService_kick, METH_VARARGS, "Kick out a specific connection." }, - { "post", (PyCFunction) PyPortalService_post, METH_VARARGS | METH_KEYWORDS, "Post a message to a specific control channel." }, - { "narrowcast", (PyCFunction) PyPortalService_narrowcast, METH_VARARGS | METH_KEYWORDS, "Post a message to control channels with a specific tag." }, - { "broadcast", (PyCFunction) PyPortalService_broadcast, METH_VARARGS | METH_KEYWORDS, "Broadcast a message to all control channels." }, - { "enumerate_tags", (PyCFunction) PyPortalService_enumerate_tags, METH_VARARGS, "Enumerate tags of a specific connection." }, - { "tag", (PyCFunction) PyPortalService_tag, METH_VARARGS | METH_KEYWORDS, "Tag a specific control channel." }, - { "untag", (PyCFunction) PyPortalService_untag, METH_VARARGS | METH_KEYWORDS, "Untag a specific control channel." }, - { NULL } -}; - -static PyMemberDef PyPortalService_members[] = -{ - { "device", T_OBJECT_EX, G_STRUCT_OFFSET (PyPortalService, device), READONLY, "Device for in-process control." }, - { NULL } -}; - -static PyMethodDef PyCompiler_methods[] = -{ - { "build", (PyCFunction) PyCompiler_build, METH_VARARGS | METH_KEYWORDS, "Build an agent." }, - { "watch", (PyCFunction) PyCompiler_watch, METH_VARARGS | METH_KEYWORDS, "Continuously build an agent." }, - { NULL } -}; - -static PyGetSetDef PyPackageManager_getset[] = -{ - { "registry", (getter) PyPackageManager_get_registry, (setter) PyPackageManager_set_registry, "The registry to use.", NULL }, - { NULL } -}; - -static PyMethodDef PyPackageManager_methods[] = -{ - { "search", (PyCFunction) PyPackageManager_search, METH_VARARGS | METH_KEYWORDS, "Search for packages to install." }, - { "install", (PyCFunction) PyPackageManager_install, METH_VARARGS | METH_KEYWORDS, "Install one or more packages." }, - { NULL } -}; - -static PyMemberDef PyPackage_members[] = -{ - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, name), READONLY, "Package name." }, - { "version", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, version), READONLY, "Package version." }, - { "description", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, description), READONLY, "Package description." }, - { "url", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, url), READONLY, "Package URL." }, - { NULL } -}; - -static PyMemberDef PyPackageSearchResult_members[] = -{ - { "packages", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackageSearchResult, packages), READONLY, "Batch of matching packages." }, - { "total", T_UINT, G_STRUCT_OFFSET (PyPackageSearchResult, total), READONLY, "Total matching packages." }, - { NULL } -}; - -static PyMemberDef PyPackageInstallResult_members[] = -{ - { "packages", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackageInstallResult, packages), READONLY, "The toplevel packages that are installed." }, - { NULL } -}; - -static PyMethodDef PyFileMonitor_methods[] = -{ - { "enable", (PyCFunction) PyFileMonitor_enable, METH_NOARGS, "Enable the file monitor." }, - { "disable", (PyCFunction) PyFileMonitor_disable, METH_NOARGS, "Disable the file monitor." }, - { NULL } -}; - -static PyMethodDef PyIOStream_methods[] = -{ - { "is_closed", (PyCFunction) PyIOStream_is_closed, METH_NOARGS, "Query whether the stream is closed." }, - { "close", (PyCFunction) PyIOStream_close, METH_NOARGS, "Close the stream." }, - { "read", (PyCFunction) PyIOStream_read, METH_VARARGS, "Read up to the specified number of bytes from the stream." }, - { "read_all", (PyCFunction) PyIOStream_read_all, METH_VARARGS, "Read exactly the specified number of bytes from the stream." }, - { "write", (PyCFunction) PyIOStream_write, METH_VARARGS, "Write as much as possible of the provided data to the stream." }, - { "write_all", (PyCFunction) PyIOStream_write_all, METH_VARARGS, "Write all of the provided data to the stream." }, - { NULL } -}; - -static PyMethodDef PyCancellable_methods[] = -{ - { "is_cancelled", (PyCFunction) PyCancellable_is_cancelled, METH_NOARGS, "Query whether cancellable has been cancelled." }, - { "raise_if_cancelled", (PyCFunction) PyCancellable_raise_if_cancelled, METH_NOARGS, "Raise an exception if cancelled." }, - { "get_fd", (PyCFunction) PyCancellable_get_fd, METH_NOARGS, "Get file descriptor for integrating with an event loop." }, - { "release_fd", (PyCFunction) PyCancellable_release_fd, METH_NOARGS, "Release a resource previously allocated by get_fd()." }, - { "get_current", (PyCFunction) PyCancellable_get_current, METH_CLASS | METH_NOARGS, "Get the top cancellable from the stack." }, - { "push_current", (PyCFunction) PyCancellable_push_current, METH_NOARGS, "Push cancellable onto the cancellable stack." }, - { "pop_current", (PyCFunction) PyCancellable_pop_current, METH_NOARGS, "Pop cancellable off the cancellable stack." }, - { "connect", (PyCFunction) PyCancellable_connect, METH_VARARGS, "Register notification callback." }, - { "disconnect", (PyCFunction) PyCancellable_disconnect, METH_VARARGS, "Unregister notification callback." }, - { "cancel", (PyCFunction) PyCancellable_cancel, METH_NOARGS, "Set cancellable to cancelled." }, - { NULL } -}; - -PYFRIDA_DEFINE_BASETYPE ("_frida.Object", GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Object" }, - { Py_tp_init, PyGObject_init }, - { Py_tp_dealloc, PyGObject_dealloc }, - { Py_tp_methods, PyGObject_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.DeviceManager", DeviceManager, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Device Manager" }, - { Py_tp_init, PyDeviceManager_init }, - { Py_tp_dealloc, PyDeviceManager_dealloc }, - { Py_tp_methods, PyDeviceManager_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Device", Device, GObject, PyDevice_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Device" }, - { Py_tp_init, PyDevice_init }, - { Py_tp_dealloc, PyDevice_dealloc }, - { Py_tp_repr, PyDevice_repr }, - { Py_tp_methods, PyDevice_methods }, - { Py_tp_members, PyDevice_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Application", Application, GObject, PyApplication_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Application" }, - { Py_tp_init, PyApplication_init }, - { Py_tp_dealloc, PyApplication_dealloc }, - { Py_tp_repr, PyApplication_repr }, - { Py_tp_members, PyApplication_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Process", Process, GObject, PyProcess_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Process" }, - { Py_tp_init, PyProcess_init }, - { Py_tp_dealloc, PyProcess_dealloc }, - { Py_tp_repr, PyProcess_repr }, - { Py_tp_members, PyProcess_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Spawn", Spawn, GObject, PySpawn_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Spawn" }, - { Py_tp_init, PySpawn_init }, - { Py_tp_dealloc, PySpawn_dealloc }, - { Py_tp_repr, PySpawn_repr }, - { Py_tp_members, PySpawn_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Child", Child, GObject, PyChild_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Child" }, - { Py_tp_init, PyChild_init }, - { Py_tp_dealloc, PyChild_dealloc }, - { Py_tp_repr, PyChild_repr }, - { Py_tp_members, PyChild_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Crash", Crash, GObject, PyCrash_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Crash Details" }, - { Py_tp_init, PyCrash_init }, - { Py_tp_dealloc, PyCrash_dealloc }, - { Py_tp_repr, PyCrash_repr }, - { Py_tp_members, PyCrash_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Bus", Bus, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Message Bus" }, - { Py_tp_methods, PyBus_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Service", Service, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Service" }, - { Py_tp_methods, PyService_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Session", Session, GObject, PySession_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Session" }, - { Py_tp_init, PySession_init }, - { Py_tp_repr, PySession_repr }, - { Py_tp_methods, PySession_methods }, - { Py_tp_members, PySession_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Script", Script, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Script" }, - { Py_tp_methods, PyScript_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Relay", Relay, GObject, PyRelay_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Relay" }, - { Py_tp_init, PyRelay_init }, - { Py_tp_dealloc, PyRelay_dealloc }, - { Py_tp_repr, PyRelay_repr }, - { Py_tp_members, PyRelay_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PortalMembership", PortalMembership, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Portal Membership" }, - { Py_tp_methods, PyPortalMembership_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PortalService", PortalService, GObject, PyPortalService_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Portal Service" }, - { Py_tp_init, PyPortalService_init }, - { Py_tp_dealloc, PyPortalService_dealloc }, - { Py_tp_methods, PyPortalService_methods }, - { Py_tp_members, PyPortalService_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.EndpointParameters", EndpointParameters, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida EndpointParameters" }, - { Py_tp_init, PyEndpointParameters_init }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Compiler", Compiler, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Compiler" }, - { Py_tp_init, PyCompiler_init }, - { Py_tp_dealloc, PyCompiler_dealloc }, - { Py_tp_methods, PyCompiler_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageManager", PackageManager, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Package Manager" }, - { Py_tp_init, PyPackageManager_init }, - { Py_tp_dealloc, PyPackageManager_dealloc }, - { Py_tp_repr, PyPackageManager_repr }, - { Py_tp_getset, PyPackageManager_getset }, - { Py_tp_methods, PyPackageManager_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Package", Package, GObject, PyPackage_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package" }, - { Py_tp_init, PyPackage_init }, - { Py_tp_dealloc, PyPackage_dealloc }, - { Py_tp_repr, PyPackage_repr }, - { Py_tp_members, PyPackage_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageSearchResult", PackageSearchResult, GObject, PyPackageSearchResult_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package Search Result" }, - { Py_tp_init, PyPackageSearchResult_init }, - { Py_tp_dealloc, PyPackageSearchResult_dealloc }, - { Py_tp_repr, PyPackageSearchResult_repr }, - { Py_tp_members, PyPackageSearchResult_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageInstallResult", PackageInstallResult, GObject, PyPackageInstallResult_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package Install Result" }, - { Py_tp_init, PyPackageInstallResult_init }, - { Py_tp_dealloc, PyPackageInstallResult_dealloc }, - { Py_tp_repr, PyPackageInstallResult_repr }, - { Py_tp_members, PyPackageInstallResult_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.FileMonitor", FileMonitor, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida File Monitor" }, - { Py_tp_init, PyFileMonitor_init }, - { Py_tp_dealloc, PyFileMonitor_dealloc }, - { Py_tp_methods, PyFileMonitor_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.IOStream", IOStream, GObject, PyIOStream_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida IOStream" }, - { Py_tp_init, PyIOStream_init }, - { Py_tp_repr, PyIOStream_repr }, - { Py_tp_methods, PyIOStream_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Cancellable", Cancellable, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Cancellable" }, - { Py_tp_init, PyCancellable_init }, - { Py_tp_repr, PyCancellable_repr }, - { Py_tp_methods, PyCancellable_methods }, -); - - -static PyObject * -PyGObject_new_take_handle (gpointer handle, const PyGObjectType * pytype) -{ - PyObject * object; - - if (handle == NULL) - PyFrida_RETURN_NONE; - - object = PyGObject_try_get_from_handle (handle); - if (object == NULL) - { - object = PyObject_CallFunction (pytype->object, NULL); - PyGObject_take_handle (PY_GOBJECT (object), handle, pytype); - - if (pytype->init_from_handle != NULL) - pytype->init_from_handle (object, handle); - } - else - { - pytype->destroy (handle); - Py_IncRef (object); - } - - return object; -} - -static PyObject * -PyGObject_try_get_from_handle (gpointer handle) -{ - return g_object_get_data (handle, "pyobject"); -} - -static int -PyGObject_init (PyGObject * self) -{ - self->handle = NULL; - self->type = PYFRIDA_TYPE (GObject); - - self->signal_closures = NULL; - - return 0; -} - -static void -PyGObject_dealloc (PyGObject * self) -{ - gpointer handle; - - handle = PyGObject_steal_handle (self); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - self->type->destroy (handle); - Py_END_ALLOW_THREADS - } - - ((freefunc) PyType_GetSlot (Py_TYPE (self), Py_tp_free)) (self); -} - -static void -PyGObject_take_handle (PyGObject * self, gpointer handle, const PyGObjectType * type) -{ - self->handle = handle; - self->type = type; - - if (handle != NULL) - g_object_set_data (G_OBJECT (handle), "pyobject", self); -} - -static gpointer -PyGObject_steal_handle (PyGObject * self) -{ - gpointer handle = self->handle; - GSList * entry; - - if (handle == NULL) - return NULL; - - for (entry = self->signal_closures; entry != NULL; entry = entry->next) - { - PyGObjectSignalClosure * closure = entry->data; - G_GNUC_UNUSED guint num_matches; - - num_matches = g_signal_handlers_disconnect_matched (handle, G_SIGNAL_MATCH_CLOSURE, closure->signal_id, 0, &closure->parent, NULL, NULL); - g_assert (num_matches == 1); - } - g_clear_pointer (&self->signal_closures, g_slist_free); - - g_object_set_data (G_OBJECT (handle), "pyobject", NULL); - - self->handle = NULL; - - return handle; -} - -static PyObject * -PyGObject_on (PyGObject * self, PyObject * args) -{ - GType instance_type; - guint signal_id; - PyObject * callback; - guint max_arg_count, allowed_arg_count_including_sender; - GSignalQuery query; - GClosure * closure; - - instance_type = G_OBJECT_TYPE (self->handle); - - if (!PyGObject_parse_signal_method_args (args, instance_type, &signal_id, &callback)) - return NULL; - - max_arg_count = PyFrida_get_max_argument_count (callback); - if (max_arg_count != G_MAXUINT) - { - g_signal_query (signal_id, &query); - - allowed_arg_count_including_sender = 1 + query.n_params; - - if (max_arg_count > allowed_arg_count_including_sender) - goto too_many_arguments; - } - - closure = PyGObject_make_closure_for_signal (signal_id, callback, max_arg_count); - g_signal_connect_closure_by_id (self->handle, signal_id, 0, closure, TRUE); - - self->signal_closures = g_slist_prepend (self->signal_closures, closure); - - PyFrida_RETURN_NONE; - -too_many_arguments: - { - return PyErr_Format (PyExc_TypeError, - "callback expects too many arguments, the '%s' signal only has %u but callback expects %u", - g_signal_name (signal_id), query.n_params, max_arg_count); - } -} - -static PyObject * -PyGObject_off (PyGObject * self, PyObject * args) -{ - guint signal_id; - PyObject * callback; - GSList * entry; - GClosure * closure; - G_GNUC_UNUSED guint num_matches; - - if (!PyGObject_parse_signal_method_args (args, G_OBJECT_TYPE (self->handle), &signal_id, &callback)) - return NULL; - - entry = g_slist_find_custom (self->signal_closures, callback, (GCompareFunc) PyGObject_compare_signal_closure_callback); - if (entry == NULL) - goto unknown_callback; - - closure = entry->data; - self->signal_closures = g_slist_delete_link (self->signal_closures, entry); - - num_matches = g_signal_handlers_disconnect_matched (self->handle, G_SIGNAL_MATCH_CLOSURE, signal_id, 0, closure, NULL, NULL); - g_assert (num_matches == 1); - - PyFrida_RETURN_NONE; - -unknown_callback: - { - PyErr_SetString (PyExc_ValueError, "unknown callback"); - return NULL; - } -} - -static gint -PyGObject_compare_signal_closure_callback (PyGObjectSignalClosure * closure, - PyObject * callback) -{ - int result; - - result = PyObject_RichCompareBool (closure->parent.data, callback, Py_EQ); - - return (result == 1) ? 0 : -1; -} - -static gboolean -PyGObject_parse_signal_method_args (PyObject * args, GType instance_type, guint * signal_id, PyObject ** callback) -{ - const gchar * signal_name; - - if (!PyArg_ParseTuple (args, "sO", &signal_name, callback)) - return FALSE; - - if (!PyCallable_Check (*callback)) - { - PyErr_SetString (PyExc_TypeError, "second argument must be callable"); - return FALSE; - } - - *signal_id = g_signal_lookup (signal_name, instance_type); - if (*signal_id == 0) - goto invalid_signal_name; - - return TRUE; - -invalid_signal_name: - { - GString * message; - guint * ids, n_ids, i; - - message = g_string_sized_new (128); - - g_string_append (message, PyGObject_class_name_from_c (g_type_name (instance_type))); - - ids = g_signal_list_ids (instance_type, &n_ids); - - if (n_ids > 0) - { - g_string_append_printf (message, " does not have a signal named '%s', it only has: ", signal_name); - - for (i = 0; i != n_ids; i++) - { - if (i != 0) - g_string_append (message, ", "); - g_string_append_c (message, '\''); - g_string_append (message, g_signal_name (ids[i])); - g_string_append_c (message, '\''); - } - } - else - { - g_string_append (message, " does not have any signals"); - } - - g_free (ids); - - PyErr_SetString (PyExc_ValueError, message->str); - - g_string_free (message, TRUE); - - return FALSE; - } -} - -static const gchar * -PyGObject_class_name_from_c (const gchar * cname) -{ - if (g_str_has_prefix (cname, "Frida")) - return cname + 5; - - return cname; -} - -static void -PyGObject_class_init (void) -{ - pygobject_type_spec_by_type = g_hash_table_new_full (NULL, NULL, NULL, NULL); -} - -static void -PyGObject_register_type (GType instance_type, PyGObjectType * python_type) -{ - g_hash_table_insert (pygobject_type_spec_by_type, GSIZE_TO_POINTER (instance_type), python_type); -} - -static GClosure * -PyGObject_make_closure_for_signal (guint signal_id, PyObject * callback, guint max_arg_count) -{ - GClosure * closure; - PyGObjectSignalClosure * pyclosure; - - closure = g_closure_new_simple (sizeof (PyGObjectSignalClosure), callback); - Py_IncRef (callback); - - g_closure_add_finalize_notifier (closure, callback, (GClosureNotify) PyGObjectSignalClosure_finalize); - g_closure_set_marshal (closure, PyGObjectSignalClosure_marshal); - - pyclosure = PY_GOBJECT_SIGNAL_CLOSURE (closure); - pyclosure->signal_id = signal_id; - pyclosure->max_arg_count = max_arg_count; - - return closure; -} - -static void -PyGObjectSignalClosure_finalize (PyObject * callback) -{ - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - Py_DecRef (callback); - PyGILState_Release (gstate); -} - -static void -PyGObjectSignalClosure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, - gpointer invocation_hint, gpointer marshal_data) -{ - PyGObjectSignalClosure * self = PY_GOBJECT_SIGNAL_CLOSURE (closure); - PyObject * callback = closure->data; - PyGILState_STATE gstate; - PyObject * args, * result; - - (void) return_gvalue; - (void) invocation_hint; - (void) marshal_data; - - if (g_atomic_int_get (&toplevel_objects_alive) == 0) - return; - - gstate = PyGILState_Ensure (); - - if (PyGObject_try_get_from_handle (g_value_get_object (¶m_values[0])) == NULL) - goto beach; - - if (self->max_arg_count == n_param_values) - args = PyGObjectSignalClosure_marshal_params (param_values, n_param_values); - else - args = PyGObjectSignalClosure_marshal_params (param_values + 1, MIN (n_param_values - 1, self->max_arg_count)); - if (args == NULL) - { - PyErr_Print (); - goto beach; - } - - result = PyObject_CallObject (callback, args); - if (result != NULL) - Py_DecRef (result); - else - PyErr_Print (); - - Py_DecRef (args); - -beach: - PyGILState_Release (gstate); -} - -static PyObject * -PyGObjectSignalClosure_marshal_params (const GValue * params, guint params_length) -{ - PyObject * args; - guint i; - - args = PyTuple_New (params_length); - - for (i = 0; i != params_length; i++) - { - PyObject * arg; - - arg = PyGObject_marshal_value (¶ms[i]); - if (arg == NULL) - goto marshal_error; - - PyTuple_SetItem (args, i, arg); - } - - return args; - -marshal_error: - { - Py_DecRef (args); - return NULL; - } -} - -static PyObject * -PyGObject_marshal_value (const GValue * value) -{ - GType type; - - type = G_VALUE_TYPE (value); - - switch (type) - { - case G_TYPE_BOOLEAN: - return PyBool_FromLong (g_value_get_boolean (value)); - - case G_TYPE_INT: - return PyLong_FromLong (g_value_get_int (value)); - - case G_TYPE_UINT: - return PyLong_FromUnsignedLong (g_value_get_uint (value)); - - case G_TYPE_FLOAT: - return PyFloat_FromDouble (g_value_get_float (value)); - - case G_TYPE_DOUBLE: - return PyFloat_FromDouble (g_value_get_double (value)); - - case G_TYPE_STRING: - return PyGObject_marshal_string (g_value_get_string (value)); - - case G_TYPE_VARIANT: - return PyGObject_marshal_variant (g_value_get_variant (value)); - - default: - if (G_TYPE_IS_ENUM (type)) - return PyGObject_marshal_enum (g_value_get_enum (value), type); - - if (type == G_TYPE_BYTES) - return PyGObject_marshal_bytes (g_value_get_boxed (value)); - - if (G_TYPE_IS_OBJECT (type)) - return PyGObject_marshal_object (g_value_get_object (value), type); - - goto unsupported_type; - } - - g_assert_not_reached (); - -unsupported_type: - { - return PyErr_Format (PyExc_NotImplementedError, - "unsupported type: '%s'", - g_type_name (type)); - } -} - -static PyObject * -PyGObject_marshal_string (const gchar * str) -{ - if (str == NULL) - PyFrida_RETURN_NONE; - - return PyUnicode_FromString (str); -} - -static gboolean -PyGObject_unmarshal_string (PyObject * value, gchar ** str) -{ - PyObject * bytes; - - *str = NULL; - - bytes = PyUnicode_AsUTF8String (value); - if (bytes == NULL) - return FALSE; - - *str = g_strdup (PyBytes_AsString (bytes)); - - Py_DecRef (bytes); - - return *str != NULL; -} - -static PyObject * -PyGObject_marshal_datetime (const gchar * iso8601_text) -{ - PyObject * result; - GDateTime * raw_dt, * dt; - - raw_dt = g_date_time_new_from_iso8601 (iso8601_text, NULL); - if (raw_dt == NULL) - PyFrida_RETURN_NONE; - - dt = g_date_time_to_local (raw_dt); - - result = PyObject_CallFunction (datetime_constructor, "iiiiiii", - g_date_time_get_year (dt), - g_date_time_get_month (dt), - g_date_time_get_day_of_month (dt), - g_date_time_get_hour (dt), - g_date_time_get_minute (dt), - g_date_time_get_second (dt), - g_date_time_get_microsecond (dt)); - - g_date_time_unref (dt); - g_date_time_unref (raw_dt); - - return result; -} - -static PyObject * -PyGObject_marshal_strv (gchar * const * strv, gint length) -{ - PyObject * result; - gint i; - - if (strv == NULL) - PyFrida_RETURN_NONE; - - result = PyList_New (length); - - for (i = 0; i != length; i++) - { - PyList_SetItem (result, i, PyGObject_marshal_string (strv[i])); - } - - return result; -} - -static gboolean -PyGObject_unmarshal_strv (PyObject * value, gchar *** strv, gint * length) -{ - gint n, i; - gchar ** elements; - - if (!PyList_Check (value) && !PyTuple_Check (value)) - goto invalid_type; - - n = PySequence_Size (value); - elements = g_new0 (gchar *, n + 1); - - for (i = 0; i != n; i++) - { - PyObject * element; - - element = PySequence_GetItem (value, i); - if (PyUnicode_Check (element)) - { - Py_DecRef (element); - element = PyUnicode_AsUTF8String (element); - } - if (PyBytes_Check (element)) - elements[i] = g_strdup (PyBytes_AsString (element)); - Py_DecRef (element); - - if (elements[i] == NULL) - goto invalid_element; - } - - *strv = elements; - *length = n; - - return TRUE; - -invalid_type: - { - PyErr_SetString (PyExc_TypeError, "expected list or tuple of strings"); - return FALSE; - } -invalid_element: - { - g_strfreev (elements); - - PyErr_SetString (PyExc_TypeError, "expected list or tuple with string elements only"); - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_envp (gchar * const * envp, gint length) -{ - PyObject * result; - gint i; - - if (envp == NULL) - PyFrida_RETURN_NONE; - - result = PyDict_New (); - - for (i = 0; i != length; i++) - { - gchar ** tokens; - - tokens = g_strsplit (envp[i], "=", 2); - - if (g_strv_length (tokens) == 2) - { - const gchar * name; - PyObject * value; - - name = tokens[0]; - value = PyGObject_marshal_string (tokens[1]); - - PyDict_SetItemString (result, name, value); - - Py_DecRef (value); - } - - g_strfreev (tokens); - } - - return result; -} - -static gboolean -PyGObject_unmarshal_envp (PyObject * dict, gchar *** envp, gint * length) -{ - gint n; - gchar ** elements; - gint i; - Py_ssize_t pos; - PyObject * name, * value; - gchar * raw_name = NULL; - gchar * raw_value = NULL; - - if (!PyDict_Check (dict)) - goto invalid_type; - - n = PyDict_Size (dict); - elements = g_new0 (gchar *, n + 1); - - i = 0; - pos = 0; - while (PyDict_Next (dict, &pos, &name, &value)) - { - if (!PyGObject_unmarshal_string (name, &raw_name)) - goto invalid_dict_key; - - if (!PyGObject_unmarshal_string (value, &raw_value)) - goto invalid_dict_value; - - elements[i] = g_strconcat (raw_name, "=", raw_value, NULL); - - g_free (g_steal_pointer (&raw_value)); - g_free (g_steal_pointer (&raw_name)); - - i++; - } - - *envp = elements; - *length = n; - - return TRUE; - -invalid_type: - { - PyErr_SetString (PyExc_TypeError, "expected dict"); - return FALSE; - } -invalid_dict_key: -invalid_dict_value: - { - g_free (raw_value); - g_free (raw_name); - g_strfreev (elements); - - PyErr_SetString (PyExc_TypeError, "expected dict with strings only"); - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_enum (gint value, GType type) -{ - GEnumClass * enum_class; - GEnumValue * enum_value; - PyObject * result; - - enum_class = g_type_class_ref (type); - - enum_value = g_enum_get_value (enum_class, value); - g_assert (enum_value != NULL); - - result = PyUnicode_FromString (enum_value->value_nick); - - g_type_class_unref (enum_class); - - return result; -} - -static gboolean -PyGObject_unmarshal_enum (const gchar * str, GType type, gpointer value) -{ - GEnumClass * enum_class; - GEnumValue * enum_value; - - enum_class = g_type_class_ref (type); - - enum_value = g_enum_get_value_by_nick (enum_class, str); - if (enum_value == NULL) - goto invalid_value; - - *((gint *) value) = enum_value->value; - - g_type_class_unref (enum_class); - - return TRUE; - -invalid_value: - { - GString * message; - guint i; - - message = g_string_sized_new (128); - - g_string_append_printf (message, - "Enum type %s does not have a value named '%s', it only has: ", - PyGObject_class_name_from_c (g_type_name (type)), str); - - for (i = 0; i != enum_class->n_values; i++) - { - if (i != 0) - g_string_append (message, ", "); - g_string_append_c (message, '\''); - g_string_append (message, enum_class->values[i].value_nick); - g_string_append_c (message, '\''); - } - - PyErr_SetString (PyExc_ValueError, message->str); - - g_string_free (message, TRUE); - - g_type_class_unref (enum_class); - - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_bytes (GBytes * bytes) -{ - if (bytes == NULL) - PyFrida_RETURN_NONE; - - return PyGObject_marshal_bytes_non_nullable (bytes); -} - -static PyObject * -PyGObject_marshal_bytes_non_nullable (GBytes * bytes) -{ - gconstpointer data; - gsize size; - - data = g_bytes_get_data (bytes, &size); - - return PyBytes_FromStringAndSize (data, size); -} - -static PyObject * -PyGObject_marshal_variant (GVariant * variant) -{ - switch (g_variant_classify (variant)) - { - case G_VARIANT_CLASS_STRING: - return PyGObject_marshal_string (g_variant_get_string (variant, NULL)); - case G_VARIANT_CLASS_INT64: - return PyLong_FromLongLong (g_variant_get_int64 (variant)); - case G_VARIANT_CLASS_UINT64: - return PyLong_FromLongLong (g_variant_get_uint64 (variant)); - case G_VARIANT_CLASS_DOUBLE: - return PyFloat_FromDouble (g_variant_get_double (variant)); - case G_VARIANT_CLASS_BOOLEAN: - return PyBool_FromLong (g_variant_get_boolean (variant)); - case G_VARIANT_CLASS_ARRAY: - if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) - return PyGObject_marshal_variant_byte_array (variant); - - if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)) - return PyGObject_marshal_variant_dict (variant); - - return PyGObject_marshal_variant_array (variant); - default: - break; - } - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyGObject_marshal_variant_byte_array (GVariant * variant) -{ - gconstpointer elements; - gsize n_elements; - - elements = g_variant_get_fixed_array (variant, &n_elements, sizeof (guint8)); - - return PyBytes_FromStringAndSize (elements, n_elements); -} - -static PyObject * -PyGObject_marshal_variant_dict (GVariant * variant) -{ - PyObject * dict; - GVariantIter iter; - gchar * key; - GVariant * raw_value; - - dict = PyDict_New (); - - g_variant_iter_init (&iter, variant); - - while (g_variant_iter_next (&iter, "{sv}", &key, &raw_value)) - { - PyObject * value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (dict, key, value); - - Py_DecRef (value); - g_variant_unref (raw_value); - g_free (key); - } - - return dict; -} - -static PyObject * -PyGObject_marshal_variant_array (GVariant * variant) -{ - GVariantIter iter; - PyObject * list; - guint i; - GVariant * child; - - g_variant_iter_init (&iter, variant); - - list = PyList_New (g_variant_iter_n_children (&iter)); - - for (i = 0; (child = g_variant_iter_next_value (&iter)) != NULL; i++) - { - if (g_variant_is_of_type (child, G_VARIANT_TYPE_VARIANT)) - { - GVariant * inner = g_variant_get_variant (child); - g_variant_unref (child); - child = inner; - } - - PyList_SetItem (list, i, PyGObject_marshal_variant (child)); - - g_variant_unref (child); - } - - return list; -} - -static gboolean -PyGObject_unmarshal_variant (PyObject * value, GVariant ** variant) -{ - if (PyUnicode_Check (value)) - { - gchar * str; - - PyGObject_unmarshal_string (value, &str); - - *variant = g_variant_new_take_string (str); - - return TRUE; - } - - if (PyBool_Check (value)) - { - *variant = g_variant_new_boolean (value == Py_True); - - return TRUE; - } - - if (PyLong_Check (value)) - { - PY_LONG_LONG l; - - l = PyLong_AsLongLong (value); - if (l == -1 && PyErr_Occurred ()) - return FALSE; - - *variant = g_variant_new_int64 (l); - - return TRUE; - } - - if (PyFloat_Check (value)) - { - *variant = g_variant_new_double (PyFloat_AsDouble (value)); - - return TRUE; - } - - if (PyBytes_Check (value)) - { - char * buffer; - Py_ssize_t length; - gpointer copy; - - PyBytes_AsStringAndSize (value, &buffer, &length); - - copy = g_memdup2 (buffer, length); - *variant = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING, copy, length, TRUE, g_free, copy); - - return TRUE; - } - - if (PySequence_Check (value)) - return PyGObject_unmarshal_variant_from_sequence (value, variant); - - if (PyMapping_Check (value)) - return PyGObject_unmarshal_variant_from_mapping (value, variant); - - PyErr_SetString (PyExc_TypeError, "unsupported type"); - return FALSE; -} - -static gboolean -PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, GVariant ** variant) -{ - GVariantBuilder builder; - PyObject * items = NULL; - Py_ssize_t n, i; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - - items = PyMapping_Items (mapping); - if (items == NULL) - goto propagate_error; - - n = PyList_Size (items); - - for (i = 0; i != n; i++) - { - PyObject * pair, * key, * val, * key_bytes; - GVariant * raw_value; - - pair = PyList_GetItem (items, i); - key = PyTuple_GetItem (pair, 0); - val = PyTuple_GetItem (pair, 1); - - if (!PyGObject_unmarshal_variant (val, &raw_value)) - goto propagate_error; - - key_bytes = PyUnicode_AsUTF8String (key); - - g_variant_builder_add (&builder, "{sv}", PyBytes_AsString (key_bytes), raw_value); - - Py_DecRef (key_bytes); - } - - Py_DecRef (items); - - *variant = g_variant_builder_end (&builder); - - return TRUE; - -propagate_error: - { - Py_DecRef (items); - g_variant_builder_clear (&builder); - - return FALSE; - } -} - -static gboolean -PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, GVariant ** variant) -{ - gboolean is_tuple; - GVariantBuilder builder; - Py_ssize_t n, i; - PyObject * val = NULL; - - is_tuple = PyTuple_Check (sequence); - - g_variant_builder_init (&builder, is_tuple ? G_VARIANT_TYPE_TUPLE : G_VARIANT_TYPE ("av")); - - n = PySequence_Length (sequence); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - GVariant * raw_value; - - val = PySequence_GetItem (sequence, i); - if (val == NULL) - goto propagate_error; - - if (!PyGObject_unmarshal_variant (val, &raw_value)) - goto propagate_error; - - if (is_tuple) - g_variant_builder_add_value (&builder, raw_value); - else - g_variant_builder_add (&builder, "v", raw_value); - - Py_DecRef (val); - } - - *variant = g_variant_builder_end (&builder); - - return TRUE; - -propagate_error: - { - Py_DecRef (val); - g_variant_builder_clear (&builder); - - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - -static PyObject * -PyGObject_marshal_object (gpointer handle, GType type) -{ - const PyGObjectType * pytype; - - if (handle == NULL) - PyFrida_RETURN_NONE; - - pytype = g_hash_table_lookup (pygobject_type_spec_by_type, GSIZE_TO_POINTER (type)); - if (pytype == NULL) - pytype = PYFRIDA_TYPE (GObject); - - if (G_IS_SOCKET_ADDRESS (handle)) - return PyGObject_marshal_socket_address (handle); - - return PyGObject_new_take_handle (g_object_ref (handle), pytype); -} - -static PyObject * -PyGObject_marshal_socket_address (GSocketAddress * address) -{ - PyObject * result = NULL; - - if (G_IS_INET_SOCKET_ADDRESS (address)) - { - GInetSocketAddress * sa; - GInetAddress * ia; - gchar * host; - guint16 port; - - sa = G_INET_SOCKET_ADDRESS (address); - ia = g_inet_socket_address_get_address (sa); - - host = g_inet_address_to_string (ia); - port = g_inet_socket_address_get_port (sa); - - if (g_socket_address_get_family (address) == G_SOCKET_FAMILY_IPV4) - result = Py_BuildValue ("(sH)", host, port); - else - result = Py_BuildValue ("(sHII)", host, port, g_inet_socket_address_get_flowinfo (sa), g_inet_socket_address_get_scope_id (sa)); - - g_free (host); - } - else if (G_IS_UNIX_SOCKET_ADDRESS (address)) - { - GUnixSocketAddress * sa = G_UNIX_SOCKET_ADDRESS (address); - - switch (g_unix_socket_address_get_address_type (sa)) - { - case G_UNIX_SOCKET_ADDRESS_ANONYMOUS: - { - result = PyUnicode_FromString (""); - break; - } - case G_UNIX_SOCKET_ADDRESS_PATH: - { - gchar * path = g_filename_to_utf8 (g_unix_socket_address_get_path (sa), -1, NULL, NULL, NULL); - result = PyUnicode_FromString (path); - g_free (path); - break; - } - case G_UNIX_SOCKET_ADDRESS_ABSTRACT: - case G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED: - { - result = PyBytes_FromStringAndSize (g_unix_socket_address_get_path (sa), g_unix_socket_address_get_path_len (sa)); - break; - } - default: - { - Py_IncRef (Py_None); - result = Py_None; - break; - } - } - } - - if (result == NULL) - result = PyGObject_new_take_handle (g_object_ref (address), PYFRIDA_TYPE (GObject)); - - return result; -} - -static gboolean -PyGObject_unmarshal_certificate (const gchar * str, GTlsCertificate ** certificate) -{ - GError * error = NULL; - - if (strchr (str, '\n') != NULL) - *certificate = g_tls_certificate_new_from_pem (str, -1, &error); - else - *certificate = g_tls_certificate_new_from_file (str, &error); - if (error != NULL) - goto propagate_error; - - return TRUE; - -propagate_error: - { - PyFrida_raise (g_error_new_literal (FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, error->message)); - g_error_free (error); - - return FALSE; - } -} - - -static int -PyDeviceManager_init (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_device_manager_new (), PYFRIDA_TYPE (DeviceManager)); - - return 0; -} - -static void -PyDeviceManager_dealloc (PyDeviceManager * self) -{ - FridaDeviceManager * handle; - - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - handle = PyGObject_steal_handle (&self->parent); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - frida_device_manager_close_sync (handle, NULL, NULL); - frida_unref (handle); - Py_END_ALLOW_THREADS - } - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyDeviceManager_close (PyDeviceManager * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_manager_close_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDeviceManager_get_device_matching (PyDeviceManager * self, PyObject * args) -{ - PyObject * predicate; - gint timeout; - GError * error = NULL; - FridaDevice * result; - - if (!PyArg_ParseTuple (args, "Oi", &predicate, &timeout)) - return NULL; - - if (!PyCallable_Check (predicate)) - goto not_callable; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_manager_get_device_sync (PY_GOBJECT_HANDLE (self), (FridaDeviceManagerPredicate) PyDeviceManager_is_matching_device, - predicate, timeout, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyDevice_new_take_handle (result); - -not_callable: - { - PyErr_SetString (PyExc_TypeError, "object must be callable"); - return NULL; - } -} - -static gboolean -PyDeviceManager_is_matching_device (FridaDevice * device, PyObject * predicate) -{ - gboolean is_matching = FALSE; - PyGILState_STATE gstate; - PyObject * device_object, * result; - - gstate = PyGILState_Ensure (); - - device_object = PyDevice_new_take_handle (g_object_ref (device)); - - result = PyObject_CallFunction (predicate, "O", device_object); - if (result != NULL) - { - is_matching = result == Py_True; - - Py_DecRef (result); - } - else - { - PyErr_Print (); - } - - Py_DecRef (device_object); - - PyGILState_Release (gstate); - - return is_matching; -} - -static PyObject * -PyDeviceManager_enumerate_devices (PyDeviceManager * self) -{ - GError * error = NULL; - FridaDeviceList * result; - gint result_length, i; - PyObject * devices; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_manager_enumerate_devices_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_device_list_size (result); - devices = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (devices, i, PyDevice_new_take_handle (frida_device_list_get (result, i))); - } - frida_unref (result); - - return devices; -} - -static PyObject * -PyDeviceManager_add_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "address", "certificate", "origin", "token", "keepalive_interval", NULL }; - char * address; - char * certificate = NULL; - char * origin = NULL; - char * token = NULL; - int keepalive_interval = -1; - FridaRemoteDeviceOptions * options; - GError * error = NULL; - FridaDevice * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esesesi", keywords, - "utf-8", &address, - "utf-8", &certificate, - "utf-8", &origin, - "utf-8", &token, - &keepalive_interval)) - return NULL; - - options = PyDeviceManager_parse_remote_device_options (certificate, origin, token, keepalive_interval); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_device_manager_add_remote_device_sync (PY_GOBJECT_HANDLE (self), address, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyDevice_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (token); - PyMem_Free (origin); - PyMem_Free (certificate); - PyMem_Free (address); - - return result; -} - -static PyObject * -PyDeviceManager_remove_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "address", NULL }; - char * address; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es", keywords, "utf-8", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_manager_remove_remote_device_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - PyMem_Free (address); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static FridaRemoteDeviceOptions * -PyDeviceManager_parse_remote_device_options (const gchar * certificate_value, const gchar * origin, const gchar * token, - gint keepalive_interval) -{ - FridaRemoteDeviceOptions * options; - - options = frida_remote_device_options_new (); - - if (certificate_value != NULL) - { - GTlsCertificate * certificate; - - if (!PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto propagate_error; - - frida_remote_device_options_set_certificate (options, certificate); - - g_object_unref (certificate); - } - - if (origin != NULL) - frida_remote_device_options_set_origin (options, origin); - - if (token != NULL) - frida_remote_device_options_set_token (options, token); - - if (keepalive_interval != -1) - frida_remote_device_options_set_keepalive_interval (options, keepalive_interval); - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyDevice_new_take_handle (FridaDevice * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Device)); -} - -static int -PyDevice_init (PyDevice * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->id = NULL; - self->name = NULL; - self->icon = NULL; - self->type = NULL; - self->bus = NULL; - - return 0; -} - -static void -PyDevice_init_from_handle (PyDevice * self, FridaDevice * handle) -{ - GVariant * icon; - - self->id = PyUnicode_FromString (frida_device_get_id (handle)); - self->name = PyUnicode_FromString (frida_device_get_name (handle)); - icon = frida_device_get_icon (handle); - if (icon != NULL) - { - self->icon = PyGObject_marshal_variant (icon); - } - else - { - self->icon = Py_None; - Py_IncRef (Py_None); - } - self->type = PyGObject_marshal_enum (frida_device_get_dtype (handle), FRIDA_TYPE_DEVICE_TYPE); - self->bus = PyBus_new_take_handle (g_object_ref (frida_device_get_bus (handle))); -} - -static void -PyDevice_dealloc (PyDevice * self) -{ - Py_DecRef (self->bus); - Py_DecRef (self->type); - Py_DecRef (self->icon); - Py_DecRef (self->name); - Py_DecRef (self->id); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyDevice_repr (PyDevice * self) -{ - PyObject * id_bytes, * name_bytes, * type_bytes, * result; - - id_bytes = PyUnicode_AsUTF8String (self->id); - name_bytes = PyUnicode_AsUTF8String (self->name); - type_bytes = PyUnicode_AsUTF8String (self->type); - - result = PyUnicode_FromFormat ("Device(id=\"%s\", name=\"%s\", type='%s')", - PyBytes_AsString (id_bytes), - PyBytes_AsString (name_bytes), - PyBytes_AsString (type_bytes)); - - Py_DecRef (type_bytes); - Py_DecRef (name_bytes); - Py_DecRef (id_bytes); - - return result; -} - -static PyObject * -PyDevice_is_lost (PyDevice * self) -{ - gboolean is_lost; - - Py_BEGIN_ALLOW_THREADS - is_lost = frida_device_is_lost (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_lost); -} - -static PyObject * -PyDevice_query_system_parameters (PyDevice * self) -{ - GError * error = NULL; - GHashTable * result; - PyObject * parameters; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_query_system_parameters_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - parameters = PyGObject_marshal_parameters_dict (result); - g_hash_table_unref (result); - - return parameters; -} - -static PyObject * -PyDevice_get_frontmost_application (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "scope", NULL }; - const char * scope_value = NULL; - FridaFrontmostQueryOptions * options; - GError * error = NULL; - FridaApplication * result; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|s", keywords, &scope_value)) - return NULL; - - options = frida_frontmost_query_options_new (); - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto invalid_argument; - - frida_frontmost_query_options_set_scope (options, scope); - } - - Py_BEGIN_ALLOW_THREADS - result = frida_device_get_frontmost_application_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - if (result != NULL) - return PyApplication_new_take_handle (result); - else - PyFrida_RETURN_NONE; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enumerate_applications (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "identifiers", "scope", NULL }; - PyObject * identifiers = NULL; - const char * scope = NULL; - FridaApplicationQueryOptions * options; - GError * error = NULL; - FridaApplicationList * result; - gint result_length, i; - PyObject * applications; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|Os", keywords, &identifiers, &scope)) - return NULL; - - options = PyDevice_parse_application_query_options (identifiers, scope); - if (options == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_applications_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_application_list_size (result); - applications = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (applications, i, PyApplication_new_take_handle (frida_application_list_get (result, i))); - } - g_object_unref (result); - - return applications; -} - -static FridaApplicationQueryOptions * -PyDevice_parse_application_query_options (PyObject * identifiers_value, const gchar * scope_value) -{ - FridaApplicationQueryOptions * options; - - options = frida_application_query_options_new (); - - if (identifiers_value != NULL) - { - gint n, i; - - n = PySequence_Size (identifiers_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * identifier = NULL; - - element = PySequence_GetItem (identifiers_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &identifier); - Py_DecRef (element); - if (identifier == NULL) - goto propagate_error; - - frida_application_query_options_select_identifier (options, identifier); - - g_free (identifier); - } - } - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto propagate_error; - - frida_application_query_options_set_scope (options, scope); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enumerate_processes (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "pids", "scope", NULL }; - PyObject * pids = NULL; - const char * scope = NULL; - FridaProcessQueryOptions * options; - GError * error = NULL; - FridaProcessList * result; - gint result_length, i; - PyObject * processes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|Os", keywords, &pids, &scope)) - return NULL; - - options = PyDevice_parse_process_query_options (pids, scope); - if (options == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_processes_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_process_list_size (result); - processes = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (processes, i, PyProcess_new_take_handle (frida_process_list_get (result, i))); - } - g_object_unref (result); - - return processes; -} - -static FridaProcessQueryOptions * -PyDevice_parse_process_query_options (PyObject * pids_value, const gchar * scope_value) -{ - FridaProcessQueryOptions * options; - - options = frida_process_query_options_new (); - - if (pids_value != NULL) - { - gint n, i; - - n = PySequence_Size (pids_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - long long pid; - - element = PySequence_GetItem (pids_value, i); - if (element == NULL) - goto propagate_error; - pid = PyLong_AsLongLong (element); - Py_DecRef (element); - if (pid == -1) - goto propagate_error; - - frida_process_query_options_select_pid (options, pid); - } - } - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto propagate_error; - - frida_process_query_options_set_scope (options, scope); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enable_spawn_gating (PyDevice * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_enable_spawn_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_disable_spawn_gating (PyDevice * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_disable_spawn_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_enumerate_pending_spawn (PyDevice * self) -{ - GError * error = NULL; - FridaSpawnList * result; - gint result_length, i; - PyObject * spawn; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_pending_spawn_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_spawn_list_size (result); - spawn = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (spawn, i, PySpawn_new_take_handle (frida_spawn_list_get (result, i))); - } - g_object_unref (result); - - return spawn; -} - -static PyObject * -PyDevice_enumerate_pending_children (PyDevice * self) -{ - GError * error = NULL; - FridaChildList * result; - gint result_length, i; - PyObject * children; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_pending_children_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_child_list_size (result); - children = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (children, i, PyChild_new_take_handle (frida_child_list_get (result, i))); - } - g_object_unref (result); - - return children; -} - -static PyObject * -PyDevice_spawn (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "program", "argv", "envp", "env", "cwd", "stdio", "aux", NULL }; - const char * program; - PyObject * argv_value = Py_None; - PyObject * envp_value = Py_None; - PyObject * env_value = Py_None; - const char * cwd = NULL; - const char * stdio_value = NULL; - PyObject * aux_value = Py_None; - FridaSpawnOptions * options; - GError * error = NULL; - guint pid; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|OOOzzO", keywords, - &program, - &argv_value, - &envp_value, - &env_value, - &cwd, - &stdio_value, - &aux_value)) - return NULL; - - options = frida_spawn_options_new (); - - if (argv_value != Py_None) - { - gchar ** argv; - gint argv_length; - - if (!PyGObject_unmarshal_strv (argv_value, &argv, &argv_length)) - goto invalid_argument; - - frida_spawn_options_set_argv (options, argv, argv_length); - - g_strfreev (argv); - } - - if (envp_value != Py_None) - { - gchar ** envp; - gint envp_length; - - if (!PyGObject_unmarshal_envp (envp_value, &envp, &envp_length)) - goto invalid_argument; - - frida_spawn_options_set_envp (options, envp, envp_length); - - g_strfreev (envp); - } - - if (env_value != Py_None) - { - gchar ** env; - gint env_length; - - if (!PyGObject_unmarshal_envp (env_value, &env, &env_length)) - goto invalid_argument; - - frida_spawn_options_set_env (options, env, env_length); - - g_strfreev (env); - } - - if (cwd != NULL) - frida_spawn_options_set_cwd (options, cwd); - - if (stdio_value != NULL) - { - FridaStdio stdio; - - if (!PyGObject_unmarshal_enum (stdio_value, FRIDA_TYPE_STDIO, &stdio)) - goto invalid_argument; - - frida_spawn_options_set_stdio (options, stdio); - } - - if (aux_value != Py_None) - { - GHashTable * aux; - Py_ssize_t pos; - PyObject * key, * value; - - aux = frida_spawn_options_get_aux (options); - - if (!PyDict_Check (aux_value)) - goto invalid_aux_dict; - - pos = 0; - while (PyDict_Next (aux_value, &pos, &key, &value)) - { - gchar * raw_key; - GVariant * raw_value; - - if (!PyGObject_unmarshal_string (key, &raw_key)) - goto invalid_dict_key; - - if (!PyGObject_unmarshal_variant (value, &raw_value)) - { - g_free (raw_key); - goto invalid_dict_value; - } - - g_hash_table_insert (aux, raw_key, g_variant_ref_sink (raw_value)); - } - } - - Py_BEGIN_ALLOW_THREADS - pid = frida_device_spawn_sync (PY_GOBJECT_HANDLE (self), program, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (pid); - -invalid_argument: -invalid_dict_key: -invalid_dict_value: - { - g_object_unref (options); - - return NULL; - } -invalid_aux_dict: - { - g_object_unref (options); - - PyErr_SetString (PyExc_TypeError, "unsupported parameter"); - - return NULL; - } -} - -static PyObject * -PyDevice_input (PyDevice * self, PyObject * args) -{ - long pid; - gconstpointer data_buffer; - Py_ssize_t data_size; - GBytes * data; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "ly#", &pid, &data_buffer, &data_size)) - return NULL; - - data = g_bytes_new (data_buffer, data_size); - - Py_BEGIN_ALLOW_THREADS - frida_device_input_sync (PY_GOBJECT_HANDLE (self), (guint) pid, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_resume (PyDevice * self, PyObject * args) -{ - long pid; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "l", &pid)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_resume_sync (PY_GOBJECT_HANDLE (self), (guint) pid, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_kill (PyDevice * self, PyObject * args) -{ - long pid; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "l", &pid)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_kill_sync (PY_GOBJECT_HANDLE (self), (guint) pid, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_attach (PyDevice * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "pid", "realm", "persist_timeout", NULL }; - long pid; - char * realm_value = NULL; - unsigned int persist_timeout = 0; - FridaSessionOptions * options = NULL; - GError * error = NULL; - FridaSession * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "l|esI", keywords, - &pid, - "utf-8", &realm_value, - &persist_timeout)) - return NULL; - - options = PyDevice_parse_session_options (realm_value, persist_timeout); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_device_attach_sync (PY_GOBJECT_HANDLE (self), (guint) pid, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PySession_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (realm_value); - - return result; -} - -static FridaSessionOptions * -PyDevice_parse_session_options (const gchar * realm_value, - guint persist_timeout) -{ - FridaSessionOptions * options; - - options = frida_session_options_new (); - - if (realm_value != NULL) - { - FridaRealm realm; - - if (!PyGObject_unmarshal_enum (realm_value, FRIDA_TYPE_REALM, &realm)) - goto propagate_error; - - frida_session_options_set_realm (options, realm); - } - - frida_session_options_set_persist_timeout (options, persist_timeout); - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_inject_library_file (PyDevice * self, PyObject * args) -{ - long pid; - const char * path, * entrypoint, * data; - GError * error = NULL; - guint id; - - if (!PyArg_ParseTuple (args, "lsss", &pid, &path, &entrypoint, &data)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - id = frida_device_inject_library_file_sync (PY_GOBJECT_HANDLE (self), (guint) pid, path, entrypoint, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (id); -} - -static PyObject * -PyDevice_inject_library_blob (PyDevice * self, PyObject * args) -{ - long pid; - GBytes * blob; - gconstpointer blob_buffer; - Py_ssize_t blob_size; - const char * entrypoint, * data; - GError * error = NULL; - guint id; - - if (!PyArg_ParseTuple (args, "ly#ss", &pid, &blob_buffer, &blob_size, &entrypoint, &data)) - return NULL; - - blob = g_bytes_new (blob_buffer, blob_size); - - Py_BEGIN_ALLOW_THREADS - id = frida_device_inject_library_blob_sync (PY_GOBJECT_HANDLE (self), (guint) pid, blob, entrypoint, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_bytes_unref (blob); - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (id); -} - -static PyObject * -PyDevice_open_channel (PyDevice * self, PyObject * args) -{ - const char * address; - GError * error = NULL; - GIOStream * stream; - - if (!PyArg_ParseTuple (args, "s", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - stream = frida_device_open_channel_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyIOStream_new_take_handle (stream); -} - -static PyObject * -PyDevice_open_service (PyDevice * self, PyObject * args) -{ - const char * address; - GError * error = NULL; - FridaService * service; - - if (!PyArg_ParseTuple (args, "s", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - service = frida_device_open_service_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyService_new_take_handle (service); -} - -static PyObject * -PyDevice_unpair (PyDevice * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_unpair_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyApplication_new_take_handle (FridaApplication * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Application)); -} - -static int -PyApplication_init (PyApplication * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->identifier = NULL; - self->name = NULL; - self->pid = 0; - self->parameters = NULL; - - return 0; -} - -static void -PyApplication_init_from_handle (PyApplication * self, FridaApplication * handle) -{ - self->identifier = PyUnicode_FromString (frida_application_get_identifier (handle)); - self->name = PyUnicode_FromString (frida_application_get_name (handle)); - self->pid = frida_application_get_pid (handle); - self->parameters = PyApplication_marshal_parameters_dict (frida_application_get_parameters (handle)); -} - -static void -PyApplication_dealloc (PyApplication * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->name); - Py_DecRef (self->identifier); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyApplication_repr (PyApplication * self) -{ - PyObject * result; - FridaApplication * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Application("); - - g_string_append_printf (repr, "identifier=\"%s\", name=\"%s\"", - frida_application_get_identifier (handle), - frida_application_get_name (handle)); - - if (self->pid != 0) - g_string_append_printf (repr, ", pid=%u", self->pid); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - -static PyObject * -PyApplication_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value; - - if (strcmp (key, "started") == 0 && g_variant_is_of_type (raw_value, G_VARIANT_TYPE_STRING)) - value = PyGObject_marshal_datetime (g_variant_get_string (raw_value, NULL)); - else - value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - - -static PyObject * -PyProcess_new_take_handle (FridaProcess * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Process)); -} - -static int -PyProcess_init (PyProcess * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->name = NULL; - self->parameters = NULL; - - return 0; -} - -static void -PyProcess_init_from_handle (PyProcess * self, FridaProcess * handle) -{ - self->pid = frida_process_get_pid (handle); - self->name = PyUnicode_FromString (frida_process_get_name (handle)); - self->parameters = PyProcess_marshal_parameters_dict (frida_process_get_parameters (handle)); -} - -static void -PyProcess_dealloc (PyProcess * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyProcess_repr (PyProcess * self) -{ - PyObject * result; - FridaProcess * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Process("); - - g_string_append_printf (repr, "pid=%u, name=\"%s\"", - self->pid, - frida_process_get_name (handle)); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - -static PyObject * -PyProcess_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value; - - if (strcmp (key, "started") == 0 && g_variant_is_of_type (raw_value, G_VARIANT_TYPE_STRING)) - value = PyGObject_marshal_datetime (g_variant_get_string (raw_value, NULL)); - else - value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - - -static PyObject * -PySpawn_new_take_handle (FridaSpawn * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Spawn)); -} - -static int -PySpawn_init (PySpawn * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->identifier = NULL; - - return 0; -} - -static void -PySpawn_init_from_handle (PySpawn * self, FridaSpawn * handle) -{ - self->pid = frida_spawn_get_pid (handle); - self->identifier = PyGObject_marshal_string (frida_spawn_get_identifier (handle)); -} - -static void -PySpawn_dealloc (PySpawn * self) -{ - Py_DecRef (self->identifier); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PySpawn_repr (PySpawn * self) -{ - PyObject * result; - - if (self->identifier != Py_None) - { - PyObject * identifier_bytes; - - identifier_bytes = PyUnicode_AsUTF8String (self->identifier); - - result = PyUnicode_FromFormat ("Spawn(pid=%u, identifier=\"%s\")", - self->pid, - PyBytes_AsString (identifier_bytes)); - - Py_DecRef (identifier_bytes); - } - else - { - result = PyUnicode_FromFormat ("Spawn(pid=%u)", - self->pid); - } - - return result; -} - - -static PyObject * -PyChild_new_take_handle (FridaChild * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Child)); -} - -static int -PyChild_init (PyChild * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->parent_pid = 0; - self->origin = NULL; - self->identifier = NULL; - self->path = NULL; - self->argv = NULL; - self->envp = NULL; - - return 0; -} - -static void -PyChild_init_from_handle (PyChild * self, FridaChild * handle) -{ - gchar * const * argv, * const * envp; - gint argv_length, envp_length; - - self->pid = frida_child_get_pid (handle); - self->parent_pid = frida_child_get_parent_pid (handle); - - self->origin = PyGObject_marshal_enum (frida_child_get_origin (handle), FRIDA_TYPE_CHILD_ORIGIN); - - self->identifier = PyGObject_marshal_string (frida_child_get_identifier (handle)); - - self->path = PyGObject_marshal_string (frida_child_get_path (handle)); - - argv = frida_child_get_argv (handle, &argv_length); - self->argv = PyGObject_marshal_strv (argv, argv_length); - - envp = frida_child_get_envp (handle, &envp_length); - self->envp = PyGObject_marshal_envp (envp, envp_length); -} - -static void -PyChild_dealloc (PyChild * self) -{ - Py_DecRef (self->envp); - Py_DecRef (self->argv); - Py_DecRef (self->path); - Py_DecRef (self->identifier); - Py_DecRef (self->origin); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyChild_repr (PyChild * self) -{ - PyObject * result; - FridaChild * handle; - GString * repr; - FridaChildOrigin origin; - GEnumClass * origin_class; - GEnumValue * origin_value; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Child("); - - g_string_append_printf (repr, "pid=%u, parent_pid=%u", self->pid, self->parent_pid); - - origin = frida_child_get_origin (handle); - origin_class = g_type_class_ref (FRIDA_TYPE_CHILD_ORIGIN); - origin_value = g_enum_get_value (origin_class, origin); - g_string_append_printf (repr, ", origin=%s", origin_value->value_nick); - g_type_class_unref (origin_class); - - if (self->identifier != Py_None) - { - gchar * identifier; - - identifier = PyFrida_repr (self->identifier); - - g_string_append_printf (repr, ", identifier=%s", identifier); - - g_free (identifier); - } - - if (origin != FRIDA_CHILD_ORIGIN_FORK) - { - gchar * path, * argv, * envp; - - path = PyFrida_repr (self->path); - argv = PyFrida_repr (self->argv); - envp = PyFrida_repr (self->envp); - - g_string_append_printf (repr, ", path=%s, argv=%s, envp=%s", path, argv, envp); - - g_free (envp); - g_free (argv); - g_free (path); - } - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static int -PyCrash_init (PyCrash * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->process_name = NULL; - self->summary = NULL; - self->report = NULL; - self->parameters = NULL; - - return 0; -} - -static void -PyCrash_init_from_handle (PyCrash * self, FridaCrash * handle) -{ - self->pid = frida_crash_get_pid (handle); - self->process_name = PyGObject_marshal_string (frida_crash_get_process_name (handle)); - self->summary = PyGObject_marshal_string (frida_crash_get_summary (handle)); - self->report = PyGObject_marshal_string (frida_crash_get_report (handle)); - self->parameters = PyGObject_marshal_parameters_dict (frida_crash_get_parameters (handle)); -} - -static void -PyCrash_dealloc (PyCrash * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->report); - Py_DecRef (self->summary); - Py_DecRef (self->process_name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyCrash_repr (PyCrash * self) -{ - PyObject * result; - FridaCrash * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Crash("); - - g_string_append_printf (repr, "pid=%u, process_name=\"%s\", summary=\"%s\", report=<%u bytes>", - self->pid, - frida_crash_get_process_name (handle), - frida_crash_get_summary (handle), - (guint) strlen (frida_crash_get_report (handle))); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyBus_new_take_handle (FridaBus * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Bus)); -} - -static PyObject * -PyBus_attach (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_bus_attach_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyBus_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, "utf-8", &message, &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_bus_post (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyService_new_take_handle (FridaService * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Service)); -} - -static PyObject * -PyService_activate (PyService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_service_activate_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyService_cancel (PyService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_service_cancel_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyService_request (PyService * self, PyObject * args) -{ - PyObject * result, * params; - GVariant * raw_params, * raw_result; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "O", ¶ms)) - return NULL; - - if (!PyGObject_unmarshal_variant (params, &raw_params)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - raw_result = frida_service_request_sync (PY_GOBJECT_HANDLE (self), raw_params, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_variant_unref (raw_params); - - if (error != NULL) - return PyFrida_raise (error); - - result = PyGObject_marshal_variant (raw_result); - g_variant_unref (raw_result); - - return result; -} - - -static PyObject * -PySession_new_take_handle (FridaSession * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Session)); -} - -static int -PySession_init (PySession * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - - return 0; -} - -static void -PySession_init_from_handle (PySession * self, FridaSession * handle) -{ - self->pid = frida_session_get_pid (handle); -} - -static PyObject * -PySession_repr (PySession * self) -{ - return PyUnicode_FromFormat ("Session(pid=%u)", self->pid); -} - -static PyObject * -PySession_is_detached (PySession * self) -{ - gboolean is_detached; - - Py_BEGIN_ALLOW_THREADS - is_detached = frida_session_is_detached (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_detached); -} - -static PyObject * -PySession_detach (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_detach_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_resume (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_resume_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_enable_child_gating (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_enable_child_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_disable_child_gating (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_disable_child_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_create_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "source", "name", "snapshot", "runtime", NULL }; - char * source; - char * name = NULL; - gconstpointer snapshot_data = NULL; - Py_ssize_t snapshot_size = 0; - const char * runtime_value = NULL; - FridaScriptOptions * options; - GError * error = NULL; - FridaScript * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esy#z", keywords, "utf-8", &source, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) - return NULL; - - options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_create_script_sync (PY_GOBJECT_HANDLE (self), source, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyScript_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (name); - PyMem_Free (source); - - return result; -} - -static PyObject * -PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "data", "name", "snapshot", "runtime", NULL }; - guint8 * data; - Py_ssize_t size; - char * name = NULL; - gconstpointer snapshot_data = NULL; - Py_ssize_t snapshot_size = 0; - const char * runtime_value = NULL; - GBytes * bytes; - FridaScriptOptions * options; - GError * error = NULL; - FridaScript * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "y#|esy#z", keywords, &data, &size, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) - return NULL; - - bytes = g_bytes_new (data, size); - - options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_create_script_from_bytes_sync (PY_GOBJECT_HANDLE (self), bytes, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyScript_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - g_bytes_unref (bytes); - - PyMem_Free (name); - - return result; -} - -static PyObject * -PySession_compile_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "source", "name", "runtime", NULL }; - char * source; - char * name = NULL; - const char * runtime_value = NULL; - FridaScriptOptions * options; - GError * error = NULL; - GBytes * bytes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &source, "utf-8", &name, &runtime_value)) - return NULL; - - options = PySession_parse_script_options (name, NULL, 0, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - bytes = frida_session_compile_script_sync (PY_GOBJECT_HANDLE (self), source, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - result = PyGObject_marshal_bytes_non_nullable (bytes); - - g_bytes_unref (bytes); - } - else - { - result = PyFrida_raise (error); - } - -beach: - g_clear_object (&options); - - PyMem_Free (name); - PyMem_Free (source); - - return result; -} - -static FridaScriptOptions * -PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, const gchar * runtime_value) -{ - FridaScriptOptions * options; - - options = frida_script_options_new (); - - if (name != NULL) - frida_script_options_set_name (options, name); - - if (snapshot_data != NULL) - { - GBytes * snapshot = g_bytes_new (snapshot_data, snapshot_size); - frida_script_options_set_snapshot (options, snapshot); - g_bytes_unref (snapshot); - } - - if (runtime_value != NULL) - { - FridaScriptRuntime runtime; - - if (!PyGObject_unmarshal_enum (runtime_value, FRIDA_TYPE_SCRIPT_RUNTIME, &runtime)) - goto invalid_argument; - - frida_script_options_set_runtime (options, runtime); - } - - return options; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "embed_script", "warmup_script", "runtime", NULL }; - char * embed_script; - char * warmup_script = NULL; - const char * runtime_value = NULL; - FridaSnapshotOptions * options; - GError * error = NULL; - GBytes * bytes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &embed_script, "utf-8", &warmup_script, &runtime_value)) - return NULL; - - options = PySession_parse_snapshot_options (warmup_script, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - bytes = frida_session_snapshot_script_sync (PY_GOBJECT_HANDLE (self), embed_script, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - result = PyGObject_marshal_bytes_non_nullable (bytes); - - g_bytes_unref (bytes); - } - else - { - result = PyFrida_raise (error); - } - -beach: - g_clear_object (&options); - - PyMem_Free (warmup_script); - PyMem_Free (embed_script); - - return result; -} - -static FridaSnapshotOptions * -PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value) -{ - FridaSnapshotOptions * options; - - options = frida_snapshot_options_new (); - - if (warmup_script != NULL) - frida_snapshot_options_set_warmup_script (options, warmup_script); - - if (runtime_value != NULL) - { - FridaScriptRuntime runtime; - - if (!PyGObject_unmarshal_enum (runtime_value, FRIDA_TYPE_SCRIPT_RUNTIME, &runtime)) - goto invalid_argument; - - frida_snapshot_options_set_runtime (options, runtime); - } - - return options; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw) -{ - gboolean success = FALSE; - static char * keywords[] = { "stun_server", "relays", NULL }; - char * stun_server = NULL; - PyObject * relays = NULL; - FridaPeerOptions * options = NULL; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|esO", keywords, - "utf-8", &stun_server, - &relays)) - return NULL; - - options = PySession_parse_peer_options (stun_server, relays); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - frida_session_setup_peer_connection_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - goto propagate_error; - - success = TRUE; - goto beach; - -propagate_error: - { - PyFrida_raise (error); - goto beach; - } -beach: - { - g_clear_object (&options); - - PyMem_Free (stun_server); - - if (!success) - return NULL; - - PyFrida_RETURN_NONE; - } -} - -static FridaPeerOptions * -PySession_parse_peer_options (const gchar * stun_server, PyObject * relays) -{ - FridaPeerOptions * options; - PyObject * relay; - - options = frida_peer_options_new (); - - frida_peer_options_set_stun_server (options, stun_server); - - if (relays != NULL) - { - Py_ssize_t n, i; - - n = PySequence_Length (relays); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - relay = PySequence_GetItem (relays, i); - if (relay == NULL) - goto propagate_error; - - if (!PyObject_IsInstance (relay, PYFRIDA_TYPE_OBJECT (Relay))) - goto expected_relay; - - frida_peer_options_add_relay (options, PY_GOBJECT_HANDLE (relay)); - - Py_DecRef (relay); - } - } - - return options; - -expected_relay: - { - Py_DecRef (relay); - - PyErr_SetString (PyExc_TypeError, "expected sequence of Relay objects"); - goto propagate_error; - } -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_join_portal (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "address", "certificate", "token", "acl", NULL }; - char * address; - char * certificate = NULL; - char * token = NULL; - PyObject * acl = NULL; - FridaPortalOptions * options; - GError * error = NULL; - FridaPortalMembership * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esesO", keywords, - "utf-8", &address, - "utf-8", &certificate, - "utf-8", &token, - &acl)) - return NULL; - - options = PySession_parse_portal_options (certificate, token, acl); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_join_portal_sync (PY_GOBJECT_HANDLE (self), address, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyPortalMembership_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (token); - PyMem_Free (certificate); - PyMem_Free (address); - - return result; -} - -static FridaPortalOptions * -PySession_parse_portal_options (const gchar * certificate_value, const gchar * token, PyObject * acl_value) -{ - FridaPortalOptions * options; - - options = frida_portal_options_new (); - - if (certificate_value != NULL) - { - GTlsCertificate * certificate; - - if (!PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto propagate_error; - - frida_portal_options_set_certificate (options, certificate); - - g_object_unref (certificate); - } - - if (token != NULL) - frida_portal_options_set_token (options, token); - - if (acl_value != NULL) - { - gchar ** acl; - gint acl_length; - - if (!PyGObject_unmarshal_strv (acl_value, &acl, &acl_length)) - goto propagate_error; - - frida_portal_options_set_acl (options, acl, acl_length); - - g_strfreev (acl); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyScript_new_take_handle (FridaScript * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Script)); -} - -static PyObject * -PyScript_is_destroyed (PyScript * self) -{ - gboolean is_destroyed; - - Py_BEGIN_ALLOW_THREADS - is_destroyed = frida_script_is_destroyed (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_destroyed); -} - -static PyObject * -PyScript_load (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_load_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_unload (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_unload_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_eternalize (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_eternalize_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, "utf-8", &message, &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_post (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_enable_debugger (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "port", NULL }; - unsigned short int port = 0; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|H", keywords, &port)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_enable_debugger_sync (PY_GOBJECT_HANDLE (self), port, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_disable_debugger (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_disable_debugger_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static int -PyRelay_init (PyRelay * self, PyObject * args, PyObject * kw) -{ - int result = -1; - static char * keywords[] = { "address", "username", "password", "kind", NULL }; - char * address = NULL; - char * username = NULL; - char * password = NULL; - char * kind_value = NULL; - FridaRelayKind kind; - FridaRelay * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "eseseses", keywords, - "utf-8", &address, - "utf-8", &username, - "utf-8", &password, - "utf-8", &kind_value)) - return -1; - - if (!PyGObject_unmarshal_enum (kind_value, FRIDA_TYPE_RELAY_KIND, &kind)) - goto beach; - - handle = frida_relay_new (address, username, password, kind); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (Relay)); - - PyRelay_init_from_handle (self, handle); - - result = 0; - -beach: - PyMem_Free (kind_value); - PyMem_Free (password); - PyMem_Free (username); - PyMem_Free (address); - - return result; -} - -static void -PyRelay_init_from_handle (PyRelay * self, FridaRelay * handle) -{ - self->address = PyUnicode_FromString (frida_relay_get_address (handle)); - self->username = PyUnicode_FromString (frida_relay_get_username (handle)); - self->password = PyUnicode_FromString (frida_relay_get_password (handle)); - self->kind = PyGObject_marshal_enum (frida_relay_get_kind (handle), FRIDA_TYPE_RELAY_KIND); -} - -static void -PyRelay_dealloc (PyRelay * self) -{ - Py_DecRef (self->kind); - Py_DecRef (self->password); - Py_DecRef (self->username); - Py_DecRef (self->address); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyRelay_repr (PyRelay * self) -{ - PyObject * result, * address_bytes, * username_bytes, * password_bytes, * kind_bytes; - - address_bytes = PyUnicode_AsUTF8String (self->address); - username_bytes = PyUnicode_AsUTF8String (self->username); - password_bytes = PyUnicode_AsUTF8String (self->password); - kind_bytes = PyUnicode_AsUTF8String (self->kind); - - result = PyUnicode_FromFormat ("Relay(address=\"%s\", username=\"%s\", password=\"%s\", kind='%s')", - PyBytes_AsString (address_bytes), - PyBytes_AsString (username_bytes), - PyBytes_AsString (password_bytes), - PyBytes_AsString (kind_bytes)); - - Py_DecRef (kind_bytes); - Py_DecRef (password_bytes); - Py_DecRef (username_bytes); - Py_DecRef (address_bytes); - - return result; -} - - -static PyObject * -PyPortalMembership_new_take_handle (FridaPortalMembership * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PortalMembership)); -} - -static PyObject * -PyPortalMembership_terminate (PyPortalMembership * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_membership_terminate_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static int -PyPortalService_init (PyPortalService * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "cluster_params", "control_params", NULL }; - PyEndpointParameters * cluster_params; - PyEndpointParameters * control_params = NULL; - FridaPortalService * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "O!|O!", keywords, - PYFRIDA_TYPE_OBJECT (EndpointParameters), &cluster_params, - PYFRIDA_TYPE_OBJECT (EndpointParameters), &control_params)) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - handle = frida_portal_service_new (PY_GOBJECT_HANDLE (cluster_params), - (control_params != NULL) ? PY_GOBJECT_HANDLE (control_params) : NULL); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (PortalService)); - - PyPortalService_init_from_handle (self, handle); - - return 0; -} - -static void -PyPortalService_init_from_handle (PyPortalService * self, FridaPortalService * handle) -{ - self->device = PyDevice_new_take_handle (g_object_ref (frida_portal_service_get_device (handle))); -} - -static void -PyPortalService_dealloc (PyPortalService * self) -{ - FridaPortalService * handle; - - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - handle = PyGObject_steal_handle (&self->parent); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - frida_portal_service_stop_sync (handle, NULL, NULL); - frida_unref (handle); - Py_END_ALLOW_THREADS - } - - Py_DecRef (self->device); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPortalService_start (PyPortalService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_start_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_stop (PyPortalService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_stop_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_kick (PyScript * self, PyObject * args) -{ - unsigned int connection_id; - - if (!PyArg_ParseTuple (args, "I", &connection_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_kick (PY_GOBJECT_HANDLE (self), connection_id); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "message", "data", NULL }; - unsigned int connection_id; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies|z#", keywords, - &connection_id, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_post (PY_GOBJECT_HANDLE (self), connection_id, message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_narrowcast (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "tag", "message", "data", NULL }; - char * tag, * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "eses|z#", keywords, - "utf-8", &tag, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_narrowcast (PY_GOBJECT_HANDLE (self), tag, message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_broadcast (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_broadcast (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_enumerate_tags (PyScript * self, PyObject * args) -{ - PyObject * result; - unsigned int connection_id; - gchar ** tags; - gint tags_length; - - if (!PyArg_ParseTuple (args, "I", &connection_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - tags = frida_portal_service_enumerate_tags (PY_GOBJECT_HANDLE (self), connection_id, &tags_length); - Py_END_ALLOW_THREADS - - result = PyGObject_marshal_strv (tags, tags_length); - g_strfreev (tags); - - return result; -} - -static PyObject * -PyPortalService_tag (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "tag", NULL }; - unsigned int connection_id; - char * tag; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies", keywords, - &connection_id, - "utf-8", &tag)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_tag (PY_GOBJECT_HANDLE (self), connection_id, tag); - Py_END_ALLOW_THREADS - - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_untag (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "tag", NULL }; - unsigned int connection_id; - char * tag; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies", keywords, - &connection_id, - "utf-8", &tag)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_untag (PY_GOBJECT_HANDLE (self), connection_id, tag); - Py_END_ALLOW_THREADS - - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - - -static int -PyEndpointParameters_init (PyEndpointParameters * self, PyObject * args, PyObject * kw) -{ - int result = -1; - static char * keywords[] = { "address", "port", "certificate", "origin", "auth_token", "auth_callback", "asset_root", NULL }; - char * address = NULL; - unsigned short int port = 0; - char * certificate_value = NULL; - char * origin = NULL; - char * auth_token = NULL; - PyObject * auth_callback = NULL; - char * asset_root_value = NULL; - GTlsCertificate * certificate = NULL; - FridaAuthenticationService * auth_service = NULL; - GFile * asset_root = NULL; - FridaEndpointParameters * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHesesesOes", keywords, - "utf-8", &address, - &port, - "utf-8", &certificate_value, - "utf-8", &origin, - "utf-8", &auth_token, - &auth_callback, - "utf-8", &asset_root_value)) - return -1; - - if (certificate_value != NULL && !PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto beach; - - if (auth_token != NULL) - auth_service = FRIDA_AUTHENTICATION_SERVICE (frida_static_authentication_service_new (auth_token)); - else if (auth_callback != NULL) - auth_service = FRIDA_AUTHENTICATION_SERVICE (frida_python_authentication_service_new (auth_callback)); - - if (asset_root_value != NULL) - asset_root = g_file_new_for_path (asset_root_value); - - handle = frida_endpoint_parameters_new (address, port, certificate, origin, auth_service, asset_root); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (EndpointParameters)); - - result = 0; - -beach: - g_clear_object (&asset_root); - g_clear_object (&auth_service); - g_clear_object (&certificate); - - PyMem_Free (asset_root_value); - PyMem_Free (auth_token); - PyMem_Free (origin); - PyMem_Free (certificate_value); - PyMem_Free (address); - - return result; -} - - -G_DEFINE_TYPE_EXTENDED (FridaPythonAuthenticationService, frida_python_authentication_service, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (FRIDA_TYPE_AUTHENTICATION_SERVICE, frida_python_authentication_service_iface_init)) - -static FridaPythonAuthenticationService * -frida_python_authentication_service_new (PyObject * callback) -{ - FridaPythonAuthenticationService * service; - - service = g_object_new (FRIDA_TYPE_PYTHON_AUTHENTICATION_SERVICE, NULL); - service->callback = callback; - Py_IncRef (callback); - - return service; -} - -static void -frida_python_authentication_service_class_init (FridaPythonAuthenticationServiceClass * klass) -{ - GObjectClass * object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = frida_python_authentication_service_dispose; -} - -static void -frida_python_authentication_service_iface_init (gpointer g_iface, gpointer iface_data) -{ - FridaAuthenticationServiceIface * iface = g_iface; - - iface->authenticate = frida_python_authentication_service_authenticate; - iface->authenticate_finish = frida_python_authentication_service_authenticate_finish; -} - -static void -frida_python_authentication_service_init (FridaPythonAuthenticationService * self) -{ - self->pool = g_thread_pool_new ((GFunc) frida_python_authentication_service_do_authenticate, self, 1, FALSE, NULL); -} - -static void -frida_python_authentication_service_dispose (GObject * object) -{ - FridaPythonAuthenticationService * self = FRIDA_PYTHON_AUTHENTICATION_SERVICE (object); - - if (self->pool != NULL) - { - g_thread_pool_free (self->pool, FALSE, FALSE); - self->pool = NULL; - } - - if (self->callback != NULL) - { - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - - Py_DecRef (self->callback); - self->callback = NULL; - - PyGILState_Release (gstate); - } - - G_OBJECT_CLASS (frida_python_authentication_service_parent_class)->dispose (object); -} - -static void -frida_python_authentication_service_authenticate (FridaAuthenticationService * service, const gchar * token, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - FridaPythonAuthenticationService * self; - GTask * task; - - self = FRIDA_PYTHON_AUTHENTICATION_SERVICE (service); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_task_data (task, g_strdup (token), g_free); - - g_thread_pool_push (self->pool, task, NULL); -} - -static gchar * -frida_python_authentication_service_authenticate_finish (FridaAuthenticationService * service, GAsyncResult * result, GError ** error) -{ - return g_task_propagate_pointer (G_TASK (result), error); -} - -static void -frida_python_authentication_service_do_authenticate (GTask * task, FridaPythonAuthenticationService * self) -{ - const gchar * token; - PyGILState_STATE gstate; - PyObject * result; - gchar * session_info = NULL; - gchar * message = NULL; - - token = g_task_get_task_data (task); - - gstate = PyGILState_Ensure (); - - result = PyObject_CallFunction (self->callback, "s", token); - if (result == NULL || !PyGObject_unmarshal_string (result, &session_info)) - { - PyObject * type, * value, * traceback; - - PyErr_Fetch (&type, &value, &traceback); - - if (value != NULL) - { - PyObject * message_value = PyObject_Str (value); - PyGObject_unmarshal_string (message_value, &message); - Py_DecRef (message_value); - } - else - { - message = g_strdup ("Internal error"); - } - - Py_DecRef (type); - Py_DecRef (value); - Py_DecRef (traceback); - } - - Py_DecRef (result); - - PyGILState_Release (gstate); - - if (session_info != NULL) - g_task_return_pointer (task, session_info, g_free); - else - g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, "%s", message); - - g_free (message); - g_object_unref (task); -} - - -static int -PyCompiler_init (PyCompiler * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_compiler_new (NULL), PYFRIDA_TYPE (Compiler)); - - return 0; -} - -static void -PyCompiler_dealloc (PyCompiler * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyCompiler_build (PyCompiler * self, PyObject * args, PyObject * kw) -{ - PyObject * result; - static char * keywords[] = { "entrypoint", "project_root", "output_format", "bundle_format", "type_check", "source_maps", "compression", - "platform", "externals", NULL }; - const char * entrypoint; - const char * project_root = NULL; - const char * output_format = NULL; - const char * bundle_format = NULL; - const char * type_check = NULL; - const char * source_maps = NULL; - const char * compression = NULL; - const char * platform = NULL; - PyObject * externals = NULL; - FridaBuildOptions * options; - GError * error = NULL; - gchar * bundle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|sssssssO", keywords, &entrypoint, &project_root, &output_format, &bundle_format, - &type_check, &source_maps, &compression, &platform, &externals)) - return NULL; - - options = frida_build_options_new (); - if (!PyCompiler_set_options (FRIDA_COMPILER_OPTIONS (options), project_root, output_format, bundle_format, type_check, source_maps, - compression, platform, externals)) - goto invalid_option_value; - - Py_BEGIN_ALLOW_THREADS - bundle = frida_compiler_build_sync (PY_GOBJECT_HANDLE (self), entrypoint, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result = PyUnicode_FromString (bundle); - g_free (bundle); - - return result; - -invalid_option_value: - { - g_object_unref (options); - return NULL; - } -} - -static PyObject * -PyCompiler_watch (PyCompiler * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "entrypoint", "project_root", "output_format", "bundle_format", "type_check", "source_maps", "compression", - "platform", "externals", NULL }; - const char * entrypoint; - const char * project_root = NULL; - const char * output_format = NULL; - const char * bundle_format = NULL; - const char * type_check = NULL; - const char * source_maps = NULL; - const char * compression = NULL; - const char * platform = NULL; - PyObject * externals = NULL; - FridaWatchOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|sssssssO", keywords, &entrypoint, &project_root, &output_format, &bundle_format, - &type_check, &source_maps, &compression, &platform, &externals)) - return NULL; - - options = frida_watch_options_new (); - if (!PyCompiler_set_options (FRIDA_COMPILER_OPTIONS (options), project_root, output_format, bundle_format, type_check, source_maps, - compression, platform, externals)) - goto invalid_option_value; - - Py_BEGIN_ALLOW_THREADS - frida_compiler_watch_sync (PY_GOBJECT_HANDLE (self), entrypoint, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; - -invalid_option_value: - { - g_object_unref (options); - return NULL; - } -} - -static gboolean -PyCompiler_set_options (FridaCompilerOptions * options, const gchar * project_root_value, const gchar * output_format_value, - const gchar * bundle_format_value, const gchar * type_check_value, const gchar * source_maps_value, const gchar * compression_value, - const gchar * platform_value, PyObject * externals_value) -{ - if (project_root_value != NULL) - frida_compiler_options_set_project_root (options, project_root_value); - - if (output_format_value != NULL) - { - FridaOutputFormat output_format; - - if (!PyGObject_unmarshal_enum (output_format_value, FRIDA_TYPE_OUTPUT_FORMAT, &output_format)) - return FALSE; - - frida_compiler_options_set_output_format (options, output_format); - } - - if (bundle_format_value != NULL) - { - FridaBundleFormat bundle_format; - - if (!PyGObject_unmarshal_enum (bundle_format_value, FRIDA_TYPE_BUNDLE_FORMAT, &bundle_format)) - return FALSE; - - frida_compiler_options_set_bundle_format (options, bundle_format); - } - - if (type_check_value != NULL) - { - FridaTypeCheckMode type_check; - - if (!PyGObject_unmarshal_enum (type_check_value, FRIDA_TYPE_TYPE_CHECK_MODE, &type_check)) - return FALSE; - - frida_compiler_options_set_type_check (options, type_check); - } - - if (source_maps_value != NULL) - { - FridaSourceMaps source_maps; - - if (!PyGObject_unmarshal_enum (source_maps_value, FRIDA_TYPE_SOURCE_MAPS, &source_maps)) - return FALSE; - - frida_compiler_options_set_source_maps (options, source_maps); - } - - if (compression_value != NULL) - { - FridaJsCompression compression; - - if (!PyGObject_unmarshal_enum (compression_value, FRIDA_TYPE_JS_COMPRESSION, &compression)) - return FALSE; - - frida_compiler_options_set_compression (options, compression); - } - - if (platform_value != NULL) - { - FridaJsPlatform platform; - - if (!PyGObject_unmarshal_enum (platform_value, FRIDA_TYPE_JS_PLATFORM, &platform)) - return FALSE; - - frida_compiler_options_set_platform (options, platform); - } - - if (externals_value != NULL) - { - gint n, i; - - n = PySequence_Size (externals_value); - if (n == -1) - return FALSE; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * external = NULL; - - element = PySequence_GetItem (externals_value, i); - if (element == NULL) - return FALSE; - PyGObject_unmarshal_string (element, &external); - Py_DecRef (element); - if (external == NULL) - return FALSE; - - frida_compiler_options_add_external (options, external); - - g_free (external); - } - } - - return TRUE; -} - - -static int -PyPackageManager_init (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_package_manager_new (), PYFRIDA_TYPE (PackageManager)); - - return 0; -} - -static void -PyPackageManager_dealloc (PyPackageManager * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageManager_repr (PyPackageManager * self) -{ - PyObject * result; - gchar * repr; - - repr = g_strdup_printf ("PackageManager(registry=\"%s\")", frida_package_manager_get_registry (PY_GOBJECT_HANDLE (self))); - result = PyUnicode_FromString (repr); - g_free (repr); - - return result; -} - -static PyObject * -PyPackageManager_get_registry (PyPackageManager * self, void * closure) -{ - return PyUnicode_FromString (frida_package_manager_get_registry (PY_GOBJECT_HANDLE (self))); -} - -static int -PyPackageManager_set_registry (PyPackageManager * self, PyObject * val, void * closure) -{ - gchar * registry; - - if (!PyGObject_unmarshal_string (val, ®istry)) - return -1; - frida_package_manager_set_registry (PY_GOBJECT_HANDLE (self), registry); - g_free (registry); - - return 0; -} - -static PyObject * -PyPackageManager_search (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - FridaPackageSearchResult * result; - static char * keywords[] = { "query", "offset", "limit", NULL }; - const char * query; - guint offset = G_MAXUINT; - guint limit = G_MAXUINT; - FridaPackageSearchOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|II", keywords, &query, &offset, &limit)) - return NULL; - - options = frida_package_search_options_new (); - - if (offset != G_MAXUINT) - frida_package_search_options_set_offset (options, offset); - - if (limit != G_MAXUINT) - frida_package_search_options_set_limit (options, limit); - - Py_BEGIN_ALLOW_THREADS - result = frida_package_manager_search_sync (PY_GOBJECT_HANDLE (self), query, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyPackageSearchResult_new_take_handle (result); -} - -static PyObject * -PyPackageManager_install (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - FridaPackageInstallResult * result; - static char * keywords[] = { "project_root", "role", "specs", "omits", NULL }; - const char * project_root = NULL; - const char * role_value = NULL; - PyObject * specs = NULL; - PyObject * omits = NULL; - FridaPackageInstallOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|ssOO", keywords, &project_root, &role_value, &specs, &omits)) - return NULL; - - options = PyPackageManager_parse_install_options (project_root, role_value, specs, omits); - - Py_BEGIN_ALLOW_THREADS - result = frida_package_manager_install_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyPackageInstallResult_new_take_handle (result); -} - -static FridaPackageInstallOptions * -PyPackageManager_parse_install_options (const gchar * project_root, const char * role_value, PyObject * specs_value, PyObject * omits_value) -{ - FridaPackageInstallOptions * options; - - options = frida_package_install_options_new (); - - if (project_root != NULL) - frida_package_install_options_set_project_root (options, project_root); - - if (role_value != NULL) - { - FridaPackageRole role; - - if (!PyGObject_unmarshal_enum (role_value, FRIDA_TYPE_PACKAGE_ROLE, &role)) - goto propagate_error; - - frida_package_install_options_set_role (options, role); - } - - if (specs_value != NULL) - { - gint n, i; - - n = PySequence_Size (specs_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * spec = NULL; - - element = PySequence_GetItem (specs_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &spec); - Py_DecRef (element); - if (spec == NULL) - goto propagate_error; - - frida_package_install_options_add_spec (options, spec); - - g_free (spec); - } - } - - if (omits_value != NULL) - { - gint n, i; - - n = PySequence_Size (omits_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * str = NULL; - FridaPackageRole role; - - element = PySequence_GetItem (omits_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &str); - Py_DecRef (element); - if (str == NULL) - goto propagate_error; - - if (!PyGObject_unmarshal_enum (str, FRIDA_TYPE_PACKAGE_ROLE, &role)) - goto propagate_error; - - frida_package_install_options_add_omit (options, role); - } - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyPackage_new_take_handle (FridaPackage * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Package)); -} - -static int -PyPackage_init (PyPackage * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->name = NULL; - self->version = NULL; - self->description = NULL; - self->url = NULL; - - return 0; -} - -static void -PyPackage_init_from_handle (PyPackage * self, FridaPackage * handle) -{ - self->name = PyUnicode_FromString (frida_package_get_name (handle)); - self->version = PyUnicode_FromString (frida_package_get_version (handle)); - self->description = PyGObject_marshal_string (frida_package_get_description (handle)); - self->url = PyGObject_marshal_string (frida_package_get_url (handle)); -} - -static void -PyPackage_dealloc (PyPackage * self) -{ - Py_DecRef (self->url); - Py_DecRef (self->description); - Py_DecRef (self->version); - Py_DecRef (self->name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackage_repr (PyPackage * self) -{ - PyObject * result; - FridaPackage * handle; - GString * repr; - const gchar * description, * url; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_sized_new (256); - - g_string_append_printf (repr, "Package(name=\"%s\", version=\"%s\"", - frida_package_get_name (handle), - frida_package_get_version (handle)); - - description = frida_package_get_description (handle); - if (description != NULL) - { - gchar * escaped = g_strescape (description, NULL); - g_string_append_printf (repr, ", description=\"%s\"", escaped); - g_free (escaped); - } - - url = frida_package_get_url (handle); - if (url != NULL) - g_string_append_printf (repr, ", url=\"%s\"", url); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyPackageList_marshal (FridaPackageList * list) -{ - PyObject * result; - gint n, i; - - n = frida_package_list_size (list); - result = PyList_New (n); - for (i = 0; i != n; i++) - PyList_SetItem (result, i, PyPackage_new_take_handle (frida_package_list_get (list, i))); - - return result; -} - - -static PyObject * -PyPackageSearchResult_new_take_handle (FridaPackageSearchResult * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PackageSearchResult)); -} - -static int -PyPackageSearchResult_init (PyPackageSearchResult * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->packages = NULL; - self->total = 0; - - return 0; -} - -static void -PyPackageSearchResult_init_from_handle (PyPackageSearchResult * self, FridaPackageSearchResult * handle) -{ - self->packages = PyPackageList_marshal (frida_package_search_result_get_packages (handle)); - self->total = frida_package_search_result_get_total (handle); -} - -static void -PyPackageSearchResult_dealloc (PyPackageSearchResult * self) -{ - Py_DecRef (self->packages); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageSearchResult_repr (PyPackageSearchResult * self) -{ - PyObject * result; - GString * repr; - gint num_packages; - - repr = g_string_new ("PackageSearchResult(packages="); - - num_packages = frida_package_list_size (frida_package_search_result_get_packages (PY_GOBJECT_HANDLE (self))); - if (num_packages != 0) - g_string_append_printf (repr, "[<%u package%s>]", num_packages, (num_packages == 1) ? "" : "s"); - else - g_string_append (repr, "[]"); - - g_string_append_printf (repr, ", total=%u)", self->total); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyPackageInstallResult_new_take_handle (FridaPackageInstallResult * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PackageInstallResult)); -} - -static int -PyPackageInstallResult_init (PyPackageInstallResult * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->packages = NULL; - - return 0; -} - -static void -PyPackageInstallResult_init_from_handle (PyPackageInstallResult * self, FridaPackageInstallResult * handle) -{ - self->packages = PyPackageList_marshal (frida_package_install_result_get_packages (handle)); -} - -static void -PyPackageInstallResult_dealloc (PyPackageInstallResult * self) -{ - Py_DecRef (self->packages); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageInstallResult_repr (PyPackageInstallResult * self) -{ - PyObject * result; - GString * repr; - gint num_packages; - - repr = g_string_new ("PackageInstallResult(packages="); - - num_packages = frida_package_list_size (frida_package_install_result_get_packages (PY_GOBJECT_HANDLE (self))); - if (num_packages != 0) - g_string_append_printf (repr, "[<%u package%s>]", num_packages, (num_packages == 1) ? "" : "s"); - else - g_string_append (repr, "[]"); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static int -PyFileMonitor_init (PyFileMonitor * self, PyObject * args, PyObject * kw) -{ - const char * path; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTuple (args, "s", &path)) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_file_monitor_new (path), PYFRIDA_TYPE (FileMonitor)); - - return 0; -} - -static void -PyFileMonitor_dealloc (PyFileMonitor * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyFileMonitor_enable (PyFileMonitor * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_file_monitor_enable_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyFileMonitor_disable (PyFileMonitor * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_file_monitor_disable_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyIOStream_new_take_handle (GIOStream * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (IOStream)); -} - -static int -PyIOStream_init (PyIOStream * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->input = NULL; - self->output = NULL; - - return 0; -} - -static void -PyIOStream_init_from_handle (PyIOStream * self, GIOStream * handle) -{ - self->input = g_io_stream_get_input_stream (handle); - self->output = g_io_stream_get_output_stream (handle); -} - -static PyObject * -PyIOStream_repr (PyIOStream * self) -{ - GIOStream * handle = PY_GOBJECT_HANDLE (self); - - return PyUnicode_FromFormat ("IOStream(handle=%p, is_closed=%s)", - handle, - g_io_stream_is_closed (handle) ? "TRUE" : "FALSE"); -} - -static PyObject * -PyIOStream_is_closed (PyIOStream * self) -{ - return PyBool_FromLong (g_io_stream_is_closed (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyIOStream_close (PyIOStream * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - g_io_stream_close (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyIOStream_read (PyIOStream * self, PyObject * args) -{ - PyObject * result; - unsigned long count; - PyObject * buffer; - GError * error = NULL; - gssize bytes_read; - - if (!PyArg_ParseTuple (args, "k", &count)) - return NULL; - - buffer = PyBytes_FromStringAndSize (NULL, count); - if (buffer == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - bytes_read = g_input_stream_read (self->input, PyBytes_AsString (buffer), count, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - if ((unsigned long) bytes_read == count) - { - result = buffer; - } - else - { - result = PyBytes_FromStringAndSize (PyBytes_AsString (buffer), bytes_read); - - Py_DecRef (buffer); - } - } - else - { - result = PyFrida_raise (error); - - Py_DecRef (buffer); - } - - return result; -} - -static PyObject * -PyIOStream_read_all (PyIOStream * self, PyObject * args) -{ - PyObject * result; - unsigned long count; - PyObject * buffer; - gsize bytes_read; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "k", &count)) - return NULL; - - buffer = PyBytes_FromStringAndSize (NULL, count); - if (buffer == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_input_stream_read_all (self->input, PyBytes_AsString (buffer), count, &bytes_read, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - if ((unsigned long) bytes_read != count) - { - Py_DecRef (buffer); - buffer = PyBytes_FromString (""); - } - - result = buffer; - } - else - { - result = PyFrida_raise (error); - - Py_DecRef (buffer); - } - - return result; -} - -static PyObject * -PyIOStream_write (PyIOStream * self, PyObject * args) -{ - const char * data; - Py_ssize_t size; - GError * error = NULL; - gssize bytes_written; - - if (!PyArg_ParseTuple (args, "y#", &data, &size)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - bytes_written = g_output_stream_write (self->output, data, size, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromSsize_t (bytes_written); -} - -static PyObject * -PyIOStream_write_all (PyIOStream * self, PyObject * args) -{ - const char * data; - Py_ssize_t size; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "y#", &data, &size)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_output_stream_write_all (self->output, data, size, NULL, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyCancellable_new_take_handle (GCancellable * handle) -{ - PyObject * object; - - object = (handle != NULL) ? PyGObject_try_get_from_handle (handle) : NULL; - if (object == NULL) - { - object = PyObject_CallFunction (PYFRIDA_TYPE_OBJECT (Cancellable), "z#", (char *) &handle, (Py_ssize_t) sizeof (handle)); - } - else - { - g_object_unref (handle); - Py_IncRef (object); - } - - return object; -} - -static int -PyCancellable_init (PyCancellable * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "handle", NULL }; - GCancellable ** handle_buffer = NULL; - Py_ssize_t handle_size = 0; - GCancellable * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|z#", keywords, &handle_buffer, &handle_size)) - return -1; - - if (handle_size == sizeof (gpointer)) - handle = *handle_buffer; - else - handle = g_cancellable_new (); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (Cancellable)); - - return 0; -} - -static PyObject * -PyCancellable_repr (PyCancellable * self) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - - return PyUnicode_FromFormat ("Cancellable(handle=%p, is_cancelled=%s)", - handle, - g_cancellable_is_cancelled (handle) ? "TRUE" : "FALSE"); -} - -static PyObject * -PyCancellable_is_cancelled (PyCancellable * self) -{ - return PyBool_FromLong (g_cancellable_is_cancelled (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyCancellable_raise_if_cancelled (PyCancellable * self) -{ - GError * error = NULL; - - g_cancellable_set_error_if_cancelled (PY_GOBJECT_HANDLE (self), &error); - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_get_fd (PyCancellable * self) -{ - return PyLong_FromLong (g_cancellable_get_fd (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyCancellable_release_fd (PyCancellable * self) -{ - g_cancellable_release_fd (PY_GOBJECT_HANDLE (self)); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_get_current (PyCancellable * self) -{ - GCancellable * handle; - - handle = g_cancellable_get_current (); - - if (handle != NULL) - g_object_ref (handle); - - return PyCancellable_new_take_handle (handle); -} - -static PyObject * -PyCancellable_push_current (PyCancellable * self) -{ - g_cancellable_push_current (PY_GOBJECT_HANDLE (self)); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_pop_current (PyCancellable * self) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - - if (g_cancellable_get_current () != handle) - goto invalid_operation; - - g_cancellable_pop_current (handle); - - PyFrida_RETURN_NONE; - -invalid_operation: - { - return PyFrida_raise (g_error_new ( - FRIDA_ERROR, - FRIDA_ERROR_INVALID_OPERATION, - "Cancellable is not on top of the stack")); - } -} - -static PyObject * -PyCancellable_connect (PyCancellable * self, PyObject * args) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - gulong handler_id; - PyObject * callback; - - if (!PyArg_ParseTuple (args, "O", &callback)) - return NULL; - - if (!PyCallable_Check (callback)) - goto not_callable; - - if (handle != NULL) - { - Py_IncRef (callback); - - Py_BEGIN_ALLOW_THREADS - handler_id = g_cancellable_connect (handle, G_CALLBACK (PyCancellable_on_cancelled), callback, - (GDestroyNotify) PyCancellable_destroy_callback); - Py_END_ALLOW_THREADS - } - else - { - handler_id = 0; - } - - return PyLong_FromUnsignedLong (handler_id); - -not_callable: - { - PyErr_SetString (PyExc_TypeError, "object must be callable"); - return NULL; - } -} - -static PyObject * -PyCancellable_disconnect (PyCancellable * self, PyObject * args) -{ - gulong handler_id; - - if (!PyArg_ParseTuple (args, "k", &handler_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_cancellable_disconnect (PY_GOBJECT_HANDLE (self), handler_id); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - -static void -PyCancellable_on_cancelled (GCancellable * cancellable, PyObject * callback) -{ - PyGILState_STATE gstate; - PyObject * result; - - gstate = PyGILState_Ensure (); - - result = PyObject_CallObject (callback, NULL); - if (result != NULL) - Py_DecRef (result); - else - PyErr_Print (); - - PyGILState_Release (gstate); -} - -static void -PyCancellable_destroy_callback (PyObject * callback) -{ - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - Py_DecRef (callback); - PyGILState_Release (gstate); -} - -static PyObject * -PyCancellable_cancel (PyCancellable * self) -{ - Py_BEGIN_ALLOW_THREADS - g_cancellable_cancel (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - - -static void -PyFrida_object_decref (gpointer obj) -{ - PyObject * o = obj; - Py_DecRef (o); -} - -static PyObject * -PyFrida_raise (GError * error) -{ - PyObject * exception; - GString * message; - - if (error->domain == FRIDA_ERROR) - { - exception = g_hash_table_lookup (frida_exception_by_error_code, GINT_TO_POINTER (error->code)); - g_assert (exception != NULL); - } - else - { - g_assert (error->domain == G_IO_ERROR); - g_assert (error->code == G_IO_ERROR_CANCELLED); - exception = cancelled_exception; - } - - message = g_string_new (""); - g_string_append_unichar (message, g_unichar_tolower (g_utf8_get_char (error->message))); - g_string_append (message, g_utf8_offset_to_pointer (error->message, 1)); - - PyErr_SetString (exception, message->str); - - g_string_free (message, TRUE); - g_error_free (error); - - return NULL; -} - -static gchar * -PyFrida_repr (PyObject * obj) -{ - gchar * result; - PyObject * repr_value; - - repr_value = PyObject_Repr (obj); - - PyGObject_unmarshal_string (repr_value, &result); - - Py_DecRef (repr_value); - - return result; -} - -static guint -PyFrida_get_max_argument_count (PyObject * callable) -{ - guint result = G_MAXUINT; - PyObject * spec; - PyObject * varargs = NULL; - PyObject * args = NULL; - PyObject * is_method; - - spec = PyObject_CallFunction (inspect_getargspec, "O", callable); - if (spec == NULL) - { - PyErr_Clear (); - goto beach; - } - - varargs = PyTuple_GetItem (spec, 1); - if (varargs != Py_None) - goto beach; - - args = PyTuple_GetItem (spec, 0); - - result = PyObject_Size (args); - - is_method = PyObject_CallFunction (inspect_ismethod, "O", callable); - g_assert (is_method != NULL); - if (is_method == Py_True) - result--; - Py_DecRef (is_method); - -beach: - Py_DecRef (spec); - - return result; -} - - -PyMODINIT_FUNC -PyInit__frida (void) -{ - PyObject * inspect, * datetime, * module; - - inspect = PyImport_ImportModule ("inspect"); - inspect_getargspec = PyObject_GetAttrString (inspect, "getfullargspec"); - inspect_ismethod = PyObject_GetAttrString (inspect, "ismethod"); - Py_DecRef (inspect); - - datetime = PyImport_ImportModule ("datetime"); - datetime_constructor = PyObject_GetAttrString (datetime, "datetime"); - Py_DecRef (datetime); - - frida_init (); - - PyGObject_class_init (); - - module = PyModule_Create (&PyFrida_moduledef); - - PyModule_AddStringConstant (module, "__version__", frida_version_string ()); - - PYFRIDA_REGISTER_TYPE (GObject, G_TYPE_OBJECT); - PyGObject_tp_init = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_init); - PyGObject_tp_dealloc = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_dealloc); - - PYFRIDA_REGISTER_TYPE (DeviceManager, FRIDA_TYPE_DEVICE_MANAGER); - PYFRIDA_REGISTER_TYPE (Device, FRIDA_TYPE_DEVICE); - PYFRIDA_REGISTER_TYPE (Application, FRIDA_TYPE_APPLICATION); - PYFRIDA_REGISTER_TYPE (Process, FRIDA_TYPE_PROCESS); - PYFRIDA_REGISTER_TYPE (Spawn, FRIDA_TYPE_SPAWN); - PYFRIDA_REGISTER_TYPE (Child, FRIDA_TYPE_CHILD); - PYFRIDA_REGISTER_TYPE (Crash, FRIDA_TYPE_CRASH); - PYFRIDA_REGISTER_TYPE (Bus, FRIDA_TYPE_BUS); - PYFRIDA_REGISTER_TYPE (Service, FRIDA_TYPE_SERVICE); - PYFRIDA_REGISTER_TYPE (Session, FRIDA_TYPE_SESSION); - PYFRIDA_REGISTER_TYPE (Script, FRIDA_TYPE_SCRIPT); - PYFRIDA_REGISTER_TYPE (Relay, FRIDA_TYPE_RELAY); - PYFRIDA_REGISTER_TYPE (PortalMembership, FRIDA_TYPE_PORTAL_MEMBERSHIP); - PYFRIDA_REGISTER_TYPE (PortalService, FRIDA_TYPE_PORTAL_SERVICE); - PYFRIDA_REGISTER_TYPE (EndpointParameters, FRIDA_TYPE_ENDPOINT_PARAMETERS); - PYFRIDA_REGISTER_TYPE (Compiler, FRIDA_TYPE_COMPILER); - PYFRIDA_REGISTER_TYPE (PackageManager, FRIDA_TYPE_PACKAGE_MANAGER); - PYFRIDA_REGISTER_TYPE (Package, FRIDA_TYPE_PACKAGE); - PYFRIDA_REGISTER_TYPE (PackageSearchResult, FRIDA_TYPE_PACKAGE_SEARCH_RESULT); - PYFRIDA_REGISTER_TYPE (PackageInstallResult, FRIDA_TYPE_PACKAGE_INSTALL_RESULT); - PYFRIDA_REGISTER_TYPE (FileMonitor, FRIDA_TYPE_FILE_MONITOR); - PYFRIDA_REGISTER_TYPE (IOStream, G_TYPE_IO_STREAM); - PYFRIDA_REGISTER_TYPE (Cancellable, G_TYPE_CANCELLABLE); - - frida_exception_by_error_code = g_hash_table_new_full (NULL, NULL, NULL, PyFrida_object_decref); -#define PYFRIDA_DECLARE_EXCEPTION(code, name) \ - do \ - { \ - PyObject * exception = PyErr_NewException ("frida." name "Error", NULL, NULL); \ - g_hash_table_insert (frida_exception_by_error_code, GINT_TO_POINTER (G_PASTE (FRIDA_ERROR_, code)), exception); \ - Py_IncRef (exception); \ - PyModule_AddObject (module, name "Error", exception); \ - } while (FALSE) - PYFRIDA_DECLARE_EXCEPTION (SERVER_NOT_RUNNING, "ServerNotRunning"); - PYFRIDA_DECLARE_EXCEPTION (EXECUTABLE_NOT_FOUND, "ExecutableNotFound"); - PYFRIDA_DECLARE_EXCEPTION (EXECUTABLE_NOT_SUPPORTED, "ExecutableNotSupported"); - PYFRIDA_DECLARE_EXCEPTION (PROCESS_NOT_FOUND, "ProcessNotFound"); - PYFRIDA_DECLARE_EXCEPTION (PROCESS_NOT_RESPONDING, "ProcessNotResponding"); - PYFRIDA_DECLARE_EXCEPTION (INVALID_ARGUMENT, "InvalidArgument"); - PYFRIDA_DECLARE_EXCEPTION (INVALID_OPERATION, "InvalidOperation"); - PYFRIDA_DECLARE_EXCEPTION (PERMISSION_DENIED, "PermissionDenied"); - PYFRIDA_DECLARE_EXCEPTION (ADDRESS_IN_USE, "AddressInUse"); - PYFRIDA_DECLARE_EXCEPTION (TIMED_OUT, "TimedOut"); - PYFRIDA_DECLARE_EXCEPTION (NOT_SUPPORTED, "NotSupported"); - PYFRIDA_DECLARE_EXCEPTION (PROTOCOL, "Protocol"); - PYFRIDA_DECLARE_EXCEPTION (TRANSPORT, "Transport"); - - cancelled_exception = PyErr_NewException ("frida.OperationCancelledError", NULL, NULL); - Py_IncRef (cancelled_exception); - PyModule_AddObject (module, "OperationCancelledError", cancelled_exception); - - return module; -} diff --git a/frida/_frida/extension.version b/frida/_frida/extension.version deleted file mode 100644 index a7b13ee..0000000 --- a/frida/_frida/extension.version +++ /dev/null @@ -1,7 +0,0 @@ -{ - global: - PyInit__frida; - - local: - *; -}; diff --git a/frida/_frida/meson.build b/frida/_frida/meson.build deleted file mode 100644 index 21b02d4..0000000 --- a/frida/_frida/meson.build +++ /dev/null @@ -1,21 +0,0 @@ -py_sources = [ - '__init__.pyi', - 'py.typed', -] -python.install_sources(py_sources, subdir: 'frida' / '_frida', pure: false) - -extra_link_args = [] -if host_os_family == 'darwin' - extra_link_args += '-Wl,-exported_symbol,_PyInit__frida' -elif host_os_family != 'windows' - extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / 'extension.version' -endif - -extension = python.extension_module('_frida', 'extension.c', - limited_api: '3.7', - c_args: frida_component_cflags, - link_args: extra_link_args, - dependencies: [python_dep, frida_core_dep, os_deps], - install: true, - subdir: 'frida', -) diff --git a/frida/core.py b/frida/core.py deleted file mode 100644 index 1f1c99c..0000000 --- a/frida/core.py +++ /dev/null @@ -1,1822 +0,0 @@ -from __future__ import annotations - -import asyncio -import dataclasses -import fnmatch -import functools -import json -import sys -import threading -import traceback -import warnings -from types import TracebackType -from typing import ( - Any, - AnyStr, - Awaitable, - Callable, - Dict, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - Type, - TypeVar, - Union, - overload, -) - -if sys.version_info >= (3, 8): - from typing import Literal, TypedDict -else: - from typing_extensions import Literal, TypedDict - -if sys.version_info >= (3, 10): - from typing import ParamSpec -else: - from typing_extensions import ParamSpec - -if sys.version_info >= (3, 11): - from typing import NotRequired, cast -else: - from typing_extensions import NotRequired, cast - -from . import _frida - -_device_manager = None - -_Cancellable = _frida.Cancellable - -ProcessTarget = Union[int, str] -Spawn = _frida.Spawn - - -@dataclasses.dataclass -class RPCResult: - finished: bool = False - value: Any = None - error: Optional[Exception] = None - - -def get_device_manager() -> "DeviceManager": - """ - Get or create a singleton DeviceManager that let you manage all the devices - """ - - global _device_manager - if _device_manager is None: - _device_manager = DeviceManager(_frida.DeviceManager()) - return _device_manager - - -def _filter_missing_kwargs(d: MutableMapping[Any, Any]) -> None: - for key in list(d.keys()): - if d[key] is None: - d.pop(key) - - -P = ParamSpec("P") -R = TypeVar("R", covariant=True) - - -def cancellable(f: Callable[P, R]) -> Callable[P, R]: - # currently there is no way to type properly the extended callable with optional cancellable parameter - # ref: https://github.com/python/typing/discussions/1905#discussioncomment-11696995 - @functools.wraps(f) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - cancellable = kwargs.pop("cancellable", None) - if cancellable is not None: - with cast(Cancellable, cancellable): - return f(*args, **kwargs) - - return f(*args, **kwargs) - - return wrapper - - -class IOStream: - """ - Frida's own implementation of an input/output stream - """ - - def __init__(self, impl: _frida.IOStream) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_closed(self) -> bool: - """ - Query whether the stream is closed - """ - - return self._impl.is_closed() - - @cancellable - def close(self) -> None: - """ - Close the stream. - """ - - self._impl.close() - - @cancellable - def read(self, count: int) -> bytes: - """ - Read up to the specified number of bytes from the stream - """ - - return self._impl.read(count) - - @cancellable - def read_all(self, count: int) -> bytes: - """ - Read exactly the specified number of bytes from the stream - """ - - return self._impl.read_all(count) - - @cancellable - def write(self, data: bytes) -> int: - """ - Write as much as possible of the provided data to the stream - """ - - return self._impl.write(data) - - @cancellable - def write_all(self, data: bytes) -> None: - """ - Write all of the provided data to the stream - """ - - self._impl.write_all(data) - - -class PortalMembership: - def __init__(self, impl: _frida.PortalMembership) -> None: - self._impl = impl - - @cancellable - def terminate(self) -> None: - """ - Terminate the membership - """ - - self._impl.terminate() - - -class ScriptExportsSync: - """ - Proxy object that expose all the RPC exports of a script as attributes on this class - - A method named exampleMethod in a script will be called with instance.example_method on this object - """ - - def __init__(self, script: "Script") -> None: - self._script = script - - def __getattr__(self, name: str) -> Callable[..., Any]: - script = self._script - js_name = _to_camel_case(name) - - def method(*args: Any, **kwargs: Any) -> Any: - request, data = make_rpc_call_request(js_name, args) - return script._rpc_request(request, data, **kwargs) - - return method - - def __dir__(self) -> List[str]: - return self._script.list_exports_sync() - - -ScriptExports = ScriptExportsSync - - -class ScriptExportsAsync: - """ - Proxy object that expose all the RPC exports of a script as attributes on this class - - A method named exampleMethod in a script will be called with instance.example_method on this object - """ - - def __init__(self, script: "Script") -> None: - self._script = script - - def __getattr__(self, name: str) -> Callable[..., Awaitable[Any]]: - script = self._script - js_name = _to_camel_case(name) - - async def method(*args: Any, **kwargs: Any) -> Any: - request, data = make_rpc_call_request(js_name, args) - return await script._rpc_request_async(request, data, **kwargs) - - return method - - def __dir__(self) -> List[str]: - return self._script.list_exports_sync() - - -def make_rpc_call_request(js_name: str, args: Sequence[Any]) -> Tuple[List[Any], Optional[bytes]]: - if args and isinstance(args[-1], bytes): - raw_args = args[:-1] - data = args[-1] - else: - raw_args = args - data = None - return (["call", js_name, raw_args], data) - - -class ScriptErrorMessage(TypedDict): - type: Literal["error"] - description: str - stack: NotRequired[str] - fileName: NotRequired[str] - lineNumber: NotRequired[int] - columnNumber: NotRequired[int] - - -class ScriptPayloadMessage(TypedDict): - type: Literal["send"] - payload: NotRequired[Any] - - -ScriptMessage = Union[ScriptPayloadMessage, ScriptErrorMessage] -ScriptMessageCallback = Callable[[ScriptMessage, Optional[bytes]], None] -ScriptDestroyedCallback = Callable[[], None] - - -class RPCException(Exception): - """ - Wraps remote errors from the script RPC - """ - - def __str__(self) -> str: - return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) - - -class Script: - def __init__(self, impl: _frida.Script) -> None: - self.exports_sync = ScriptExportsSync(self) - self.exports_async = ScriptExportsAsync(self) - - self._impl = impl - - self._on_message_callbacks: List[ScriptMessageCallback] = [] - self._log_handler: Callable[[str, str], None] = self.default_log_handler - - self._pending: Dict[ - int, Callable[[Optional[Any], Optional[Union[RPCException, _frida.InvalidOperationError]]], None] - ] = {} - self._next_request_id = 1 - self._cond = threading.Condition() - - impl.on("destroyed", self._on_destroyed) - impl.on("message", self._on_message) - - @property - def exports(self) -> ScriptExportsSync: - """ - The old way of retrieving the synchronous exports caller - """ - - warnings.warn( - "Script.exports will become asynchronous in the future, use the explicit Script.exports_sync instead", - DeprecationWarning, - stacklevel=2, - ) - return self.exports_sync - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_destroyed(self) -> bool: - """ - Query whether the script has been destroyed - """ - - return self._impl.is_destroyed() - - @cancellable - def load(self) -> None: - """ - Load the script. - """ - - self._impl.load() - - @cancellable - def unload(self) -> None: - """ - Unload the script - """ - - self._impl.unload() - - @cancellable - def eternalize(self) -> None: - """ - Eternalize the script - """ - - self._impl.eternalize() - - def post(self, message: Any, data: Optional[AnyStr] = None) -> None: - """ - Post a JSON-encoded message to the script - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(raw_message, **kwargs) - - @cancellable - def enable_debugger(self, port: Optional[int] = None) -> None: - """ - Enable the Node.js compatible script debugger - """ - - kwargs = {"port": port} - _filter_missing_kwargs(kwargs) - self._impl.enable_debugger(**kwargs) - - @cancellable - def disable_debugger(self) -> None: - """ - Disable the Node.js compatible script debugger - """ - - self._impl.disable_debugger() - - @overload - def on(self, signal: Literal["destroyed"], callback: ScriptDestroyedCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: ScriptMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["destroyed"], callback: ScriptDestroyedCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: ScriptMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def get_log_handler(self) -> Callable[[str, str], None]: - """ - Get the method that handles the script logs - """ - - return self._log_handler - - def set_log_handler(self, handler: Callable[[str, str], None]) -> None: - """ - Set the method that handles the script logs - :param handler: a callable that accepts two parameters: - 1. the log level name - 2. the log message - """ - - self._log_handler = handler - - def default_log_handler(self, level: str, text: str) -> None: - """ - The default implementation of the log handler, prints the message to stdout - or stderr, depending on the level - """ - - if level == "info": - print(text, file=sys.stdout) - else: - print(text, file=sys.stderr) - - async def list_exports_async(self) -> List[str]: - """ - Asynchronously list all the exported attributes from the script's rpc - """ - - result = await self._rpc_request_async(["list"]) - assert isinstance(result, list) - return result - - def list_exports_sync(self) -> List[str]: - """ - List all the exported attributes from the script's rpc - """ - - result = self._rpc_request(["list"]) - assert isinstance(result, list) - return result - - def list_exports(self) -> List[str]: - """ - List all the exported attributes from the script's rpc - """ - - warnings.warn( - "Script.list_exports will become asynchronous in the future, use the explicit Script.list_exports_sync instead", - DeprecationWarning, - stacklevel=2, - ) - return self.list_exports_sync() - - def _rpc_request_async(self, args: Any, data: Optional[bytes] = None) -> asyncio.Future[Any]: - loop = asyncio.get_event_loop() - future: asyncio.Future[Any] = asyncio.Future() - - def on_complete(value: Any, error: Optional[Union[RPCException, _frida.InvalidOperationError]]) -> None: - if error is not None: - loop.call_soon_threadsafe(future.set_exception, error) - else: - loop.call_soon_threadsafe(future.set_result, value) - - request_id = self._append_pending(on_complete) - - if not self.is_destroyed: - self._send_rpc_call(request_id, args, data) - else: - self._on_destroyed() - - return future - - @cancellable - def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> Any: - result = RPCResult() - - def on_complete(value: Any, error: Optional[Union[RPCException, _frida.InvalidOperationError]]) -> None: - with self._cond: - result.finished = True - result.value = value - result.error = error - self._cond.notify_all() - - def on_cancelled() -> None: - self._pending.pop(request_id, None) - on_complete(None, None) - - request_id = self._append_pending(on_complete) - - if not self.is_destroyed: - self._send_rpc_call(request_id, args, data) - - cancellable = Cancellable.get_current() - cancel_handler = cancellable.connect(on_cancelled) - try: - with self._cond: - while not result.finished: - self._cond.wait() - finally: - cancellable.disconnect(cancel_handler) - - cancellable.raise_if_cancelled() - else: - self._on_destroyed() - - if result.error is not None: - raise result.error - - return result.value - - def _append_pending( - self, callback: Callable[[Any, Optional[Union[RPCException, _frida.InvalidOperationError]]], None] - ) -> int: - with self._cond: - request_id = self._next_request_id - self._next_request_id += 1 - self._pending[request_id] = callback - return request_id - - def _send_rpc_call(self, request_id: int, args: Any, data: Optional[bytes]) -> None: - self.post(["frida:rpc", request_id, *args], data) - - def _on_rpc_message(self, request_id: int, operation: str, params: List[Any], data: Optional[Any]) -> None: - if operation in ("ok", "error"): - callback = self._pending.pop(request_id, None) - if callback is None: - return - - value = None - error = None - if operation == "ok": - if data is not None: - value = (params[1], data) if len(params) > 1 else data - else: - value = params[0] - else: - error = RPCException(*params[0:3]) - - callback(value, error) - - def _on_destroyed(self) -> None: - while True: - next_pending = None - - with self._cond: - pending_ids = list(self._pending.keys()) - if len(pending_ids) > 0: - next_pending = self._pending.pop(pending_ids[0]) - - if next_pending is None: - break - - next_pending(None, _frida.InvalidOperationError("script has been destroyed")) - - def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: - message = json.loads(raw_message) - - mtype = message["type"] - payload = message.get("payload", None) - if mtype == "log": - level = message["level"] - text = payload - self._log_handler(level, text) - elif mtype == "send" and isinstance(payload, list) and len(payload) > 0 and payload[0] == "frida:rpc": - request_id = payload[1] - operation = payload[2] - params = payload[3:] - self._on_rpc_message(request_id, operation, params, data) - else: - for callback in self._on_message_callbacks[:]: - try: - callback(message, data) - except: - traceback.print_exc() - - -SessionDetachedCallback = Callable[ - [ - Literal[ - "application-requested", "process-replaced", "process-terminated", "connection-terminated", "device-lost" - ], - Optional[_frida.Crash], - ], - None, -] - - -class Session: - def __init__(self, impl: _frida.Session) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_detached(self) -> bool: - """ - Query whether the session is detached - """ - - return self._impl.is_detached() - - @cancellable - def detach(self) -> None: - """ - Detach session from the process - """ - - self._impl.detach() - - @cancellable - def resume(self) -> None: - """ - Resume session after network error - """ - - self._impl.resume() - - @cancellable - def enable_child_gating(self) -> None: - """ - Enable child gating - """ - - self._impl.enable_child_gating() - - @cancellable - def disable_child_gating(self) -> None: - """ - Disable child gating - """ - - self._impl.disable_child_gating() - - @cancellable - def create_script( - self, source: str, name: Optional[str] = None, snapshot: Optional[bytes] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script - """ - - kwargs = {"name": name, "snapshot": snapshot, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return Script(self._impl.create_script(source, **kwargs)) # type: ignore - - @cancellable - def create_script_from_bytes( - self, data: bytes, name: Optional[str] = None, snapshot: Optional[bytes] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script from bytecode - """ - - kwargs = {"name": name, "snapshot": snapshot, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return Script(self._impl.create_script_from_bytes(data, **kwargs)) # type: ignore - - @cancellable - def compile_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> bytes: - """ - Compile script source code to bytecode - """ - - kwargs = {"name": name, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return self._impl.compile_script(source, **kwargs) - - @cancellable - def snapshot_script(self, embed_script: str, warmup_script: Optional[str], runtime: Optional[str] = None) -> bytes: - """ - Evaluate script and snapshot the resulting VM state - """ - kwargs = {"warmup_script": warmup_script, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return self._impl.snapshot_script(embed_script, **kwargs) - - @cancellable - def setup_peer_connection( - self, stun_server: Optional[str] = None, relays: Optional[Sequence[_frida.Relay]] = None - ) -> None: - """ - Set up a peer connection with the target process - """ - - kwargs = {"stun_server": stun_server, "relays": relays} - _filter_missing_kwargs(kwargs) - self._impl.setup_peer_connection(**kwargs) # type: ignore - - @cancellable - def join_portal( - self, - address: str, - certificate: Optional[str] = None, - token: Optional[str] = None, - acl: Union[None, List[str], Tuple[str]] = None, - ) -> PortalMembership: - """ - Join a portal - """ - - kwargs: Dict[str, Any] = {"certificate": certificate, "token": token, "acl": acl} - _filter_missing_kwargs(kwargs) - return PortalMembership(self._impl.join_portal(address, **kwargs)) - - def on( - self, - signal: Literal["detached"], - callback: SessionDetachedCallback, - ) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - def off( - self, - signal: Literal["detached"], - callback: SessionDetachedCallback, - ) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -BusDetachedCallback = Callable[[], None] -BusMessageCallback = Callable[[Mapping[Any, Any], Optional[bytes]], None] - - -class Bus: - def __init__(self, impl: _frida.Bus) -> None: - self._impl = impl - self._on_message_callbacks: List[Callable[..., Any]] = [] - - impl.on("message", self._on_message) - - @cancellable - def attach(self) -> None: - """ - Attach to the bus - """ - - self._impl.attach() - - def post(self, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a JSON-encoded message to the bus - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(raw_message, **kwargs) - - @overload - def on(self, signal: Literal["detached"], callback: BusDetachedCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: BusMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["detached"], callback: BusDetachedCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: BusMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def _on_message(self, raw_message: str, data: Any) -> None: - message = json.loads(raw_message) - - for callback in self._on_message_callbacks[:]: - try: - callback(message, data) - except: - traceback.print_exc() - - -ServiceCloseCallback = Callable[[], None] -ServiceMessageCallback = Callable[[Any], None] - - -class Service: - def __init__(self, impl: _frida.Service) -> None: - self._impl = impl - - @cancellable - def activate(self) -> None: - """ - Activate the service - """ - - self._impl.activate() - - @cancellable - def cancel(self) -> None: - """ - Cancel the service - """ - - self._impl.cancel() - - def request(self, parameters: Any) -> Any: - """ - Perform a request - """ - - return self._impl.request(parameters) - - @overload - def on(self, signal: Literal["close"], callback: ServiceCloseCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: ServiceMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["close"], callback: ServiceCloseCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: ServiceMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -DeviceSpawnAddedCallback = Callable[[_frida.Spawn], None] -DeviceSpawnRemovedCallback = Callable[[_frida.Spawn], None] -DeviceChildAddedCallback = Callable[[_frida.Child], None] -DeviceChildRemovedCallback = Callable[[_frida.Child], None] -DeviceProcessCrashedCallback = Callable[[_frida.Crash], None] -DeviceOutputCallback = Callable[[int, int, bytes], None] -DeviceUninjectedCallback = Callable[[int], None] -DeviceLostCallback = Callable[[], None] - - -class Device: - """ - Represents a device that Frida connects to - """ - - def __init__(self, device: _frida.Device) -> None: - assert device.bus is not None - self.id = device.id - self.name = device.name - self.icon = device.icon - self.type = device.type - self.bus = Bus(device.bus) - - self._impl = device - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_lost(self) -> bool: - """ - Query whether the device has been lost - """ - - return self._impl.is_lost() - - @cancellable - def query_system_parameters(self) -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system - """ - - return self._impl.query_system_parameters() - - @cancellable - def get_frontmost_application(self, scope: Optional[str] = None) -> Optional[_frida.Application]: - """ - Get details about the frontmost application - """ - - kwargs = {"scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.get_frontmost_application(**kwargs) - - @cancellable - def enumerate_applications( - self, identifiers: Optional[Sequence[str]] = None, scope: Optional[str] = None - ) -> List[_frida.Application]: - """ - Enumerate applications - """ - - kwargs = {"identifiers": identifiers, "scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.enumerate_applications(**kwargs) # type: ignore - - @cancellable - def enumerate_processes( - self, pids: Optional[Sequence[int]] = None, scope: Optional[str] = None - ) -> List[_frida.Process]: - """ - Enumerate processes - """ - - kwargs = {"pids": pids, "scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.enumerate_processes(**kwargs) # type: ignore - - @cancellable - def get_process(self, process_name: str) -> _frida.Process: - """ - Get the process with the given name - :raises ProcessNotFoundError: if the process was not found or there were more than one process with the given name - """ - - process_name_lc = process_name.lower() - matching = [ - process - for process in self._impl.enumerate_processes() - if fnmatch.fnmatchcase(process.name.lower(), process_name_lc) - ] - if len(matching) == 1: - return matching[0] - elif len(matching) > 1: - matches_list = ", ".join([f"{process.name} (pid: {process.pid})" for process in matching]) - raise _frida.ProcessNotFoundError(f"ambiguous name; it matches: {matches_list}") - else: - raise _frida.ProcessNotFoundError(f"unable to find process with name '{process_name}'") - - @cancellable - def enable_spawn_gating(self) -> None: - """ - Enable spawn gating - """ - - self._impl.enable_spawn_gating() - - @cancellable - def disable_spawn_gating(self) -> None: - """ - Disable spawn gating - """ - - self._impl.disable_spawn_gating() - - @cancellable - def enumerate_pending_spawn(self) -> List[_frida.Spawn]: - """ - Enumerate pending spawn - """ - - return self._impl.enumerate_pending_spawn() - - @cancellable - def enumerate_pending_children(self) -> List[_frida.Child]: - """ - Enumerate pending children - """ - - return self._impl.enumerate_pending_children() - - @cancellable - def spawn( - self, - program: Union[str, List[Union[str, bytes]], Tuple[Union[str, bytes]]], - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, - ) -> int: - """ - Spawn a process into an attachable state - """ - - if not isinstance(program, str): - argv = program - if isinstance(argv[0], bytes): - program = argv[0].decode() - else: - program = argv[0] - if len(argv) == 1: - argv = None - - kwargs = {"argv": argv, "envp": envp, "env": env, "cwd": cwd, "stdio": stdio, "aux": kwargs} - _filter_missing_kwargs(kwargs) - return self._impl.spawn(program, **kwargs) - - @cancellable - def input(self, target: ProcessTarget, data: bytes) -> None: - """ - Input data on stdin of a spawned process - :param target: the PID or name of the process - """ - - self._impl.input(self._pid_of(target), data) - - @cancellable - def resume(self, target: ProcessTarget) -> None: - """ - Resume a process from the attachable state - :param target: the PID or name of the process - """ - - self._impl.resume(self._pid_of(target)) - - @cancellable - def kill(self, target: ProcessTarget) -> None: - """ - Kill a process - :param target: the PID or name of the process - """ - self._impl.kill(self._pid_of(target)) - - @cancellable - def attach( - self, - target: ProcessTarget, - realm: Optional[str] = None, - persist_timeout: Optional[int] = None, - ) -> Session: - """ - Attach to a process - :param target: the PID or name of the process - """ - - kwargs = {"realm": realm, "persist_timeout": persist_timeout} - _filter_missing_kwargs(kwargs) - return Session(self._impl.attach(self._pid_of(target), **kwargs)) # type: ignore - - @cancellable - def inject_library_file(self, target: ProcessTarget, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a process - :param target: the PID or name of the process - """ - - return self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data) - - @cancellable - def inject_library_blob(self, target: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a process - :param target: the PID or name of the process - """ - - return self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data) - - @cancellable - def open_channel(self, address: str) -> IOStream: - """ - Open a device-specific communication channel - """ - - return IOStream(self._impl.open_channel(address)) - - @cancellable - def open_service(self, address: str) -> Service: - """ - Open a device-specific service - """ - - return Service(self._impl.open_service(address)) - - @cancellable - def unpair(self) -> None: - """ - Unpair device - """ - - self._impl.unpair() - - @cancellable - def get_bus(self) -> Bus: - """ - Get the message bus of the device - """ - - return self.bus - - @overload - def on(self, signal: Literal["spawn-added"], callback: DeviceSpawnAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["spawn-removed"], callback: DeviceSpawnRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["child-added"], callback: DeviceChildAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["child-removed"], callback: DeviceChildRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["process-crashed"], callback: DeviceProcessCrashedCallback) -> None: ... - - @overload - def on(self, signal: Literal["output"], callback: DeviceOutputCallback) -> None: ... - - @overload - def on(self, signal: Literal["uninjected"], callback: DeviceUninjectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["lost"], callback: DeviceLostCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["spawn-added"], callback: DeviceSpawnAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["spawn-removed"], callback: DeviceSpawnRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["child-added"], callback: DeviceChildAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["child-removed"], callback: DeviceChildRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["process-crashed"], callback: DeviceProcessCrashedCallback) -> None: ... - - @overload - def off(self, signal: Literal["output"], callback: DeviceOutputCallback) -> None: ... - - @overload - def off(self, signal: Literal["uninjected"], callback: DeviceUninjectedCallback) -> None: ... - - @overload - def off(self, signal: Literal["lost"], callback: DeviceLostCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - def _pid_of(self, target: ProcessTarget) -> int: - if isinstance(target, str): - return self.get_process(target).pid - else: - return target - - -DeviceManagerAddedCallback = Callable[[_frida.Device], None] -DeviceManagerRemovedCallback = Callable[[_frida.Device], None] -DeviceManagerChangedCallback = Callable[[], None] - - -class DeviceManager: - def __init__(self, impl: _frida.DeviceManager) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - def get_local_device(self) -> Device: - """ - Get the local device - """ - - return self.get_device_matching(lambda d: d.type == "local", timeout=0) - - def get_remote_device(self) -> Device: - """ - Get the first remote device in the devices list - """ - - return self.get_device_matching(lambda d: d.type == "remote", timeout=0) - - def get_usb_device(self, timeout: int = 0) -> Device: - """ - Get the first device connected over USB in the devices list - """ - - return self.get_device_matching(lambda d: d.type == "usb", timeout) - - def get_device(self, id: Optional[str], timeout: int = 0) -> Device: - """ - Get a device by its id - """ - - return self.get_device_matching(lambda d: d.id == id, timeout) - - @cancellable - def get_device_matching(self, predicate: Callable[[Device], bool], timeout: int = 0) -> Device: - """ - Get device matching predicate - :param predicate: a function to filter the devices - :param timeout: operation timeout in seconds - """ - - if timeout < 0: - raw_timeout = -1 - elif timeout == 0: - raw_timeout = 0 - else: - raw_timeout = int(timeout * 1000.0) - return Device(self._impl.get_device_matching(lambda d: predicate(Device(d)), raw_timeout)) - - @cancellable - def enumerate_devices(self) -> List[Device]: - """ - Enumerate devices - """ - - return [Device(device) for device in self._impl.enumerate_devices()] - - @cancellable - def add_remote_device( - self, - address: str, - certificate: Optional[str] = None, - origin: Optional[str] = None, - token: Optional[str] = None, - keepalive_interval: Optional[int] = None, - ) -> Device: - """ - Add a remote device - """ - - kwargs: Dict[str, Any] = { - "certificate": certificate, - "origin": origin, - "token": token, - "keepalive_interval": keepalive_interval, - } - _filter_missing_kwargs(kwargs) - return Device(self._impl.add_remote_device(address, **kwargs)) - - @cancellable - def remove_remote_device(self, address: str) -> None: - """ - Remove a remote device - """ - - self._impl.remove_remote_device(address=address) - - @overload - def on(self, signal: Literal["added"], callback: DeviceManagerAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["removed"], callback: DeviceManagerRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["changed"], callback: DeviceManagerChangedCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["added"], callback: DeviceManagerAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["removed"], callback: DeviceManagerRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["changed"], callback: DeviceManagerChangedCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -class EndpointParameters: - def __init__( - self, - address: Optional[str] = None, - port: Optional[int] = None, - certificate: Optional[str] = None, - origin: Optional[str] = None, - authentication: Optional[Tuple[str, Union[str, Callable[[str], Any]]]] = None, - asset_root: Optional[str] = None, - ): - kwargs: Dict[str, Any] = {"address": address, "port": port, "certificate": certificate, "origin": origin} - if asset_root is not None: - kwargs["asset_root"] = str(asset_root) - _filter_missing_kwargs(kwargs) - - if authentication is not None: - (auth_scheme, auth_data) = authentication - if auth_scheme == "token": - kwargs["auth_token"] = auth_data - elif auth_scheme == "callback": - if not callable(auth_data): - raise ValueError( - "Authentication data must provide a Callable if the authentication scheme is callback" - ) - kwargs["auth_callback"] = make_auth_callback(auth_data) - else: - raise ValueError("invalid authentication scheme") - - self._impl = _frida.EndpointParameters(**kwargs) - - -PortalServiceNodeJoinedCallback = Callable[[int, _frida.Application], None] -PortalServiceNodeLeftCallback = Callable[[int, _frida.Application], None] -PortalServiceNodeConnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceNodeDisconnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceControllerConnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceControllerDisconnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceAuthenticatedCallback = Callable[[int, Mapping[Any, Any]], None] -PortalServiceSubscribeCallback = Callable[[int], None] -PortalServiceMessageCallback = Callable[[int, Mapping[Any, Any], Optional[bytes]], None] - - -class PortalService: - def __init__( - self, - cluster_params: EndpointParameters = EndpointParameters(), - control_params: Optional[EndpointParameters] = None, - ) -> None: - args = [cluster_params._impl] - if control_params is not None: - args.append(control_params._impl) - impl = _frida.PortalService(*args) - - self.device = impl.device - self._impl = impl - self._on_authenticated_callbacks: List[PortalServiceAuthenticatedCallback] = [] - self._on_message_callbacks: List[PortalServiceMessageCallback] = [] - - impl.on("authenticated", self._on_authenticated) - impl.on("message", self._on_message) - - @cancellable - def start(self) -> None: - """ - Start listening for incoming connections - :raises InvalidOperationError: if the service isn't stopped - :raises AddressInUseError: if the given address is already in use - """ - - self._impl.start() - - @cancellable - def stop(self) -> None: - """ - Stop listening for incoming connections, and kick any connected clients - :raises InvalidOperationError: if the service is already stopped - """ - - self._impl.stop() - - def post(self, connection_id: int, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to a specific control channel. - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(connection_id, raw_message, **kwargs) - - def narrowcast(self, tag: str, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to control channels with a specific tag - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.narrowcast(tag, raw_message, **kwargs) - - def broadcast(self, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Broadcast a message to all control channels - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.broadcast(raw_message, **kwargs) - - def enumerate_tags(self, connection_id: int) -> List[str]: - """ - Enumerate tags of a specific connection - """ - - return self._impl.enumerate_tags(connection_id) - - def tag(self, connection_id: int, tag: str) -> None: - """ - Tag a specific control channel - """ - - self._impl.tag(connection_id, tag) - - def untag(self, connection_id: int, tag: str) -> None: - """ - Untag a specific control channel - """ - - self._impl.untag(connection_id, tag) - - @overload - def on(self, signal: Literal["node-joined"], callback: PortalServiceNodeJoinedCallback) -> None: ... - - @overload - def on(self, signal: Literal["node-left"], callback: PortalServiceNodeLeftCallback) -> None: ... - - @overload - def on( - self, signal: Literal["controller-connected"], callback: PortalServiceControllerConnectedCallback - ) -> None: ... - - @overload - def on( - self, signal: Literal["controller-disconnected"], callback: PortalServiceControllerDisconnectedCallback - ) -> None: ... - - @overload - def on(self, signal: Literal["node-connected"], callback: PortalServiceNodeConnectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["node-disconnected"], callback: PortalServiceNodeDisconnectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["authenticated"], callback: PortalServiceAuthenticatedCallback) -> None: ... - - @overload - def on(self, signal: Literal["subscribe"], callback: PortalServiceSubscribeCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: PortalServiceMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "authenticated": - self._on_authenticated_callbacks.append(callback) - elif signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "authenticated": - self._on_authenticated_callbacks.remove(callback) - elif signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def _on_authenticated(self, connection_id: int, raw_session_info: str) -> None: - session_info = json.loads(raw_session_info) - - for callback in self._on_authenticated_callbacks[:]: - try: - callback(connection_id, session_info) - except: - traceback.print_exc() - - def _on_message(self, connection_id: int, raw_message: str, data: Optional[bytes]) -> None: - message = json.loads(raw_message) - - for callback in self._on_message_callbacks[:]: - try: - callback(connection_id, message, data) - except: - traceback.print_exc() - - -class CompilerDiagnosticFile(TypedDict): - path: str - line: int - character: int - - -class CompilerDiagnostic(TypedDict): - category: str - code: int - file: NotRequired[CompilerDiagnosticFile] - text: str - - -CompilerStartingCallback = Callable[[], None] -CompilerFinishedCallback = Callable[[], None] -CompilerOutputCallback = Callable[[str], None] -CompilerDiagnosticsCallback = Callable[[List[CompilerDiagnostic]], None] - -CompilerOutputFormat = Literal["unescaped", "hex-bytes", "c-string"] -CompilerBundleFormat = Literal["esm", "iife"] -CompilerTypeCheck = Literal["full", "none"] -CompilerSourceMaps = Literal["included", "omitted"] -CompilerCompression = Literal["none", "terser"] -CompilerPlatform = Literal["neutral", "gum", "browser"] - - -class Compiler: - def __init__(self) -> None: - self._impl = _frida.Compiler() - - def __repr__(self) -> str: - return repr(self._impl) - - @cancellable - def build( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[CompilerOutputFormat] = None, - bundle_format: Optional[CompilerBundleFormat] = None, - type_check: Optional[CompilerTypeCheck] = None, - source_maps: Optional[CompilerSourceMaps] = None, - compression: Optional[CompilerCompression] = None, - platform: Optional[CompilerPlatform] = None, - externals: Optional[Sequence[str]] = None, - ) -> str: - kwargs: dict[str, Any] = { - "project_root": project_root, - "output_format": output_format, - "bundle_format": bundle_format, - "type_check": type_check, - "source_maps": source_maps, - "compression": compression, - "platform": platform, - "externals": externals, - } - _filter_missing_kwargs(kwargs) - return self._impl.build(entrypoint, **kwargs) - - @cancellable - def watch( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[CompilerOutputFormat] = None, - bundle_format: Optional[CompilerBundleFormat] = None, - type_check: Optional[CompilerTypeCheck] = None, - source_maps: Optional[CompilerSourceMaps] = None, - compression: Optional[CompilerCompression] = None, - platform: Optional[CompilerPlatform] = None, - externals: Optional[Sequence[str]] = None, - ) -> None: - kwargs: dict[str, Any] = { - "project_root": project_root, - "output_format": output_format, - "bundle_format": bundle_format, - "type_check": type_check, - "source_maps": source_maps, - "compression": compression, - "platform": platform, - "externals": externals, - } - _filter_missing_kwargs(kwargs) - return self._impl.watch(entrypoint, **kwargs) - - @overload - def on(self, signal: Literal["starting"], callback: CompilerStartingCallback) -> None: ... - - @overload - def on(self, signal: Literal["finished"], callback: CompilerFinishedCallback) -> None: ... - - @overload - def on(self, signal: Literal["output"], callback: CompilerOutputCallback) -> None: ... - - @overload - def on(self, signal: Literal["diagnostics"], callback: CompilerDiagnosticsCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["starting"], callback: CompilerStartingCallback) -> None: ... - - @overload - def off(self, signal: Literal["finished"], callback: CompilerFinishedCallback) -> None: ... - - @overload - def off(self, signal: Literal["output"], callback: CompilerOutputCallback) -> None: ... - - @overload - def off(self, signal: Literal["diagnostics"], callback: CompilerDiagnosticsCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -PackageManagerInstallProgressCallback = Callable[ - [ - Literal[ - "initializing", - "preparing-dependencies", - "resolving-package", - "fetching-resource", - "package-already-installed", - "downloading-package", - "package-installed", - "resolving-and-installing-all", - "complete", - ], - float, - Optional[str], - ], - None, -] - -PackageRole = Literal["runtime", "development", "optional", "peer"] - - -class PackageManager: - def __init__(self) -> None: - self._impl = _frida.PackageManager() - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def registry(self): - return self._impl.registry - - @registry.setter - def registry(self, value): - self._impl.registry = value - - @cancellable - def search( - self, - query: str, - offset: Optional[int] = None, - limit: Optional[int] = None, - ) -> _frida.PackageSearchResult: - kwargs = { - "offset": offset, - "limit": limit, - } - _filter_missing_kwargs(kwargs) - return self._impl.search(query, **kwargs) - - @cancellable - def install( - self, - project_root: Optional[str] = None, - role: Optional[PackageRole] = None, - specs: Optional[Sequence[str]] = None, - omits: Optional[Sequence[PackageRole]] = None, - ) -> _frida.PackageInstallResult: - kwargs: Dict[str, Any] = { - "project_root": project_root, - "role": role, - "specs": specs, - "omits": omits, - } - _filter_missing_kwargs(kwargs) - return self._impl.install(**kwargs) - - def on(self, signal: Literal["install-progress"], callback: PackageManagerInstallProgressCallback) -> None: - self._impl.on(signal, callback) - - def off(self, signal: Literal["install-progress"], callback: PackageManagerInstallProgressCallback) -> None: - self._impl.off(signal, callback) - - -class CancellablePollFD: - def __init__(self, cancellable: _Cancellable) -> None: - self.handle = cancellable.get_fd() - self._cancellable: Optional[_Cancellable] = cancellable - - def __del__(self) -> None: - self.release() - - def release(self) -> None: - if self._cancellable is not None: - if self.handle != -1: - self._cancellable.release_fd() - self.handle = -1 - self._cancellable = None - - def __repr__(self) -> str: - return repr(self.handle) - - def __enter__(self) -> int: - return self.handle - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - trace: Optional[TracebackType], - ) -> None: - self.release() - - -class Cancellable: - def __init__(self) -> None: - self._impl = _Cancellable() - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_cancelled(self) -> bool: - """ - Query whether cancellable has been cancelled - """ - - return self._impl.is_cancelled() - - def raise_if_cancelled(self) -> None: - """ - Raise an exception if cancelled - :raises OperationCancelledError: - """ - - self._impl.raise_if_cancelled() - - def get_pollfd(self) -> CancellablePollFD: - return CancellablePollFD(self._impl) - - @classmethod - def get_current(cls) -> _frida.Cancellable: - """ - Get the top cancellable from the stack - """ - - return _Cancellable.get_current() - - def __enter__(self) -> None: - self._impl.push_current() - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - trace: Optional[TracebackType], - ) -> None: - self._impl.pop_current() - - def connect(self, callback: Callable[..., Any]) -> int: - """ - Register notification callback - :returns: the created handler id - """ - - return self._impl.connect(callback) - - def disconnect(self, handler_id: int) -> None: - """ - Unregister notification callback. - """ - - self._impl.disconnect(handler_id) - - def cancel(self) -> None: - """ - Set cancellable to cancelled - """ - - self._impl.cancel() - - -def make_auth_callback(callback: Callable[[str], Any]) -> Callable[[Any], str]: - """ - Wraps authenticated callbacks with JSON marshaling - """ - - def authenticate(token: str) -> str: - session_info = callback(token) - return json.dumps(session_info) - - return authenticate - - -def _to_camel_case(name: str) -> str: - result = "" - uppercase_next = False - for c in name: - if c == "_": - uppercase_next = True - elif uppercase_next: - result += c.upper() - uppercase_next = False - else: - result += c.lower() - return result diff --git a/frida/_frida/py.typed b/frida/frida_bindgen/__init__.py similarity index 100% rename from frida/_frida/py.typed rename to frida/frida_bindgen/__init__.py diff --git a/frida/frida_bindgen/__main__.py b/frida/frida_bindgen/__main__.py new file mode 100644 index 0000000..9ae637f --- /dev/null +++ b/frida/frida_bindgen/__main__.py @@ -0,0 +1,4 @@ +from .cli import main + +if __name__ == "__main__": + main() diff --git a/frida/frida_bindgen/assets/codegen_helpers.c b/frida/frida_bindgen/assets/codegen_helpers.c new file mode 100644 index 0000000..fbececa --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_helpers.c @@ -0,0 +1,1970 @@ +static gboolean +fdn_is_null (napi_env env, + napi_value value) +{ + napi_valuetype type; + + napi_typeof (env, value, &type); + + return type == napi_null; +} + +static gboolean +fdn_is_undefined_or_null (napi_env env, + napi_value value) +{ + napi_valuetype type; + + napi_typeof (env, value, &type); + + return type == napi_undefined || type == napi_null; +} + +static gboolean +fdn_is_function (napi_env env, + napi_value value) +{ + napi_valuetype type; + + napi_typeof (env, value, &type); + + return type == napi_function; +} + +static gboolean +fdn_boolean_from_value (napi_env env, + napi_value value, + gboolean * b) +{ + bool napi_b; + + if (napi_get_value_bool (env, value, &napi_b) != napi_ok) + goto invalid_argument; + + *b = napi_b; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected a boolean"); + return FALSE; + } +} + +static napi_value +fdn_boolean_to_value (napi_env env, + gboolean b) +{ + napi_value result; + napi_get_boolean (env, b, &result); + return result; +} + +static gboolean +fdn_size_from_value (napi_env env, + napi_value value, + gsize * s) +{ + double d; + + if (napi_get_value_double (env, value, &d) != napi_ok) + goto invalid_argument; + + if (d < 0 || d > G_MAXSIZE) + goto invalid_argument; + + *s = d; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an unsigned integer"); + return FALSE; + } +} + +static napi_value +fdn_size_to_value (napi_env env, + gsize s) +{ + return fdn_ssize_to_value (env, s); +} + +static napi_value +fdn_ssize_to_value (napi_env env, + gssize s) +{ + napi_value result; + napi_create_int64 (env, s, &result); + return result; +} + +static gboolean +fdn_int_from_value (napi_env env, + napi_value value, + gint * i) +{ + int32_t napi_i; + + if (napi_get_value_int32 (env, value, &napi_i) != napi_ok) + goto invalid_argument; + + *i = napi_i; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an integer"); + return FALSE; + } +} + +static napi_value +fdn_int_to_value (napi_env env, + gint i) +{ + napi_value result; + napi_create_int32 (env, i, &result); + return result; +} + +static gboolean +fdn_uint_from_value (napi_env env, + napi_value value, + guint * u) +{ + uint32_t napi_u; + + if (napi_get_value_uint32 (env, value, &napi_u) != napi_ok) + goto invalid_argument; + + *u = napi_u; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an unsigned integer"); + return FALSE; + } +} + +static napi_value +fdn_uint_to_value (napi_env env, + guint u) +{ + napi_value result; + napi_create_uint32 (env, u, &result); + return result; +} + +static gboolean +fdn_uint16_from_value (napi_env env, + napi_value value, + guint16 * u) +{ + uint32_t napi_u; + + if (napi_get_value_uint32 (env, value, &napi_u) != napi_ok) + goto invalid_argument; + + if (napi_u > G_MAXUINT16) + goto invalid_argument; + + *u = napi_u; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an unsigned 16-bit integer"); + return FALSE; + } +} + +static napi_value +fdn_uint16_to_value (napi_env env, + guint16 u) +{ + return fdn_uint32_to_value (env, u); +} + +static napi_value +fdn_uint32_to_value (napi_env env, + guint32 u) +{ + napi_value result; + napi_create_uint32 (env, u, &result); + return result; +} + +static gboolean +fdn_int64_from_value (napi_env env, + napi_value value, + gint64 * i) +{ + int64_t napi_i; + + if (napi_get_value_int64 (env, value, &napi_i) != napi_ok) + goto invalid_argument; + + *i = napi_i; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an integer"); + return FALSE; + } +} + +static napi_value +fdn_int64_to_value (napi_env env, + gint64 i) +{ + napi_value result; + napi_create_int64 (env, i, &result); + return result; +} + +static napi_value +fdn_uint64_to_value (napi_env env, + guint64 u) +{ + napi_value result; + napi_create_double (env, u, &result); + return result; +} + +static gboolean +fdn_ulong_from_value (napi_env env, + napi_value value, + gulong * u) +{ + double d; + + if (napi_get_value_double (env, value, &d) != napi_ok) + goto invalid_argument; + + if (d < 0 || d > G_MAXULONG) + goto invalid_argument; + + *u = d; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an unsigned integer"); + return FALSE; + } +} + +static gboolean +fdn_double_from_value (napi_env env, + napi_value value, + gdouble * d) +{ + if (napi_get_value_double (env, value, d) != napi_ok) + goto invalid_argument; + + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected a number"); + return FALSE; + } +} + +static napi_value +fdn_double_to_value (napi_env env, + gdouble d) +{ + napi_value result; + napi_create_double (env, d, &result); + return result; +} + +static gboolean +fdn_enum_from_value (napi_env env, + GType enum_type, + napi_value value, + gint * e) +{ + gboolean success = FALSE; + gchar * nick; + GEnumClass * enum_class; + guint i; + + if (!fdn_utf8_from_value (env, value, &nick)) + return FALSE; + + enum_class = G_ENUM_CLASS (g_type_class_ref (enum_type)); + + for (i = 0; i != enum_class->n_values; i++) + { + GEnumValue * enum_value = &enum_class->values[i]; + if (strcmp (enum_value->value_nick, nick) == 0) + { + *e = enum_value->value; + success = TRUE; + break; + } + } + + g_type_class_unref (enum_class); + + g_free (nick); + + if (!success) + napi_throw_error (env, NULL, "invalid enumeration value"); + + return success; +} + +static napi_value +fdn_enum_to_value (napi_env env, + GType enum_type, + gint e) +{ + napi_value result; + GEnumClass * enum_class; + GEnumValue * enum_value; + + enum_class = G_ENUM_CLASS (g_type_class_ref (enum_type)); + + enum_value = g_enum_get_value (enum_class, e); + g_assert (enum_value != NULL); + + result = fdn_utf8_to_value (env, enum_value->value_nick); + + g_type_class_unref (enum_class); + + return result; +} + +static gboolean +fdn_utf8_from_value (napi_env env, + napi_value value, + gchar ** str) +{ + gchar * result = NULL; + size_t length; + + if (napi_get_value_string_utf8 (env, value, NULL, 0, &length) != napi_ok) + goto invalid_argument; + + result = g_malloc (length + 1); + if (napi_get_value_string_utf8 (env, value, result, length + 1, &length) != napi_ok) + goto invalid_argument; + + *str = result; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected a string"); + g_free (result); + return FALSE; + } +} + +static napi_value +fdn_utf8_to_value (napi_env env, + const gchar * str) +{ + napi_value result; + napi_create_string_utf8 (env, str, NAPI_AUTO_LENGTH, &result); + return result; +} + +static gboolean +fdn_strv_from_value (napi_env env, + napi_value value, + gchar *** strv) +{ + uint32_t length, i; + gchar ** vector = NULL; + + if (napi_get_array_length (env, value, &length) != napi_ok) + goto invalid_argument; + + vector = g_new0 (gchar *, length + 1); + + for (i = 0; i != length; i++) + { + napi_value js_str; + + if (napi_get_element (env, value, i, &js_str) != napi_ok) + goto invalid_argument; + + if (!fdn_utf8_from_value (env, js_str, &vector[i])) + goto invalid_argument; + } + + *strv = vector; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected an array of strings"); + g_strfreev (vector); + return FALSE; + } +} + +static napi_value +fdn_strv_to_value (napi_env env, + gchar ** strv) +{ + napi_value result; + uint32_t length, i; + + length = g_strv_length (strv); + + napi_create_array_with_length (env, length, &result); + + for (i = 0; i != length; i++) + napi_set_element (env, result, i, fdn_utf8_to_value (env, strv[i])); + + return result; +} + +static napi_value +fdn_buffer_to_value (napi_env env, + const guint8 * data, + gsize size) +{ + napi_value result; + napi_create_buffer_copy (env, size, data, NULL, &result); + return result; +} + +static gboolean +fdn_bytes_from_value (napi_env env, + napi_value value, + GBytes ** bytes) +{ + void * data; + size_t size; + + if (napi_get_buffer_info (env, value, &data, &size) != napi_ok) + goto invalid_argument; + + *bytes = g_bytes_new (data, size); + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected a buffer"); + return FALSE; + } +} + +static napi_value +fdn_bytes_to_value (napi_env env, + GBytes * bytes) +{ + const guint8 * data; + gsize size; + + data = g_bytes_get_data (bytes, &size); + + return fdn_buffer_to_value (env, data, size); +} + +static gboolean +fdn_vardict_from_value (napi_env env, + napi_value value, + GHashTable ** vardict) +{ + napi_value keys; + uint32_t length, i; + GHashTable * dict = NULL; + gchar * key = NULL; + + if (napi_get_property_names (env, value, &keys) != napi_ok) + goto invalid_argument; + if (napi_get_array_length (env, keys, &length) != napi_ok) + goto propagate_error; + + dict = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); + + for (i = 0; i != length; i++) + { + napi_value js_key, js_val; + GVariant * val; + + if (napi_get_element (env, keys, i, &js_key) != napi_ok) + goto propagate_error; + if (!fdn_utf8_from_value (env, js_key, &key)) + goto invalid_argument; + + if (napi_get_property (env, value, js_key, &js_val) != napi_ok) + goto propagate_error; + if (!fdn_variant_from_value (env, js_val, &val)) + goto propagate_error; + + g_hash_table_insert (dict, g_steal_pointer (&key), g_variant_ref_sink (val)); + } + + *vardict = dict; + return TRUE; + +invalid_argument: + { + napi_throw_error (env, NULL, "expected a vardict"); + goto propagate_error; + } +propagate_error: + { + g_free (key); + g_clear_pointer (&dict, g_hash_table_unref); + return FALSE; + } +} + +static napi_value +fdn_vardict_to_value (napi_env env, + GHashTable * vardict) +{ + napi_value result; + GHashTableIter iter; + gpointer key, value; + + napi_create_object (env, &result); + + g_hash_table_iter_init (&iter, vardict); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + napi_value js_key, js_value; + + js_key = fdn_utf8_to_value (env, key); + js_value = fdn_variant_to_value (env, value); + + napi_set_property (env, result, js_key, js_value); + } + + return result; +} + +static gboolean +fdn_variant_from_value (napi_env env, + napi_value value, + GVariant ** variant) +{ + napi_valuetype type; + + napi_typeof (env, value, &type); + + switch (type) + { + case napi_boolean: + { + gboolean b; + + if (!fdn_boolean_from_value (env, value, &b)) + return FALSE; + + *variant = g_variant_new_boolean (b); + return TRUE; + } + case napi_number: + { + gint64 i; + + if (!fdn_int64_from_value (env, value, &i)) + return FALSE; + + *variant = g_variant_new_int64 (i); + return TRUE; + } + case napi_string: + { + gchar * str; + + if (!fdn_utf8_from_value (env, value, &str)) + return FALSE; + + *variant = g_variant_new_take_string (str); + return TRUE; + } + case napi_object: + { + bool is_buffer, is_array; + GVariantBuilder builder; + napi_value keys; + uint32_t length, i; + + if (napi_is_buffer (env, value, &is_buffer) != napi_ok) + return FALSE; + if (is_buffer) + { + void * data; + size_t size; + gpointer copy; + + if (napi_get_buffer_info (env, value, &data, &size) != napi_ok) + return FALSE; + + copy = g_memdup2 (data, size); + *variant = g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING, copy, size, TRUE, g_free, copy); + return TRUE; + } + + if (napi_is_array (env, value, &is_array) != napi_ok) + return FALSE; + if (is_array) + { + uint32_t length; + + if (napi_get_array_length (env, value, &length) != napi_ok) + return FALSE; + + if (length == 2) + { + napi_value first; + napi_valuetype first_type; + + if (napi_get_element (env, value, 0, &first) != napi_ok) + return FALSE; + + napi_typeof (env, first, &first_type); + + if (first_type == napi_symbol) + { + napi_value second; + GVariant * val; + napi_value desc; + gchar * type; + GVariant * t[2]; + + if (napi_get_element (env, value, 1, &second) != napi_ok) + return FALSE; + + if (!fdn_variant_from_value (env, second, &val)) + return FALSE; + + napi_coerce_to_string (env, first, &desc); + fdn_utf8_from_value (env, desc, &type); + + t[0] = g_variant_new_take_string (type); + t[1] = val; + + *variant = g_variant_new_tuple (t, G_N_ELEMENTS (t)); + return TRUE; + } + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); + + for (i = 0; i != length; i++) + { + napi_value element; + GVariant * v; + + if (napi_get_element (env, value, i, &element) != napi_ok) + { + g_variant_builder_clear (&builder); + return FALSE; + } + + if (!fdn_variant_from_value (env, element, &v)) + { + g_variant_builder_clear (&builder); + return FALSE; + } + + g_variant_builder_add (&builder, "v", v); + } + + *variant = g_variant_builder_end (&builder); + return TRUE; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + if (napi_get_property_names (env, value, &keys) != napi_ok) + return FALSE; + + if (napi_get_array_length (env, keys, &length) != napi_ok) + return FALSE; + + for (i = 0; i != length; i++) + { + napi_value key; + gchar * key_str; + napi_value val; + GVariant * v; + + if (napi_get_element (env, keys, i, &key) != napi_ok) + return FALSE; + + if (!fdn_utf8_from_value (env, key, &key_str)) + return FALSE; + + if (napi_get_property (env, value, key, &val) != napi_ok) + { + g_free (key_str); + return FALSE; + } + + if (!fdn_variant_from_value (env, val, &v)) + { + g_free (key_str); + return FALSE; + } + + g_variant_builder_add (&builder, "{sv}", key_str, v); + g_free (key_str); + } + + *variant = g_variant_builder_end (&builder); + return TRUE; + } + default: + break; + } + + napi_throw_type_error (env, NULL, "expected value serializable to GVariant"); + return FALSE; +} + +static napi_value +fdn_variant_to_value (napi_env env, + GVariant * variant) +{ + napi_value result; + + switch (g_variant_classify (variant)) + { + case G_VARIANT_CLASS_STRING: + { + const gchar * str = g_variant_get_string (variant, NULL); + return fdn_utf8_to_value (env, str); + } + case G_VARIANT_CLASS_INT64: + return fdn_int64_to_value (env, g_variant_get_int64 (variant)); + case G_VARIANT_CLASS_UINT64: + return fdn_uint64_to_value (env, g_variant_get_uint64 (variant)); + case G_VARIANT_CLASS_DOUBLE: + return fdn_double_to_value (env, g_variant_get_double (variant)); + case G_VARIANT_CLASS_BOOLEAN: + return fdn_boolean_to_value (env, g_variant_get_boolean (variant)); + case G_VARIANT_CLASS_ARRAY: + if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) + { + gsize size; + g_variant_get_fixed_array (variant, &size, sizeof (guint8)); + return fdn_buffer_to_value (env, g_variant_get_data (variant), size); + } + + if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)) + { + napi_value dict; + GVariantIter iter; + gchar * key; + GVariant * value; + + napi_create_object (env, &dict); + + g_variant_iter_init (&iter, variant); + while (g_variant_iter_next (&iter, "{sv}", &key, &value)) + { + napi_value js_key, js_value; + + js_key = fdn_utf8_to_value (env, key); + js_value = fdn_variant_to_value (env, value); + + napi_set_property (env, dict, js_key, js_value); + + g_variant_unref (value); + g_free (key); + } + + return dict; + } + + if (g_variant_is_of_type (variant, G_VARIANT_TYPE_ARRAY)) + { + napi_value array; + GVariantIter iter; + uint32_t i; + GVariant * child; + + napi_create_array (env, &array); + + g_variant_iter_init (&iter, variant); + i = 0; + while ((child = g_variant_iter_next_value (&iter)) != NULL) + { + napi_value element = fdn_variant_to_value (env, child); + napi_set_element (env, array, i++, element); + g_variant_unref (child); + } + + return array; + } + + break; + case G_VARIANT_CLASS_TUPLE: + napi_get_undefined (env, &result); + return result; + default: + break; + } + + napi_get_null (env, &result); + return result; +} + +static gboolean +fdn_gvalue_from_value (napi_env env, + GType type, + napi_value js_value, + GValue * value) +{ + g_value_init (value, type); + + switch (type) + { + case G_TYPE_BOOLEAN: + { + gboolean b; + + if (!fdn_boolean_from_value (env, js_value, &b)) + return FALSE; + g_value_set_boolean (value, b); + + break; + } + case G_TYPE_INT: + { + gint i; + + if (!fdn_int_from_value (env, js_value, &i)) + return FALSE; + g_value_set_int (value, i); + + break; + } + case G_TYPE_UINT: + { + guint u; + + if (!fdn_uint_from_value (env, js_value, &u)) + return FALSE; + g_value_set_uint (value, u); + + break; + } + case G_TYPE_FLOAT: + { + gdouble d; + + if (!fdn_double_from_value (env, js_value, &d)) + return FALSE; + g_value_set_float (value, d); + + break; + } + case G_TYPE_DOUBLE: + { + gdouble d; + + if (!fdn_double_from_value (env, js_value, &d)) + return FALSE; + g_value_set_double (value, d); + + break; + } + case G_TYPE_STRING: + { + gchar * str; + + if (!fdn_utf8_from_value (env, js_value, &str)) + return FALSE; + g_value_take_string (value, str); + + break; + } + default: + { + gchar * msg; + + if (G_TYPE_IS_ENUM (type)) + { + gint e; + + if (!fdn_enum_from_value (env, type, js_value, &e)) + return FALSE; + g_value_set_enum (value, e); + + return TRUE; + } + + if (type == G_TYPE_STRV) + { + gchar ** strv; + + if (!fdn_strv_from_value (env, js_value, &strv)) + return FALSE; + g_value_take_boxed (value, strv); + + return TRUE; + } + + if (type == G_TYPE_BYTES) + { + GBytes * bytes; + + if (!fdn_bytes_from_value (env, js_value, &bytes)) + return FALSE; + g_value_take_boxed (value, bytes); + + return TRUE; + } + + if (type == G_TYPE_HASH_TABLE) + { + GHashTable * vardict; + + if (!fdn_vardict_from_value (env, js_value, &vardict)) + return FALSE; + g_value_take_boxed (value, vardict); + + return TRUE; + } + + if (type == G_TYPE_TLS_CERTIFICATE) + { + GTlsCertificate * certificate; + + if (!fdn_tls_certificate_from_value (env, js_value, &certificate)) + return FALSE; + g_value_take_object (value, certificate); + + return TRUE; + } + + msg = g_strdup_printf ("unsupported property type: %s", g_type_name (type)); + napi_throw_type_error (env, NULL, msg); + g_free (msg); + + return FALSE; + } + } + + return TRUE; +} + +static napi_value +fdn_gvalue_to_value (napi_env env, + GValue * value) +{ + GType gtype; + + gtype = G_VALUE_TYPE (value); + + switch (gtype) + { + case G_TYPE_BOOLEAN: + return fdn_boolean_to_value (env, g_value_get_boolean (value)); + case G_TYPE_INT: + return fdn_int_to_value (env, g_value_get_int (value)); + case G_TYPE_UINT: + return fdn_uint_to_value (env, g_value_get_uint (value)); + case G_TYPE_FLOAT: + return fdn_double_to_value (env, g_value_get_float (value)); + case G_TYPE_DOUBLE: + return fdn_double_to_value (env, g_value_get_double (value)); + case G_TYPE_STRING: + { + const gchar * str; + + str = g_value_get_string (value); + if (str == NULL) + { + napi_value result; + napi_get_null (env, &result); + return result; + } + + return fdn_utf8_to_value (env, str); + } + default: + { + napi_value result; + + if (G_TYPE_IS_ENUM (gtype)) + return fdn_enum_to_value (env, gtype, g_value_get_enum (value)); + + if (gtype == G_TYPE_BYTES) + { + GBytes * bytes = g_value_get_boxed (value); + if (bytes != NULL) + { + return fdn_bytes_to_value (env, bytes); + } + else + { + napi_get_null (env, &result); + return result; + } + } + + if (G_TYPE_IS_OBJECT (gtype)) + result = fdn_object_subclass_to_value (env, g_value_get_object (value)); + else + napi_get_null (env, &result); + + return result; + } + } +} + +static gboolean +fdn_error_from_value (napi_env env, + napi_value value, + GError ** error) +{ + napi_value js_message; + gchar * raw_message; + GString * message; + + if (napi_get_named_property (env, value, "message", &js_message) != napi_ok) + return FALSE; + + if (!fdn_utf8_from_value (env, js_message, &raw_message)) + return FALSE; + + message = g_string_new (""); + g_string_append_unichar (message, g_unichar_toupper (g_utf8_get_char (raw_message))); + g_string_append (message, g_utf8_offset_to_pointer (raw_message, 1)); + + *error = g_error_new_literal (FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, message->str); + + g_free (raw_message); + g_string_free (message, TRUE); + + return TRUE; +} + +static napi_value +fdn_error_to_value (napi_env env, + GError * error) +{ + napi_value result; + napi_create_error (env, NULL, fdn_utf8_to_value (env, error->message), &result); + return result; +} + +static gboolean +fdn_file_from_value (napi_env env, + napi_value value, + GFile ** file) +{ + gchar * path; + + if (!fdn_utf8_from_value (env, value, &path)) + return FALSE; + *file = g_file_new_for_path (path); + g_free (path); + + return TRUE; +} + +static napi_value +fdn_file_to_value (napi_env env, + GFile * file) +{ + napi_value result; + gchar * path; + + path = g_file_get_path (file); + result = fdn_utf8_to_value (env, path); + g_free (path); + + return result; +} + +static gboolean +fdn_tls_certificate_from_value (napi_env env, + napi_value value, + GTlsCertificate ** certificate) +{ + gchar * str; + GError * error = NULL; + + if (!fdn_utf8_from_value (env, value, &str)) + return FALSE; + + if (strchr (str, '\n') != NULL) + *certificate = g_tls_certificate_new_from_pem (str, -1, &error); + else + *certificate = g_tls_certificate_new_from_file (str, &error); + + g_free (str); + + if (error != NULL) + goto invalid_argument; + return TRUE; + +invalid_argument: + { + napi_throw (env, fdn_error_to_value (env, error)); + g_error_free (error); + return FALSE; + } +} + +static napi_value +fdn_tls_certificate_to_value (napi_env env, + GTlsCertificate * certificate) +{ + napi_value result; + gchar * pem; + + g_object_get (certificate, "certificate-pem", &pem, NULL); + result = fdn_utf8_to_value (env, pem); + g_free (pem); + + return result; +} + +static gboolean +fdn_options_from_value (napi_env env, + GType object_type, + napi_value value, + gpointer * options) +{ + gboolean success = FALSE; + napi_valuetype value_type; + napi_value keys; + uint32_t n_keys; + guint n_properties = 0; + const char ** property_names = NULL; + GValue * property_values = NULL; + GObjectClass * object_class = NULL; + uint32_t i; + gchar * gobject_property_name = NULL; + + if (napi_typeof (env, value, &value_type) != napi_ok || value_type != napi_object) + goto expected_an_object; + + if (napi_get_property_names (env, value, &keys) != napi_ok) + goto beach; + + if (napi_get_array_length (env, keys, &n_keys) != napi_ok) + goto beach; + + property_names = g_newa (const char *, n_keys); + property_values = g_newa (GValue, n_keys); + + object_class = G_OBJECT_CLASS (g_type_class_ref (object_type)); + + for (i = 0; i != n_keys; i++) + { + napi_value js_key, js_value; + gchar * property_name; + GParamSpec * pspec; + + if (napi_get_element (env, keys, i, &js_key) != napi_ok) + goto beach; + + if (!fdn_utf8_from_value (env, js_key, &property_name)) + goto beach; + + gobject_property_name = fdn_camel_case_to_kebab_case (property_name); + g_free (property_name); + + pspec = g_object_class_find_property (object_class, gobject_property_name); + if (pspec == NULL) + { + g_free (gobject_property_name); + gobject_property_name = NULL; + continue; + } + + if (napi_get_property (env, value, js_key, &js_value) != napi_ok) + goto beach; + + if (!fdn_gvalue_from_value (env, pspec->value_type, js_value, &property_values[n_properties])) + goto beach; + + property_names[n_properties] = g_steal_pointer (&gobject_property_name); + n_properties++; + } + + *options = g_object_new_with_properties (object_type, n_properties, property_names, property_values); + + success = TRUE; + goto beach; + +expected_an_object: + { + napi_throw_type_error (env, NULL, "expected an object"); + goto beach; + } +beach: + { + g_free (gobject_property_name); + + for (i = 0; i != n_properties; i++) + { + g_free ((gchar *) property_names[i]); + g_value_unset (&property_values[i]); + } + + g_clear_pointer (&object_class, g_type_class_unref); + + return success; + } +} + +static napi_value +fdn_object_subclass_to_value (napi_env env, + GObject * object) +{ + napi_value result; + napi_ref ctor; + + if (object == NULL) + { + napi_get_null (env, &result); + return result; + } + + ctor = g_hash_table_lookup (fdn_constructors, GSIZE_TO_POINTER (G_OBJECT_TYPE (object))); + if (ctor == NULL) + goto unsupported_type; + + return fdn_object_new (env, object, ctor); + +unsupported_type: + { + napi_get_null (env, &result); + return result; + } +} + +static napi_value +fdn_object_new (napi_env env, + GObject * handle, + napi_ref constructor) +{ + napi_value result, ctor, handle_wrapper; + napi_ref wrapper_ref; + + wrapper_ref = g_object_get_data (handle, "fdn-wrapper"); + if (wrapper_ref != NULL) + { + if (napi_get_reference_value (env, wrapper_ref, &result) == napi_ok && result != NULL) + return result; + } + + napi_get_reference_value (env, constructor, &ctor); + + napi_create_external (env, handle, NULL, NULL, &handle_wrapper); + napi_type_tag_object (env, handle_wrapper, &fdn_handle_wrapper_type_tag); + + napi_new_instance (env, ctor, 1, &handle_wrapper, &result); + + return result; +} + +static gboolean +fdn_object_wrap (napi_env env, + napi_value wrapper, + GObject * handle, + napi_finalize finalizer) +{ + napi_ref ref; + + if (napi_type_tag_object (env, wrapper, &fdn_object_type_tag) != napi_ok) + return FALSE; + + if (napi_wrap (env, wrapper, handle, NULL, NULL, NULL) != napi_ok) + return FALSE; + + if (napi_add_finalizer (env, wrapper, handle, finalizer, NULL, NULL) != napi_ok) + return FALSE; + + napi_create_reference (env, wrapper, 0, &ref); + g_object_set_data (handle, "fdn-wrapper", ref); + + return TRUE; +} + +static gboolean +fdn_object_unwrap (napi_env env, + napi_value wrapper, + GType expected_type, + GObject ** handle) +{ + bool is_instance; + GObject * obj; + + if (napi_check_object_type_tag (env, wrapper, &fdn_object_type_tag, &is_instance) != napi_ok || !is_instance) + goto invalid_tag; + + if (napi_unwrap (env, wrapper, (void **) &obj) != napi_ok) + goto invalid_tag; + + if (!g_type_is_a (G_OBJECT_TYPE (obj), expected_type)) + goto invalid_type; + + *handle = g_object_ref (obj); + return TRUE; + +invalid_tag: + { + gchar * msg; + + msg = g_strdup_printf ("expected an instance of %s", g_type_name (expected_type)); + napi_throw_type_error (env, NULL, msg); + g_free (msg); + + return FALSE; + } +invalid_type: + { + gchar * msg; + + msg = g_strdup_printf ("expected an instance of %s, got a %s", + g_type_name (expected_type), + g_type_name (G_OBJECT_TYPE (obj))); + napi_throw_type_error (env, NULL, msg); + g_free (msg); + + return FALSE; + } +} + +static void +fdn_object_finalize (napi_env env, + void * finalize_data, + void * finalize_hint) +{ + GObject * handle = G_OBJECT (finalize_data); + + napi_delete_reference (env, g_object_steal_data (handle, "fdn-wrapper")); + + g_object_unref (handle); +} + +static napi_value +fdn_object_get_signal (napi_env env, + napi_callback_info info, + const gchar * name, + const gchar * js_storage_name, + FdnSignalBehavior behavior) +{ + napi_value result, jsthis, js_storage_name_value; + napi_valuetype type; + + if (napi_get_cb_info (env, info, NULL, NULL, &jsthis, NULL) != napi_ok) + return NULL; + + js_storage_name_value = fdn_utf8_to_value (env, js_storage_name); + + if (napi_get_property (env, jsthis, js_storage_name_value, &result) != napi_ok) + return NULL; + + if (napi_typeof (env, result, &type) != napi_ok) + return NULL; + + if (type == napi_undefined) + {{ + GObject * handle; + + if (napi_unwrap (env, jsthis, (void **) &handle) != napi_ok) + return NULL; + + result = fdn_signal_new (env, handle, name, behavior); + napi_set_property (env, jsthis, js_storage_name_value, result); + }} + + return result; +} + +static napi_value +fdn_signal_new (napi_env env, + GObject * handle, + const gchar * name, + FdnSignalBehavior behavior) +{ + napi_value result, constructor, handle_wrapper; + napi_value args[3]; + + napi_get_reference_value (env, fdn_signal_constructor, &constructor); + + napi_create_external (env, handle, NULL, NULL, &handle_wrapper); + napi_type_tag_object (env, handle_wrapper, &fdn_handle_wrapper_type_tag); + + args[0] = handle_wrapper; + args[1] = fdn_utf8_to_value (env, name); + args[2] = fdn_int_to_value (env, behavior); + + napi_new_instance (env, constructor, G_N_ELEMENTS (args), args, &result); + + return result; +} + +static void +fdn_signal_register (napi_env env, + napi_value exports) +{ + napi_property_descriptor properties[] = + { + { "connect", NULL, fdn_signal_connect, NULL, NULL, NULL, napi_default, NULL }, + { "disconnect", NULL, fdn_signal_disconnect, NULL, NULL, NULL, napi_default, NULL }, + }; + napi_value constructor; + + napi_define_class (env, "Signal", NAPI_AUTO_LENGTH, fdn_signal_construct, NULL, G_N_ELEMENTS (properties), properties, &constructor); + napi_create_reference (env, constructor, 1, &fdn_signal_constructor); + + napi_set_named_property (env, exports, "Signal", constructor); +} + +static napi_value +fdn_signal_construct (napi_env env, + napi_callback_info info) +{ + size_t argc = 3; + napi_value args[3]; + napi_value jsthis; + GObject * handle; + bool is_instance; + gchar * name = NULL; + FdnSignalBehavior behavior; + FdnSignal * sig = NULL; + + if (napi_get_cb_info (env, info, &argc, args, &jsthis, NULL) != napi_ok) + goto propagate_error; + + if (argc != 3) + goto missing_argument; + + if (napi_check_object_type_tag (env, args[0], &fdn_handle_wrapper_type_tag, &is_instance) != napi_ok || !is_instance) + goto invalid_handle; + + if (napi_get_value_external (env, args[0], (void **) &handle) != napi_ok) + goto propagate_error; + + if (!fdn_utf8_from_value (env, args[1], &name)) + goto propagate_error; + + if (!fdn_int_from_value (env, args[2], (gint *) &behavior)) + goto propagate_error; + if (behavior != FDN_SIGNAL_ALLOW_EXIT && behavior != FDN_SIGNAL_KEEP_ALIVE) + goto invalid_behavior; + + sig = g_slice_new (FdnSignal); + sig->handle = g_object_ref (handle); + sig->id = g_signal_lookup (name, G_OBJECT_TYPE (sig->handle)); + sig->behavior = behavior; + sig->closures = NULL; + if (sig->id == 0) + goto invalid_signal_name; + + if (napi_wrap (env, jsthis, sig, NULL, NULL, NULL) != napi_ok) + goto propagate_error; + + if (napi_add_finalizer (env, jsthis, sig, fdn_signal_finalize, NULL, NULL) != napi_ok) + goto propagate_error; + + g_free (name); + + return jsthis; + +missing_argument: + { + napi_throw_error (env, NULL, "missing argument"); + goto propagate_error; + } +invalid_handle: + { + napi_throw_type_error (env, NULL, "expected an object handle"); + goto propagate_error; + } +invalid_behavior: + { + napi_throw_error (env, NULL, "invalid behavior"); + goto propagate_error; + } +invalid_signal_name: + { + napi_throw_type_error (env, NULL, "bad signal name"); + goto propagate_error; + } +propagate_error: + { + if (sig != NULL) + fdn_signal_finalize (env, sig, NULL); + + g_free (name); + + return NULL; + } +} + +static void +fdn_signal_finalize (napi_env env, + void * finalize_data, + void * finalize_hint) +{ + FdnSignal * sig = finalize_data; + GSList * cur; + + for (cur = sig->closures; cur != NULL; cur = cur->next) + fdn_signal_disconnect_closure (sig, cur->data); + g_slist_free (sig->closures); + + g_object_unref (sig->handle); + + g_slice_free (FdnSignal, sig); +} + +static napi_value +fdn_signal_connect (napi_env env, + napi_callback_info info) +{ + napi_value js_retval; + FdnSignal * self; + napi_value js_self, handler; + FdnSignalClosure * sc; + GClosure * closure; + + if (!fdn_signal_parse_arguments (env, info, &self, &js_self, &handler)) + return NULL; + + sc = fdn_signal_closure_new (env, self, js_self, handler); + + closure = (GClosure *) sc; + g_closure_ref (closure); + g_closure_sink (closure); + self->closures = g_slist_prepend (self->closures, sc); + + sc->handler_id = g_signal_connect_closure_by_id (self->handle, self->id, 0, closure, TRUE); + + napi_get_undefined (env, &js_retval); + + return js_retval; +} + +static napi_value +fdn_signal_disconnect (napi_env env, + napi_callback_info info) +{ + napi_value js_retval; + FdnSignal * self; + napi_value handler; + GSList * cur; + + if (!fdn_signal_parse_arguments (env, info, &self, NULL, &handler)) + return NULL; + + for (cur = self->closures; cur != NULL; cur = cur->next) + { + FdnSignalClosure * closure = cur->data; + napi_value candidate_handler; + bool same_handler; + + napi_get_reference_value (env, closure->handler, &candidate_handler); + + napi_strict_equals (env, candidate_handler, handler, &same_handler); + + if (same_handler) + { + fdn_signal_disconnect_closure (self, closure); + self->closures = g_slist_delete_link (self->closures, cur); + break; + } + } + + napi_get_undefined (env, &js_retval); + + return js_retval; +} + +static void +fdn_signal_disconnect_closure (FdnSignal * self, + FdnSignalClosure * closure) +{ + g_signal_handler_disconnect (self->handle, closure->handler_id); + closure->handler_id = 0; + + closure->state = FDN_SIGNAL_CLOSURE_CLOSED; + + g_closure_unref ((GClosure *) closure); +} + +static gboolean +fdn_signal_parse_arguments (napi_env env, + napi_callback_info info, + FdnSignal ** self, + napi_value * js_self, + napi_value * handler) +{ + size_t argc = 1; + napi_value jsthis; + + if (napi_get_cb_info (env, info, &argc, handler, &jsthis, NULL) != napi_ok) + goto propagate_error; + + if (napi_unwrap (env, jsthis, (void **) self) != napi_ok) + goto propagate_error; + + if (js_self != NULL) + *js_self = jsthis; + + if (argc != 1) + goto missing_handler; + + if (!fdn_is_function (env, *handler)) + goto invalid_handler; + + return TRUE; + +missing_handler: + { + napi_throw_error (env, NULL, "missing argument: handler"); + return FALSE; + } +invalid_handler: + { + napi_throw_error (env, NULL, "expected a function"); + return FALSE; + } +propagate_error: + { + return FALSE; + } +} + +static FdnSignalClosure * +fdn_signal_closure_new (napi_env env, + FdnSignal * sig, + napi_value js_sig, + napi_value handler) +{ + FdnSignalClosure * sc; + GClosure * closure; + + closure = g_closure_new_simple (sizeof (FdnSignalClosure), NULL); + g_closure_add_finalize_notifier (closure, NULL, fdn_signal_closure_finalize); + g_closure_set_marshal (closure, fdn_signal_closure_marshal); + + sc = (FdnSignalClosure *) closure; + sc->sig = sig; + napi_create_reference (env, js_sig, 1, &sc->js_sig); + sc->state = FDN_SIGNAL_CLOSURE_OPEN; + napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, g_signal_name (sig->id)), 0, 1, NULL, NULL, sc, + fdn_signal_closure_deliver, &sc->tsfn); + + if (sig->behavior == FDN_SIGNAL_ALLOW_EXIT) + napi_unref_threadsafe_function (env, sc->tsfn); + + napi_create_reference (env, handler, 1, &sc->handler); + + return sc; +} + +static void +fdn_signal_closure_finalize (gpointer data, + GClosure * closure) +{ + FdnSignalClosure * self = (FdnSignalClosure *) closure; + FdnSignalClosureMessage * message; + FdnSignalClosureMessageDestroy * d; + + if (fdn_in_cleanup) + return; + + message = g_slice_new (FdnSignalClosureMessage); + message->type = FDN_SIGNAL_CLOSURE_MESSAGE_DESTROY; + + d = &message->payload.destroy; + d->js_sig = self->js_sig; + d->tsfn = self->tsfn; + d->handler = self->handler; + + napi_call_threadsafe_function (self->tsfn, message, napi_tsfn_blocking); +} + +static void +fdn_signal_closure_marshal (GClosure * closure, + GValue * return_gvalue, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + FdnSignalClosure * self = (FdnSignalClosure *) closure; + FdnSignalClosureMessage * message; + GArray * args; + guint i; + + message = g_slice_new (FdnSignalClosureMessage); + message->type = FDN_SIGNAL_CLOSURE_MESSAGE_MARSHAL; + + g_assert (n_param_values >= 1); + args = g_array_sized_new (FALSE, FALSE, sizeof (GValue), n_param_values - 1); + message->payload.marshal.args = args; + + for (i = 1; i != n_param_values; i++) + { + GValue val; + + g_value_init (&val, param_values[i].g_type); + g_value_copy (¶m_values[i], &val); + g_array_append_val (args, val); + } + + g_closure_ref (closure); + napi_call_threadsafe_function (self->tsfn, message, napi_tsfn_blocking); +} + +static void +fdn_signal_closure_deliver (napi_env env, + napi_value js_cb, + void * context, + void * data) +{ + FdnSignalClosureMessage * message = data; + + switch (message->type) + { + case FDN_SIGNAL_CLOSURE_MESSAGE_DESTROY: + { + FdnSignalClosureMessageDestroy * d = &message->payload.destroy; + napi_value js_sig; + FdnSignal * sig; + FdnSignalBehavior behavior; + + napi_get_reference_value (env, d->js_sig, &js_sig); + napi_unwrap (env, js_sig, (void **) &sig); + behavior = sig->behavior; + + napi_delete_reference (env, d->handler); + napi_delete_reference (env, d->js_sig); + if (behavior == FDN_SIGNAL_KEEP_ALIVE) + napi_unref_threadsafe_function (env, d->tsfn); + napi_release_threadsafe_function (d->tsfn, napi_tsfn_abort); + + break; + } + case FDN_SIGNAL_CLOSURE_MESSAGE_MARSHAL: + { + FdnSignalClosure * self = context; + GArray * args; + guint i; + + args = message->payload.marshal.args; + + if (self->state == FDN_SIGNAL_CLOSURE_OPEN) + { + napi_value * js_args; + napi_value global, handler, js_result; + + js_args = g_newa (napi_value, args->len); + for (i = 0; i != args->len; i++) + js_args[i] = fdn_gvalue_to_value (env, &g_array_index (args, GValue, i)); + + napi_get_global (env, &global); + napi_get_reference_value (env, self->handler, &handler); + + napi_call_function (env, global, handler, args->len, js_args, &js_result); + } + + for (i = 0; i != args->len; i++) + g_value_reset (&g_array_index (args, GValue, i)); + g_array_free (args, TRUE); + + g_closure_unref ((GClosure *) self); + + break; + } + default: + g_assert_not_reached (); + } + + g_slice_free (FdnSignalClosureMessage, message); +} + +static void +fdn_keep_alive_until (napi_env env, + napi_value js_object, + GObject * handle, + FdnIsDestroyedFunc is_destroyed, + const gchar * destroy_signal_name) +{ + FdnKeepAliveContext * context; + + context = g_slice_new (FdnKeepAliveContext); + context->ref_count = 2; + context->handle = g_object_ref (handle); + context->signal_handler_id = 0; + + napi_ref_threadsafe_function (env, fdn_keep_alive_tsfn); + + napi_add_finalizer (env, js_object, context, fdn_keep_alive_on_finalize, NULL, NULL); + + context->signal_handler_id = g_signal_connect_data (handle, destroy_signal_name, G_CALLBACK (fdn_keep_alive_on_destroy_signal), context, + fdn_keep_alive_on_destroy_signal_handler_detached, 0); + + if (is_destroyed (handle)) + { + g_atomic_int_inc (&context->ref_count); + fdn_keep_alive_schedule_cleanup (context); + } +} + +static void +fdn_keep_alive_on_finalize (napi_env env, + void * finalize_data, + void * finalize_hint) +{ + FdnKeepAliveContext * context = finalize_data; + + fdn_keep_alive_schedule_cleanup (context); +} + +static void +fdn_keep_alive_on_destroy_signal (GObject * handle, + gpointer user_data) +{ + FdnKeepAliveContext * context = user_data; + + g_atomic_int_inc (&context->ref_count); + fdn_keep_alive_schedule_cleanup (context); +} + +static void +fdn_keep_alive_on_destroy_signal_handler_detached (gpointer data, + GClosure * closure) +{ + FdnKeepAliveContext * context = data; + + fdn_keep_alive_schedule_cleanup (context); +} + +static void +fdn_keep_alive_schedule_cleanup (FdnKeepAliveContext * context) +{ + if (fdn_in_cleanup) + return; + + napi_call_threadsafe_function (fdn_keep_alive_tsfn, context, napi_tsfn_blocking); +} + +static void +fdn_keep_alive_on_tsfn_invoke (napi_env env, + napi_value js_cb, + void * context, + void * data) +{ + FdnKeepAliveContext * ctx = data; + + if (ctx->signal_handler_id != 0) + { + g_signal_handler_disconnect (ctx->handle, ctx->signal_handler_id); + ctx->signal_handler_id = 0; + + g_object_unref (ctx->handle); + ctx->handle = NULL; + + napi_unref_threadsafe_function (env, fdn_keep_alive_tsfn); + } + + if (g_atomic_int_dec_and_test (&ctx->ref_count)) + g_slice_free (FdnKeepAliveContext, ctx); +} + +static void +fdn_inherit_val_val (napi_env env, + napi_value sub_ctor, + napi_value super_ctor, + napi_value object_ctor, + napi_value set_proto) +{ + napi_value argv[2], sub_proto, super_proto; + + argv[0] = sub_ctor; + argv[1] = super_ctor; + napi_call_function (env, object_ctor, set_proto, G_N_ELEMENTS (argv), argv, NULL); + + napi_get_named_property (env, sub_ctor, "prototype", &sub_proto); + napi_get_named_property (env, super_ctor, "prototype", &super_proto); + argv[0] = sub_proto; + argv[1] = super_proto; + napi_call_function (env, object_ctor, set_proto, G_N_ELEMENTS (argv), argv, NULL); +} + +static void +fdn_inherit_val_ref (napi_env env, + napi_value sub_ctor, + napi_ref super_ctor, + napi_value object_ctor, + napi_value set_proto) +{ + napi_value super_ctor_val; + + napi_get_reference_value (env, super_ctor, &super_ctor_val); + + fdn_inherit_val_val (env, sub_ctor, super_ctor_val, object_ctor, set_proto); +} + +static void +fdn_inherit_ref_val (napi_env env, + napi_ref sub_ctor, + napi_value super_ctor, + napi_value object_ctor, + napi_value set_proto) +{ + napi_value sub_ctor_val; + + napi_get_reference_value (env, sub_ctor, &sub_ctor_val); + + fdn_inherit_val_val (env, sub_ctor_val, super_ctor, object_ctor, set_proto); +} + +static void +fdn_inherit_ref_ref (napi_env env, + napi_ref sub_ctor, + napi_ref super_ctor, + napi_value object_ctor, + napi_value set_proto) +{ + napi_value sub_ctor_val, super_ctor_val; + + napi_get_reference_value (env, sub_ctor, &sub_ctor_val); + napi_get_reference_value (env, super_ctor, &super_ctor_val); + + fdn_inherit_val_val (env, sub_ctor_val, super_ctor_val, object_ctor, set_proto); +} + +static gchar * +fdn_camel_case_to_kebab_case (const gchar * name) +{ + GString * result; + const gchar * p; + + result = g_string_new (NULL); + + for (p = name; *p != '\0'; p++) + { + if (g_ascii_isupper (*p)) + { + if (p != name) + g_string_append_c (result, '-'); + g_string_append_c (result, g_ascii_tolower (*p)); + } + else + { + g_string_append_c (result, *p); + } + } + + return g_string_free (result, FALSE); +} diff --git a/frida/frida_bindgen/assets/codegen_helpers.ts b/frida/frida_bindgen/assets/codegen_helpers.ts new file mode 100644 index 0000000..09506fe --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_helpers.ts @@ -0,0 +1,100 @@ +type SignalTransformer< + Source extends SignalHandler, + Target extends SignalHandler +> = (...args: Parameters) => Parameters; + +type SignalInterceptor = (...args: Parameters) => boolean; + +interface SignalWrapperOptionsNoTransform { + transform?: undefined; + intercept?: SignalInterceptor; +} + +interface SignalWrapperOptionsTransform< + Source extends SignalHandler, + Target extends SignalHandler +> { + transform: SignalTransformer; + intercept?: SignalInterceptor; +} + +type SignalWrapperOptions< + Source extends SignalHandler, + Target extends SignalHandler +> = + | SignalWrapperOptionsNoTransform + | SignalWrapperOptionsTransform; + +class SignalWrapper< + SourceHandler extends SignalHandler, + TargetHandler extends SignalHandler +> { + #source: Signal; + #transform?: SignalTransformer; + #intercept?: SignalInterceptor; + + #handlers = new Set(); + + constructor( + source: Signal, + options?: SignalWrapperOptions + ) { + this.#source = source; + + if (options === undefined || options.transform === undefined) { + this.#intercept = options?.intercept; + } else { + this.#transform = options.transform; + this.#intercept = options.intercept; + } + } + + connect(handler: TargetHandler): void { + this.#handlers.add(handler); + if (this.#handlers.size === 1) { + this.#source.connect(this.#wrappedHandler); + } + } + + disconnect(handler: TargetHandler): void { + this.#handlers.delete(handler); + if (this.#handlers.size === 0) { + this.#source.disconnect(this.#wrappedHandler); + } + } + + #wrappedHandler = ((...sourceArgs: Parameters) => { + let targetArgs: Parameters; + const transform = this.#transform; + if (transform === undefined) { + targetArgs = sourceArgs as unknown as Parameters; + } else { + targetArgs = transform(...sourceArgs); + } + + const intercept = this.#intercept; + if (intercept !== undefined) { + if (!intercept(...targetArgs)) { + return; + } + } + + for (const handler of this.#handlers) { + handler(...targetArgs); + } + }) as SourceHandler; +} + +function inspectWrapper(object: any, name: string, properties: string[], depth: number, options: util.InspectOptionsStylized): string { + if (depth < 0) { + return options.stylize(`[${name}]`, "special"); + } + + const summary = Object.fromEntries(properties.map(name => [name, object[name]])); + + const nextOptions = Object.assign({}, options, { + depth: (options.depth === null) ? null : depth - 1 + }); + + return name + " " + inspect(summary, nextOptions); +} diff --git a/frida/frida_bindgen/assets/codegen_prototypes.h b/frida/frida_bindgen/assets/codegen_prototypes.h new file mode 100644 index 0000000..b456282 --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_prototypes.h @@ -0,0 +1,78 @@ +G_GNUC_UNUSED static gboolean fdn_is_null (napi_env env, napi_value value); +static gboolean fdn_is_undefined_or_null (napi_env env, napi_value value); +static gboolean fdn_is_function (napi_env env, napi_value value); + +static gboolean fdn_boolean_from_value (napi_env env, napi_value value, gboolean * b); +static napi_value fdn_boolean_to_value (napi_env env, gboolean b); +static gboolean fdn_size_from_value (napi_env env, napi_value value, gsize * s); +static napi_value fdn_size_to_value (napi_env env, gsize s); +static napi_value fdn_ssize_to_value (napi_env env, gssize s); +static gboolean fdn_int_from_value (napi_env env, napi_value value, gint * i); +static napi_value fdn_int_to_value (napi_env env, gint i); +static gboolean fdn_uint_from_value (napi_env env, napi_value value, guint * u); +static napi_value fdn_uint_to_value (napi_env env, guint u); +static gboolean fdn_uint16_from_value (napi_env env, napi_value value, guint16 * u); +static napi_value fdn_uint16_to_value (napi_env env, guint16 u); +static napi_value fdn_uint32_to_value (napi_env env, guint32 u); +static gboolean fdn_int64_from_value (napi_env env, napi_value value, gint64 * i); +static napi_value fdn_int64_to_value (napi_env env, gint64 i); +static napi_value fdn_uint64_to_value (napi_env env, guint64 u); +static gboolean fdn_ulong_from_value (napi_env env, napi_value value, gulong * u); +static gboolean fdn_double_from_value (napi_env env, napi_value value, gdouble * d); +static napi_value fdn_double_to_value (napi_env env, gdouble d); +static gboolean fdn_enum_from_value (napi_env env, GType enum_type, napi_value value, gint * e); +static napi_value fdn_enum_to_value (napi_env env, GType enum_type, gint e); +static gboolean fdn_utf8_from_value (napi_env env, napi_value value, gchar ** str); +static napi_value fdn_utf8_to_value (napi_env env, const gchar * str); +static gboolean fdn_strv_from_value (napi_env env, napi_value value, gchar *** strv); +static napi_value fdn_strv_to_value (napi_env env, gchar ** strv); +static napi_value fdn_buffer_to_value (napi_env env, const guint8 * data, gsize size); +static gboolean fdn_bytes_from_value (napi_env env, napi_value value, GBytes ** bytes); +static napi_value fdn_bytes_to_value (napi_env env, GBytes * bytes); +static gboolean fdn_vardict_from_value (napi_env env, napi_value value, GHashTable ** vardict); +static napi_value fdn_vardict_to_value (napi_env env, GHashTable * vardict); +static gboolean fdn_variant_from_value (napi_env env, napi_value value, GVariant ** variant); +static napi_value fdn_variant_to_value (napi_env env, GVariant * variant); +static gboolean fdn_gvalue_from_value (napi_env env, GType type, napi_value js_value, GValue * value); +static napi_value fdn_gvalue_to_value (napi_env env, GValue * value); +static gboolean fdn_error_from_value (napi_env env, napi_value value, GError ** error); +static napi_value fdn_error_to_value (napi_env env, GError * error); +static gboolean fdn_file_from_value (napi_env env, napi_value value, GFile ** file); +static napi_value fdn_file_to_value (napi_env env, GFile * file); +static gboolean fdn_tls_certificate_from_value (napi_env env, napi_value value, GTlsCertificate ** certificate); +static napi_value fdn_tls_certificate_to_value (napi_env env, GTlsCertificate * certificate); +static gboolean fdn_options_from_value (napi_env env, GType object_type, napi_value value, gpointer * options); + +static napi_value fdn_object_subclass_to_value (napi_env env, GObject * object); +static napi_value fdn_object_new (napi_env env, GObject * handle, napi_ref constructor); +static gboolean fdn_object_wrap (napi_env env, napi_value wrapper, GObject * handle, napi_finalize finalizer); +static gboolean fdn_object_unwrap (napi_env env, napi_value wrapper, GType expected_type, GObject ** handle); +static void fdn_object_finalize (napi_env env, void * finalize_data, void * finalize_hint); +static napi_value fdn_object_get_signal (napi_env env, napi_callback_info info, const gchar * name, const gchar * js_storage_name, FdnSignalBehavior behavior); + +static napi_value fdn_signal_new (napi_env env, GObject * handle, const gchar * name, FdnSignalBehavior behavior); +static void fdn_signal_register (napi_env env, napi_value exports); +static napi_value fdn_signal_construct (napi_env env, napi_callback_info info); +static void fdn_signal_finalize (napi_env env, void * finalize_data, void * finalize_hint); +static napi_value fdn_signal_connect (napi_env env, napi_callback_info info); +static napi_value fdn_signal_disconnect (napi_env env, napi_callback_info info); +static void fdn_signal_disconnect_closure (FdnSignal * self, FdnSignalClosure * closure); +static gboolean fdn_signal_parse_arguments (napi_env env, napi_callback_info info, FdnSignal ** self, napi_value * js_self, napi_value * handler); +static FdnSignalClosure * fdn_signal_closure_new (napi_env env, FdnSignal * sig, napi_value js_sig, napi_value handler); +static void fdn_signal_closure_finalize (gpointer data, GClosure * closure); +static void fdn_signal_closure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, gpointer invocation_hint, gpointer marshal_data); +static void fdn_signal_closure_deliver (napi_env env, napi_value js_cb, void * context, void * data); + +static void fdn_keep_alive_until (napi_env env, napi_value js_object, GObject * handle, FdnIsDestroyedFunc is_destroyed, const gchar * destroy_signal_name); +static void fdn_keep_alive_on_finalize (napi_env env, void * finalize_data, void * finalize_hint); +static void fdn_keep_alive_on_destroy_signal (GObject * handle, gpointer user_data); +static void fdn_keep_alive_on_destroy_signal_handler_detached (gpointer data, GClosure * closure); +static void fdn_keep_alive_schedule_cleanup (FdnKeepAliveContext * context); +static void fdn_keep_alive_on_tsfn_invoke (napi_env env, napi_value js_cb, void * context, void * data); + +static void fdn_inherit_val_val (napi_env env, napi_value sub_ctor, napi_value super_ctor, napi_value object_ctor, napi_value set_proto); +G_GNUC_UNUSED static void fdn_inherit_val_ref (napi_env env, napi_value sub_ctor, napi_ref super_ctor, napi_value object_ctor, napi_value set_proto); +static void fdn_inherit_ref_val (napi_env env, napi_ref sub_ctor, napi_value super_ctor, napi_value object_ctor, napi_value set_proto); +static void fdn_inherit_ref_ref (napi_env env, napi_ref sub_ctor, napi_ref super_ctor, napi_value object_ctor, napi_value set_proto); + +static gchar * fdn_camel_case_to_kebab_case (const gchar * name); diff --git a/frida/frida_bindgen/assets/codegen_types.h b/frida/frida_bindgen/assets/codegen_types.h new file mode 100644 index 0000000..9489249 --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_types.h @@ -0,0 +1,57 @@ +typedef enum { + FDN_SIGNAL_ALLOW_EXIT, + FDN_SIGNAL_KEEP_ALIVE +} FdnSignalBehavior; + +typedef struct { + GObject * handle; + guint id; + FdnSignalBehavior behavior; + GSList * closures; +} FdnSignal; + +typedef enum { + FDN_SIGNAL_CLOSURE_OPEN, + FDN_SIGNAL_CLOSURE_CLOSED, +} FdnSignalClosureState; + +typedef struct { + GClosure closure; + FdnSignal * sig; + napi_ref js_sig; + FdnSignalClosureState state; + napi_threadsafe_function tsfn; + napi_ref handler; + gulong handler_id; +} FdnSignalClosure; + +typedef enum { + FDN_SIGNAL_CLOSURE_MESSAGE_DESTROY, + FDN_SIGNAL_CLOSURE_MESSAGE_MARSHAL, +} FdnSignalClosureMessageType; + +typedef struct { + napi_ref js_sig; + napi_threadsafe_function tsfn; + napi_ref handler; +} FdnSignalClosureMessageDestroy; + +typedef struct { + GArray * args; +} FdnSignalClosureMessageMarshal; + +typedef struct { + FdnSignalClosureMessageType type; + union { + FdnSignalClosureMessageDestroy destroy; + FdnSignalClosureMessageMarshal marshal; + } payload; +} FdnSignalClosureMessage; + +typedef struct { + guint ref_count; + GObject * handle; + gulong signal_handler_id; +} FdnKeepAliveContext; + +typedef gboolean (* FdnIsDestroyedFunc) (GObject * handle); diff --git a/frida/frida_bindgen/assets/customization_facade.exports b/frida/frida_bindgen/assets/customization_facade.exports new file mode 100644 index 0000000..a4d7c4d --- /dev/null +++ b/frida/frida_bindgen/assets/customization_facade.exports @@ -0,0 +1,13 @@ +querySystemParameters +spawn +resume +kill +attach +injectLibraryFile +injectLibraryBlob +enumerateDevices +getDeviceManager +getLocalDevice +getRemoteDevice +getUsbDevice +getDevice diff --git a/frida/frida_bindgen/assets/customization_facade.ts b/frida/frida_bindgen/assets/customization_facade.ts new file mode 100644 index 0000000..a0eced1 --- /dev/null +++ b/frida/frida_bindgen/assets/customization_facade.ts @@ -0,0 +1,157 @@ +let sharedDeviceManager: DeviceManager | null = null; + +export async function querySystemParameters(cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + return await device.querySystemParameters(cancellable); +} + +export async function spawn(program: string | string[], options?: SpawnOptions, cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + return await device.spawn(program, options, cancellable); +} + +export async function resume(target: TargetProcess, cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + await device.resume(target, cancellable); +} + +export async function kill(target: TargetProcess, cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + await device.kill(target, cancellable); +} + +export async function attach(target: TargetProcess, options?: SessionOptions, cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + return await device.attach(target, options, cancellable); +} + +export async function injectLibraryFile(target: TargetProcess, path: string, entrypoint: string, data: string, + cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + return await device.injectLibraryFile(target, path, entrypoint, data, cancellable); +} + +export async function injectLibraryBlob(target: TargetProcess, blob: Buffer, entrypoint: string, data: string, + cancellable?: Cancellable | null): Promise { + const device = await getLocalDevice(cancellable); + return await device.injectLibraryBlob(target, blob, entrypoint, data, cancellable); +} + +export async function enumerateDevices(cancellable?: Cancellable | null): Promise { + const deviceManager = getDeviceManager(); + return await deviceManager.enumerateDevices(cancellable); +}; + +export function getDeviceManager(): DeviceManager { + if (sharedDeviceManager === null) { + sharedDeviceManager = new DeviceManager(); + } + return sharedDeviceManager; +} + +export function getLocalDevice(cancellable?: Cancellable | null): Promise { + return getMatchingDevice(device => device.type === DeviceType.Local, {}, cancellable); +} + +export function getRemoteDevice(cancellable?: Cancellable | null): Promise { + return getMatchingDevice(device => device.type === DeviceType.Remote, {}, cancellable); +} + +export function getUsbDevice(options?: GetDeviceOptions, cancellable?: Cancellable | null): Promise { + return getMatchingDevice(device => device.type === DeviceType.Usb, options, cancellable); +} + +export function getDevice(id: string, options?: GetDeviceOptions, cancellable?: Cancellable | null): Promise { + return getMatchingDevice(device => device.id === id, options, cancellable); +} + +export interface GetDeviceOptions { + timeout?: number | null; +} + +async function getMatchingDevice(predicate: DevicePredicate, options: GetDeviceOptions = {}, cancellable: Cancellable | null = null): Promise { + const device = await findMatchingDevice(predicate, cancellable); + if (device !== null) { + return device; + } + + const { timeout = 0 } = options; + if (timeout === 0) { + throw new Error("Device not found"); + } + + const getDeviceEventually = new Promise((resolve: (device: Device) => void, reject: (error: Error) => void) => { + const deviceManager = getDeviceManager(); + + deviceManager.added.connect(onDeviceAdded); + + const timer = (timeout !== null) ? setTimeout(onTimeout, timeout) : null; + + if (cancellable !== null) { + cancellable.cancelled.connect(onCancel); + if (cancellable.isCancelled) { + onCancel(); + return; + } + } + + findMatchingDevice(predicate, cancellable) + .then(device => { + if (device !== null) { + onSuccess(device); + } + }) + .catch(onError); + + function onDeviceAdded(device: Device): void { + if (predicate(device)) { + onSuccess(device); + } + } + + function onSuccess(device: Device): void { + stopMonitoring(); + resolve(device); + } + + function onError(error: Error): void { + stopMonitoring(); + reject(error); + } + + function onTimeout(): void { + onError(new Error("Timed out while waiting for device to appear")); + } + + function onCancel(): void { + onError(new Error("Operation was cancelled")); + } + + function stopMonitoring(): void { + cancellable?.cancelled.disconnect(onCancel); + + if (timer !== null) { + clearTimeout(timer); + } + + deviceManager.added.disconnect(onDeviceAdded); + } + }); + + return await getDeviceEventually; +} + +async function findMatchingDevice(predicate: DevicePredicate, cancellable?: Cancellable | null): Promise { + const deviceManager = getDeviceManager(); + + const devices = await deviceManager.enumerateDevices(cancellable); + + const matching = devices.filter(predicate); + if (matching.length === 0) { + return null; + } + + return matching[0]; +} + +type DevicePredicate = (device: Device) => boolean; diff --git a/frida/frida_bindgen/assets/customization_helpers.imports b/frida/frida_bindgen/assets/customization_helpers.imports new file mode 100644 index 0000000..d970c65 --- /dev/null +++ b/frida/frida_bindgen/assets/customization_helpers.imports @@ -0,0 +1,2 @@ +import { Minimatch } from "minimatch"; +import { Duplex } from "stream"; diff --git a/frida/frida_bindgen/assets/customization_helpers.ts b/frida/frida_bindgen/assets/customization_helpers.ts new file mode 100644 index 0000000..b2d8569 --- /dev/null +++ b/frida/frida_bindgen/assets/customization_helpers.ts @@ -0,0 +1,396 @@ +const STANDARD_SPAWN_OPTION_NAMES = new Set([ + "argv", + "envp", + "env", + "cwd", + "stdio", +]); + +interface LogMessage { + type: "log"; + level: LogLevel; + payload: string; +} + +class ScriptServices implements RpcController { + exportsProxy = new ScriptExportsProxy(this); + + #script: Script; + #pendingRequests = new Map void>(); + #nextRequestId = 1; + + constructor(script: Script) { + this.#script = script; + process.nextTick(() => { + script.message.connect(() => {}); + }); + } + + handleMessageIntercept = (message: Message, data: Buffer | null): boolean => { + if (message.type === MessageType.Send && isRpcSendMessage(message)) { + const [ , id, operation, ...params ] = message.payload; + this.#onRpcMessage(id, operation, params, data); + return false; + } else if (isLogMessage(message)) { + const opaqueMessage: any = message; + const logMessage: LogMessage = opaqueMessage; + this.#script.logHandler(logMessage.level, logMessage.payload); + return false; + } + + return true; + }; + + request(operation: string, params: any[], data: Buffer | null, cancellable: Cancellable | null = null): Promise { + return new Promise((resolve, reject) => { + const id = this.#nextRequestId++; + + const complete = (error: Error | null, result?: any): void => { + if (cancellable !== null) { + cancellable.cancelled.disconnect(onOperationCancelled); + } + this.#script.destroyed.disconnect(onScriptDestroyed); + + this.#pendingRequests.delete(id); + + if (error === null) { + resolve(result); + } else { + reject(error); + } + }; + + function onScriptDestroyed(): void { + complete(new Error("Script is destroyed")); + } + + function onOperationCancelled(): void { + complete(new Error("Operation was cancelled")); + } + + this.#pendingRequests.set(id, complete); + + this.#script.post(["frida:rpc", id, operation, ...params], data); + this.#script.destroyed.connect(onScriptDestroyed); + if (cancellable !== null) { + cancellable.cancelled.connect(onOperationCancelled); + if (cancellable.isCancelled) { + onOperationCancelled(); + return; + } + } + if (this.#script.isDestroyed) { + onScriptDestroyed(); + } + }); + } + + #onRpcMessage(id: number, operation: RpcOperation, params: any[], data: Buffer | null) { + if (operation === RpcOperation.Ok || operation === RpcOperation.Error) { + const callback = this.#pendingRequests.get(id); + if (callback === undefined) { + return; + } + + let value = null; + let error = null; + if (operation === RpcOperation.Ok) { + if (data !== null) { + value = (params.length > 1) ? [params[1], data] : data; + } else { + value = params[0]; + } + } else { + const [message, name, stack, rawErr] = params; + error = new Error(message); + error.name = name; + error.stack = stack; + Object.assign(error, rawErr); + } + + callback(error, value); + } + } +} + +class ScriptExportsProxy implements ScriptExports { + [name: string]: (...args: any[]) => Promise; + + constructor(rpcController: RpcController) { + return new Proxy(this, { + has(target, property) { + return !isReservedMethodName(property); + }, + get(target, property, receiver) { + if (typeof property === "symbol") { + if (property === inspect.custom) { + return inspectProxy; + } + + return undefined; + } + + if (property in target) { + return target[property]; + } + + if (isReservedMethodName(property)) { + return undefined; + } + + return (...args: any[]): Promise => { + let cancellable: Cancellable | null = null; + if (args[args.length - 1] instanceof Cancellable) { + cancellable = args.pop(); + } + + let data: Buffer | null = null; + if (Buffer.isBuffer(args[args.length - 1])) { + data = args.pop(); + } + + return rpcController.request("call", [property, args], data, cancellable); + }; + }, + set(target, property, value, receiver) { + if (typeof property === "symbol") { + return false; + } + target[property] = value; + return true; + }, + ownKeys(target) { + return Object.getOwnPropertyNames(target); + }, + getOwnPropertyDescriptor(target, property) { + if (property in target) { + return Object.getOwnPropertyDescriptor(target, property); + } + + if (isReservedMethodName(property)) { + return undefined; + } + + return { + writable: true, + configurable: true, + enumerable: true + }; + }, + }); + } +} + +function inspectProxy() { + return "ScriptExportsProxy {}"; +} + +interface RpcController { + request(operation: string, params: any[], data: ArrayBuffer | null, cancellable: Cancellable | null): Promise; +} + +enum RpcOperation { + Ok = "ok", + Error = "error" +} + +function isInternalMessage(message: Message): boolean { + return isRpcMessage(message) || isLogMessage(message); +} + +function isRpcMessage(message: Message): boolean { + return message.type === MessageType.Send && isRpcSendMessage(message); +} + +function isRpcSendMessage(message: SendMessage): boolean { + const payload = message.payload; + if (!Array.isArray(payload)) { + return false; + } + + return payload[0] === "frida:rpc"; +} + +function isLogMessage(message: Message): boolean { + return message.type as string === "log"; +} + +function log(level: LogLevel, text: string): void { + switch (level) { + case LogLevel.Info: + console.log(text); + break; + case LogLevel.Warning: + console.warn(text); + break; + case LogLevel.Error: + console.error(text); + break; + } +} + +const reservedMethodNames = new Set([ + "then", + "catch", + "finally", +]); + +function isReservedMethodName(name: string | number | symbol): boolean { + return reservedMethodNames.has(name.toString()); +} + +const IO_PRIORITY_DEFAULT = 0; + +class IOStreamAdapter extends Duplex { + #impl: IOStream; + #input: InputStream; + #output: OutputStream; + #pending = new Set>(); + + #cancellable = new Cancellable(); + + constructor(impl: IOStream) { + super({}); + + this.#impl = impl; + this.#input = impl.inputStream; + this.#output = impl.outputStream; + } + + async _destroy(error: Error | null, callback: (error: Error | null) => void): Promise { + this.#cancellable.cancel(); + + for (const operation of this.#pending) { + try { + await operation; + } catch (e) { + } + } + + try { + await this.#impl.close(IO_PRIORITY_DEFAULT); + } catch (e) { + } + + callback(error); + } + + _read(size: number): void { + const operation = this.#input.read(size, IO_PRIORITY_DEFAULT, this.#cancellable) + .then((data: Buffer): void => { + const isEof = data.length === 0; + if (isEof) { + this.push(null); + return; + } + + this.push(data); + }) + .catch((error: Error): void => { + if (this.#impl.closed) { + this.push(null); + } + this.emit("error", error); + }); + this.#track(operation); + } + + _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void { + let data: Buffer; + if (Buffer.isBuffer(chunk)) { + data = chunk; + } else { + data = Buffer.from(chunk, encoding); + } + const operation = this.#writeAll(data) + .then((): void => { + callback(null); + }) + .catch((error: Error): void => { + callback(error); + }); + this.#track(operation); + } + + async #writeAll(data: Buffer): Promise { + let offset = 0; + do { + const n = await this.#output.write(data.slice(offset), IO_PRIORITY_DEFAULT, this.#cancellable); + offset += n; + } while (offset !== data.length); + } + + #track(operation: Promise): void { + this.#pending.add(operation); + operation + .catch(_ => {}) + .finally(() => { + this.#pending.delete(operation); + }); + } +} + +class CallbackAuthenticationService extends binding.AbstractAuthenticationService { + #callback: AuthenticationCallback; + + constructor(callback: AuthenticationCallback) { + super(); + this.#callback = callback; + } + + async authenticate(token: string, cancellable: Cancellable | null): Promise { + const info = await this.#callback(token); + return JSON.stringify(info); + } +} + +function parseSocketAddress(address: BaseSocketAddress): SocketAddress { + const family = address.family; + switch (family) { + case SocketFamily.Unix: { + const addr = address as UnixSocketAddress; + switch (addr.addressType) { + case UnixSocketAddressType.Anonymous: + return { + family: "unix:anonymous", + }; + case UnixSocketAddressType.Path: + return { + family: "unix:path", + path: addr.path.toString(), + }; + case UnixSocketAddressType.Abstract: + case UnixSocketAddressType.AbstractPadded: + return { + family: "unix:abstract", + path: addr.path, + }; + } + break; + } + case SocketFamily.Ipv4: { + const addr = address as InetSocketAddress; + return { + family: "ipv4", + address: addr.address.toString(), + port: addr.port, + }; + } + case SocketFamily.Ipv6: { + const addr = address as InetSocketAddress; + return { + family: "ipv6", + address: addr.address.toString(), + port: addr.port, + flowlabel: addr.flowinfo, + scopeid: addr.scopeId, + }; + } + } + + throw new Error("invalid BaseSocketAddress"); +} + +function objectToStrv(object: { [name: string]: string }): string[] { + return Object.entries(object).map(([k, v]) => `${k}=${v}`); +} diff --git a/frida/frida_bindgen/cli.py b/frida/frida_bindgen/cli.py new file mode 100644 index 0000000..3a32207 --- /dev/null +++ b/frida/frida_bindgen/cli.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import argparse +from io import StringIO +from pathlib import Path + +from . import codegen +from .customization import load_customizations +from .loader import compute_model + + +def main(): + run(build_arguments()) + + +def build_arguments() -> argparse.Namespace: + p = argparse.ArgumentParser( + description="Generate TypeScript and Node-API bindings for Frida." + ) + p.add_argument( + "--frida-gir", + required=True, + type=Path, + help="Path to the Frida .gir file.", + ) + p.add_argument( + "--glib-gir", + required=True, + type=Path, + help="Path to the GLib .gir file.", + ) + p.add_argument( + "--gobject-gir", + required=True, + type=Path, + help="Path to the GObject .gir file.", + ) + p.add_argument( + "--gio-gir", + required=True, + type=Path, + help="Path to the GIO .gir file.", + ) + p.add_argument( + "--output-py", + required=True, + type=Path, + help="Path to the output .py file.", + ) + p.add_argument( + "--output-pyi", + required=True, + type=Path, + help="Path to the output .pyi file.", + ) + p.add_argument( + "--output-c", + required=True, + type=Path, + help="Path to the output C file for the Python extension.", + ) + return p.parse_args() + + +def run(args: argparse.Namespace) -> None: + customizations = load_customizations() + model = compute_model( + args.frida_gir, args.glib_gir, args.gobject_gir, args.gio_gir, customizations + ) + + artefacts = codegen.generate_all(model) + + with OutputFile(args.output_py) as f: + f.write(artefacts["py"]) + with OutputFile(args.output_pyi) as f: + f.write(artefacts["pyi"]) + with OutputFile(args.output_c) as f: + f.write(artefacts["c"]) + + +class OutputFile: + def __init__(self, output_path): + self._output_path = output_path + self._io = StringIO() + + def __enter__(self): + return self._io + + def __exit__(self, *exc): + result = self._io.getvalue() + if self._output_path.exists(): + existing_contents = self._output_path.read_text(encoding="utf-8") + if existing_contents == result: + return False + self._output_path.write_text(result, encoding="utf-8") + return False diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py new file mode 100644 index 0000000..73c09bd --- /dev/null +++ b/frida/frida_bindgen/codegen.py @@ -0,0 +1,2233 @@ +from __future__ import annotations + +import textwrap +from pathlib import Path +from typing import Dict, List, Optional + +from .model import (ClassObjectType, CustomTypeKind, Enumeration, + InterfaceObjectType, Method, Model, ObjectType, Parameter, + Procedure, Property, Signal, Tuple, to_pascal_case) + +ASSETS_DIR = Path(__file__).resolve().parent / "assets" +CODEGEN_HELPERS_TS = (ASSETS_DIR / "codegen_helpers.ts").read_text(encoding="utf-8") +CODEGEN_TYPES_H = (ASSETS_DIR / "codegen_types.h").read_text(encoding="utf-8") +CODEGEN_PROTOTYPES_H = (ASSETS_DIR / "codegen_prototypes.h").read_text(encoding="utf-8") +CODEGEN_HELPERS_C = (ASSETS_DIR / "codegen_helpers.c").read_text(encoding="utf-8") + + +def generate_all(model: Model) -> Dict[str, str]: + return { + "py": generate_py(model), + "pyi": generate_extension_pyi(model), + "c": generate_extension_c(model), + } + + +def generate_py(model: Model) -> str: + type_imports = [] + for t in model.public_types.values(): + type_imports.append(f"{t.js_name} as _{t.js_name}") + if isinstance(t, ObjectType): + for s in t.signals: + type_imports.append(f"{s.handler_type_name} as _{s.handler_type_name}") + prefixed_name = s.prefixed_handler_type_name + if prefixed_name != s.handler_type_name: + type_imports.append(f"{prefixed_name} as _{prefixed_name}") + if isinstance(t, InterfaceObjectType) and t.has_abstract_base: + type_imports.append(f"Abstract{t.js_name} as _Abstract{t.js_name}") + for name in model.customizations.custom_types.keys(): + type_imports.append(f"{name} as _{name}") + + lines = [ + 'import bindings from "bindings";', + "import type {", + " FridaBinding,", + *[f" {i}," for i in type_imports], + " Signal,", + " SignalHandler,", + '} from "./frida_binding.d.ts";', + 'import util from "util";', + *model.customizations.helper_imports, + "", + "const { inspect } = util;", + ] + + lines.append( + """ +const binding: FridaBinding = bindings({ + bindings: "frida_binding", + try: [ + ["module_root", "build", "bindings"], + [process.cwd(), "bindings"], + ] +});""" + ) + + for name, cust in model.customizations.custom_types.items(): + if cust.kind == CustomTypeKind.ENUM: + lines += [ + "", + f"enum {name}Impl {{", + indent_ts_code(cust.typing.strip(), 1), + "}", + f"(binding as any).{name} = {name}Impl;", + f"export const {name} = binding.{name};", + ] + + lines += [ + "", + "{", + indent_ts_code(model.customizations.helper_code.rstrip(), 1), + indent_ts_code(CODEGEN_HELPERS_TS.rstrip(), 1), + ] + + ol = [] + for otype in model.object_types.values(): + if not otype.is_frida_options and not otype.is_frida_list: + prop_names = ", ".join([f'"{prop.js_name}"' for prop in otype.properties]) + ol += [ + "", + f"(binding as any).{otype.prefixed_js_name}.prototype[inspect.custom] = function (depth: number, options: util.InspectOptionsStylized): string {{", + f' return inspectWrapper(this, "{otype.js_name}", [{prop_names}], depth, options);', + "};", + ] + + if not otype.needs_wrapper: + continue + + ol += [ + "", + f"class {otype.js_name} extends binding._{otype.js_name} {{", + ] + + num_members = 0 + + custom_code = otype.customizations.custom_code + if custom_code is not None: + for declaration in custom_code.declarations: + if num_members != 0: + ol.append("") + ol.append(indent_ts_code(declaration.code.strip(), 1)) + num_members += 1 + + for method in custom_code.methods: + if num_members != 0: + ol.append("") + ol.append(indent_ts_code(method.code.strip(), 1)) + num_members += 1 + + ctor = otype.constructors[0] if otype.constructors else None + if ctor is not None and ctor.needs_wrapper: + ol.append(f" constructor({', '.join(ctor.param_typings)}) {{") + + custom_logic = ctor.customizations.custom_logic + if custom_logic is not None: + ol += [ + indent_ts_code(custom_logic.strip(), 2), + "", + ] + + ol += [ + f" super({', '.join(param.js_name for param in ctor.parameters)});", + " }", + ] + + for method in otype.wrapped_methods: + custom = method.customizations + + maybe_async = "async " if method.is_async else "" + maybe_await = "await " if method.is_async else "" + + if num_members != 0: + ol.append("") + ol.append( + f" {maybe_async}{method.js_name}({', '.join(method.param_typings)}): {method.return_typing} {{" + ) + + custom_logic = custom.custom_logic + if custom_logic is not None: + ol += [ + indent_ts_code(custom_logic.strip(), 2), + "", + ] + + return_capture = "const result = " if method.return_typing != "void" else "" + + ol.append( + f" {return_capture}{maybe_await}this._{method.js_name}({', '.join(param.js_name for param in method.input_parameters)});" + ) + + if return_capture: + ol.append("") + return_wrapper = custom.return_wrapper + if return_wrapper is not None: + if return_wrapper.startswith("as "): + ol.append(f" return result {return_wrapper};") + else: + ol.append(f" return {return_wrapper}(result);") + else: + ol.append(" return result;") + + ol.append(" }") + + num_members += 1 + + for signal in otype.wrapped_signals: + custom = signal.customizations + + option_lines = [] + + transform = custom.transform + if transform is not None: + param_typings = [] + transformed_params = [] + for i, param in enumerate(signal.parameters): + if transform is not None and i in transform: + transformed_name_and_type, transform_function = transform[i] + param_typings.append(transformed_name_and_type) + if transform_function is not None: + transformed_params.append( + f"{transform_function}({param.js_name})" + ) + else: + transformed_params.append(param.js_name) + else: + param_typings.append(param.typing) + transformed_params.append(param.js_name) + + transformed_params_str = ", ".join(transformed_params) + + option_lines += [ + f"transform({', '.join(p.js_name for p in signal.parameters)}) {{", + f" return [{transformed_params_str}];", + "},", + ] + + intercept = custom.intercept + if intercept is not None: + option_lines.append(f"intercept: {intercept},") + + if num_members != 0: + ol.append("") + option_indent = 8 * " " + ol += [ + f" {signal.js_name}: Signal<_{signal.handler_type_name}> = new SignalWrapper<__{signal.handler_type_name}, _{signal.handler_type_name}>(this._{signal.js_name}, {{", + *[option_indent + line for line in option_lines], + " });", + ] + + num_members += 1 + + ol += [ + "}", + "", + f"binding.{otype.js_name} = {otype.js_name};", + ] + + lines += [ + indent_ts_code("\n".join(ol), 1), + "}", + "", + "binding.commitConstructors();", + "", + "export const {", + ] + for t in model.public_types.values(): + if isinstance(t, InterfaceObjectType): + if t.has_abstract_base: + lines.append(f" Abstract{t.js_name},") + continue + lines.append(f" {t.js_name},") + lines += [ + "} = binding;", + "", + "const frida = {", + ] + for t in model.public_types.values(): + if isinstance(t, InterfaceObjectType): + if t.has_abstract_base: + lines.append(f" Abstract{t.js_name},") + continue + lines.append(f" {t.js_name},") + for name, cust in model.customizations.custom_types.items(): + if cust.kind == CustomTypeKind.ENUM: + lines.append(f" {name},") + lines += [ + *[f" {e}," for e in model.customizations.facade_exports], + "} as const;", + "", + "export default frida;", + ] + + type_exports = [] + for t in model.public_types.values(): + type_exports.append(f"export type {t.js_name} = _{t.js_name};") + if isinstance(t, ObjectType): + if isinstance(t, InterfaceObjectType) and t.has_abstract_base: + type_exports.append( + f"export type Abstract{t.js_name} = _Abstract{t.js_name};" + ) + type_exports += [ + f"export type {s.handler_type_name} = _{s.handler_type_name};" + for s in t.signals + ] + for name in model.customizations.custom_types.keys(): + type_exports.append(f"export type {name} = _{name};") + + lines += [ + "", + "namespace frida {", + ] + for e in type_exports: + lines.append(indent_ts_code(e, 1)) + lines += [ + "}", + "", + ] + for e in type_exports: + lines.append(e) + + lines.append("") + lines.append(model.customizations.facade_code) + + return "\n".join(lines) + + +def generate_extension_pyi(model: Model) -> str: + lines = [ + "export interface FridaBinding {", + " commitConstructors(): void;", + ] + for t in model.public_types.values(): + if t.is_frida_options: + lines.append(f" {t.prefixed_js_name}: {t.prefixed_js_name};") + else: + if isinstance(t, InterfaceObjectType): + if t.has_abstract_base: + lines.append( + f" Abstract{t.js_name}: typeof Abstract{t.js_name};" + ) + continue + if isinstance(t, ClassObjectType): + if t.needs_wrapper: + lines.append(f" {t.js_name}: typeof {t.js_name};") + lines.append(f" {t.prefixed_js_name}: typeof {t.prefixed_js_name};") + for name, cust in model.customizations.custom_types.items(): + if cust.kind == CustomTypeKind.ENUM: + lines.append(f" {name}: typeof {name};") + lines.append("}") + + for otype in model.object_types.values(): + if not otype.is_public: + continue + + if otype.needs_wrapper: + lines += [ + "", + f"export class {otype.js_name} extends {otype.prefixed_js_name} {{", + ] + + custom_code = otype.customizations.custom_code + if custom_code is not None: + for declaration in custom_code.declarations: + typing = declaration.typing + if typing is not None: + lines.append(f" {typing};") + + for method in custom_code.methods: + typing = method.typing + if typing is not None: + lines.append(f" {typing};") + + ctor = otype.constructors[0] if otype.constructors else None + if ctor is not None and ctor.needs_wrapper: + lines.append(f" constructor({', '.join(ctor.param_typings)});") + + for method in otype.wrapped_methods: + params = ", ".join(method.param_typings) + lines.append(f" {method.js_name}({params}): {method.return_typing};") + + for signal in otype.wrapped_signals: + lines.append( + f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;" + ) + + lines.append("}") + + if otype.wrapped_signals: + lines.append("") + for signal in otype.wrapped_signals: + params = ", ".join( + signal.customizations.transform.get(i, (param.typing, ""))[0] + for i, param in enumerate(signal.parameters) + ) + lines.append( + f"export type {signal.handler_type_name} = ({params}) => void;" + ) + + lines.append("") + + parent = otype.parent + parent_name = parent.js_name if parent is not None else None + extends = ( + "" + if (parent_name is None or otype.is_frida_options) + else f" extends {parent_name}" + ) + if isinstance(otype, InterfaceObjectType) or otype.is_frida_options: + lines.append(f"export interface {otype.prefixed_js_name}{extends} {{") + else: + implements = ( + f" implements {', '.join([t.js_name for t in otype.implements])}" + if otype.implements + else "" + ) + lines.append( + f"export class {otype.prefixed_js_name}{extends}{implements} {{" + ) + + if otype.constructors: + constructor = otype.constructors[0] + params = ", ".join(param.typing for param in constructor.parameters) + lines.append(f" constructor({params});") + + if otype.is_frida_options: + for method in otype.methods: + if method.is_select_method: + lines.append( + f" {method.select_plural_noun}?: {model.resolve_js_type(method.select_element_type)}[];" + ) + else: + for method in otype.methods: + if method.is_property_accessor: + continue + visibility = ( + "protected " if method.prefixed_js_name != method.js_name else "" + ) + lines.append( + f" {visibility}{method.prefixed_js_name}({', '.join(method.prefixed_param_typings)}): {method.prefixed_return_typing};" + ) + + for prop in otype.properties: + lines.append(f" {prop.typing};") + + for signal in otype.signals: + visibility = ( + "protected " if signal.prefixed_js_name != signal.js_name else "" + ) + lines.append( + f" {visibility}readonly {signal.prefixed_js_name}: Signal<{signal.prefixed_handler_type_name}>;" + ) + + if isinstance(otype, ClassObjectType): + for itype in otype.implements: + for method in itype.methods: + lines.append( + f" {method.js_name}({', '.join(method.param_typings)}): {method.return_typing};" + ) + + for prop in itype.properties: + lines.append(f" {prop.typing};") + + for signal in itype.signals: + lines.append( + f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;" + ) + + lines.append("}") + + if otype.signals: + lines.append("") + for signal in otype.signals: + lines.append( + f"export type {signal.prefixed_handler_type_name} = {signal.typing};" + ) + + if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base: + lines.append("") + + object_js_name = model.resolve_object_type("Object").js_name + lines.append( + f"export abstract class Abstract{otype.js_name} extends {object_js_name} implements {otype.js_name} {{" + ) + + for method in otype.methods: + params = ", ".join([t.replace("?:", ":") for t in method.param_typings]) + lines.append(f" {method.js_name}({params}): {method.return_typing};") + + for prop in otype.properties: + lines.append(f" {prop.typing};") + + for signal in otype.signals: + lines.append( + f" readonly {signal.js_name}: Signal<{signal.handler_type_name}>;" + ) + + lines.append("}") + + for enum in model.enumerations.values(): + members = ",\n ".join( + f'{member.js_name} = "{member.nick}"' for member in enum.members + ) + lines += [ + "", + f"export enum {enum.js_name} {{", + f" {members}", + "}", + ] + + for name, cust in model.customizations.custom_types.items(): + code = f"\nexport {cust.kind.value} {name}" + if cust.kind == CustomTypeKind.TYPE: + code += " = " + code += cust.typing.strip() + code += ";" + else: + code += " {\n" + code += indent_ts_code(cust.typing.strip(), 1) + code += "\n}" + lines.append(code) + + lines.append( + """ +export class Signal { + connect(handler: H): void; + disconnect(handler: H): void; +} + +export type SignalHandler = (...args: any[]) => void; +""" + ) + + return "\n".join(lines) + + +def generate_extension_c(model: Model) -> str: + object_types = model.object_types.values() + enumerations = model.enumerations.values() + + code = generate_includes() + code += generate_abstract_base_type_declarations(model) + code += generate_operation_structs(object_types) + code += CODEGEN_TYPES_H + code += generate_prototypes(object_types, enumerations) + code += generate_abstract_base_define_type_invocations(model) + code += generate_shared_globals() + code += generate_type_tags(object_types) + code += generate_constructor_declarations(object_types) + code += generate_tsfn_declarations(object_types) + code += generate_init_function(object_types, enumerations) + code += generate_commit_constructors_function(object_types) + + for otype in object_types: + if otype.is_frida_options: + code += generate_options_conversion_functions(otype) + continue + if otype.is_frida_list: + code += generate_list_conversion_functions(otype) + continue + + code += generate_object_type_registration_code(otype, model) + code += generate_object_type_conversion_functions(otype) + code += generate_object_type_constructor(otype) + code += generate_object_type_finalizer(otype) + code += generate_object_type_cleanup_code(otype) + + for method in otype.methods: + code += generate_method_code(method) + + for signal in otype.signals: + code += generate_signal_getter_code(otype, signal) + + if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base: + code += generate_abstract_base_registration_code(otype) + code += generate_abstract_base_constructor(otype) + code += generate_abstract_base_gobject_glue(otype) + for method in otype.methods: + code += generate_abstract_base_method_code(method) + + for enum in enumerations: + code += generate_enum_registration_code(enum) + code += generate_enum_conversion_functions(enum) + + code += CODEGEN_HELPERS_C + + return code + + +def generate_includes() -> str: + return """\ +#include +#include +#include + +""" + + +def generate_operation_structs(object_types: List[ObjectType]) -> str: + structs = [] + + for otype in object_types: + is_iface_with_abstract_base = ( + isinstance(otype, InterfaceObjectType) and otype.has_abstract_base + ) + + for method in otype.methods: + if method.is_async: + param_declarations = generate_parameter_variable_declarations(method) + return_declaration = generate_return_variable_declaration(method) + decls = "".join( + [ + indent_c_code(param_declarations, 1, prologue="\n"), + indent_c_code(return_declaration, 1, prologue="\n"), + ] + ) + structs.append( + f"""\ +typedef struct {{ + napi_deferred deferred; + {otype.c_type} * handle;{decls} +}} {method.operation_type_name}; +""" + ) + + if is_iface_with_abstract_base: + param_declarations = generate_parameter_variable_declarations(method) + return_declaration = generate_return_variable_declaration(method) + decls = "".join( + [ + indent_c_code(param_declarations, 1, prologue="\n"), + indent_c_code(return_declaration, 1, prologue="\n"), + ] + ) + structs.append( + f"""\ +typedef struct {{ + {otype.abstract_base_c_type} * self;{decls} +}} {method.abstract_base_operation_type_name}; +""" + ) + + return "\n".join(structs) + + +def generate_prototypes( + object_types: List[ObjectType], enumerations: List[Enumeration] +) -> str: + prototypes = [ + "static void fdn_deinit (void * data);", + "", + "static napi_value fdn_commit_constructors (napi_env env, napi_callback_info info);", + ] + + for otype in object_types: + otype_cprefix = otype.c_symbol_prefix + + prototypes.append("") + + if not otype.is_frida_options and not otype.is_frida_list: + prototypes.append( + f"static void {otype_cprefix}_register (napi_env env, napi_value exports);" + ) + + if not otype.is_frida_list: + prototypes.append( + f"G_GNUC_UNUSED static gboolean {otype_cprefix}_from_value (napi_env env, napi_value value, {otype.c_type} ** handle);" + ) + + if not otype.is_frida_options: + prototypes += [ + f"G_GNUC_UNUSED static napi_value {otype_cprefix}_to_value (napi_env env, {otype.c_type} * handle);", + ] + + if not otype.is_frida_options and not otype.is_frida_list: + prototypes.append( + f"static napi_value {otype_cprefix}_construct (napi_env env, napi_callback_info info);" + ) + + custom = otype.customizations + if custom is not None and custom.cleanup is not None: + prototypes += [ + f"static void {otype_cprefix}_finalize (napi_env env, void * finalize_data, void * finalize_hint);", + "", + f"static void {otype.c_symbol_prefix}_handle_cleanup (void * data);", + ] + + for method in otype.methods: + method_cprefix = f"{otype_cprefix}_{method.name}" + prototypes += [ + "", + f"static napi_value {method_cprefix} (napi_env env, napi_callback_info info);", + ] + if method.is_async: + prototypes += [ + f"static gboolean {method_cprefix}_begin (gpointer user_data);", + f"static void {method_cprefix}_end (GObject * source_object, GAsyncResult * res, gpointer user_data);", + f"static void {method_cprefix}_deliver (napi_env env, napi_value js_cb, void * context, void * data);", + f"static void {method_cprefix}_operation_free ({method.operation_type_name} * operation);", + ] + + for i, signal in enumerate(otype.signals): + if i == 0: + prototypes.append("") + prototypes.append( + f"static napi_value {otype_cprefix}_get_{signal.c_name}_signal (napi_env env, napi_callback_info info);" + ) + + if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base: + cprefix = otype.abstract_base_c_symbol_prefix + + prototypes += [ + "", + f"static void {cprefix}_register (napi_env env, napi_value exports);", + f"static napi_value {cprefix}_construct (napi_env env, napi_callback_info info);", + f"static void {cprefix}_iface_init (gpointer g_iface, gpointer iface_data);", + f"static void {cprefix}_dispose (GObject * object);", + f"static void {cprefix}_release_js_resources (napi_env env, napi_value js_cb, void * context, void * data);", + ] + + for m in otype.methods: + method_cprefix = f"{cprefix}_{m.name}" + prototypes += [ + f"static void {method_cprefix} ({', '.join(m.param_ctypings)});", + f"static void {method_cprefix}_operation_free ({m.abstract_base_operation_type_name} * operation);", + f"static void {method_cprefix}_begin (napi_env env, napi_value js_cb, void * context, void * data);", + f"static napi_value {method_cprefix}_on_success (napi_env env, napi_callback_info info);", + f"static napi_value {method_cprefix}_on_failure (napi_env env, napi_callback_info info);", + f"static {m.return_ctyping} {method_cprefix}_finish ({', '.join(m.finish_param_ctypings)});", + ] + + for enum in enumerations: + enum_cprefix = enum.c_symbol_prefix + prototypes += [ + "", + f"static void {enum_cprefix}_register (napi_env env, napi_value exports);", + f"G_GNUC_UNUSED static gboolean {enum_cprefix}_from_value (napi_env env, napi_value value, {enum.c_type} * e);", + f"G_GNUC_UNUSED static napi_value {enum_cprefix}_to_value (napi_env env, {enum.c_type} e);", + ] + + prototypes.append(CODEGEN_PROTOTYPES_H.rstrip()) + + return "\n".join(prototypes) + "\n\n" + + +def generate_shared_globals() -> str: + return "\n".join( + [ + "static napi_ref fdn_exports;", + "static GHashTable * fdn_constructors;", + "static gboolean fdn_in_cleanup = FALSE;", + "", + "", + ] + ) + + +def generate_type_tags(object_types: List[ObjectType]) -> str: + type_tags = [ + "static napi_type_tag fdn_handle_wrapper_type_tag = { 0xdd596d4f2dad45f9, 0x844585a48e8d05ba };", + "static napi_type_tag fdn_object_type_tag = { 0x4eeacfcdc22c425a, 0x91346eafdc89fedc };", + ] + return "\n".join(type_tags) + "\n" + + +def generate_constructor_declarations(object_types: List[ObjectType]) -> str: + declarations = [] + + for otype in object_types: + if otype.is_frida_options or otype.is_frida_list: + continue + declarations.append(f"static napi_ref {otype.c_symbol_prefix}_constructor;") + + declarations += [ + "", + "static napi_ref fdn_signal_constructor;", + ] + + return "\n" + "\n".join(declarations) + "\n" + + +def generate_tsfn_declarations(object_types: List[ObjectType]) -> str: + declarations = [] + + for otype in object_types: + async_methods = [method for method in otype.methods if method.is_async] + if async_methods: + declarations.append("") + for method in async_methods: + declarations.append( + f"static napi_threadsafe_function {otype.c_symbol_prefix}_{method.name}_tsfn;" + ) + + if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base: + declarations.append( + f"static napi_threadsafe_function {otype.abstract_base_c_symbol_prefix}_release_js_resources_tsfn;" + ) + for method in otype.methods: + declarations.append( + f"static napi_threadsafe_function {otype.abstract_base_c_symbol_prefix}_{method.name}_tsfn;" + ) + + declarations += [ + "", + "static napi_threadsafe_function fdn_keep_alive_tsfn;", + ] + + return "\n".join(declarations) + "\n" + + +def generate_init_function( + object_types: List[ObjectType], enumerations: List[Enumeration] +) -> str: + object_type_prefixes = [] + for otype in object_types: + if otype.is_frida_options or otype.is_frida_list: + continue + object_type_prefixes.append(otype.c_symbol_prefix) + if isinstance(otype, InterfaceObjectType) and otype.has_abstract_base: + object_type_prefixes.append(otype.abstract_base_c_symbol_prefix) + object_type_registration_calls = "\n ".join( + [f"{prefix}_register (env, exports);" for prefix in object_type_prefixes] + ) + + enum_type_registration_calls = "\n ".join( + [f"{enum.c_symbol_prefix}_register (env, exports);" for enum in enumerations] + ) + + return f""" +static napi_value +fdn_init (napi_env env, + napi_value exports) +{{ + napi_value commit_ctors; + + frida_init (); + + napi_create_reference (env, exports, 1, &fdn_exports); + fdn_constructors = g_hash_table_new (NULL, NULL); + + napi_create_function (env, "commitConstructors", NAPI_AUTO_LENGTH, fdn_commit_constructors, NULL, &commit_ctors); + napi_set_named_property (env, exports, "commitConstructors", commit_ctors); + + {object_type_registration_calls} + + {enum_type_registration_calls} + + fdn_signal_register (env, exports); + + napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "FridaKeepAlive"), 0, 1, NULL, NULL, NULL, fdn_keep_alive_on_tsfn_invoke, &fdn_keep_alive_tsfn); + napi_unref_threadsafe_function (env, fdn_keep_alive_tsfn); + + napi_add_env_cleanup_hook (env, fdn_deinit, NULL); + + return exports; +}} + +static void +fdn_deinit (void * data) +{{ + fdn_in_cleanup = TRUE; +}} + +NAPI_MODULE (NODE_GYP_MODULE_NAME, fdn_init) +""" + + +def generate_commit_constructors_function(object_types: List[ObjectType]) -> str: + commits = "" + for otype in object_types: + if otype.is_frida_options or otype.is_frida_list: + continue + + otype_cprefix = otype.c_symbol_prefix + + if otype.needs_wrapper: + commits += f""" if ({otype_cprefix}_constructor == NULL) + {{ + napi_get_named_property (env, exports, "{otype.js_name}", &ctor); + napi_create_reference (env, ctor, 1, &{otype_cprefix}_constructor); + }} +""" + + commits += f" g_hash_table_insert (fdn_constructors, GSIZE_TO_POINTER ({otype.get_type} ()), {otype_cprefix}_constructor);\n\n" + + inherits = "" + for otype in object_types: + if otype.is_frida_options or otype.is_frida_list: + continue + + parent = otype.parent + if parent is None: + continue + + if otype.needs_wrapper: + inherits += f""" + napi_get_named_property (env, exports, "{otype.prefixed_js_name}", &ctor); + fdn_inherit_ref_val (env, {otype.c_symbol_prefix}_constructor, ctor, object_ctor, set_proto); +""" + if parent.name == "Object": + inherits += " fdn_inherit_val_val (env, ctor, fdn_object_ctor, object_ctor, set_proto);" + else: + inherits += " fdn_inherit_val_ref (env, ctor, {parent.c_symbol_prefix}_constructor, object_ctor, set_proto);" + else: + if parent.name == "Object": + inherits += f""" + fdn_inherit_ref_val (env, {otype.c_symbol_prefix}_constructor, fdn_object_ctor, object_ctor, set_proto); +""" + else: + inherits += f""" + fdn_inherit_ref_ref (env, {otype.c_symbol_prefix}_constructor, {parent.c_symbol_prefix}_constructor, object_ctor, set_proto); +""" + + return f""" +static napi_value +fdn_commit_constructors (napi_env env, + napi_callback_info info) +{{ + napi_value result, exports, ctor, global, object_ctor, set_proto, fdn_object_ctor; + + napi_get_reference_value (env, fdn_exports, &exports); +{commits} napi_get_global (env, &global); + napi_get_named_property (env, global, "Object", &object_ctor); + napi_get_named_property (env, object_ctor, "setPrototypeOf", &set_proto); + + napi_get_reference_value (env, fdn_object_constructor, &fdn_object_ctor); +{inherits} + napi_get_undefined (env, &result); + return result; +}} +""" + + +def generate_object_type_registration_code(otype: ObjectType, model: Model) -> str: + otype_cprefix = otype.c_symbol_prefix + + ctor_ref_creation = ( + "" + if otype.needs_wrapper + else f"\n napi_create_reference (env, constructor, 1, &{otype_cprefix}_constructor);" + ) + jsprop_registrations = [] + tsfn_initializations = [] + + for method in otype.methods: + if method.is_property_accessor: + continue + jsprop_registrations.append(generate_method_registration_entry(method)) + if method.is_async: + tsfn_initializations.append( + f"""\ +napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "{method.prefixed_js_name}"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_{method.name}_deliver, &{otype_cprefix}_{method.name}_tsfn); +napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);""" + ) + + for prop in otype.properties: + jsprop_registrations.append(generate_property_registration_entry(prop)) + + for signal in otype.signals: + jsprop_registrations.append(generate_signal_registration_entry(signal)) + + if isinstance(otype, ClassObjectType): + for itype in otype.implements: + for method in itype.methods: + if method.is_property_accessor: + continue + jsprop_registrations.append(generate_method_registration_entry(method)) + + for prop in itype.properties: + jsprop_registrations.append(generate_property_registration_entry(prop)) + + for signal in itype.signals: + jsprop_registrations.append(generate_signal_registration_entry(signal)) + + jsprop_registrations_str = "\n ".join(jsprop_registrations) + tsfn_initializations_code = "\n\n".join(tsfn_initializations) + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + two_newlines = "\n\n" + + return f""" +static void +{otype_cprefix}_register (napi_env env, +{calculate_indent("_register")}napi_value exports) +{{ + napi_property_descriptor properties[] = + {{ + {jsprop_registrations_str} + }}; + napi_value constructor; + + napi_define_class (env, "{otype.prefixed_js_name}", NAPI_AUTO_LENGTH, {otype_cprefix}_construct, NULL, G_N_ELEMENTS (properties), properties, &constructor);{ctor_ref_creation} + + napi_set_named_property (env, exports, "{otype.prefixed_js_name}", constructor);{indent_c_code(tsfn_initializations_code, 1, prologue=two_newlines)} +}} +""" + + +def generate_method_registration_entry(method: Method) -> str: + return f'{{ "{method.prefixed_js_name}", NULL, {method.object_type.c_symbol_prefix}_{method.name}, NULL, NULL, NULL, napi_default, NULL }},' + + +def generate_property_registration_entry(prop: Property) -> str: + otype_cprefix = prop.object_type.c_symbol_prefix + + setter_str = f"{otype_cprefix}_{prop.setter}" if prop.setter is not None else "NULL" + + attrs = ["enumerable", "configurable"] + if prop.setter is not None: + attrs.insert(0, "writable") + attrs_str = " | ".join([f"napi_{attr}" for attr in attrs]) + + return f'{{ "{prop.js_name}", NULL, NULL, {otype_cprefix}_{prop.getter}, {setter_str}, NULL, {attrs_str}, NULL }},' + + +def generate_signal_registration_entry(signal: Signal) -> str: + return f'{{ "{signal.prefixed_js_name}", NULL, NULL, {signal.object_type.c_symbol_prefix}_get_{signal.c_name}_signal, NULL, NULL, napi_default, NULL }},' + + +def generate_object_type_conversion_functions(otype: ObjectType) -> str: + otype_cprefix = otype.c_symbol_prefix + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + from_value_function = f""" +static gboolean +{otype_cprefix}_from_value (napi_env env, +{calculate_indent("_from_value")}napi_value value, +{calculate_indent("_from_value")}{otype.c_type} ** handle) +{{ + return fdn_object_unwrap (env, value, {otype.get_type} (), (GObject **) handle); +}} +""" + + to_value_function = f""" +static napi_value +{otype_cprefix}_to_value (napi_env env, +{calculate_indent("_to_value")}{otype.c_type} * handle) +{{ + return fdn_object_new (env, G_OBJECT (handle), {otype_cprefix}_constructor); +}} +""" + + return from_value_function + to_value_function + + +def generate_object_type_constructor(otype: ObjectType) -> str: + otype_cprefix = otype.c_symbol_prefix + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + ctor = next(iter(otype.constructors), None) + + n_parameters = max(len(ctor.parameters) if ctor is not None else 0, 1) + + storage_prefix = "" + invalid_arg_label = "propagate_error" + error_check = "" + construction_failed_logic = "" + + if ctor is not None: + param_declarations = generate_parameter_variable_declarations( + ctor, initialize=True + ) + if ctor.parameters: + param_conversions = generate_input_parameter_conversions_code( + ctor, storage_prefix, invalid_arg_label + ) + else: + param_conversions = """if (argc != 0) + goto invalid_handle;""" + param_destructions = generate_parameter_destructions_code(ctor, storage_prefix) + + call_args = generate_call_arguments_code(ctor, storage_prefix) + constructor_call = ( + f"handle = {otype.c_cast_macro} ({ctor.c_identifier} ({call_args}));" + ) + unconstructable_logic = "" + + if ctor.throws: + error_check = """if (error != NULL) + goto construction_failed;""" + construction_failed_logic = """construction_failed: + { + napi_throw (env, fdn_error_to_value (env, error)); + g_error_free (error); + goto propagate_error; + } +""" + else: + param_declarations = "" + param_conversions = """if (argc == 0) + goto unconstructable; + +goto invalid_handle;""" + param_destructions = "" + constructor_call = "" + unconstructable_logic = f"""unconstructable: + {{ + napi_throw_error (env, NULL, "type {otype.js_name} cannot be constructed"); + return NULL; + }} +""" + + if ctor is not None and ctor.parameters: + invalid_handle_logic = "" + else: + invalid_handle_logic = f"""invalid_handle: + {{ + napi_throw_type_error (env, NULL, "expected a {otype.js_name} handle"); + goto propagate_error; + }} +""" + + custom = otype.customizations + + finalizer = "fdn_object_finalize" + + cleanup_code = "" + if custom is not None and custom.cleanup is not None: + cleanup_code = f""" + + napi_add_env_cleanup_hook (env, {otype_cprefix}_handle_cleanup, handle); + g_object_set_data (G_OBJECT (handle), "fdn-cleanup-hook", {otype_cprefix}_handle_cleanup);""" + finalizer = f"{otype_cprefix}_finalize" + + keep_alive_code = "" + if custom is not None and custom.keep_alive is not None: + keep_alive = custom.keep_alive + method = next( + ( + method + for method in otype.methods + if method.name == keep_alive.is_destroyed_function + ) + ) + keep_alive_code = f""" + + fdn_keep_alive_until (env, jsthis, G_OBJECT (handle), (FdnIsDestroyedFunc) {method.c_identifier}, "{keep_alive.destroy_signal_name}");""" + + one_newline = "\n" + two_newlines = "\n\n" + + return f""" +static napi_value +{otype_cprefix}_construct (napi_env env, +{calculate_indent("_construct")}napi_callback_info info) +{{ + napi_value result = NULL; + size_t argc = {n_parameters}; + napi_value args[{n_parameters}], jsthis; + bool is_instance;{indent_c_code(param_declarations, 1, prologue=one_newline)} + {otype.c_type} * handle = NULL; + + if (napi_get_cb_info (env, info, &argc, args, &jsthis, NULL) != napi_ok) + goto propagate_error; + + if (argc != 0 && napi_check_object_type_tag (env, args[0], &fdn_handle_wrapper_type_tag, &is_instance) == napi_ok && is_instance) + {{ + if (napi_get_value_external (env, args[0], (void **) &handle) != napi_ok) + goto propagate_error; + + g_object_ref (handle); + }} + else + {{ +{indent_c_code(param_conversions, 2)}{indent_c_code(constructor_call, 2, prologue=two_newlines)}{indent_c_code(error_check, 2, prologue=one_newline)} + }} + + if (!fdn_object_wrap (env, jsthis, G_OBJECT (handle), {finalizer})) + goto propagate_error;{cleanup_code}{keep_alive_code} + + result = jsthis; + goto beach; + +{unconstructable_logic}{invalid_handle_logic}{construction_failed_logic}propagate_error: + {{ + g_clear_object (&handle); + goto beach; + }} +beach: + {{{indent_c_code(param_destructions, 2, prologue=one_newline)} + return result; + }} +}} +""" + + +def generate_object_type_finalizer(otype: ObjectType) -> str: + custom = otype.customizations + if custom is None or custom.cleanup is None: + return "" + + otype_cprefix = otype.c_symbol_prefix + + indent = " " * (len(otype_cprefix) + len("_finalize") + 2) + + return f""" +static void +{otype_cprefix}_finalize (napi_env env, +{indent}void * finalize_data, +{indent}void * finalize_hint) +{{ + {otype.c_type} * self = finalize_data; + + if (g_object_steal_data (G_OBJECT (self), "fdn-cleanup-hook") != NULL) + napi_remove_env_cleanup_hook (env, {otype_cprefix}_handle_cleanup, self); + + fdn_object_finalize (env, finalize_data, finalize_hint); +}} +""" + + +def generate_object_type_cleanup_code(otype: ObjectType) -> str: + custom = otype.customizations + if custom is None or custom.cleanup is None: + return "" + + cleanup_method = next( + (method for method in otype.methods if method.name == custom.cleanup) + ) + + return f""" +static void +{otype.c_symbol_prefix}_handle_cleanup (void * data) +{{ + {otype.c_type} * self = data; + + g_object_steal_data (G_OBJECT (self), "fdn-cleanup-hook"); + + {cleanup_method.c_identifier}_sync (self, NULL, NULL); +}} +""" + + +def generate_method_code(method: Method) -> str: + otype = method.object_type + operation_type_name = method.operation_type_name + otype_cprefix = otype.c_symbol_prefix + + storage_prefix = "operation->" if method.is_async else "" + invalid_arg_label = "invalid_argument" if method.is_async else "beach" + + if method.input_parameters: + args_declarations = f"""\ +size_t argc = {len(method.input_parameters)}; +napi_value args[{len(method.input_parameters)}];""" + get_cb_info_argc_args = "&argc, args" + else: + args_declarations = "" + get_cb_info_argc_args = "NULL, NULL" + + param_conversions = generate_input_parameter_conversions_code( + method, storage_prefix, invalid_arg_label + ) + param_destructions = generate_parameter_destructions_code(method, storage_prefix) + + return_assignment = generate_return_assignment_code(method, storage_prefix) + return_conversion = generate_return_conversion_code(method, storage_prefix) + return_destruction = generate_return_destruction_code(method, storage_prefix) + + keep_alive_code = "" + custom = method.customizations + if custom is not None: + if custom.ref_keep_alive: + keep_alive_code = ( + "\n\nnapi_ref_threadsafe_function (env, fdn_keep_alive_tsfn);" + ) + elif custom.unref_keep_alive: + keep_alive_code = ( + "\n\nnapi_unref_threadsafe_function (env, fdn_keep_alive_tsfn);" + ) + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + 1 + len(method.name) + len(suffix) + 2) + + one_newline = "\n" + two_newlines = "\n\n" + + if method.is_async: + operation_free_function = f"""\ +static void +{otype_cprefix}_{method.name}_operation_free ({operation_type_name} * operation) +{{{indent_c_code(param_destructions, 1, prologue=one_newline)}{indent_c_code(return_destruction, 1, prologue=one_newline)} + g_slice_free ({operation_type_name}, operation); +}}""" + + code = f""" +static napi_value +{otype_cprefix}_{method.name} (napi_env env, +{calculate_indent('')}napi_callback_info info) +{{{indent_c_code(args_declarations, 1, prologue=one_newline)} + napi_value jsthis; + {otype.c_type} * handle; + napi_deferred deferred; + napi_value promise; + {operation_type_name} * operation; + GSource * source; + + if (napi_get_cb_info (env, info, {get_cb_info_argc_args}, &jsthis, NULL) != napi_ok) + return NULL; + + if (napi_unwrap (env, jsthis, (void **) &handle) != napi_ok) + return NULL; + + napi_create_promise (env, &deferred, &promise); + + operation = g_slice_new0 ({operation_type_name}); + operation->deferred = deferred; + operation->handle = handle; + operation->error = NULL;{indent_c_code(param_conversions, 1, prologue=two_newlines)} + + source = g_idle_source_new (); + g_source_set_callback (source, {otype_cprefix}_{method.name}_begin, + operation, NULL); + g_source_attach (source, frida_get_main_context ()); + g_source_unref (source); + + napi_ref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn); + + return promise; + +invalid_argument: + {{ + napi_reject_deferred (env, deferred, NULL); + {otype_cprefix}_{method.name}_operation_free (operation); + return NULL; + }} +}} + +static gboolean +{otype_cprefix}_{method.name}_begin (gpointer user_data) +{{ + {operation_type_name} * operation = user_data; + + {method.c_identifier} (operation->handle, + {", ".join([f"operation->{param.name}" for param in method.parameters])}, + {otype_cprefix}_{method.name}_end, operation); + + return G_SOURCE_REMOVE; +}} + +static void +{otype_cprefix}_{method.name}_end (GObject * source_object, +{calculate_indent("_end")}GAsyncResult * res, +{calculate_indent("_end")}gpointer user_data) +{{ + {operation_type_name} * operation = user_data; + + {return_assignment}{method.finish_c_identifier} (operation->handle, res, &operation->error); + + napi_call_threadsafe_function ({otype_cprefix}_{method.name}_tsfn, operation, napi_tsfn_blocking); +}} + +static void +{otype_cprefix}_{method.name}_deliver (napi_env env, +{calculate_indent("_deliver")}napi_value js_cb, +{calculate_indent("_deliver")}void * context, +{calculate_indent("_deliver")}void * data) +{{ + {operation_type_name} * operation = data; + + if (operation->error != NULL) + {{ + napi_value error_obj = fdn_error_to_value (env, operation->error); + napi_reject_deferred (env, operation->deferred, error_obj); + g_error_free (operation->error); + }} + else + {{ + napi_value js_retval; +{indent_c_code(return_conversion, 2)} + napi_resolve_deferred (env, operation->deferred, js_retval);{indent_c_code(keep_alive_code, 2)} + }} + + {otype_cprefix}_{method.name}_operation_free (operation); + + napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn); +}} + +{operation_free_function} +""" + else: + param_declarations = generate_parameter_variable_declarations( + method, initialize=True + ) + return_declaration = generate_return_variable_declaration(method) + call_args = generate_call_arguments_code( + method, storage_prefix, instance_arg="handle" + ) + + if method.throws: + error_check = """if (error != NULL) + goto call_failed;""" + call_failed_logic = """call_failed: + { + napi_throw (env, fdn_error_to_value (env, error)); + g_error_free (error); + goto beach; + } +""" + else: + error_check = "" + call_failed_logic = "" + + post_call_logic = "".join( + [ + indent_c_code(error_check, 1, prologue=one_newline), + indent_c_code(keep_alive_code, 1, prologue=two_newlines), + indent_c_code(return_conversion, 1, prologue=two_newlines), + indent_c_code(return_destruction, 1, prologue=one_newline), + ] + ) + + code = f""" +static napi_value +{otype_cprefix}_{method.name} (napi_env env, +{calculate_indent('')}napi_callback_info info) +{{ + napi_value js_retval = NULL;{indent_c_code(args_declarations, 1, prologue=one_newline)} + napi_value jsthis; + {otype.c_type} * handle;{indent_c_code(param_declarations, 1, prologue=one_newline)}{indent_c_code(return_declaration, 1, prologue=one_newline)} + + if (napi_get_cb_info (env, info, {get_cb_info_argc_args}, &jsthis, NULL) != napi_ok) + goto beach; + + if (napi_unwrap (env, jsthis, (void **) &handle) != napi_ok) + goto beach;{indent_c_code(param_conversions, 1, prologue=two_newlines)} + + {return_assignment}{method.c_identifier} ({call_args});{post_call_logic} + goto beach; + +{call_failed_logic}beach: + {{{indent_c_code(param_destructions, 2, prologue=one_newline)} + return js_retval; + }} +}} +""" + return code + + +def generate_parameter_variable_declarations( + proc: Procedure, initialize: bool = False +) -> str: + decls = [] + + for param in proc.parameters: + line = f"{param.type.c.replace('const ', '')} {param.name}" + if initialize: + default_val = param.type.default_value + if default_val is not None: + line += f" = {default_val}" + line += ";" + decls.append(line) + + if proc.throws: + line = "GError * error" + if initialize: + line += " = NULL" + line += ";" + decls.append(line) + + return "\n".join(decls) + + +def generate_input_parameter_conversions_code( + proc: Procedure, storage_prefix: str, invalid_arg_label: str +) -> str: + conversions = [ + generate_parameter_conversion_code(param, i, storage_prefix, invalid_arg_label) + for i, param in enumerate(proc.input_parameters) + ] + return "\n\n".join(conversions) + + +def generate_parameter_destructions_code(proc: Procedure, storage_prefix: str) -> str: + destructions = [ + generate_parameter_destruction_code(param, storage_prefix) + for param in proc.parameters + ] + return "\n".join([d for d in destructions if d is not None]) + + +def generate_parameter_conversion_code( + param: Parameter, index: int, storage_prefix: str, invalid_arg_label: str +) -> str: + code = f"""\ +if (argc > {index} && !fdn_is_undefined_or_null (env, args[{index}])) +{{ + if (!fdn_{param.type.nick}_from_value (env, args[{index}], &{storage_prefix}{param.name})) + goto {invalid_arg_label}; +}} +else +{{ +""" + + if param.nullable: + code += f" {storage_prefix}{param.name} = NULL;" + else: + code += f""" napi_throw_type_error (env, NULL, "missing argument: {param.js_name}"); + goto {invalid_arg_label};""" + + code += "\n}" + + return code + + +def generate_parameter_destruction_code( + param: Parameter, storage_prefix: str +) -> Optional[str]: + func = param.destroy_func + if func is None: + return None + return generate_destruction_code(f"{storage_prefix}{param.name}", func) + + +def generate_call_arguments_code( + proc: Procedure, storage_prefix: str, instance_arg: Optional[str] = None +) -> str: + names = [] + if instance_arg is not None: + names.append(instance_arg) + names += [f"{storage_prefix}{param.name}" for param in proc.parameters] + if proc.throws: + names.append(f"&{storage_prefix}error") + return ", ".join(names) + + +def generate_return_variable_declaration(method: Method) -> str: + return ( + f"{method.return_value.type.c} retval;" + if method.return_value is not None + else "" + ) + + +def generate_return_assignment_code(method: Method, storage_prefix: str) -> str: + return f"{storage_prefix}retval = " if method.return_value is not None else "" + + +def generate_return_conversion_code(method: Method, storage_prefix: str) -> str: + if method.return_value is not None: + custom = method.customizations + if custom is not None and custom.return_cconversion is not None: + code = f"js_retval = {custom.return_cconversion};" + else: + code = f"js_retval = fdn_{method.return_value.type.nick}_to_value (env, {storage_prefix}retval);" + if method.return_value.nullable: + code = f"if ({storage_prefix}retval != NULL)\n {code}\nelse\n napi_get_null (env, &js_retval);" + else: + code = "napi_get_undefined (env, &js_retval);" + return code + + +def generate_return_destruction_code(method: Method, storage_prefix: str) -> str: + retval = method.return_value + if retval is None: + return "" + func = retval.destroy_func + if func is None: + return "" + return generate_destruction_code(f"{storage_prefix}retval", func) + + +def generate_destruction_code(variable: str, destroy_func: str): + if destroy_func == "g_free": + return f"g_free ({variable});" + return f"g_clear_pointer (&{variable}, {destroy_func});" + + +def generate_signal_getter_code(otype: ObjectType, signal: Signal) -> str: + cprefix = otype.c_symbol_prefix + + custom = signal.customizations + behavior = custom.behavior if custom is not None else "FDN_SIGNAL_ALLOW_EXIT" + + indent = " " * (len(cprefix) + 5 + len(signal.c_name) + 9) + + return f""" +static napi_value +{cprefix}_get_{signal.c_name}_signal (napi_env env, +{indent}napi_callback_info info) +{{ + return fdn_object_get_signal (env, info, "{signal.name}", "_{signal.prefixed_js_name}", {behavior}); +}} +""" + + +def generate_abstract_base_type_declarations(model: Model) -> str: + decls = [] + for itype in model.interface_types_with_abstract_base: + ctype = itype.abstract_base_c_type + cprefix = itype.abstract_base_c_symbol_prefix + module_upper, obj_name_upper = cprefix.upper().split("_", maxsplit=1) + decls.append( + f"""G_DECLARE_FINAL_TYPE ({ctype}, {cprefix}, {module_upper}, {obj_name_upper}, GObject) + +struct _{ctype} +{{ + GObject parent; + + napi_ref wrapper; + gint disposed; +}}; +""" + ) + return "\n".join(decls) + "\n" + + +def generate_abstract_base_define_type_invocations(model: Model) -> str: + invocations = [] + for itype in model.interface_types_with_abstract_base: + cprefix = itype.abstract_base_c_symbol_prefix + invocations.append( + f"""G_DEFINE_TYPE_EXTENDED ({itype.abstract_base_c_type}, + {cprefix}, + G_TYPE_OBJECT, + 0, + G_IMPLEMENT_INTERFACE ({itype.get_type} (), + {cprefix}_iface_init))""" + ) + return "\n\n".join(invocations) + "\n\n" + + +def generate_abstract_base_registration_code(otype: ObjectType) -> str: + otype_cprefix = otype.abstract_base_c_symbol_prefix + + tsfn_initializations = [ + f"""\ +napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "cleanup"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_release_js_resources, &{otype_cprefix}_release_js_resources_tsfn); +napi_unref_threadsafe_function (env, {otype_cprefix}_release_js_resources_tsfn);""" + ] + + for method in otype.methods: + if method.is_property_accessor: + continue + tsfn_initializations.append( + f"""\ +napi_create_threadsafe_function (env, NULL, NULL, fdn_utf8_to_value (env, "{method.prefixed_js_name}"), 0, 1, NULL, NULL, NULL, {otype_cprefix}_{method.name}_begin, &{otype_cprefix}_{method.name}_tsfn); +napi_unref_threadsafe_function (env, {otype_cprefix}_{method.name}_tsfn);""" + ) + + tsfn_initializations_code = "\n\n".join(tsfn_initializations) + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + two_newlines = "\n\n" + + return f""" +static void +{otype_cprefix}_register (napi_env env, +{calculate_indent("_register")}napi_value exports) +{{ + napi_value constructor; + napi_define_class (env, "Abstract{otype.js_name}", NAPI_AUTO_LENGTH, {otype_cprefix}_construct, NULL, 0, NULL, &constructor); + + napi_set_named_property (env, exports, "Abstract{otype.js_name}", constructor);{indent_c_code(tsfn_initializations_code, 1, prologue=two_newlines)} +}} +""" + + +def generate_abstract_base_constructor(otype: ObjectType) -> str: + otype_cprefix = otype.abstract_base_c_symbol_prefix + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + return f""" +static napi_value +{otype_cprefix}_construct (napi_env env, +{calculate_indent("_construct")}napi_callback_info info) +{{ + napi_value jsthis; + {otype.abstract_base_c_type} * handle = NULL; + + if (napi_get_cb_info (env, info, NULL, NULL, &jsthis, NULL) != napi_ok) + goto propagate_error; + + handle = g_object_new ({otype_cprefix}_get_type (), NULL); + + if (!fdn_object_wrap (env, jsthis, G_OBJECT (handle), fdn_object_finalize)) + goto propagate_error; + + napi_create_reference (env, jsthis, 1, &handle->wrapper); + + return jsthis; + +propagate_error: + {{ + g_clear_object (&handle); + return NULL; + }} +}} +""" + + +def generate_abstract_base_gobject_glue(otype: ObjectType) -> str: + otype_cprefix = otype.abstract_base_c_symbol_prefix + ctype = otype.abstract_base_c_type + + vmethod_lines = [] + for m in otype.methods: + vmethod_lines += [ + f"iface->{m.name} = {otype_cprefix}_{m.name};", + f"iface->{m.name}_finish = {otype_cprefix}_{m.name}_finish;", + ] + vmethod_assignments = indent_c_code("\n".join(vmethod_lines), 1) + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(suffix) + 2) + + return f""" +static void +{otype_cprefix}_class_init ({ctype}Class * klass) +{{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = {otype_cprefix}_dispose; +}} + +static void +{otype_cprefix}_iface_init (gpointer g_iface, +{calculate_indent("_iface_init")}gpointer iface_data) +{{ + {otype.type_struct} * iface = g_iface; + +{vmethod_assignments} +}} + +static void +{otype_cprefix}_init ({ctype} * self) +{{ + self->disposed = FALSE; +}} + +static void +{otype_cprefix}_dispose (GObject * object) +{{ + {otype.abstract_base_c_type} * self = {otype.abstract_base_c_cast_macro} (object); + + if (g_atomic_int_compare_and_exchange (&self->disposed, FALSE, TRUE) && !fdn_in_cleanup) + {{ + napi_call_threadsafe_function ({otype_cprefix}_release_js_resources_tsfn, g_object_ref (self), napi_tsfn_blocking); + }} + + G_OBJECT_CLASS ({otype_cprefix}_parent_class)->dispose (object); +}} + +static void +{otype_cprefix}_release_js_resources (napi_env env, +{calculate_indent("_release_js_resources")}napi_value js_cb, +{calculate_indent("_release_js_resources")}void * context, +{calculate_indent("_release_js_resources")}void * data) +{{ + {otype.abstract_base_c_type} * self = data; + + napi_delete_reference (env, self->wrapper); + self->wrapper = NULL; + + g_object_unref (self); +}} +""" + + +def generate_abstract_base_method_code(method: Method) -> str: + otype = method.object_type + operation_type_name = method.abstract_base_operation_type_name + otype_cprefix = otype.abstract_base_c_symbol_prefix + + method_name_pascal = to_pascal_case(method.name) + method_cprefix = f"{otype_cprefix}_{method.name}" + + def calculate_indent(suffix: str) -> str: + return " " * (len(otype_cprefix) + len(method.name) + len(suffix) + 3) + + params = f",\n{calculate_indent('')}".join(method.param_ctypings) + finish_params = f",\n{calculate_indent('_finish')}".join( + method.finish_param_ctypings + ) + + storage_prefix = "operation->" + + param_assignments = generate_abstract_base_input_parameter_assignment_code( + method, storage_prefix + ) + param_conversions = generate_abstract_base_input_parameter_conversions_code( + method, storage_prefix + ) + param_destructions = generate_parameter_destructions_code(method, storage_prefix) + cancellable_name = next( + (p.name for p in method.parameters if p.type.name == "Gio.Cancellable"), "NULL" + ) + + result_declaration = generate_return_variable_declaration(method) + result_conversion, result_destroy = generate_abstract_base_return_conversion_code( + method, "propagate_error" + ) + + finish_error = "error" if method.throws else "NULL" + finish_statement = f"g_task_propagate_pointer (G_TASK (result), {finish_error})" + retval = method.return_value + if retval is not None: + from_pointer_func = retval.type.from_pointer_func + if from_pointer_func is not None: + finish_statement = f"{from_pointer_func} ({finish_statement})" + finish_statement = f"return {finish_statement}" + finish_code = f"{finish_statement};" + + one_newline = "\n" + two_newlines = "\n\n" + + operation_free_function = f"""\ +static void +{method_cprefix}_operation_free ({operation_type_name} * operation) +{{{indent_c_code(param_destructions, 1, prologue=one_newline)} + g_slice_free ({operation_type_name}, operation); +}}""" + + return f""" +static void +{method_cprefix} ({params}) +{{ + {otype.abstract_base_c_type} * self; + {operation_type_name} * operation; + GTask * task; + + self = {otype.abstract_base_c_cast_macro} ({method.cself_name}); + + operation = g_slice_new0 ({operation_type_name}); + operation->self = self;{indent_c_code(param_assignments, 1, prologue=one_newline)} + + task = g_task_new (self, {cancellable_name}, callback, user_data); + g_task_set_task_data (task, operation, (GDestroyNotify) {otype_cprefix}_{method.name}_operation_free); + + napi_call_threadsafe_function ({otype_cprefix}_{method.name}_tsfn, task, napi_tsfn_blocking); +}} + +{operation_free_function} + +static void +{method_cprefix}_begin (napi_env env, +{calculate_indent("_begin")}napi_value js_cb, +{calculate_indent("_begin")}void * context, +{calculate_indent("_begin")}void * data) +{{ + GTask * task = data; + {operation_type_name} * operation; + {otype.abstract_base_c_type} * self; + napi_value wrapper, method, args[{len(method.input_parameters)}], js_retval, then, then_args[2], then_retval; + + operation = g_task_get_task_data (task); + self = operation->self; + + if (napi_get_reference_value (env, self->wrapper, &wrapper) != napi_ok) + goto propagate_error; + + if (napi_get_named_property (env, wrapper, "{method.prefixed_js_name}", &method) != napi_ok) + goto propagate_error;{indent_c_code(param_conversions, 1, prologue=two_newlines)} + + if (napi_call_function (env, wrapper, method, G_N_ELEMENTS (args), args, &js_retval) != napi_ok) + goto propagate_error; + + if (napi_get_named_property (env, js_retval, "then", &then) != napi_ok) + goto propagate_error; + + napi_create_function (env, "on{method_name_pascal}Success", NAPI_AUTO_LENGTH, {otype_cprefix}_{method.name}_on_success, task, &then_args[0]); + napi_create_function (env, "on{method_name_pascal}Failure", NAPI_AUTO_LENGTH, {otype_cprefix}_{method.name}_on_failure, task, &then_args[1]); + + if (napi_call_function (env, js_retval, then, G_N_ELEMENTS (then_args), then_args, &then_retval) != napi_ok) + goto propagate_error; + + return; + +propagate_error: + {{ + napi_value js_error; + GError * error; + + napi_get_and_clear_last_exception (env, &js_error); + fdn_error_from_value (env, js_error, &error); + + g_task_return_error (task, error); + g_object_unref (task); + + return; + }} +}} + +static napi_value +{method_cprefix}_on_success (napi_env env, +{calculate_indent("_on_success")}napi_callback_info info) +{{ + size_t argc = 1; + napi_value js_retval; + GTask * task;{indent_c_code(result_declaration, 1, prologue=one_newline)} + gpointer raw_result; + + if (napi_get_cb_info (env, info, &argc, &js_retval, NULL, (void **) &task) != napi_ok) + goto propagate_error; + if (argc != 1) + goto internal_error; + +{indent_c_code(result_conversion, 1)} + + g_task_return_pointer (task, raw_result, {result_destroy}); + goto beach; + +propagate_error: + {{ + napi_value js_error; + GError * error; + + napi_get_and_clear_last_exception (env, &js_error); + fdn_error_from_value (env, js_error, &error); + + g_task_return_error (task, error); + + goto beach; + }} +internal_error: + {{ + g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_OPERATION, + "Internal error"); + + goto beach; + }} +beach: + {{ + napi_value val; + + g_object_unref (task); + + napi_get_undefined (env, &val); + return val; + }} +}} + +static napi_value +{method_cprefix}_on_failure (napi_env env, +{calculate_indent("_on_failure")}napi_callback_info info) +{{ + size_t argc = 1; + napi_value js_error; + GTask * task; + GError * error; + + if (napi_get_cb_info (env, info, &argc, &js_error, NULL, (void **) &task) != napi_ok) + goto propagate_error; + if (argc != 1) + goto internal_error; + + if (!fdn_error_from_value (env, js_error, &error)) + goto propagate_error; + + g_task_return_error (task, error); + goto beach; + +propagate_error: + {{ + napi_value js_error; + GError * error; + + napi_get_and_clear_last_exception (env, &js_error); + fdn_error_from_value (env, js_error, &error); + + g_task_return_error (task, error); + + goto beach; + }} +internal_error: + {{ + g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_OPERATION, + "Internal error"); + + goto beach; + }} +beach: + {{ + napi_value val; + + g_object_unref (task); + + napi_get_undefined (env, &val); + return val; + }} +}} + +static {method.return_ctyping} +{method_cprefix}_finish ({finish_params}) +{{ + {finish_code} +}} +""" + + +def generate_abstract_base_input_parameter_assignment_code( + proc: Procedure, storage_prefix: str +) -> str: + assigments = [ + generate_abstract_base_parameter_assignment_code(param, i, storage_prefix) + for i, param in enumerate(proc.input_parameters) + ] + return "\n".join(assigments) + + +def generate_abstract_base_input_parameter_conversions_code( + proc: Procedure, storage_prefix: str +) -> str: + conversions = [ + generate_abstract_base_parameter_conversion_code(param, i, storage_prefix) + for i, param in enumerate(proc.input_parameters) + ] + return "\n\n".join(conversions) + + +def generate_abstract_base_parameter_conversion_code( + param: Parameter, index: int, storage_prefix: str +) -> str: + lval = f"{storage_prefix}{param.name}" + + code = f"args[{index}] = fdn_{param.type.nick}_to_value (env, {lval});" + if param.nullable: + code = f"if ({lval} != NULL)\n {code}\nelse\n napi_get_null (env, &args[{index}]);" + + return code + + +def generate_abstract_base_parameter_assignment_code( + param: Parameter, index: int, storage_prefix: str +) -> str: + lval = f"{storage_prefix}{param.name}" + + copy_func = param.copy_func + if copy_func is not None: + if param.nullable and copy_func not in {"g_strdup", "g_strdupv"}: + return ( + f"{lval} = ({param.name} != NULL) ? {copy_func} ({param.name}) : NULL;" + ) + return f"{lval} = {copy_func} ({param.name});" + + return f"{lval} = {param.name};" + + +def generate_abstract_base_return_conversion_code( + method: Method, invalid_label: str +) -> Tuple[str, str]: + retval = method.return_value + if retval is not None: + destroy_func = retval.destroy_func + if destroy_func is None: + destroy_func = "NULL" + + result_conversion = ( + f"{retval.type.to_pointer_func} (retval)" + if retval.type.to_pointer_func is not None + else "retval" + ) + + code = f"""\ +if (!fdn_{retval.type.nick}_from_value (env, js_retval, &retval)) + goto {invalid_label}; + +raw_result = {result_conversion};""" + + if retval.nullable: + code = f"if (!fdn_is_null (js_result))\n{{ {indent_c_code(code, 1)}\n}} else {{\n raw_result = NULL;\n }}" + + else: + code = "raw_result = NULL;" + destroy_func = "NULL" + + return (code, destroy_func) + + +def generate_enum_registration_code(enum: Enumeration) -> str: + cprefix = enum.c_symbol_prefix + + properties = [] + for member in enum.members: + properties.append( + f'{{ "{member.js_name}", NULL, NULL, NULL, NULL, fdn_utf8_to_value (env, "{member.nick}"), napi_enumerable, NULL }}' + ) + + properties_str = ",\n ".join(properties) + + def calculate_indent(suffix: str) -> str: + return " " * (len(cprefix) + len(suffix) + 2) + + return f""" +static void +{cprefix}_register (napi_env env, +{calculate_indent("_register")}napi_value exports) +{{ + napi_value enum_object; + napi_property_descriptor properties[] = {{ + {properties_str} + }}; + + napi_create_object (env, &enum_object); + napi_define_properties (env, enum_object, G_N_ELEMENTS (properties), properties); + + napi_set_named_property (env, exports, "{enum.js_name}", enum_object); +}} +""" + + +def generate_enum_conversion_functions(enum: Enumeration) -> str: + cprefix = enum.c_symbol_prefix + + def calculate_indent(suffix: str) -> str: + return " " * (len(cprefix) + len(suffix) + 2) + + return f""" +static gboolean +{cprefix}_from_value (napi_env env, +{calculate_indent("_from_value")}napi_value value, +{calculate_indent("_from_value")}{enum.c_type} * e) +{{ + return fdn_enum_from_value (env, {enum.get_type} (), value, (gint *) e); +}} + +static napi_value +{cprefix}_to_value (napi_env env, +{calculate_indent("_to_value")}{enum.c_type} e) +{{ + return fdn_enum_to_value (env, {enum.get_type} (), e); +}} +""" + + +def generate_options_conversion_functions(otype: ObjectType) -> str: + cprefix = otype.c_symbol_prefix + + def calculate_indent(suffix: str) -> str: + return " " * (len(cprefix) + len(suffix) + 2) + + selection_code = "" + for method in otype.methods: + if not method.is_select_method: + continue + + plural_noun = method.select_plural_noun + param_type = method.select_element_type + param_from_value = f"fdn_{param_type.nick}_from_value" + + element_destroy_code = "" + destroy_func = param_type.destroy_func + if destroy_func is not None: + element_destroy_code = f"\n\n {destroy_func} (element);" + + selection_code += f""" + + {{ + napi_value js_{plural_noun}; + napi_valuetype value_type; + + if (napi_get_named_property (env, value, "{plural_noun}", &js_{plural_noun}) != napi_ok) + goto propagate_error; + + if (napi_typeof (env, js_{plural_noun}, &value_type) != napi_ok) + goto propagate_error; + + if (value_type != napi_undefined) + {{ + uint32_t length, i; + + if (napi_get_array_length (env, js_{plural_noun}, &length) != napi_ok) + goto propagate_error; + + for (i = 0; i != length; i++) + {{ + napi_value js_element; + {param_type.c.replace('const ', '')} element; + + if (napi_get_element (env, js_{plural_noun}, i, &js_element) != napi_ok) + goto propagate_error; + + if (!{param_from_value} (env, js_element, &element)) + goto propagate_error; + + {method.c_identifier} (opts, element);{element_destroy_code} + }} + }} + }}""" + + cleanup_code = "" + if selection_code: + cleanup_code += """ + +propagate_error: + { + g_object_unref (opts); + return FALSE; + }""" + + return f""" +static gboolean +{cprefix}_from_value (napi_env env, +{calculate_indent("_from_value")}napi_value value, +{calculate_indent("_from_value")}{otype.c_type} ** options) +{{ + {otype.c_type} * opts; + + if (!fdn_options_from_value (env, {otype.get_type} (), value, (gpointer *) &opts)) + return FALSE;{selection_code} + + *options = opts; + return TRUE;{cleanup_code} +}} +""" + + +def generate_list_conversion_functions(otype: ObjectType) -> str: + cprefix = otype.c_symbol_prefix + + size_method = next((method for method in otype.methods if method.name == "size")) + get_method = next((method for method in otype.methods if method.name == "get")) + + element_type = get_method.return_value.type + + def calculate_indent(suffix: str) -> str: + return " " * (len(cprefix) + len(suffix) + 2) + + return f""" +static napi_value +{cprefix}_to_value (napi_env env, +{calculate_indent("_to_value")}{otype.c_type} * list) +{{ + napi_value result; + gint size, i; + + size = {size_method.c_identifier} (list); + napi_create_array_with_length (env, size, &result); + + for (i = 0; i != size; i++) + {{ + {element_type.c} handle = {get_method.c_identifier} (list, i); + napi_set_element (env, result, i, fdn_{element_type.nick}_to_value (env, handle)); + g_object_unref (handle); + }} + + return result; +}} +""" + + +def indent_ts_code(code: str, level: int, prologue: str = "") -> str: + prefix = (level * 4) * " " + return indent_code(code, prefix, prologue) + + +def indent_c_code(code: str, level: int, prologue: str = "") -> str: + prefix = (level * 2) * " " + return indent_code(code, prefix, prologue) + + +def indent_code(code: str, prefix: str, prologue: str = "") -> str: + if not code: + return "" + return prologue + textwrap.indent(code, prefix, lambda line: line.strip() != "") diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py new file mode 100644 index 0000000..d20af5d --- /dev/null +++ b/frida/frida_bindgen/customization.py @@ -0,0 +1,924 @@ +from __future__ import annotations + +from pathlib import Path +from typing import List, Mapping + +from .model import (ConstructorCustomizations, CustomCode, CustomDeclaration, + Customizations, CustomMethod, CustomType, CustomTypeKind, + EnumerationCustomizations, EnumerationMemberCustomizations, + KeepAliveCustomization, MethodCustomizations, + ObjectTypeCustomizations, PropertyCustomizations, + SignalCustomizations, TypeCustomizations) + +ASSETS_DIR = Path(__file__).resolve().parent / "assets" +CUSTOMIZATION_FACADE_EXPORTS = ( + (ASSETS_DIR / "customization_facade.exports") + .read_text(encoding="utf-8") + .strip() + .split("\n") +) +CUSTOMIZATION_FACADE_TS = (ASSETS_DIR / "customization_facade.ts").read_text( + encoding="utf-8" +) +CUSTOMIZATION_HELPERS_IMPORTS = ( + (ASSETS_DIR / "customization_helpers.imports") + .read_text(encoding="utf-8") + .strip() + .split("\n") +) +CUSTOMIZATION_HELPERS_TS = (ASSETS_DIR / "customization_helpers.ts").read_text( + encoding="utf-8" +) + + +def load_customizations() -> Customizations: + custom_types: List[CustomType] = { + "TargetProcess": CustomType(CustomTypeKind.TYPE, "ProcessID | ProcessName"), + "ProcessID": CustomType(CustomTypeKind.TYPE, "number"), + "InjecteeID": CustomType(CustomTypeKind.TYPE, "number"), + "FileDescriptor": CustomType(CustomTypeKind.TYPE, "number"), + "ProcessName": CustomType(CustomTypeKind.TYPE, "string"), + "SystemParameters": CustomType( + CustomTypeKind.INTERFACE, + """ +/** + * Operating System details. + */ +os: { + /** + * ID, e.g.: windows, macos, linux, ios, android, qnx, fedora, ubuntu, etc. + */ + id: string; + + /** + * Human-readable name, e.g. `"macOS"`. + */ + name: string; + + /** + * Human-readable version string, e.g. `"11.2.2"`. + */ + version?: string; + + /** + * Build version, e.g. `"21B91"`. + */ + build?: string; +} + +/** + * Platform, same as `Process.platform` in GumJS. + */ +platform: "windows" | "darwin" | "linux" | "freebsd" | "qnx"; + +/** + * Architecture, same as `Process.arch` in GumJS. + */ +arch: "ia32" | "x64" | "arm" | "arm64" | "mips"; + +/** + * Hardware details. + */ +hardware?: { + /** + * Product type, e.g. `"iPad6,3"`. + */ + product?: string; + + /** + * Hardware platform, e.g. `"t8010"`. + */ + platform?: string; + + /** + * Hardware model, e.g. `"J71bAP"`. + */ + model?: string; +} + +/** + * Level of access. + */ +access: "full" | "jailed"; + +/** + * System name, e.g. `"Ole André’s iPhone"`. + */ +name?: string; + +/** + * iOS UDID (Unique Device ID). + */ +udid?: string; + +/** + * Details about cellular and networking interfaces. + */ +interfaces?: SystemInterface[]; + +/** + * Android API level, e.g.: `30`. + */ +apiLevel?: number; + +[name: string]: any; + """, + ), + "SystemInterface": CustomType( + CustomTypeKind.TYPE, "NetworkInterface | CellularInterface" + ), + "NetworkInterface": CustomType( + CustomTypeKind.INTERFACE, + """ +type: "ethernet" | "wifi" | "bluetooth"; + +/** + * MAC address, e.g.: `"aa:bb:cc:dd:ee:ff"`. + */ +address: string; + """, + ), + "CellularInterface": CustomType( + CustomTypeKind.INTERFACE, + """ +type: "cellular"; + +/** + * Phone number, e.g. `"+47 123 45 678"`. + */ +phoneNumber: string; + """, + ), + "SpawnOptions": CustomType( + CustomTypeKind.INTERFACE, + """ +argv?: string[]; +envp?: { [name: string]: string }; +env?: { [name: string]: string }; +cwd?: string; +stdio?: Stdio; + +[name: string]: any; + """, + ), + "RelayProperties": CustomType( + CustomTypeKind.INTERFACE, + """ +address: string; +username: string; +password: string; +kind: RelayKind; + """, + ), + "Message": CustomType(CustomTypeKind.TYPE, "SendMessage | ErrorMessage"), + "MessageType": CustomType( + CustomTypeKind.ENUM, + """ +Send = "send", +Error = "error" + """, + ), + "SendMessage": CustomType( + CustomTypeKind.INTERFACE, + """ +type: MessageType.Send; +payload: any; + """, + ), + "ErrorMessage": CustomType( + CustomTypeKind.INTERFACE, + """ +type: MessageType.Error; +description: string; +stack?: string; +fileName?: string; +lineNumber?: number; +columnNumber?: number; + """, + ), + "ScriptLogHandler": CustomType( + CustomTypeKind.TYPE, "(level: LogLevel, text: string) => void" + ), + "ScriptExports": CustomType( + CustomTypeKind.INTERFACE, + """ +[name: string]: (...args: any[]) => Promise; + """, + ), + "LogLevel": CustomType( + CustomTypeKind.ENUM, + """ +Info = "info", +Warning = "warning", +Error = "error", + """, + ), + "EnableDebuggerOptions": CustomType( + CustomTypeKind.INTERFACE, + """ +port?: number; + """, + ), + "PortalServiceOptions": CustomType( + CustomTypeKind.INTERFACE, + """ +clusterParams?: EndpointParameters; +controlParams?: EndpointParameters; + """, + ), + "PortalConnectionId": CustomType(CustomTypeKind.TYPE, "number"), + "PortalConnectionTag": CustomType(CustomTypeKind.TYPE, "string"), + "EndpointParametersSubset": CustomType( + CustomTypeKind.INTERFACE, + """ +address?: string; +port?: number; +certificate?: string; +origin?: string; +authentication?: AuthenticationScheme; +assetRoot?: string; + """, + ), + "AuthenticationScheme": CustomType( + CustomTypeKind.TYPE, + "TokenAuthenticationScheme | CallbackAuthenticationScheme", + ), + "TokenAuthenticationScheme": CustomType( + CustomTypeKind.INTERFACE, + """ +scheme: "token"; +token: string; + """, + ), + "CallbackAuthenticationScheme": CustomType( + CustomTypeKind.INTERFACE, + """ +scheme: "callback"; +callback: AuthenticationCallback; + """, + ), + "AuthenticationCallback": CustomType( + CustomTypeKind.TYPE, + "(token: string) => Promise", + ), + "AuthenticatedSessionInfo": CustomType( + CustomTypeKind.INTERFACE, + """ +[key: string]: any; + """, + ), + "SocketAddress": CustomType( + CustomTypeKind.TYPE, + "IPV4SocketAddress | IPV6SocketAddress | AnonymousUnixSocketAddress | PathUnixSocketAddress | AbstractUnixSocketAddress", + ), + "IPV4SocketAddress": CustomType( + CustomTypeKind.INTERFACE, + """ +family: "ipv4"; +address: string; +port: number; + """, + ), + "IPV6SocketAddress": CustomType( + CustomTypeKind.INTERFACE, + """ +family: "ipv6"; +address: string; +port: number; +flowlabel: number; +scopeid: number; + """, + ), + "AnonymousUnixSocketAddress": CustomType( + CustomTypeKind.INTERFACE, + """ +family: "unix:anonymous"; + """, + ), + "PathUnixSocketAddress": CustomType( + CustomTypeKind.INTERFACE, + """ +family: "unix:path"; +path: string; + """, + ), + "AbstractUnixSocketAddress": CustomType( + CustomTypeKind.INTERFACE, + """ +family: "unix:abstract"; +path: Buffer; + """, + ), + "Variant": CustomType( + CustomTypeKind.TYPE, "VariantValue | [type: symbol, value: VariantValue]" + ), + "VariantValue": CustomType( + CustomTypeKind.TYPE, + """ +| boolean +| number +| string +| Buffer +| Variant[] +| VariantDict + """, + ), + "VariantDict": CustomType( + CustomTypeKind.INTERFACE, + """ +[key: string]: Variant; + """, + ), + } + + type_customizations: Mapping[str, TypeCustomizations] = { + "DeviceManager": ObjectTypeCustomizations( + signals={ + "added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "changed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + }, + cleanup="close", + ), + "Device": ObjectTypeCustomizations( + methods={ + "query_system_parameters": MethodCustomizations( + return_typing="Promise", + return_wrapper="as SystemParameters", + ), + "spawn": MethodCustomizations( + param_typings=[ + "programOrArgv: string | string[]", + "opts?: SpawnOptions", + "cancellable?: Cancellable | null", + ], + return_typing="Promise", + custom_logic=""" +const options: RawSpawnOptions = {}; + +let program: string; +let argv; +if (typeof programOrArgv === "string") { + program = programOrArgv; + argv = opts?.argv; +} else { + program = programOrArgv[0]; + argv = programOrArgv; + if (argv.length === 1) { + argv = undefined; + } +} +if (argv !== undefined) { + options.argv = argv; +} + +if (opts !== undefined) { + const envp = opts.envp; + if (envp !== undefined) { + options.envp = objectToStrv(envp); + } + + const env = opts.env; + if (env !== undefined) { + options.env = objectToStrv(env); + } + + const cwd = opts.cwd; + if (cwd !== undefined) { + options.cwd = cwd; + } + + options.aux = Object.fromEntries(Object.entries(opts).filter(([k, v]) => !STANDARD_SPAWN_OPTION_NAMES.has(k))); +} + """, + ), + "input": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "data: Buffer", + "cancellable?: Cancellable | null", + ], + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "resume": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "cancellable?: Cancellable | null", + ], + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "kill": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "cancellable?: Cancellable | null", + ], + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "attach": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "options?: SessionOptions", + "cancellable?: Cancellable | null", + ], + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "inject_library_file": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "path: string", + "entrypoint: string", + "data: string", + "cancellable?: Cancellable | null", + ], + return_typing="Promise", + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "inject_library_blob": MethodCustomizations( + param_typings=[ + "target: TargetProcess", + "blob: Buffer", + "entrypoint: string", + "data: string", + "cancellable?: Cancellable | null", + ], + return_typing="Promise", + custom_logic="const pid = await this.#getPid(target, cancellable);", + ), + "open_channel": MethodCustomizations( + return_typing='Promise', + return_wrapper="new IOStreamAdapter", + ), + }, + properties={ + "dtype": PropertyCustomizations( + js_name="type", + ), + }, + signals={ + "spawn-added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "spawn-removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "child-added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "child-removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "process-crashed": SignalCustomizations( + behavior="FDN_SIGNAL_KEEP_ALIVE" + ), + "output": SignalCustomizations( + behavior="FDN_SIGNAL_KEEP_ALIVE", + transform={ + 0: ("pid: ProcessID", None), + 1: ("fd: FileDescriptor", None), + }, + ), + "uninjected": SignalCustomizations( + behavior="FDN_SIGNAL_KEEP_ALIVE", + transform={ + 0: ("id: InjecteeID", None), + }, + ), + }, + custom_code=CustomCode( + methods=[ + CustomMethod( + typing="getProcess(name: string, options?: ProcessMatchOptions, cancellable?: Cancellable | null): Promise", + code=""" +async getProcess(name: string, options: ProcessMatchOptions = {}, cancellable?: Cancellable | null): Promise { + const { + scope = Scope.Minimal, + } = options; + const processes = await this.enumerateProcesses({ scope }, cancellable); + const mm = new Minimatch(name.toLowerCase()); + const matching = processes.filter(process => mm.match(process.name.toLowerCase())); + if (matching.length === 1) { + return matching[0]; + } else if (matching.length > 1) { + throw new Error("Ambiguous name; it matches: " + matching.map(process => `${process.name} (pid: ${process.pid})`).join(", ")); + } else { + throw new Error("Process not found"); + } +} +""", + ), + CustomMethod( + typing=None, + code=""" +async #getPid(target: TargetProcess, cancellable?: Cancellable | null): Promise { + if (typeof target === "number") { + return target; + } + + const process = await this.getProcess(target, {}, cancellable); + return process.pid; +} +""", + ), + ], + ), + ), + "SpawnOptions": ObjectTypeCustomizations(js_name="RawSpawnOptions"), + "Bus": ObjectTypeCustomizations( + methods={ + "post": MethodCustomizations( + param_typings=[ + "message: any", + "data?: Buffer | null", + ], + custom_logic="const json = JSON.stringify(message);", + ), + }, + signals={ + "detached": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"), + "message": SignalCustomizations( + behavior="FDN_SIGNAL_KEEP_ALIVE", + transform={ + 0: ("message: any", "JSON.parse"), + }, + ), + }, + ), + "Service": ObjectTypeCustomizations( + keep_alive=KeepAliveCustomization( + is_destroyed_function="is_closed", destroy_signal_name="close" + ), + ), + "Relay": ObjectTypeCustomizations( + constructor=ConstructorCustomizations( + param_typings=[ + "properties: RelayProperties", + ], + custom_logic="const { address, username, password, kind } = properties;", + ), + ), + "RelayKind": EnumerationCustomizations( + members={ + "turn_udp": EnumerationMemberCustomizations(js_name="TurnUDP"), + "turn_tcp": EnumerationMemberCustomizations(js_name="TurnTCP"), + "turn_tls": EnumerationMemberCustomizations(js_name="TurnTLS"), + }, + ), + "Script": ObjectTypeCustomizations( + methods={ + "is_destroyed": MethodCustomizations(hide=True), + "post": MethodCustomizations( + param_typings=[ + "message: any", + "data?: Buffer | null", + ], + custom_logic="const json = JSON.stringify(message);", + ), + "enable_debugger": MethodCustomizations( + param_typings=[ + "options?: EnableDebuggerOptions", + "cancellable?: Cancellable | null", + ], + custom_logic="const port = options?.port ?? 0;", + ), + }, + signals={ + "message": SignalCustomizations( + transform={ + 0: ("message: Message", "JSON.parse"), + }, + intercept="this.#services.handleMessageIntercept", + ), + }, + custom_code=CustomCode( + declarations=[ + CustomDeclaration( + typing=None, code="#services = new ScriptServices(this);" + ), + CustomDeclaration( + typing="logHandler: ScriptLogHandler", + code="logHandler: ScriptLogHandler = log;", + ), + ], + methods=[ + CustomMethod( + typing="readonly isDestroyed: boolean", + code=""" +get isDestroyed(): boolean { + return this._isDestroyed(); +} +""", + ), + CustomMethod( + typing="readonly exports: ScriptExports", + code=""" +get exports(): ScriptExports { + return this.#services.exportsProxy; +} +""", + ), + CustomMethod( + typing="readonly defaultLogHandler: ScriptLogHandler", + code=""" +get defaultLogHandler(): ScriptLogHandler { + return log; +} +""", + ), + ], + ), + keep_alive=KeepAliveCustomization( + is_destroyed_function="is_destroyed", destroy_signal_name="destroyed" + ), + ), + "ScriptRuntime": EnumerationCustomizations( + members={ + "qjs": EnumerationMemberCustomizations(js_name="QJS"), + }, + ), + "ControlService": ObjectTypeCustomizations( + methods={ + "start": MethodCustomizations(ref_keep_alive=True), + "stop": MethodCustomizations(unref_keep_alive=True), + "get_endpoint_params": MethodCustomizations(drop=True), + }, + properties={ + "endpoint-params": PropertyCustomizations(drop=True), + }, + ), + "PortalService": ObjectTypeCustomizations( + constructor=ConstructorCustomizations( + param_typings=[ + "options?: PortalServiceOptions", + ], + custom_logic=""" +const clusterParams = options?.clusterParams ?? new EndpointParameters(); +const controlParams = options?.controlParams ?? null; +""", + ), + methods={ + "start": MethodCustomizations(ref_keep_alive=True), + "stop": MethodCustomizations(unref_keep_alive=True), + "kick": MethodCustomizations( + param_typings=[ + "connectionId: PortalConnectionId", + ], + ), + "post": MethodCustomizations( + param_typings=[ + "connectionId: PortalConnectionId", + "message: any", + "data?: Buffer | null", + ], + custom_logic="const json = JSON.stringify(message);", + ), + "narrowcast": MethodCustomizations( + param_typings=[ + "tag: string", + "message: any", + "data?: Buffer | null", + ], + custom_logic="const json = JSON.stringify(message);", + ), + "broadcast": MethodCustomizations( + param_typings=[ + "message: any", + "data?: Buffer | null", + ], + custom_logic="const json = JSON.stringify(message);", + ), + "enumerate_tags": MethodCustomizations( + param_typings=[ + "connectionId: PortalConnectionId", + ], + ), + "tag": MethodCustomizations( + param_typings=[ + "connectionId: PortalConnectionId", + "tag: string", + ], + ), + "untag": MethodCustomizations( + param_typings=[ + "connectionId: PortalConnectionId", + "tag: string", + ], + ), + }, + signals={ + "node-connected": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("remoteAddress: SocketAddress", "parseSocketAddress"), + }, + ), + "node-joined": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + }, + ), + "node-left": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + }, + ), + "node-disconnected": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("remoteAddress: SocketAddress", "parseSocketAddress"), + }, + ), + "controller-connected": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("remoteAddress: SocketAddress", "parseSocketAddress"), + }, + ), + "controller-disconnected": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("remoteAddress: SocketAddress", "parseSocketAddress"), + }, + ), + "authenticated": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("sessionInfo: AuthenticatedSessionInfo", "JSON.parse"), + }, + ), + "subscribe": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + }, + ), + "message": SignalCustomizations( + transform={ + 0: ("connectionId: PortalConnectionId", None), + 1: ("message: any", "JSON.parse"), + }, + ), + }, + ), + "EndpointParameters": ObjectTypeCustomizations( + constructor=ConstructorCustomizations( + param_typings=[ + "params?: EndpointParametersSubset", + ], + custom_logic=""" +const address = params?.address ?? null; +const port = params?.port ?? 0; +const certificate = params?.certificate ?? null; +const origin = params?.origin ?? null; + +let authService: AuthenticationService | null = null; +const auth = params?.authentication; +if (auth !== undefined) { + if (auth.scheme === "token") { + authService = new StaticAuthenticationService(auth.token); + } else { + authService = new CallbackAuthenticationService(auth.callback); + } +} + +const assetRoot = params?.assetRoot ?? null; +""", + ), + ), + "Injector": ObjectTypeCustomizations(drop=True), + "RpcClient": ObjectTypeCustomizations(drop=True), + "RpcPeer": ObjectTypeCustomizations(drop=True), + "Cancellable": ObjectTypeCustomizations( + methods={ + "is_cancelled": MethodCustomizations(hide=True), + "set_error_if_cancelled": MethodCustomizations( + js_name="throwIfCancelled", + return_typing="void", + ), + "make_pollfd": MethodCustomizations(drop=True), + "release_fd": MethodCustomizations(drop=True), + "source_new": MethodCustomizations(drop=True), + }, + custom_code=CustomCode( + methods=[ + CustomMethod( + typing="readonly isCancelled: boolean", + code=""" +get isCancelled(): boolean { + return this._isCancelled(); +} +""", + ), + ], + ), + ), + "IOStream": ObjectTypeCustomizations( + methods={ + "close": MethodCustomizations(drop=True), + "close_async": MethodCustomizations(js_name="close"), + "splice_async": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "InputStream": ObjectTypeCustomizations( + methods={ + "close": MethodCustomizations(drop=True), + "close_async": MethodCustomizations(js_name="close"), + "read": MethodCustomizations(drop=True), + "read_async": MethodCustomizations(drop=True), + "read_all": MethodCustomizations(drop=True), + "read_all_async": MethodCustomizations(drop=True), + "read_bytes": MethodCustomizations(drop=True), + "read_bytes_async": MethodCustomizations(js_name="read"), + "skip": MethodCustomizations(drop=True), + "skip_async": MethodCustomizations(js_name="skip"), + "is_closed": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "OutputStream": ObjectTypeCustomizations( + methods={ + "close": MethodCustomizations(drop=True), + "close_async": MethodCustomizations(js_name="close"), + "flush": MethodCustomizations(drop=True), + "flush_async": MethodCustomizations(js_name="flush"), + "write": MethodCustomizations(drop=True), + "write_async": MethodCustomizations(drop=True), + "write_all": MethodCustomizations(drop=True), + "write_all_async": MethodCustomizations(drop=True), + "write_bytes": MethodCustomizations(drop=True), + "write_bytes_async": MethodCustomizations(js_name="write"), + "writev": MethodCustomizations(drop=True), + "writev_async": MethodCustomizations(drop=True), + "writev_all": MethodCustomizations(drop=True), + "writev_all_async": MethodCustomizations(drop=True), + "splice": MethodCustomizations(drop=True), + "splice_async": MethodCustomizations(drop=True), + "is_closing": MethodCustomizations(drop=True), + "is_closed": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "UnixSocketAddress": ObjectTypeCustomizations( + methods={ + "get_path": MethodCustomizations( + return_typing="Buffer", + return_cconversion="fdn_buffer_to_value (env, (const guint8 *) retval, g_unix_socket_address_get_path_len (handle))", + ), + "get_path_len": MethodCustomizations(drop=True), + "get_is_abstract": MethodCustomizations(drop=True), + }, + properties={ + "path": PropertyCustomizations(typing="path: Buffer"), + "abstract": PropertyCustomizations(drop=True), + "path-as-array": PropertyCustomizations(drop=True), + }, + ), + "SocketAddress": ObjectTypeCustomizations( + js_name="BaseSocketAddress", + constructor=ConstructorCustomizations(drop=True), + methods={ + "to_native": MethodCustomizations(drop=True), + }, + ), + "SocketConnectable": ObjectTypeCustomizations(drop_abstract_base=True), + "InetAddress": ObjectTypeCustomizations( + properties={ + "bytes": PropertyCustomizations(drop=True), + }, + ), + "Object": ObjectTypeCustomizations( + js_name="BaseObject", + methods={ + "is_floating": MethodCustomizations(drop=True), + "ref": MethodCustomizations(drop=True), + "ref_sink": MethodCustomizations(drop=True), + "unref": MethodCustomizations(drop=True), + "getv": MethodCustomizations(drop=True), + "get_property": MethodCustomizations(drop=True), + "set_property": MethodCustomizations(drop=True), + "notify_by_pspec": MethodCustomizations(drop=True), + "freeze_notify": MethodCustomizations(drop=True), + "thaw_notify": MethodCustomizations(drop=True), + "bind_property": MethodCustomizations(drop=True), + "bind_property_full": MethodCustomizations(drop=True), + "bind_property_with_closures": MethodCustomizations(drop=True), + "force_floating": MethodCustomizations(drop=True), + "get_data": MethodCustomizations(drop=True), + "get_qdata": MethodCustomizations(drop=True), + "run_dispose": MethodCustomizations(drop=True), + "set_data": MethodCustomizations(drop=True), + "steal_data": MethodCustomizations(drop=True), + "steal_qdata": MethodCustomizations(drop=True), + "watch_closure": MethodCustomizations(drop=True), + }, + signals={ + "notify": SignalCustomizations(drop=True), + }, + ), + } + + return Customizations( + custom_types, + type_customizations, + CUSTOMIZATION_FACADE_EXPORTS, + CUSTOMIZATION_FACADE_TS, + CUSTOMIZATION_HELPERS_IMPORTS, + CUSTOMIZATION_HELPERS_TS, + ) diff --git a/frida/frida_bindgen/loader.py b/frida/frida_bindgen/loader.py new file mode 100644 index 0000000..5697a51 --- /dev/null +++ b/frida/frida_bindgen/loader.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from collections import OrderedDict +from pathlib import Path + +from .model import Customizations, Model, parse_gir + +INCLUDED_GIO_OBJECT_TYPES = [ + "Cancellable", + "IOStream", + "InputStream", + "OutputStream", + "InetSocketAddress", + "InetAddress", + "UnixSocketAddress", + "SocketAddress", + "SocketAddressEnumerator", + "SocketConnectable", +] +INCLUDED_GIO_ENUMERATIONS = [ + "FileMonitorEvent", + "SocketFamily", + "UnixSocketAddressType", +] + + +def compute_model( + frida_gir: Path, + glib_gir: Path, + gobject_gir: Path, + gio_gir: Path, + customizations: Customizations, +) -> Model: + glib = parse_gir(glib_gir, []) + gobject = parse_gir(gobject_gir, [glib]) + gio = parse_gir(gio_gir, [glib, gobject]) + frida = parse_gir(frida_gir, [glib, gobject, gio]) + + object_types = OrderedDict(frida.object_types) + object_types["Object"] = gobject.object_types["Object"] + for t in INCLUDED_GIO_OBJECT_TYPES: + object_types[t] = gio.object_types[t] + + enumerations = OrderedDict(frida.enumerations) + for t in INCLUDED_GIO_ENUMERATIONS: + enumerations[t] = gio.enumerations[t] + + model = Model( + frida.namespace, + object_types, + enumerations, + customizations, + ) + + for t in object_types.values(): + t.model = model + for t in enumerations.values(): + t.model = model + + return model diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py new file mode 100644 index 0000000..fdb0c60 --- /dev/null +++ b/frida/frida_bindgen/model.py @@ -0,0 +1,1357 @@ +from __future__ import annotations + +import xml.etree.ElementTree as ET +from collections import OrderedDict, defaultdict +from dataclasses import dataclass, field +from enum import Enum +from functools import cached_property +from typing import (Callable, Iterator, List, Mapping, Optional, Sequence, + Tuple, Union) + +CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0" +C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0" +GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0" +GIR_NAMESPACES = {"": CORE_NAMESPACE, "glib": GLIB_NAMESPACE} + +CORE_TAG_PREFIX = f"{{{CORE_NAMESPACE}}}" + +NUMERIC_GIR_TYPES = { + "gsize", + "gssize", + "gint", + "guint", + "glong", + "gulong", + "gint8", + "gint16", + "gint32", + "gint64", + "guint8", + "guint16", + "guint32", + "guint64", + "GType", + "GQuark", +} + +PRIMITIVE_GIR_TYPES = NUMERIC_GIR_TYPES | { + "gpointer", + "gboolean", + "gchar", + "utf8", + "utf8[]", +} + +ResolveTypeCallback = Callable[[str], Tuple[str, ET.Element]] + + +@dataclass +class Customizations: + custom_types: Mapping[str, CustomType] = field(default_factory=OrderedDict) + type_customizations: Mapping[str, TypeCustomizations] = field( + default_factory=OrderedDict + ) + facade_exports: List[str] = field(default_factory=list) + facade_code: str = "" + helper_imports: List[str] = field(default_factory=list) + helper_code: str = "" + + +@dataclass +class Model: + namespace: Namespace + _object_types: OrderedDict[str, ObjectType] + enumerations: OrderedDict[str, Enumeration] + customizations: Customizations = field(default_factory=Customizations) + + @cached_property + def object_types(self) -> OrderedDict[str, ObjectType]: + result = OrderedDict() + type_customizations = self.customizations.type_customizations + for k, v in self._object_types.items(): + custom = type_customizations.get(k) + if custom is None or not custom.drop: + result[k] = v + return result + + @cached_property + def public_types(self) -> OrderedDict[str, Union[ObjectType, Enumeration]]: + return OrderedDict( + [(k, v) for k, v in self.object_types.items() if v.is_public] + + list(self.enumerations.items()) + ) + + @cached_property + def interface_types_with_abstract_base(self) -> List[InterfaceObjectType]: + return [ + t + for t in self.object_types.values() + if isinstance(t, InterfaceObjectType) and t.has_abstract_base + ] + + def resolve_object_type(self, name: str) -> ObjectType: + bare_name = name.split(".", maxsplit=1)[-1] + return self.object_types[bare_name] + + def resolve_js_type(self, t: Type) -> str: + js = js_type_from_gir(t.name) + otype = self.object_types.get(js) + if otype is not None: + return otype.js_name + return js + + +@dataclass +class Namespace: + name: str + identifier_prefixes: str + element: ET.Element + + @cached_property + def type_elements(self) -> Mapping[str, ET.Element]: + result = {} + for toplevel in self.element.findall("./*[@name]", GIR_NAMESPACES): + name = toplevel.get("name") + result[name] = toplevel + for callback in toplevel.findall("./callback", GIR_NAMESPACES): + result[name + callback.get("name")] = callback + return result + + +@dataclass +class ObjectType: + name: str + c_type: str + get_type: str + type_struct: str + _parent: Optional[str] + _constructors: List[ET.Element] + _methods: List[ET.Element] + _properties: List[ET.Element] + _signals: List[ET.Element] + resolve_type: ResolveTypeCallback + + model: Optional[Model] + + @cached_property + def js_name(self) -> str: + custom = self.customizations + if custom is not None and custom.js_name is not None: + return custom.js_name + return self.name + + @cached_property + def prefixed_js_name(self) -> str: + return f"_{self.js_name}" if self.needs_wrapper else self.js_name + + @cached_property + def abstract_base_c_type(self) -> str: + return f"FdnAbstract{self.name}" + + @cached_property + def parent(self) -> ObjectType: + if self._parent is None: + return None + return self.model.resolve_object_type(self._parent) + + @property + def is_public(self) -> bool: + return not self.is_frida_list + + @cached_property + def is_frida_options(self) -> bool: + return self.c_type.startswith("Frida") and self.c_type.endswith("Options") + + @cached_property + def is_frida_list(self) -> bool: + return self.c_type.startswith("Frida") and self.c_type.endswith("List") + + @cached_property + def needs_wrapper(self) -> bool: + custom = self.customizations + if custom is None: + return False + if custom.custom_code is not None: + return True + ctor = self.constructors[0] if self.constructors else None + if ctor is not None and ctor.needs_wrapper: + return True + return self.wrapped_methods or self.wrapped_signals + + @cached_property + def customizations(self) -> Optional[ObjectTypeCustomizations]: + return self.model.customizations.type_customizations.get(self.name) + + @cached_property + def c_symbol_prefix(self) -> str: + return f"fdn_{to_snake_case(self.name)}" + + @cached_property + def abstract_base_c_symbol_prefix(self) -> str: + return f"fdn_abstract_{to_snake_case(self.name)}" + + @cached_property + def c_cast_macro(self) -> str: + return to_macro_case(self.c_type) + + @cached_property + def abstract_base_c_cast_macro(self) -> str: + return to_macro_case(self.abstract_base_c_type) + + @cached_property + def constructors(self) -> List[Constructor]: + constructors = [] + custom = self.customizations + for element in self._constructors: + if element.get("introspectable") == "0" or element.get("deprecated") == "1": + continue + + name = element.get("name") + + if custom is not None: + ccust = custom.constructor + if ccust is not None and ccust.drop: + continue + + ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) = extract_callable_details(element, element, self, self.resolve_type) + if has_closure_param or finish_c_identifier is not None: + continue + + constructors.append( + Constructor( + name, c_identifier, finish_c_identifier, param_list, throws, self + ) + ) + return constructors + + @cached_property + def methods(self) -> List[Method]: + methods = [] + c_prop_names = {prop.c_name for prop in self.properties} + custom = self.customizations + for element in self._methods: + name = element.get("name") + + if ( + element.get("introspectable") == "0" + or name.startswith("_") + or name.endswith("_sync") + or name.endswith("_finish") + ): + continue + + if custom is not None: + mcust = custom.methods.get(name, None) + if mcust is not None and mcust.drop: + continue + + finish_func = element.get(f"{{{GLIB_NAMESPACE}}}finish-func") + if finish_func is None: + finish_func = f"{name}_finish" + result_element = next( + (m for m in self._methods if m.get("name") == finish_func), element + ) + + ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) = extract_callable_details( + element, result_element, self, self.resolve_type + ) + if has_closure_param: + continue + + retval_element = result_element.find(".//return-value", GIR_NAMESPACES) + rettype = extract_type_from_entity(retval_element, self.resolve_type) + if rettype is not None: + if rettype.is_frida_options: + continue + + nullable = retval_element.get("nullable") == "1" + + ownership_val = retval_element.get("transfer-ownership") + transfer_ownership = ( + TransferOwnership[ownership_val] + if ownership_val is not None + else TransferOwnership.none + ) + + retval = ReturnValue(rettype, nullable, transfer_ownership, self) + else: + retval = None + + if element.get(f"{{{GLIB_NAMESPACE}}}get-property") is not None: + is_property_accessor = True + else: + tokens = name.split("_", maxsplit=1) + is_property_accessor = ( + len(tokens) == 2 + and tokens[0] in {"get", "set"} + and tokens[1] in c_prop_names + ) + + methods.append( + Method( + name, + c_identifier, + finish_c_identifier, + param_list, + throws, + retval, + is_property_accessor, + self, + ) + ) + return methods + + @cached_property + def wrapped_methods(self) -> List[Method]: + return [m for m in self.methods if m.needs_wrapper] + + @cached_property + def properties(self) -> List[Property]: + properties = [] + custom = self.customizations + for element in self._properties: + name = element.get("name") + + if custom is not None: + pcust = custom.properties.get(name, None) + if pcust is not None and pcust.drop: + continue + + c_name = name.replace("-", "_") + type = extract_type_from_entity(element, self.resolve_type) + if type.is_frida_options: + continue + writable = element.get("writable") == "1" + construct_only = element.get("construct-only") == "1" + + getter = element.get("getter") + if getter is None: + getter = f"get_{c_name}" + + setter = element.get("setter") + if setter is None and writable and not construct_only: + setter = f"set_{c_name}" + + properties.append( + Property( + name, + c_name, + type, + writable, + construct_only, + getter, + setter, + self, + ) + ) + return properties + + @cached_property + def signals(self) -> List[Signal]: + signals = [] + custom = self.customizations + for element in self._signals: + name = element.get("name") + + if custom is not None: + scust = custom.signals.get(name, None) + if scust is not None and scust.drop: + continue + + c_name = name.replace("-", "_") + param_list = extract_parameters( + element.findall("./parameters/parameter", GIR_NAMESPACES), + nullable_implies_optional=False, + object_type=self, + resolve_type=self.resolve_type, + ) + signals.append(Signal(name, c_name, param_list, self)) + return signals + + @cached_property + def wrapped_signals(self) -> List[Signal]: + return [s for s in self.signals if s.needs_wrapper] + + +@dataclass +class ClassObjectType(ObjectType): + _implements: List[str] + + @cached_property + def implements(self) -> List[InterfaceObjectType]: + return [self.model.resolve_object_type(i) for i in self._implements] + + +@dataclass +class InterfaceObjectType(ObjectType): + @cached_property + def has_abstract_base(self) -> bool: + custom = self.customizations + if custom is None: + return True + return not custom.drop_abstract_base + + +@dataclass +class Procedure: + name: str + c_identifier: str + finish_c_identifier: Optional[str] + parameters: List[Parameter] + throws: bool + + @property + def is_async(self) -> bool: + return self.finish_c_identifier is not None + + @cached_property + def input_parameters(self) -> List[Parameter]: + return [p for p in self.parameters if p.direction != Direction.OUT] + + +@dataclass +class Constructor(Procedure): + object_type: ObjectType + + @cached_property + def param_typings(self) -> List[str]: + custom = self.customizations + if custom is not None and custom.param_typings is not None: + return custom.param_typings + return [param.typing for param in self.parameters] + + @property + def needs_wrapper(self) -> bool: + custom = self.customizations + if custom is None: + return False + return custom.custom_logic is not None + + @cached_property + def customizations(self) -> Optional[ConstructorCustomizations]: + custom = self.object_type.customizations + if custom is None: + return None + return custom.constructor + + +@dataclass +class Method(Procedure): + return_value: Optional[ReturnValue] + is_property_accessor: bool + + object_type: ObjectType + + @cached_property + def js_name(self) -> str: + custom = self.customizations + if custom is not None and custom.js_name is not None: + return custom.js_name + return to_camel_case(self.name) + + @cached_property + def prefixed_js_name(self) -> str: + custom = self.customizations + if self.needs_wrapper or (custom is not None and custom.hide): + return f"_{self.js_name}" + return self.js_name + + @cached_property + def cself_name(self) -> str: + return to_snake_case(self.object_type.name).split("_")[-1] + + @cached_property + def param_ctypings(self) -> List[str]: + result = [f"{self.object_type.c_type} * {self.cself_name}"] + result += [param.ctyping for param in self.parameters] + if self.is_async: + result += ["GAsyncReadyCallback callback", "gpointer user_data"] + return result + + @cached_property + def finish_param_ctypings(self) -> List[str]: + result = [ + f"{self.object_type.c_type} * {self.cself_name}", + "GAsyncResult * result", + ] + if self.throws: + result.append("GError ** error") + return result + + @cached_property + def param_typings(self) -> List[str]: + custom = self.customizations + if custom is not None and custom.param_typings is not None: + return custom.param_typings + return self.prefixed_param_typings + + @cached_property + def prefixed_param_typings(self) -> List[str]: + return [param.typing for param in self.input_parameters] + + @cached_property + def return_ctyping(self) -> str: + retval = self.return_value + return retval.ctyping if retval is not None else "void" + + @cached_property + def return_typing(self) -> str: + custom = self.customizations + if custom is not None and custom.return_typing is not None: + return custom.return_typing + return self.prefixed_return_typing + + @cached_property + def prefixed_return_typing(self) -> str: + retval = self.return_value + typing = retval.typing if retval is not None else "void" + return f"Promise<{typing}>" if self.is_async else typing + + @property + def needs_wrapper(self) -> bool: + custom = self.customizations + if custom is None: + return False + return custom.custom_logic is not None or custom.return_wrapper is not None + + @cached_property + def customizations(self) -> Optional[MethodCustomizations]: + custom = self.object_type.customizations + if custom is None: + return None + return custom.methods.get(self.name) + + @cached_property + def operation_type_name(self) -> str: + return f"Fdn{self.object_type.name}{to_pascal_case(self.name)}Operation" + + @cached_property + def abstract_base_operation_type_name(self) -> str: + return f"FdnAbstract{self.object_type.name}{to_pascal_case(self.name)}Operation" + + @cached_property + def is_select_method(self) -> bool: + return self.name.startswith("select_") or self.name.startswith("add_") + + @cached_property + def select_noun(self) -> str: + assert ( + self.is_select_method + ), "select_noun can only be called on selector methods" + return self.name.split("_", maxsplit=1)[1] + + @cached_property + def select_plural_noun(self) -> str: + return f"{self.select_noun}s" + + @cached_property + def select_element_type(self) -> Type: + assert ( + self.is_select_method + ), "select_element_type can only be called on selector methods" + return self.parameters[0].type + + +@dataclass +class Property: + name: str + c_name: str + type: Type + writable: bool + construct_only: bool + getter: Optional[str] + setter: Optional[str] + + object_type: ObjectType + + @cached_property + def js_name(self) -> str: + custom = self.customizations + if custom is not None and custom.js_name is not None: + return custom.js_name + return to_camel_case(self.c_name) + + @cached_property + def typing(self) -> str: + custom = self.customizations + if custom is not None and custom.typing is not None: + return custom.typing + readonly = "readonly " if not self.writable else "" + optional_str = "?" if self.object_type.is_frida_options else "" + return f"{readonly}{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}" + + @cached_property + def customizations(self) -> Optional[PropertyCustomizations]: + custom = self.object_type.customizations + if custom is None: + return None + return custom.properties.get(self.name) + + +@dataclass +class Signal: + name: str + c_name: str + parameters: List[Parameter] + + object_type: ObjectType + + @cached_property + def js_name(self) -> str: + return to_camel_case(self.c_name) + + @cached_property + def prefixed_js_name(self) -> str: + return f"_{self.js_name}" if self.needs_wrapper else self.js_name + + @cached_property + def handler_type_name(self) -> str: + # XXX: Special-cases to avoid breaking API: + class_name = self.object_type.name + if class_name == "DeviceManager": + prefix = "Device" + elif class_name == "Device": + prefix = "Device" if self.name == "lost" else "" + elif class_name == "PortalService": + prefix = "Portal" + elif class_name == "Cancellable": + prefix = "" + else: + prefix = class_name + return f"{prefix}{to_pascal_case(self.c_name)}Handler" + + @cached_property + def prefixed_handler_type_name(self) -> str: + return ( + f"_{self.handler_type_name}" + if self.needs_wrapper + else self.handler_type_name + ) + + @cached_property + def typing(self) -> str: + params = ", ".join([p.typing for p in self.parameters]) + return f"({params}) => void" + + @property + def needs_wrapper(self) -> bool: + custom = self.customizations + if custom is None: + return False + return custom.transform is not None or custom.intercept is not None + + @cached_property + def customizations(self) -> Optional[SignalCustomizations]: + custom = self.object_type.customizations + if custom is None: + return None + return custom.signals.get(self.name) + + +TransferOwnership = Enum("TransferOwnership", ["none", "full", "container"]) + + +@dataclass +class Parameter: + name: str + type: Type + optional: bool + nullable: bool + transfer_ownership: TransferOwnership + direction: Direction + + object_type: ObjectType + + @cached_property + def js_name(self) -> str: + return to_camel_case(self.name) + + @cached_property + def ctyping(self) -> str: + return f"{self.type.c} {self.name}" + + @cached_property + def typing(self) -> str: + optional_str = "?" if self.optional else "" + t = f"{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}" + if self.nullable and not self.type.is_frida_options: + t += " | null" + return t + + @cached_property + def copy_func(self) -> Optional[str]: + return self.type.copy_func + + @cached_property + def destroy_func(self) -> Optional[str]: + return self.type.destroy_func + + +@dataclass +class ReturnValue: + type: Type + nullable: bool + transfer_ownership: TransferOwnership + + object_type: ObjectType + + @cached_property + def ctyping(self) -> str: + return self.type.c + + @cached_property + def typing(self) -> str: + t = self.object_type.model.resolve_js_type(self.type) + if self.nullable: + t += " | null" + return t + + @cached_property + def destroy_func(self) -> Optional[str]: + if self.transfer_ownership == TransferOwnership.none: + return None + return self.type.destroy_func + + +@dataclass +class Type: + name: str + nick: str + c: str + default_value: Optional[str] + copy_func: Optional[str] + destroy_func: Optional[str] + + @cached_property + def from_pointer_func(self) -> Optional[str]: + if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}: + return "GPOINTER_TO_SIZE" + if self.name in {"gint", "gint8", "gint16", "gint32"}: + return "GPOINTER_TO_INT" + if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}: + return "GPOINTER_TO_UINT" + return None + + @cached_property + def to_pointer_func(self) -> Optional[str]: + if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}: + return "GSIZE_TO_POINTER" + if self.name in {"gint", "gint8", "gint16", "gint32"}: + return "GINT_TO_POINTER" + if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}: + return "GUINT_TO_POINTER" + return None + + @cached_property + def is_frida_options(self) -> bool: + return self.c.startswith("Frida") and self.c.endswith("Options *") + + +class Direction(Enum): + IN = "in" + OUT = "out" + INOUT = "inout" + + +@dataclass +class Enumeration: + name: str + c_type: str + get_type: str + _members: List[ET.Element] + + model: Optional[Model] + + @property + def js_name(self) -> str: + return self.name + + @property + def prefixed_js_name(self) -> str: + return self.name + + @cached_property + def members(self) -> List[EnumerationMember]: + members = [] + for element in self._members: + members.append(EnumerationMember(element.get("name"), self)) + return members + + @property + def is_frida_options(self) -> bool: + return False + + @cached_property + def customizations(self) -> Optional[EnumerationCustomizations]: + return self.model.customizations.type_customizations.get(self.name) + + @cached_property + def c_symbol_prefix(self) -> str: + return f"fdn_{to_snake_case(self.name)}" + + +@dataclass +class EnumerationMember: + name: str + + enumeration: Enumeration + + @cached_property + def js_name(self) -> str: + custom = self.customizations + if custom is not None and custom.js_name is not None: + return custom.js_name + return to_pascal_case(self.name) + + @cached_property + def nick(self) -> str: + return self.name.replace("_", "-") + + @cached_property + def customizations(self) -> Optional[EnumerationMemberCustomizations]: + custom = self.enumeration.customizations + if custom is None: + return None + return custom.members.get(self.name) + + +@dataclass +class CustomType: + kind: CustomTypeKind + typing: str + + +class CustomTypeKind(Enum): + TYPE = "type" + INTERFACE = "interface" + ENUM = "enum" + + +@dataclass +class TypeCustomizations: + pass + + +@dataclass +class ObjectTypeCustomizations(TypeCustomizations): + js_name: Optional[str] = None + drop: bool = False + drop_abstract_base: bool = False + constructor: Optional[ConstructorCustomizations] = None + methods: Mapping[str, MethodCustomizations] = field( + default_factory=lambda: defaultdict(dict) + ) + properties: Mapping[str, PropertyCustomizations] = field( + default_factory=lambda: defaultdict(dict) + ) + signals: Mapping[str, SignalCustomizations] = field( + default_factory=lambda: defaultdict(dict) + ) + custom_code: Optional[CustomCode] = None + cleanup: Optional[str] = None + keep_alive: Optional[KeepAliveCustomization] = None + + +@dataclass +class KeepAliveCustomization: + is_destroyed_function: str + destroy_signal_name: str + + +@dataclass +class ConstructorCustomizations: + drop: bool = False + param_typings: Optional[List[str]] = None + custom_logic: Optional[str] = None + + +@dataclass +class MethodCustomizations: + js_name: Optional[str] = None + drop: bool = False + hide: bool = False + param_typings: Optional[List[str]] = None + return_typing: Optional[str] = None + custom_logic: Optional[str] = None + return_wrapper: Optional[str] = None + return_cconversion: Optional[str] = None + ref_keep_alive: bool = False + unref_keep_alive: bool = False + + +@dataclass +class PropertyCustomizations: + js_name: Optional[str] = None + drop: bool = False + typing: Optional[str] = None + + +@dataclass +class SignalCustomizations: + drop: bool = False + behavior: str = "FDN_SIGNAL_ALLOW_EXIT" + transform: Optional[Mapping[int, Tuple[str, Optional[str]]]] = None + intercept: Optional[str] = None + + +@dataclass +class CustomCode: + declarations: List[CustomDeclaration] = field(default_factory=list) + methods: List[CustomMethod] = field(default_factory=list) + + +@dataclass +class CustomDeclaration: + typing: Optional[str] + code: str + + +@dataclass +class CustomMethod: + typing: Optional[str] + code: str + + +@dataclass +class EnumerationCustomizations(TypeCustomizations): + members: Mapping[str, EnumerationMemberCustomizations] = field( + default_factory=lambda: defaultdict(dict) + ) + + +@dataclass +class EnumerationMemberCustomizations: + js_name: Optional[str] = None + + +def parse_gir(file_path: str, dependencies: Sequence[Model]) -> Model: + tree = ET.parse(file_path) + + el = tree.getroot().find("./namespace", GIR_NAMESPACES) + namespace = Namespace( + el.get("name"), el.get(f"{{{C_NAMESPACE}}}identifier-prefixes"), el + ) + + def resolve_type(name: str) -> Tuple[str, ET.Element]: + assert ( + name not in PRIMITIVE_GIR_TYPES + ), f"unexpectedly asked to resolve primitive type: {name}" + + tokens = name.split(".", maxsplit=1) + if len(tokens) == 2: + ns_name, bare_name = tokens + if ns_name == namespace.name: + ns = namespace + else: + ns = next( + ( + dep.namespace + for dep in dependencies + if dep.namespace.name == ns_name + ), + None, + ) + if ns is None: + assert ns is not None, f"unable to resolve namespace {ns_name}" + else: + ns = namespace + bare_name = name + qualified_name = f"{ns.name}.{bare_name}" + + element = ns.type_elements.get(bare_name) + assert element is not None, f"unable to resolve type {bare_name}" + + return (qualified_name, element) + + object_types = OrderedDict() + + for element in namespace.element.findall("./class", GIR_NAMESPACES): + name = element.get("name") + c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct") + if type_struct is not None: + type_struct = namespace.identifier_prefixes + type_struct + else: + type_struct = c_type + "Class" + parent = element.get("parent") + if parent is not None: + parent, _ = resolve_type(parent) + constructors = element.findall(".//constructor", GIR_NAMESPACES) + methods = element.findall(".//method", GIR_NAMESPACES) + properties = element.findall(".//property", GIR_NAMESPACES) + signals = element.findall(".//glib:signal", GIR_NAMESPACES) + implements = [ + e.get("name") for e in element.findall(".//implements", GIR_NAMESPACES) + ] + + object_types[name] = ClassObjectType( + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + resolve_type, + None, + implements, + ) + + for element in namespace.element.findall("./interface", GIR_NAMESPACES): + name = element.get("name") + c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct") + if type_struct is not None: + type_struct = namespace.identifier_prefixes + type_struct + else: + type_struct = c_type + "Iface" + prereq = element.find(".//prerequisite", GIR_NAMESPACES) + parent = prereq.get("name") if prereq is not None else None + if parent is not None: + parent, _ = resolve_type(parent) + constructors = [] + methods = element.findall(".//method", GIR_NAMESPACES) + properties = element.findall(".//property", GIR_NAMESPACES) + signals = element.findall(".//glib:signal", GIR_NAMESPACES) + + object_types[name] = InterfaceObjectType( + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + resolve_type, + None, + ) + + enumerations = OrderedDict() + + for element in namespace.element.findall("./enumeration", GIR_NAMESPACES): + if element.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None: + continue + enum_name = element.get("name") + enum_c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + members = element.findall(".//member", GIR_NAMESPACES) + enumerations[enum_name] = Enumeration( + enum_name, enum_c_type, get_type, members, None + ) + + model = Model(namespace, object_types, enumerations) + + for t in object_types.values(): + t.model = model + for t in enumerations.values(): + t.model = model + + return model + + +def extract_callable_details( + element: ET.Element, + result_element: ET.Element, + object_type: ObjectType, + resolve_type: ResolveTypeCallback, +) -> Tuple[str, Optional[str], List[Parameter], bool, bool, ET.Element]: + c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier") + + parameters = element.findall("./parameters/parameter", GIR_NAMESPACES) + full_param_list = extract_parameters( + parameters, + nullable_implies_optional=True, + object_type=object_type, + resolve_type=resolve_type, + ) + param_list = list(all_regular_parameters(full_param_list)) + has_closure_param = any((param.get("closure") == "1" for param in parameters)) + + is_async = any( + param.type.name == "Gio.AsyncReadyCallback" for param in full_param_list + ) + if not is_async: + result_element = element + + finish_c_identifier = ( + result_element.get(f"{{{C_NAMESPACE}}}identifier") if is_async else None + ) + + throws = result_element.get("throws") == "1" + + return ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) + + +def extract_parameters( + parameter_elements: List[ET.Element], + nullable_implies_optional: bool, + object_type: ObjectType, + resolve_type: ResolveTypeCallback, +) -> List[Parameter]: + entries = [] + for param in parameter_elements: + nullable = param.get("nullable") == "1" + entries.append((param, nullable)) + + last_required_index = None + for i, (param, nullable) in enumerate(entries): + optional = nullable and nullable_implies_optional + if not optional: + last_required_index = i + + param_list = [] + for i, (param, nullable) in enumerate(entries): + name = param.get("name") + type = extract_type_from_entity(param, resolve_type) + + if last_required_index is None or i > last_required_index: + optional = nullable and nullable_implies_optional + else: + optional = False + + ownership_val = param.get("transfer-ownership") + transfer_ownership = ( + TransferOwnership[ownership_val] + if ownership_val is not None + else TransferOwnership.none + ) + + raw_direction = param.get("direction") + direction = ( + Direction(raw_direction) if raw_direction is not None else Direction.IN + ) + + param_list.append( + Parameter( + name, + type, + optional, + nullable, + transfer_ownership, + direction, + object_type, + ) + ) + return param_list + + +def all_regular_parameters(parameters: List[Parameter]) -> Iterator[Parameter]: + callback_index = None + for i, param in enumerate(parameters): + if param.type.name == "Gio.AsyncReadyCallback": + callback_index = i + continue + + if callback_index is not None and i == callback_index + 1: + continue + + yield param + + +def extract_type_from_entity( + parent_element: ET.Element, resolve_type: ResolveTypeCallback +) -> Optional[Type]: + child = parent_element.find("type", GIR_NAMESPACES) + if child is None: + child = parent_element.find("array", GIR_NAMESPACES) + assert child is not None + element_type = extract_type_from_entity(child, resolve_type) + if element_type.name == "utf8": + return Type( + "utf8[]", + "strv", + "gchar **", + "NULL", + "g_strdupv", + "g_strfreev", + ) + elif element_type.name == "gchar": + return Type("char[]", "chararray", "gchar *", "NULL", "NULL", "NULL") + elif element_type.name == "GObject.Value": + return Type("Value[]", "valuearray", "GValue *", "NULL", "NULL", "NULL") + else: + assert ( + element_type.name == "guint8" + ), f"unsupported array type: {element_type.name}" + return Type("uint8[]", "bytearray", "guint8 *", "NULL", "NULL", "NULL") + + return parse_type(child, resolve_type) + + +def parse_type( + element: ET.Element, resolve_type: ResolveTypeCallback +) -> Optional[Type]: + name = element.get("name") + assert name is not None + if name == "none": + return None + + is_primitive = name in PRIMITIVE_GIR_TYPES + c_type = element.get(f"{{{C_NAMESPACE}}}type") + + core_tag = None + if is_primitive: + type_element = element + if c_type is None: + c_type = name + else: + name, type_element = resolve_type(name) + if type_element.tag.startswith(CORE_TAG_PREFIX): + core_tag = type_element.tag[len(CORE_TAG_PREFIX) :] + c_type = type_element.get(f"{{{C_NAMESPACE}}}type") + if core_tag in {"class", "interface", "record"}: + c_type += "*" + + nick = type_nick_from_name(name, element, resolve_type) + c = c_type.replace("*", " *") + + default_value = "NULL" if "*" in c else None + + if name == "utf8": + copy_func = "g_strdup" + destroy_func = "g_free" + elif name == "utf8[]": + copy_func = "g_strdupv" + destroy_func = "g_strfreev" + elif name == "GLib.HashTable": + copy_func = "g_hash_table_ref" + destroy_func = "g_hash_table_unref" + elif name == "GLib.Quark": + copy_func = None + destroy_func = None + elif name == "GObject.Value": + copy_func = "g_value_copy" + destroy_func = "g_value_reset" + elif name == "GObject.Closure": + copy_func = "g_closure_ref" + destroy_func = "g_closure_unref" + elif core_tag in {"class", "interface"}: + copy_func = "g_object_ref" + destroy_func = "g_object_unref" + elif is_primitive or core_tag in {"bitfield", "callback", "enumeration"}: + copy_func = None + destroy_func = None + else: + copy_func = type_element.get("copy-function") + destroy_func = type_element.get("free-function") + assert ( + destroy_func is not None + ), f"unable to resolve destroy function for {name}, core_tag={core_tag}" + + return Type(name, nick, c, default_value, copy_func, destroy_func) + + +def type_nick_from_name( + name: str, element: ET.Element, resolve_type: ResolveTypeCallback +) -> str: + if name == "GLib.PollFD": + return "pollfd" + + tokens = name.split(".", maxsplit=1) + if len(tokens) == 1: + result = tokens[0] + if result.startswith("g"): + result = result[1:] + else: + result = to_snake_case(tokens[1]) + + if result == "hash_table": + key_type = parse_type(element[0], resolve_type) + value_type = parse_type(element[1], resolve_type) + assert ( + key_type.name == "utf8" and value_type.name == "GLib.Variant" + ), "only GHashTable is supported for now" + result = "vardict" + + return result + + +def js_type_from_gir(name: str) -> str: + if name == "gboolean": + return "boolean" + if name in NUMERIC_GIR_TYPES: + return "number" + if name == "utf8": + return "string" + if name == "utf8[]": + return "string[]" + if name == "GLib.Bytes": + return "Buffer" + if name == "GLib.HashTable": + return "VariantDict" + if name == "GLib.Variant": + return "any" + if name in {"Gio.File", "Gio.TlsCertificate"}: + return "string" + if name.startswith("Frida.") and name.endswith("List"): + return name[6:-4] + "[]" + return name.split(".")[-1] + + +def to_snake_case(name: str) -> str: + result = [] + i = 0 + n = len(name) + while i < n: + if name[i].isupper(): + if i > 0: + result.append("_") + start = i + if i + 1 < n and name[i + 1].islower(): + while i + 1 < n and name[i + 1].islower(): + i += 1 + else: + while i + 1 < n and name[i + 1].isupper(): + i += 1 + if i + 1 < n: + i -= 1 + result.append(name[start : i + 1].lower()) + else: + result.append(name[i]) + i += 1 + return "".join(result) + + +def to_pascal_case(name: str) -> str: + return "".join(word.capitalize() for word in name.split("_")) + + +def to_camel_case(name: str) -> str: + words = name.split("_") + return words[0] + "".join(word.capitalize() for word in words[1:]) + + +def to_macro_case(identifier: str) -> str: + result = [] + for i, char in enumerate(identifier): + if char.isupper() and i != 0: + result.append("_") + result.append(char) + return "".join(result).upper() diff --git a/frida/meson.build b/frida/meson.build index 1def27c..874c608 100644 --- a/frida/meson.build +++ b/frida/meson.build @@ -1,8 +1,90 @@ -subdir('_frida') +if frida_core_dep.type_name() == 'internal' + frida_core_subprj = subproject('frida-core') + frida_gir = frida_core_subprj.get_variable('core_public_gir') + glib_gir = frida_core_subprj.get_variable('glib_gir') + gobject_gir = frida_core_subprj.get_variable('gobject_gir') + gio_gir = frida_core_subprj.get_variable('gio_gir') +else + girdir = frida_core_dep.get_variable('frida_girdir') + frida_gir = girdir / 'Frida-1.0.gir' + glib_gir = girdir / 'GLib-2.0.gir' + gobject_gir = girdir / 'GObject-2.0.gir' + gio_gir = girdir / 'Gio-2.0.gir' +endif + +env = environment() +env.set('PYTHONPATH', meson.current_source_dir()) + +code = custom_target('binding-code', + output: [ + '__init__.py', + '__init__.pyi', + 'extension.c', + ], + input: [ + frida_gir, + glib_gir, + gobject_gir, + gio_gir, + files( + 'frida_bindgen' / '__init__.py', + 'frida_bindgen' / '__main__.py', + 'frida_bindgen' / 'cli.py', + 'frida_bindgen' / 'codegen.py', + 'frida_bindgen' / 'customization.py', + 'frida_bindgen' / 'loader.py', + 'frida_bindgen' / 'model.py', + 'frida_bindgen' / 'assets' / 'codegen_helpers.c', + 'frida_bindgen' / 'assets' / 'codegen_helpers.ts', + 'frida_bindgen' / 'assets' / 'codegen_prototypes.h', + 'frida_bindgen' / 'assets' / 'codegen_types.h', + 'frida_bindgen' / 'assets' / 'customization_facade.exports', + 'frida_bindgen' / 'assets' / 'customization_facade.ts', + 'frida_bindgen' / 'assets' / 'customization_helpers.imports', + 'frida_bindgen' / 'assets' / 'customization_helpers.ts', + ) + ], + command: [ + python, '-m', 'frida_bindgen', + '--frida-gir=@INPUT0@', + '--glib-gir=@INPUT1@', + '--gobject-gir=@INPUT2@', + '--gio-gir=@INPUT3@', + '--output-py=@OUTPUT0@', + '--output-pyi=@OUTPUT1@', + '--output-c=@OUTPUT2@', + ], + env: env, + install: true, + install_dir: [ + python.get_install_dir() / 'frida', + python.get_install_dir() / 'frida' / '_frida', + false, + ] +) py_sources = [ - '__init__.py', - 'core.py', 'py.typed', ] python.install_sources(py_sources, subdir: 'frida', pure: false) + +extension_py_sources = [ + 'py.typed', +] +python.install_sources(extension_py_sources, subdir: 'frida' / '_frida', pure: false) + +extra_link_args = [] +if host_os_family == 'darwin' + extra_link_args += '-Wl,-exported_symbol,_PyInit__frida' +elif host_os_family != 'windows' + extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / 'extension.version' +endif + +extension = python.extension_module('_frida', code[2], + limited_api: '3.7', + c_args: frida_component_cflags, + link_args: extra_link_args, + dependencies: [python_dep, frida_core_dep, os_deps], + install: true, + subdir: 'frida', +) diff --git a/meson.build b/meson.build index aa96710..af54640 100644 --- a/meson.build +++ b/meson.build @@ -32,9 +32,9 @@ endif subdir('frida') -test('frida-python', python, - args: ['-m', 'unittest', 'discover'], - workdir: meson.current_source_dir(), - env: {'PYTHONPATH': meson.current_build_dir() / 'src'}, - timeout: 30, -) +#test('frida-python', python, +# args: ['-m', 'unittest', 'discover'], +# workdir: meson.current_source_dir(), +# env: {'PYTHONPATH': meson.current_build_dir() / 'src'}, +# timeout: 30, +#)