Compare commits

...
112 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 03cf741fe0 subprojects: Prepare for release 2026-10-17 12:54:33 +02:00
Ole André Vadla Ravnås fdcea1076a subprojects: Bump outdated 2026-10-17 12:53:48 +02:00
Ole André Vadla Ravnås 1e1bea3512 submodules: Bump releng 2026-10-17 12:53:48 +02:00
Ole André Vadla Ravnås 95cbbd197c subprojects: Prepare for release 2026-10-16 15:37:47 +02:00
Ole André Vadla Ravnås 9bc7087d74 subprojects: Bump outdated 2026-10-16 15:36:33 +02:00
Ole André Vadla Ravnås ffe970e655 subprojects: Bump outdated 2026-10-16 13:54:46 +02:00
Ole André Vadla Ravnås 7ce14f42fa subprojects: Bump outdated 2026-10-16 12:42:52 +02:00
Ole André Vadla Ravnås 14c8d33b37 submodules: Bump releng 2026-10-16 12:42:52 +02:00
Ole André Vadla Ravnås ff0f50f8a6 subprojects: Bump outdated 2026-10-15 18:18:18 +02:00
Ole André Vadla Ravnås 8b526bb25d subprojects: Bump outdated 2026-10-15 15:46:11 +02:00
Ole André Vadla Ravnås 7c422c233d submodules: Bump releng 2026-10-15 15:46:11 +02:00
Ole André Vadla Ravnås bd7e80c209 subprojects: Bump outdated 2026-10-15 15:14:38 +02:00
Ole André Vadla Ravnås f80a06d7ae submodules: Bump releng 2026-10-15 15:14:38 +02:00
Ole André Vadla Ravnås 82febc16ba subprojects: Prepare for release 2026-10-09 18:57:36 +02:00
Ole André Vadla Ravnås 76d3b504fe subprojects: Bump outdated 2026-10-09 18:56:41 +02:00
Ole André Vadla Ravnås 735d34d175 subprojects: Prepare for release 2026-10-09 14:19:26 +02:00
Ole André Vadla Ravnås a6feef0770 subprojects: Bump outdated 2026-10-09 14:16:52 +02:00
Ole André Vadla Ravnås 418c5e3f43 subprojects: Prepare for release 2026-10-08 14:49:02 +02:00
Ole André Vadla Ravnås a1929a4dc5 subprojects: Bump outdated 2026-10-08 14:32:45 +02:00
Ole André Vadla Ravnås aacd1913e0 subprojects: Prepare for release 2026-10-07 14:27:03 +02:00
Ole André Vadla Ravnås ea6c66da96 subprojects: Bump outdated 2026-10-07 14:24:00 +02:00
Ole André Vadla Ravnås 57b6cbae38 submodules: Bump releng 2026-10-07 14:24:00 +02:00
Ole André Vadla Ravnås 1b18f0174d subprojects: Prepare for release 2026-10-05 10:00:33 +02:00
Ole André Vadla Ravnås 7f04d40687 subprojects: Bump outdated 2026-10-05 09:57:11 +02:00
Ole André Vadla Ravnås c69db7eb40 submodules: Bump releng 2026-10-05 09:57:10 +02:00
Ole André Vadla Ravnås ce49a3102a subprojects: Prepare for release 2026-10-04 11:35:34 +02:00
Ole André Vadla Ravnås 288133374a subprojects: Bump outdated 2026-10-04 11:34:54 +02:00
Ole André Vadla Ravnås 56843bb3f8 subprojects: Prepare for release 2026-10-03 07:56:17 +02:00
Ole André Vadla Ravnås d90f991948 subprojects: Bump outdated 2026-10-03 07:55:28 +02:00
Ole André Vadla Ravnås c744c2a12a subprojects: Prepare for release 2026-10-02 20:28:27 +02:00
Ole André Vadla Ravnås 48631baafa subprojects: Bump outdated 2026-10-02 20:27:05 +02:00
Ole André Vadla Ravnås c711f8f040 subprojects: Prepare for release 2026-10-02 01:36:43 +02:00
Ole André Vadla Ravnås 049674da0a subprojects: Bump outdated 2026-10-02 01:32:18 +02:00
Ole André Vadla Ravnås e1c24e8187 bindgen: Marshal null variants as None 2026-10-01 18:24:45 +02:00
Ole André Vadla Ravnås 9947655e6a subprojects: Prepare for release 2026-09-25 13:15:47 +02:00
Ole André Vadla Ravnås e5b15dce63 subprojects: Bump outdated 2026-09-25 13:14:11 +02:00
Ole André Vadla Ravnås e846eb2815 subprojects: Bump outdated 2026-09-25 12:18:59 +02:00
Ole André Vadla Ravnås bc685fd8c1 subprojects: Bump outdated 2026-09-24 21:35:04 +02:00
Ole André Vadla Ravnås bcaa1a099e subprojects: Bump outdated 2026-09-23 00:02:59 +02:00
Ole André Vadla Ravnås 4e782ccbdf submodules: Bump releng 2026-09-23 00:02:58 +02:00
Ole André Vadla Ravnås 9823368745 subprojects: Bump outdated 2026-09-22 23:47:25 +02:00
Ole André Vadla Ravnås c6e5a8d4d9 submodules: Bump releng 2026-09-22 23:47:25 +02:00
Ole André Vadla Ravnås eb15035950 subprojects: Bump outdated 2026-09-22 21:04:23 +02:00
Ole André Vadla Ravnås ec509287e1 subprojects: Bump outdated 2026-09-22 15:57:26 +02:00
Ole André Vadla Ravnås 0c52c00bad submodules: Bump releng 2026-09-22 15:57:26 +02:00
Ole André Vadla Ravnås 8f31efacf2 Format with the current Black
Black has changed how it lays out a call whose argument is a
multiline string, and a `with` statement holding several contexts.
2026-09-17 19:01:25 +02:00
Ole André Vadla Ravnås 01fd680353 bindgen: Fix dropped constructors and out params
A static function that makes the class it sits on was not read at all,
so every named constructor in the API was missing. One that throws was
then missing twice over: skipped for __init__ because it throws, and
skipped as a factory for being the primary, leaving nothing to make
the class with.

An out parameter was dropped along with the value it carried, so a
`gboolean` method that answers through one now binds as Optional, and
any other out parameter is refused rather than bound as a result the
caller cannot read.
2026-09-17 18:54:58 +02:00
Ole André Vadla Ravnås 00e6231b89 subprojects: Bump outdated 2026-09-16 19:28:53 +02:00
Ole André Vadla Ravnås aee40b044d subprojects: Prepare for release 2026-09-09 20:37:20 +02:00
Ole André Vadla Ravnås b8c22c18f1 subprojects: Bump outdated 2026-09-09 20:33:13 +02:00
Ole André Vadla Ravnås b0a080cd14 subprojects: Bump outdated 2026-09-09 20:16:39 +02:00
Ole André Vadla Ravnås e071ead36d subprojects: Bump outdated 2026-09-09 00:52:10 +02:00
Ole André Vadla Ravnås 2ff7776289 submodules: Bump releng 2026-09-09 00:52:10 +02:00
Ole André Vadla Ravnås df1b3af738 subprojects: Bump outdated 2026-09-09 00:47:02 +02:00
Ole André Vadla Ravnås 03d3b8ab0c subprojects: Bump outdated 2026-09-08 22:34:08 +02:00
Ole André Vadla Ravnås dd0639b6a0 submodules: Bump releng 2026-09-08 22:34:08 +02:00
Ole André Vadla Ravnås bfc6d48b72 bindgen: Fix typing imports on Python < 3.11
NotRequired, ParamSpec and TypedDict come from typing_extensions
there.
2026-09-08 14:14:07 +02:00
Ole André Vadla Ravnås 903143cfd2 subprojects: Bump outdated 2026-09-08 12:14:50 +02:00
Ole André Vadla Ravnås c91bd2d443 subprojects: Bump outdated 2026-09-08 11:39:29 +02:00
Ole André Vadla Ravnås c918c1e516 subprojects: Bump outdated 2026-09-07 17:39:36 +02:00
Ole André Vadla Ravnås ec645f2118 subprojects: Bump outdated 2026-09-07 11:47:57 +02:00
Ole André Vadla Ravnås 41a658af3f bindgen: Blacken model.py 2026-09-06 12:14:30 +02:00
Ole André Vadla Ravnås 9a86bd589b bindgen: Expose named constructors as factories
Emit them all rather than the first alone, and marshal a bytes
argument through GBytes. Keep new as the primary constructor.
2026-09-06 12:04:31 +02:00
Ole André Vadla Ravnås 82293f6c56 subprojects: Bump outdated 2026-09-06 12:02:40 +02:00
Ole André Vadla Ravnås b5c8757514 subprojects: Bump outdated 2026-09-04 17:27:46 +02:00
Ole André Vadla Ravnås 8c7b77cf52 subprojects: Bump outdated 2026-09-04 12:26:26 +02:00
Ole André Vadla Ravnås bbb129ac8d subprojects: Bump outdated 2026-09-03 22:40:11 +02:00
Ole André Vadla Ravnås 51c411d3f4 subprojects: Bump outdated 2026-09-03 18:19:36 +02:00
Ole André Vadla Ravnås a0dd2b7463 subprojects: Bump outdated 2026-09-03 14:43:19 +02:00
Ole André Vadla Ravnås fcc203f604 subprojects: Bump outdated 2026-09-02 18:42:33 +02:00
Ole André Vadla Ravnås a04b4a328e submodules: Bump releng 2026-09-02 18:42:33 +02:00
Ole André Vadla Ravnås f4109be14e subprojects: Bump outdated 2026-09-02 00:30:41 +02:00
Ole André Vadla Ravnås 786398a682 submodules: Bump releng 2026-09-02 00:30:41 +02:00
Ole André Vadla Ravnås 738060a8c5 subprojects: Bump outdated 2026-09-01 21:38:50 +02:00
Ole André Vadla Ravnås b5fb9987fc submodules: Bump releng 2026-09-01 21:38:50 +02:00
Ole André Vadla Ravnås b69d19f673 subprojects: Bump outdated 2026-09-01 15:16:05 +02:00
Ole André Vadla Ravnås 6622f997cd submodules: Bump releng 2026-09-01 15:16:05 +02:00
Ole André Vadla Ravnås 6eeeae51c8 subprojects: Bump outdated 2026-09-01 14:05:16 +02:00
Ole André Vadla Ravnås 679805bc8c submodules: Bump releng 2026-09-01 14:05:16 +02:00
Ole André Vadla Ravnås ec58231298 subprojects: Bump outdated 2026-08-27 18:26:09 +02:00
Ole André Vadla Ravnås 0caa5d4cf6 submodules: Bump releng 2026-08-27 18:26:09 +02:00
Ole André Vadla Ravnås 97d79c14a3 subprojects: Bump outdated 2026-08-18 15:32:21 +02:00
Ole André Vadla Ravnås e46178a118 submodules: Bump releng 2026-08-18 15:32:21 +02:00
Ole André Vadla Ravnås 1e631075e4 subprojects: Bump outdated 2026-08-18 12:50:57 +02:00
Ole André Vadla Ravnås 9cd85d0cfc subprojects: Bump outdated 2026-08-18 00:13:05 +02:00
Ole André Vadla Ravnås 4baee2713e subprojects: Bump outdated 2026-08-17 21:10:56 +02:00
Ole André Vadla Ravnås 4f33ee3945 submodules: Bump releng 2026-08-17 21:10:56 +02:00
Ole André Vadla Ravnås 6f890ee8d4 Take object args and settings kwargs
Sync methods dropped parameters of object type, so PeerOptions.add_relay
never reached the extension even though the facade referred to it.

Constructor kwargs were forwarded to an extension init that ignores them,
leaving them silently unapplied. Apply them to the impl instead, reusing
the options selectors so adders take a list, or a dict when they take a
name and a value.
2026-08-17 20:40:41 +02:00
Ole André Vadla Ravnås f27a8dcc73 subprojects: Bump outdated 2026-08-16 22:23:39 +02:00
Ole André Vadla Ravnås 7c81781550 submodules: Bump releng 2026-08-16 22:23:39 +02:00
Ole André Vadla Ravnås 317a796f7d subprojects: Bump outdated 2026-08-13 18:00:26 +02:00
Ole André Vadla Ravnås 01f691c075 subprojects: Bump outdated 2026-08-13 17:55:09 +02:00
Ole André Vadla Ravnås feb036961e submodules: Bump releng 2026-08-13 17:55:09 +02:00
Ole André Vadla Ravnås 62f066389e subprojects: Bump outdated 2026-08-13 16:21:25 +02:00
Ole André Vadla Ravnås 8deda449c3 submodules: Bump releng 2026-08-13 16:21:25 +02:00
Ole André Vadla Ravnås 5aa3fda547 subprojects: Bump outdated 2026-08-13 13:35:02 +02:00
Ole André Vadla Ravnås d7acc26d9d submodules: Bump releng 2026-08-13 13:35:02 +02:00
Ole André Vadla Ravnås 8cb6565adf subprojects: Bump outdated 2026-08-13 13:02:05 +02:00
Ole André Vadla Ravnås 5ba902508e submodules: Bump releng 2026-08-13 13:02:05 +02:00
Ole André Vadla Ravnås 4ee43bc18c subprojects: Bump outdated 2026-08-13 00:19:40 +02:00
Ole André Vadla Ravnås 4414c049f4 submodules: Bump releng 2026-08-13 00:19:40 +02:00
Ole André Vadla Ravnås 1649a5a4cf subprojects: Bump outdated 2026-08-12 13:00:22 +02:00
Ole André Vadla Ravnås b17f7704a3 submodules: Bump releng 2026-08-12 13:00:22 +02:00
Ole André Vadla Ravnås 4aca013c22 subprojects: Bump outdated 2026-08-11 11:41:53 +02:00
Ole André Vadla Ravnås 1cc0bcae64 subprojects: Prepare for release 2026-08-05 00:32:55 +02:00
Ole André Vadla Ravnås 14849bcb1e subprojects: Bump outdated 2026-08-05 00:32:13 +02:00
Ole André Vadla Ravnås 20def06046 submodules: Bump releng 2026-08-05 00:32:13 +02:00
Ole André Vadla Ravnås 3ffd66c91d subprojects: Bump outdated 2026-08-02 18:29:21 +02:00
Ole André Vadla Ravnås da4e16c726 submodules: Bump releng 2026-08-02 18:29:21 +02:00
Ole André Vadla Ravnås 3353755177 Fix attach() linker_notifier_offsets kwarg
Its customization hardcoded an empty selector map instead of the one
computed from the options type, so the plural kwarg was rejected. Let
codegen build the options argument for custom facade methods that take
**kwargs.
2026-08-02 18:19:14 +02:00
Ole André Vadla Ravnås 82b4f9559f Fix Script.enable_debugger() port default
Vala's default argument is absent from the .gir, so the generated facade
made the port mandatory. Declare the default in the customizations, and
let param_typings override the facade signature also when there is no
custom logic.
2026-08-02 18:13:33 +02:00
Ole André Vadla Ravnås db4590d85c Fix certificate option on remote device et al
The GTlsCertificate-typed properties had no marshaller, so their setters
were never generated. Marshal them to and from a string holding either a
PEM blob or a filename, like the old hand-written bindings did.

Reported and diagnosed by rev1si0n <[email protected]>.
2026-07-31 00:31:04 +02:00
14 changed files with 449 additions and 106 deletions

No files matched your search

+4 -2
View File
@@ -42,7 +42,8 @@ class Application:
print("✔ enable_child_gating()") print("✔ enable_child_gating()")
session.enable_child_gating() session.enable_child_gating()
print("✔ create_script()") print("✔ create_script()")
script = session.create_script("""\ script = session.create_script(
"""\
Interceptor.attach(Module.getGlobalExportByName('open'), { Interceptor.attach(Module.getGlobalExportByName('open'), {
onEnter(args) { onEnter(args) {
send({ send({
@@ -51,7 +52,8 @@ Interceptor.attach(Module.getGlobalExportByName('open'), {
}); });
} }
}); });
""") """
)
script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message))) script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message)))
print("✔ load()") print("✔ load()")
script.load() script.load()
+4 -2
View File
@@ -1,7 +1,8 @@
import frida import frida
session = frida.attach("Twitter") session = frida.attach("Twitter")
script = session.create_script("""\ script = session.create_script(
"""\
rpc.exports = { rpc.exports = {
hello: function () { hello: function () {
return 'Hello'; return 'Hello';
@@ -10,7 +11,8 @@ rpc.exports = {
oops; oops;
} }
}; };
""") """
)
script.load() script.load()
api = script.exports_sync api = script.exports_sync
print("api.hello() =>", api.hello()) print("api.hello() =>", api.hello())
+4 -2
View File
@@ -22,7 +22,8 @@ class Application:
self._session = session self._session = session
session.on("detached", lambda *args: self._reactor.schedule(lambda: self._on_detached(*args))) session.on("detached", lambda *args: self._reactor.schedule(lambda: self._on_detached(*args)))
script = session.create_script(""" script = session.create_script(
"""
let _puts = null; let _puts = null;
Interceptor.attach(DebugSymbol.getFunctionByName('f'), { Interceptor.attach(DebugSymbol.getFunctionByName('f'), {
@@ -47,7 +48,8 @@ function puts(s) {
} }
_puts(Memory.allocUtf8String(s)); _puts(Memory.allocUtf8String(s));
} }
""") """
)
self._script = script self._script = script
script.on("message", lambda *args: self._reactor.schedule(lambda: self._on_message(*args))) script.on("message", lambda *args: self._reactor.schedule(lambda: self._on_message(*args)))
script.load() script.load()
+4 -2
View File
@@ -29,7 +29,8 @@ class Application:
session = self._device.attach(pid) session = self._device.attach(pid)
session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason))) session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason)))
print("✔ create_script()") print("✔ create_script()")
script = session.create_script("""\ script = session.create_script(
"""\
const puts = new NativeFunction(Module.getGlobalExportByName('puts'), 'int', ['pointer']); const puts = new NativeFunction(Module.getGlobalExportByName('puts'), 'int', ['pointer']);
puts(Memory.allocUtf8String('Hello from Frida agent')); puts(Memory.allocUtf8String('Hello from Frida agent'));
Interceptor.attach(Module.getGlobalExportByName('open'), { Interceptor.attach(Module.getGlobalExportByName('open'), {
@@ -40,7 +41,8 @@ Interceptor.attach(Module.getGlobalExportByName('open'), {
}); });
} }
}); });
""") """
)
script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message))) script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message)))
print("✔ load()") print("✔ load()")
script.load() script.load()
@@ -1,27 +1,3 @@
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 static int
PyEndpointParameters_init (PyEndpointParameters * self, PyEndpointParameters_init (PyEndpointParameters * self,
PyObject * args, PyObject * args,
@@ -31,7 +7,7 @@ PyEndpointParameters_init (PyEndpointParameters * self,
static char * keywords[] = { "address", "port", "certificate", "origin", "auth_service", "asset_root", NULL }; static char * keywords[] = { "address", "port", "certificate", "origin", "auth_service", "asset_root", NULL };
char * address = NULL; char * address = NULL;
unsigned short int port = 0; unsigned short int port = 0;
char * certificate_value = NULL; PyObject * certificate_value = NULL;
char * origin = NULL; char * origin = NULL;
PyObject * auth_service_obj = NULL; PyObject * auth_service_obj = NULL;
char * asset_root_value = NULL; char * asset_root_value = NULL;
@@ -43,10 +19,10 @@ PyEndpointParameters_init (PyEndpointParameters * self,
if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0)
return -1; return -1;
if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHesesOes", keywords, if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHOesOes", keywords,
"utf-8", &address, "utf-8", &address,
&port, &port,
"utf-8", &certificate_value, &certificate_value,
"utf-8", &origin, "utf-8", &origin,
&auth_service_obj, &auth_service_obj,
"utf-8", &asset_root_value)) "utf-8", &asset_root_value))
@@ -74,7 +50,6 @@ beach:
PyMem_Free (asset_root_value); PyMem_Free (asset_root_value);
PyMem_Free (origin); PyMem_Free (origin);
PyMem_Free (certificate_value);
PyMem_Free (address); PyMem_Free (address);
return result; return result;
@@ -910,6 +910,9 @@ propagate_error:
static PyObject * static PyObject *
PyGObject_marshal_variant (GVariant * variant) PyGObject_marshal_variant (GVariant * variant)
{ {
if (variant == NULL)
PyFrida_RETURN_NONE;
switch (g_variant_classify (variant)) switch (g_variant_classify (variant))
{ {
case G_VARIANT_CLASS_STRING: case G_VARIANT_CLASS_STRING:
@@ -1041,6 +1044,64 @@ PyGObject_marshal_variant_tuple (GVariant * variant)
return tuple; return tuple;
} }
static gboolean
PyGObject_unmarshal_certificate (PyObject * value,
GTlsCertificate ** certificate)
{
PyObject * bytes;
const gchar * str;
GError * error = NULL;
if (value == Py_None)
{
*certificate = NULL;
return TRUE;
}
bytes = PyUnicode_AsUTF8String (value);
if (bytes == NULL)
return FALSE;
str = PyBytes_AsString (bytes);
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);
Py_DecRef (bytes);
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 PyObject *
PyGObject_marshal_certificate (GTlsCertificate * certificate)
{
PyObject * result;
gchar * pem;
if (certificate == NULL)
PyFrida_RETURN_NONE;
g_object_get (certificate, "certificate-pem", &pem, NULL);
result = PyGObject_marshal_string (pem);
g_free (pem);
return result;
}
static PyObject * static PyObject *
PyFrida_raise (GError * error) PyFrida_raise (GError * error)
{ {
@@ -35,6 +35,8 @@ static PyObject * PyGObject_marshal_variant_byte_array (GVariant * variant);
static PyObject * PyGObject_marshal_variant_dict (GVariant * variant); static PyObject * PyGObject_marshal_variant_dict (GVariant * variant);
static PyObject * PyGObject_marshal_variant_array (GVariant * variant); static PyObject * PyGObject_marshal_variant_array (GVariant * variant);
static PyObject * PyGObject_marshal_variant_tuple (GVariant * variant); static PyObject * PyGObject_marshal_variant_tuple (GVariant * variant);
static gboolean PyGObject_unmarshal_certificate (PyObject * value, GTlsCertificate ** certificate);
static PyObject * PyGObject_marshal_certificate (GTlsCertificate * certificate);
static PyObject * PyFrida_raise (GError * error); static PyObject * PyFrida_raise (GError * error);
static PyObject * PyFrida_marshal_error (GError * error); static PyObject * PyFrida_marshal_error (GError * error);
+300 -58
View File
@@ -34,10 +34,14 @@ def read_asset(name: str) -> str:
return (ASSETS_DIR / name).read_text(encoding="utf-8") return (ASSETS_DIR / name).read_text(encoding="utf-8")
FACADE_TYPING_IMPORTS = ( FACADE_TYPING_IMPORTS = """\
"from typing import (Any, Callable, Dict, List, Literal, Mapping, NotRequired, Optional, Tuple, TypedDict, " from typing import (Any, Callable, Dict, List, Literal, Mapping, Optional, Tuple, Sequence, Union, TypeVar, cast,
"Sequence, Union, ParamSpec, TypeVar, cast, overload)" overload)
)
if sys.version_info >= (3, 11):
from typing import NotRequired, ParamSpec, TypedDict
else:
from typing_extensions import NotRequired, ParamSpec, TypedDict"""
def generate_facade_preludes(model: Model) -> List[str]: def generate_facade_preludes(model: Model) -> List[str]:
@@ -88,17 +92,25 @@ def _to_envp(value):
def _make_options(cls, values, selectors): def _make_options(cls, values, selectors):
options = cls() options = cls()
_apply_settings(options, values, selectors)
return options
def _apply_settings(impl, values, selectors):
for name, value in values.items(): for name, value in values.items():
if value is None: if value is None:
continue continue
select = selectors.get(name) select = selectors.get(name)
if select is None: if select is None:
setattr(options, name, value) setattr(impl, name, _unwrap(value))
elif isinstance(value, dict):
add = getattr(impl, select)
for key, element in value.items():
add(key, _unwrap(element))
else: else:
add = getattr(options, select) add = getattr(impl, select)
for element in value: for element in value:
add(element) add(_unwrap(element))
return options
_current_cancellable = threading.local() _current_cancellable = threading.local()
@@ -182,17 +194,25 @@ def _to_envp(value):
def _make_options(cls, values, selectors): def _make_options(cls, values, selectors):
options = cls() options = cls()
_apply_settings(options, values, selectors)
return options
def _apply_settings(impl, values, selectors):
for name, value in values.items(): for name, value in values.items():
if value is None: if value is None:
continue continue
select = selectors.get(name) select = selectors.get(name)
if select is None: if select is None:
setattr(options, name, value) setattr(impl, name, _unwrap(value))
elif isinstance(value, dict):
add = getattr(impl, select)
for key, element in value.items():
add(key, _unwrap(element))
else: else:
add = getattr(options, select) add = getattr(impl, select)
for element in value: for element in value:
add(element) add(_unwrap(element))
return options
_current_cancellable: contextvars.ContextVar = contextvars.ContextVar("frida_current_cancellable", default=None) _current_cancellable: contextvars.ContextVar = contextvars.ContextVar("frida_current_cancellable", default=None)
@@ -416,7 +436,7 @@ def generate_aio_async_method(method: Method, model: Model) -> Optional[str]:
if method.return_value is not None: if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model) call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) ret = facade_return_type(method, model)
return f""" async def {method.name}({signature}) -> {ret}: return f""" async def {method.name}({signature}) -> {ret}:
return {call}""" return {call}"""
@@ -426,11 +446,12 @@ def generate_custom_facade_method(
method: Method, logic: Union[str, Tuple[str, str]], model: Model, awaitable: bool method: Method, logic: Union[str, Tuple[str, str]], model: Model, awaitable: bool
) -> str: ) -> str:
signature = ", ".join(["self"] + method.custom_facade_params) signature = ", ".join(["self"] + method.custom_facade_params)
args = facade_call_args(method, model)
if method.is_async: if method.is_async:
invoke = f"_invoke(self._impl.{method.name}, ({method.facade_call_args}))" invoke = f"_invoke(self._impl.{method.name}, ({args}))"
call = f"await {invoke}" if awaitable else invoke call = f"await {invoke}" if awaitable else invoke
else: else:
call = f"self._impl.{method.name}({method.facade_call_args})" call = f"self._impl.{method.name}({args})"
if method.return_value is not None: if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model) call = wrap_result(call, method.return_value.type, model)
@@ -439,13 +460,27 @@ def generate_custom_facade_method(
body = textwrap.indent(logic.strip(), " " * 8) body = textwrap.indent(logic.strip(), " " * 8)
keyword = "async def" if awaitable else "def" keyword = "async def" if awaitable else "def"
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) ret = facade_return_type(method, model)
return f""" {keyword} {method.name}({signature}) -> {ret}: return f""" {keyword} {method.name}({signature}) -> {ret}:
{body} {body}
return {call}""" return {call}"""
def facade_call_args(method: Method, model: Model) -> str:
takes_kwargs = any(param.split(":")[0].strip() == "**kwargs" for param in method.custom_facade_params)
args = []
for param in method.input_parameters:
if param.type.name == "Gio.Cancellable":
continue
options = resolve_options_type(param.type, model)
if options is not None and takes_kwargs:
args.append(make_options_expr(options))
else:
args.append(param.name)
return "".join(arg + ", " for arg in args)
def generate_py_exception_reexports(model: Model) -> List[str]: def generate_py_exception_reexports(model: Model) -> List[str]:
names = [f"{member.js_name}Error" for member in model.error_domain.members] names = [f"{member.js_name}Error" for member in model.error_domain.members]
names.append("OperationCancelledError") names.append("OperationCancelledError")
@@ -501,10 +536,8 @@ def generate_facade_init(otype: ObjectType) -> str:
{body}""" {body}"""
return f""" def __init__(self, *args, **kwargs): return f""" def __init__(self, *args, **kwargs):
self._impl = _frida.{otype.py_name}( self._impl = _frida.{otype.py_name}(*[_unwrap(a) for a in args])
*[_unwrap(a) for a in args], _apply_settings(self._impl, kwargs, {build_option_selectors(otype)})
**{{k: _unwrap(v) for k, v in kwargs.items()}},
)
setup = getattr(self, "_setup", None) setup = getattr(self, "_setup", None)
if setup is not None: if setup is not None:
setup()""" setup()"""
@@ -627,21 +660,29 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]:
if any(build_sync_param(param) is None for param in method.input_parameters): if any(build_sync_param(param) is None for param in method.input_parameters):
return None return None
signature = ["self"] params = [facade_param(param, model) for param in method.input_parameters]
for param in method.input_parameters: names = ", ".join(facade_argument(param, model) for param in method.input_parameters)
signature.append(facade_param(param, model))
names = ", ".join(param.name for param in method.input_parameters)
call = f"self._impl.{method.name}({names})" call = f"self._impl.{method.name}({names})"
if method.return_value is not None: if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model) call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) ret = facade_return_type(method, model)
return f""" def {method.name}({", ".join(signature)}) -> {ret}: return f""" def {method.name}({facade_signature(method, params)}) -> {ret}:
return {call}""" return {call}"""
def facade_signature(method: Method, params: List[str]) -> str:
return ", ".join(["self"] + (method.custom_facade_params or params))
def facade_argument(param, model: Model) -> str:
if resolve_input_object_type(param.type, model) is not None:
return f"_unwrap({param.name})"
return param.name
def facade_param(param, model: Model) -> str: def facade_param(param, model: Model) -> str:
annotation = pyi_type(param.type, model) annotation = pyi_type(param.type, model)
if param.nullable: if param.nullable:
@@ -662,14 +703,14 @@ def generate_py_async_method(method: Method, model: Model) -> Optional[str]:
if method.return_value is not None: if method.return_value is not None:
call = wrap_result(call, method.return_value.type, model) call = wrap_result(call, method.return_value.type, model)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) ret = facade_return_type(method, model)
return f""" def {method.name}({signature}) -> {ret}: return f""" def {method.name}({signature}) -> {ret}:
return {call}""" return {call}"""
def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str, str]]: def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str, str]]:
signature = ["self"] params = []
args = [] args = []
for param in method.input_parameters: for param in method.input_parameters:
if param.type.name == "Gio.Cancellable": if param.type.name == "Gio.Cancellable":
@@ -678,20 +719,24 @@ def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str
return None return None
options = resolve_options_type(param.type, model) options = resolve_options_type(param.type, model)
if options is not None: if options is not None:
signature.append("**kwargs") params.append("**kwargs")
args.append(f"_make_options(_frida.{options.py_name}, kwargs, {build_option_selectors(options)})") args.append(make_options_expr(options))
elif resolve_input_object_type(param.type, model) is not None: elif resolve_input_object_type(param.type, model) is not None:
signature.append(f"{param.name}: Optional[{pyi_type(param.type, model)}] = None") params.append(f"{param.name}: Optional[{pyi_type(param.type, model)}] = None")
args.append(f"_unwrap({param.name})") args.append(f"_unwrap({param.name})")
else: else:
signature.append(facade_param(param, model)) params.append(facade_param(param, model))
args.append(param.name) args.append(param.name)
if method.return_value is not None: if method.return_value is not None:
if build_return_marshal(method.return_value.type, model) is None: if build_return_marshal(method.return_value.type, model) is None:
return None return None
return ", ".join(signature), "".join(arg + ", " for arg in args) return facade_signature(method, params), "".join(arg + ", " for arg in args)
def make_options_expr(options: ObjectType) -> str:
return f"_make_options(_frida.{options.py_name}, kwargs, {build_option_selectors(options)})"
def build_option_selectors(options: ObjectType) -> str: def build_option_selectors(options: ObjectType) -> str:
@@ -748,13 +793,22 @@ def generate_pyi_class(otype: ObjectType, model: Model) -> List[str]:
body = [] body = []
ctor = next(iter(otype.constructors), None) primary = inline_constructor(otype)
if ctor is not None and not ctor.throws: if primary is not None:
params = pyi_parameters(ctor.input_parameters, model) params = pyi_parameters(primary.input_parameters, model)
if params is not None: if params is not None:
signature = ", ".join(["self"] + params) signature = ", ".join(["self"] + params)
body.append(f" def __init__({signature}) -> None: ...") body.append(f" def __init__({signature}) -> None: ...")
for ctor in otype.constructors:
if ctor is primary:
continue
params = pyi_parameters(ctor.input_parameters, model)
if params is None:
continue
body.append(" @staticmethod")
body.append(f" def {ctor.name}({', '.join(params)}) -> {otype.py_name}: ...")
if otype.name == "Object": if otype.name == "Object":
body.append(" def on(self, signal: str, callback: Callable[..., Any]) -> None: ...") body.append(" def on(self, signal: str, callback: Callable[..., Any]) -> None: ...")
body.append(" def off(self, signal: str, callback: Callable[..., Any]) -> None: ...") body.append(" def off(self, signal: str, callback: Callable[..., Any]) -> None: ...")
@@ -815,7 +869,13 @@ def pyi_method(method: Method, model: Model) -> Optional[str]:
if params is None: if params is None:
return None return None
signature = ", ".join(["self"] + params) signature = ", ".join(["self"] + params)
ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) out = method.optional_out_parameter
if out is not None:
ret = f"Optional[{pyi_type(out.type, model)}]"
elif method.return_value is None:
ret = "None"
else:
ret = pyi_type(method.return_value.type, model)
return f" def {method.name}({signature}) -> {ret}: ..." return f" def {method.name}({signature}) -> {ret}: ..."
@@ -860,6 +920,8 @@ def pyi_type(type: Type, model: Model) -> str:
return "dict" return "dict"
if name == "GLib.Variant": if name == "GLib.Variant":
return "Any" return "Any"
if name == "Gio.TlsCertificate":
return "str"
if resolve_enumeration(type, model) is not None: if resolve_enumeration(type, model) is not None:
return "str" return "str"
obj = resolve_object_type(type, model) obj = resolve_object_type(type, model)
@@ -901,6 +963,8 @@ def generate_extension_c(model: Model) -> str:
code += generate_interface_implementation(otype) code += generate_interface_implementation(otype)
code += generate_object_type_constructor(otype) code += generate_object_type_constructor(otype)
for ctor in factory_constructors(otype):
code += generate_factory_function(otype, ctor, build_constructor_marshals(ctor))
return code return code
@@ -1175,6 +1239,10 @@ def generate_prototypes(model: Model) -> str:
"", "",
f"static int {otype_cprefix}_init ({otype_cprefix} * self, PyObject * args, PyObject * kw);", f"static int {otype_cprefix}_init ({otype_cprefix} * self, PyObject * args, PyObject * kw);",
] ]
for ctor in factory_constructors(otype):
prototypes.append(
f"static PyObject * {otype_cprefix}_{ctor.name} (PyObject * self, PyObject * args, PyObject * kw);"
)
if implementable_interface(otype): if implementable_interface(otype):
prototypes += [ prototypes += [
f"static int {otype_cprefix}_traverse (PyObject * self, visitproc visit, void * arg);", f"static int {otype_cprefix}_traverse (PyObject * self, visitproc visit, void * arg);",
@@ -1235,13 +1303,15 @@ def generate_object_type_toplevel_definitions(model: Model) -> str:
cprefix = otype.c_symbol_prefix cprefix = otype.c_symbol_prefix
if otype.name == "Object": if otype.name == "Object":
defs.append(f"""PYFRIDA_DEFINE_BASETYPE ("_frida.{otype.py_name}", {otype.py_name}, g_object_unref, defs.append(
f"""PYFRIDA_DEFINE_BASETYPE ("_frida.{otype.py_name}", {otype.py_name}, g_object_unref,
{{ Py_tp_doc, "{otype.name}" }}, {{ Py_tp_doc, "{otype.name}" }},
{{ Py_tp_init, {cprefix}_init }}, {{ Py_tp_init, {cprefix}_init }},
{{ Py_tp_dealloc, {cprefix}_dealloc }}, {{ Py_tp_dealloc, {cprefix}_dealloc }},
{{ Py_tp_methods, {cprefix}_methods }}, {{ Py_tp_methods, {cprefix}_methods }},
{{ Py_tp_getset, {cprefix}_getsets }}, {{ Py_tp_getset, {cprefix}_getsets }},
);""") );"""
)
continue continue
parent = otype.parent parent = otype.parent
@@ -1261,12 +1331,14 @@ def generate_object_type_toplevel_definitions(model: Model) -> str:
) )
continue continue
defs.append(f"""PYFRIDA_DEFINE_TYPE ("_frida.{otype.py_name}", {otype.py_name}, {parent_name}, g_object_unref, defs.append(
f"""PYFRIDA_DEFINE_TYPE ("_frida.{otype.py_name}", {otype.py_name}, {parent_name}, g_object_unref,
{{ Py_tp_doc, "{otype.name}" }}, {{ Py_tp_doc, "{otype.name}" }},
{{ Py_tp_init, {cprefix}_init }}, {{ Py_tp_init, {cprefix}_init }},
{{ Py_tp_methods, {cprefix}_methods }}, {{ Py_tp_methods, {cprefix}_methods }},
{{ Py_tp_getset, {cprefix}_getsets }}, {{ Py_tp_getset, {cprefix}_getsets }},
);""") );"""
)
return "\n\n".join(defs) return "\n\n".join(defs)
@@ -1291,6 +1363,9 @@ def generate_object_type_methods(otype: ObjectType) -> str:
functions.append(function) functions.append(function)
entries.append(entry) entries.append(entry)
for ctor in factory_constructors(otype):
entries.append(factory_method_entry(otype, ctor))
return f"""{"".join(functions)} return f"""{"".join(functions)}
static PyMethodDef {otype.c_symbol_prefix}_methods[] = static PyMethodDef {otype.c_symbol_prefix}_methods[] =
{{ {{
@@ -1306,6 +1381,11 @@ def generate_method(otype: ObjectType, method: Method) -> Optional[Tuple[str, st
marshal = synchronous_method_return_marshal(method) marshal = synchronous_method_return_marshal(method)
if marshal is None: if marshal is None:
return None return None
out = method.optional_out_parameter
if out is not None:
marshal = build_return_marshal(out.type, method.object_type.model)
if marshal is None:
return None
params = [] params = []
for param in method.input_parameters: for param in method.input_parameters:
sync_param = build_sync_param(param) sync_param = build_sync_param(param)
@@ -1649,14 +1729,17 @@ def generate_synchronous_method(otype: ObjectType, method: Method, marshal: str,
indent = " " * (len(otype.c_symbol_prefix) + len(method.name) + 3) indent = " " * (len(otype.c_symbol_prefix) + len(method.name) + 3)
handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)" handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)"
returns_strv = method.return_value is not None and method.return_value.type.name == "utf8[]" returns_strv = method.return_value is not None and method.return_value.type.name == "utf8[]"
out = method.optional_out_parameter
call_arg_list = [handle] + [p.call_arg for p in params] call_arg_list = [handle] + [p.call_arg for p in params]
if returns_strv: if returns_strv:
call_arg_list.append("&retval_length") call_arg_list.append("&retval_length")
if out is not None:
call_arg_list.append(f"&{out.name}")
if method.throws: if method.throws:
call_arg_list.append("&error") call_arg_list.append("&error")
call = f"{method.c_identifier} ({', '.join(call_arg_list)})" call = f"{method.c_identifier} ({', '.join(call_arg_list)})"
if not params and not method.throws and not returns_strv: if not params and not method.throws and not returns_strv and out is None:
if method.return_value is None: if method.return_value is None:
body = f" {call};\n\n PyFrida_RETURN_NONE;" body = f" {call};\n\n PyFrida_RETURN_NONE;"
else: else:
@@ -1675,6 +1758,8 @@ static PyObject *
decls.append(" GError * error = NULL;") decls.append(" GError * error = NULL;")
if returns_strv: if returns_strv:
decls.append(" gint retval_length;") decls.append(" gint retval_length;")
if out is not None:
decls.append(f" {out_parameter_storage_type(out)} {out.name};")
for p in params: for p in params:
decls += [f" {line}" for line in p.decl.splitlines()] decls += [f" {line}" for line in p.decl.splitlines()]
@@ -1689,7 +1774,15 @@ static PyObject *
pre = "".join(f"\n {p.pre}\n" for p in params if p.pre) pre = "".join(f"\n {p.pre}\n" for p in params if p.pre)
if method.return_value is None: if out is not None:
invocation = f" gboolean found = {call};"
success = (
" if (found)\n"
f" result = {marshal.format(value=out.name)};\n"
" else\n"
" {\n Py_IncRef (Py_None);\n result = Py_None;\n }"
)
elif method.return_value is None:
invocation = f" {call};" invocation = f" {call};"
success = " Py_IncRef (Py_None);\n result = Py_None;" success = " Py_IncRef (Py_None);\n result = Py_None;"
elif returns_strv: elif returns_strv:
@@ -1734,6 +1827,22 @@ static PyObject *
""" """
def facade_return_type(method: Method, model: Model) -> str:
out = method.optional_out_parameter
if out is not None:
return f"Optional[{pyi_type(out.type, model)}]"
if method.return_value is None:
return "None"
return pyi_type(method.return_value.type, model)
def out_parameter_storage_type(param: Parameter) -> str:
"""What the caller declares: the out parameter's own type, less the pointer."""
c = param.type.c
assert c.endswith("*"), c
return c[:-1].strip()
def synchronous_method_return_marshal(method: Method) -> Optional[str]: def synchronous_method_return_marshal(method: Method) -> Optional[str]:
if method.is_async or method.is_property_accessor: if method.is_async or method.is_property_accessor:
return None return None
@@ -1816,6 +1925,16 @@ def build_sync_param(param: Parameter) -> Optional["SyncParam"]:
cleanup=f"g_clear_pointer (&{name}, g_variant_unref);", cleanup=f"g_clear_pointer (&{name}, g_variant_unref);",
) )
if resolve_input_object_type(param.type, param.object_type.model) is not None:
return SyncParam(
decl=f"PyObject * {name}_obj;\n{param.type.c} {name} = NULL;",
fmt="O",
parse_args=[f"&{name}_obj"],
pre=f"""if ({name}_obj != Py_None)
{name} = ({param.type.c}) PY_GOBJECT_HANDLE ({name}_obj);""",
call_arg=name,
)
return None return None
@@ -1994,6 +2113,17 @@ def build_setter_body(param: Parameter, set_call) -> Optional[str]:
g_hash_table_unref (dict); g_hash_table_unref (dict);
""" """
if type_name == "Gio.TlsCertificate":
return f""" GTlsCertificate * certificate;
if (!PyGObject_unmarshal_certificate (value, &certificate))
return -1;
{set_call("certificate")}
g_clear_object (&certificate);
"""
if resolve_input_object_type(param.type, param.object_type.model) is not None: if resolve_input_object_type(param.type, param.object_type.model) is not None:
handle = f"({param.type.c}) ((value != Py_None) ? PY_GOBJECT_HANDLE (value) : NULL)" handle = f"({param.type.c}) ((value != Py_None) ? PY_GOBJECT_HANDLE (value) : NULL)"
return f" {set_call(handle)}\n" return f" {set_call(handle)}\n"
@@ -2073,6 +2203,8 @@ def build_return_marshal(type: Type, model: Model) -> Optional[str]:
return "PyGObject_marshal_vardict ({value})" return "PyGObject_marshal_vardict ({value})"
if name == "GLib.Variant": if name == "GLib.Variant":
return "PyGObject_marshal_variant ({value})" return "PyGObject_marshal_variant ({value})"
if name == "Gio.TlsCertificate":
return "PyGObject_marshal_certificate ({value})"
enum = resolve_enumeration(type, model) enum = resolve_enumeration(type, model)
if enum is not None: if enum is not None:
@@ -2219,28 +2351,55 @@ def generate_object_type_constructor(otype: ObjectType) -> str:
if implementable_interface(otype): if implementable_interface(otype):
return generate_interface_init(otype) return generate_interface_init(otype)
ctor = next(iter(otype.constructors), None) ctor = primary_constructor(otype)
if ctor is None: if ctor is None:
return generate_bare_init(otype) return generate_bare_init(otype)
marshals: Optional[List[ParamMarshal]] = None if ctor.throws:
if not ctor.throws: return generate_unconstructable_init(otype)
marshals = []
for param in ctor.input_parameters:
marshal = build_param_marshal(param)
if marshal is None:
marshals = None
break
marshal.optional = param.optional
marshals.append(marshal)
marshals = build_constructor_marshals(ctor)
if marshals is None: if marshals is None:
return generate_unconstructable_init(otype) return generate_unconstructable_init(otype)
return generate_constructor_init(otype, ctor, marshals) return generate_constructor_init(otype, ctor, marshals)
def primary_constructor(otype: ObjectType) -> Optional[Procedure]:
if not otype.constructors:
return None
return next((c for c in otype.constructors if c.name == "new"), otype.constructors[0])
def inline_constructor(otype: ObjectType) -> Optional[Procedure]:
"""The constructor __init__ takes; one that throws is reached as a factory instead."""
ctor = primary_constructor(otype)
if ctor is None or ctor.throws:
return None
return ctor
def factory_constructors(otype: ObjectType) -> List[Procedure]:
primary = inline_constructor(otype)
return [
ctor
for ctor in otype.constructors
if ctor is not primary and not ctor.is_async and build_constructor_marshals(ctor) is not None
]
def build_constructor_marshals(ctor: Procedure) -> Optional[List["ParamMarshal"]]:
marshals = []
for param in ctor.input_parameters:
marshal = build_param_marshal(param)
if marshal is None:
return None
marshal.optional = param.optional
marshals.append(marshal)
return marshals
def generate_interface_init(otype: ObjectType) -> str: def generate_interface_init(otype: ObjectType) -> str:
n = interface_impl_names(otype) n = interface_impl_names(otype)
indent = constructor_indent(otype) indent = constructor_indent(otype)
@@ -2341,7 +2500,88 @@ static int
{generate_constructor_tail(marshals)}""" {generate_constructor_tail(marshals)}"""
def generate_argument_parse(marshals: List["ParamMarshal"]) -> str: def factory_method_entry(otype: ObjectType, ctor: Procedure) -> str:
return (
f' {{ "{ctor.name}", (PyCFunction) {otype.c_symbol_prefix}_{ctor.name}, '
"METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL },"
)
def generate_factory_function(otype: ObjectType, ctor: Procedure, marshals: List["ParamMarshal"]) -> str:
indent = " " * (len(otype.c_symbol_prefix) + len(ctor.name) + 3)
decls = [line for m in marshals for line in m.decls]
decls.append(f"{otype.c_type} * handle;")
if ctor.throws:
decls.append("GError * error = NULL;")
decls.append("PyObject * result = NULL;")
decls_block = indent_c_code("\n".join(decls), 1, prologue="\n")
keyword_decl = ""
parse_block = ""
if marshals:
keywords = ", ".join([f'"{m.keyword}"' for m in marshals] + ["NULL"])
keyword_decl = f"\n static char * keywords[] = {{ {keywords} }};"
parse_block = "\n" + generate_argument_parse(marshals, on_failure="return NULL;")
post_block = ""
for m in marshals:
if m.post is not None:
post_block += indent_c_code(m.post, 1, prologue="\n") + "\n"
call_args = [m.call_arg for m in marshals]
if ctor.throws:
call_args.append("&error")
call = f"handle = ({otype.c_type} *) {ctor.c_identifier} ({', '.join(call_args)});"
error_block = ""
if ctor.throws:
error_block = """
if (error != NULL)
{
result = PyFrida_raise (error);
goto beach;
}
"""
take_handle = f"result = PyGObject_new_take_handle (g_steal_pointer (&handle), PYFRIDA_TYPE ({otype.py_name}));"
return f"""
static PyObject *
{otype.c_symbol_prefix}_{ctor.name} (PyObject * self,
{indent}PyObject * args,
{indent}PyObject * kw)
{{{keyword_decl}{decls_block}
{parse_block}{post_block}
{call}
{error_block}
{take_handle}
{generate_factory_tail(marshals, ctor.throws)}"""
def generate_factory_tail(marshals: List["ParamMarshal"], throws: bool) -> str:
cleanups = [m.cleanup for m in marshals if m.cleanup is not None]
cleanup_block = indent_c_code("\n".join(reversed(cleanups)), 1) if cleanups else ""
if throws or constructor_needs_beach(marshals):
prologue = f"{cleanup_block}\n\n" if cleanup_block else ""
return f"""beach:
{prologue} return result;
}}
"""
if cleanup_block:
return f"""{cleanup_block}
return result;
}}
"""
return """ return result;
}
"""
def generate_argument_parse(marshals: List["ParamMarshal"], on_failure: str = "return -1;") -> str:
fmt = "" fmt = ""
optional_started = False optional_started = False
for m in marshals: for m in marshals:
@@ -2355,7 +2595,7 @@ def generate_argument_parse(marshals: List["ParamMarshal"]) -> str:
indent = " " * len(" if (!PyArg_ParseTupleAndKeywords (") indent = " " * len(" if (!PyArg_ParseTupleAndKeywords (")
parse_args_str = (",\n" + indent).join(parse_args) parse_args_str = (",\n" + indent).join(parse_args)
return f""" if (!PyArg_ParseTupleAndKeywords ({parse_args_str})) return f""" if (!PyArg_ParseTupleAndKeywords ({parse_args_str}))
return -1; {on_failure}
""" """
@@ -2528,10 +2768,12 @@ def generate_object_type_structs(model: Model) -> str:
for otype in model.regular_object_types: for otype in model.regular_object_types:
if otype.name == "Object": if otype.name == "Object":
continue continue
structs.append(f"""struct _{otype.c_symbol_prefix} structs.append(
f"""struct _{otype.c_symbol_prefix}
{{ {{
{otype.parent_c_symbol_prefix} parent; {otype.parent_c_symbol_prefix} parent;
}};""") }};"""
)
return "\n\n".join(structs) return "\n\n".join(structs)
+2 -5
View File
@@ -119,11 +119,7 @@ if aux:
), ),
"attach": MethodCustomizations( "attach": MethodCustomizations(
param_typings=["target: ProcessTarget", "**kwargs: Any"], param_typings=["target: ProcessTarget", "**kwargs: Any"],
custom_logic=( custom_logic=_RESOLVE_PID,
"pid = self._pid_of(target)\n" "options = _make_options(_frida.SessionOptions, kwargs, {})",
"pid = await self._pid_of(target)\n"
"options = _make_options(_frida.SessionOptions, kwargs, {})",
),
), ),
}, },
custom_code=CustomCode( custom_code=CustomCode(
@@ -141,6 +137,7 @@ if aux:
param_typings=["message: Any", "data: Optional[bytes] = None"], param_typings=["message: Any", "data: Optional[bytes] = None"],
custom_logic="json = _to_json(message)", custom_logic="json = _to_json(message)",
), ),
"enable_debugger": MethodCustomizations(param_typings=["port: int = 0"]),
"is_destroyed": MethodCustomizations(as_property=True), "is_destroyed": MethodCustomizations(as_property=True),
}, },
), ),
+8 -4
View File
@@ -17,6 +17,10 @@ class Model(core.Model):
class ObjectType(core.ObjectType): class ObjectType(core.ObjectType):
@cached_property
def methods(self) -> List["Method"]:
return [m for m in super().methods if not m.out_parameters or m.optional_out_parameter is not None]
@cached_property @cached_property
def py_name(self) -> str: def py_name(self) -> str:
custom = self.customizations custom = self.customizations
@@ -144,10 +148,6 @@ class Method(core.Method):
custom = self.customizations custom = self.customizations
return custom.custom_logic if custom is not None else None return custom.custom_logic if custom is not None else None
@cached_property
def facade_call_args(self) -> str:
return "".join(f"{param.name}, " for param in self.input_parameters if param.type.name != "Gio.Cancellable")
class Property(core.Property): class Property(core.Property):
pass pass
@@ -281,6 +281,7 @@ def _make_class(
type_struct, type_struct,
parent, parent,
constructors, constructors,
functions,
methods, methods,
properties, properties,
signals, signals,
@@ -295,6 +296,7 @@ def _make_class(
type_struct, type_struct,
parent, parent,
constructors, constructors,
functions,
methods, methods,
properties, properties,
signals, signals,
@@ -311,6 +313,7 @@ def _make_interface(
type_struct, type_struct,
parent, parent,
constructors, constructors,
functions,
methods, methods,
properties, properties,
signals, signals,
@@ -324,6 +327,7 @@ def _make_interface(
type_struct, type_struct,
parent, parent,
constructors, constructors,
functions,
methods, methods,
properties, properties,
signals, signals,
+1 -1
Submodule releng updated: abb8238970...dcadd49b93.
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git] [wrap-git]
url = https://github.com/frida/frida-core.git url = https://github.com/frida/frida-core.git
revision = 17.16.4 revision = 17.24.1
depth = 1 depth = 1
[provide] [provide]
+54
View File
@@ -17,6 +17,27 @@ BINDGEN_CORE = REPO / "frida-bindgen"
GIRDIR = REPO / "build" / "subprojects" / "frida-core" / "src" / "api" GIRDIR = REPO / "build" / "subprojects" / "frida-core" / "src" / "api"
OUTDIR = REPO / "build" / "bindgen-out" OUTDIR = REPO / "build" / "bindgen-out"
BUILT_EXTENSION = REPO / "build" / "frida" / "_frida.abi3.so" BUILT_EXTENSION = REPO / "build" / "frida" / "_frida.abi3.so"
CERTIFICATE = """\
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIUEoCZX+FRWNTzcvvrCE2chFvE0EgwDQYJKoZIhvcNAQEL
BQAwHDEaMBgGA1UEAwwRZnJpZGEtcHl0aG9uLXRlc3QwIBcNMjYwNzMwMjIyNjAy
WhgPMjEyNjA3MDYyMjI2MDJaMBwxGjAYBgNVBAMMEWZyaWRhLXB5dGhvbi10ZXN0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtKTIUbHLhkqRKdKqXuMc
u7q8OZD2eKdykp4Qvv+I5JcmNW7nlW+58zTCLaqUCsSrojDb8T2qvtRJbaJYAofE
P7bhb5ROOVwsyOjhvP7rZ4zRXIDAUlB7zE15yNkWL+Rh4l3wRxMOMIB2HQlurtDk
PIqPOITwRvrADKZt/73iMASwrkkqKnl/F3rLT0JQrd907sBdGGZNbJM9L4TUBT9t
MBBl6qXtDEFyBfeoqlnrLUTN/Jwyka+/6n6eZCN8g2g5tS1Lc7LFjX4iBG9ibgaH
hrNSTM+haLImFS3dtXFr4rOCAHnDYYvkCY1nuZOXannCvZowv0xczqpGLbj5IYJK
MQIDAQABo1MwUTAdBgNVHQ4EFgQUi83cV5yMTdWaVeRpgNO3vyeitMMwHwYDVR0j
BBgwFoAUi83cV5yMTdWaVeRpgNO3vyeitMMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEADScSFuasB4mgOb/kktq3O09rf/uUIa6OEjdFRec/i2+E
ZAUd5onsWQTHm4yhjqQwqVBte58tTqZB7S1HMCy5sEgEVUFnTrMUqtrf/WAnsWA6
SdjNr7h/aRnLHHysLkbl0Zo4fOIWIYSJpLn39e0OzcNBjEHN5U9HE/m/2LFku1Kb
KMTYXrhBKvVPpZLxZyoLz00EM7euaNx3juAgfQdSDfKGahf5CqqMJ/VmITISDJDR
jtN3wcv0/nZA5x/b73QCj0jqIEWqguYf4GLWitxGsSdT2dBEpT88Kz3Ibwb8uRT+
Ls3eRsdoqxiOT0E7Z44O8gjbqsYOr/jdMShwEytDwQ==
-----END CERTIFICATE-----
"""
@unittest.skipUnless( @unittest.skipUnless(
@@ -131,6 +152,29 @@ class TestExtension(unittest.TestCase):
options.argv = None options.argv = None
self.assertIsNone(options.argv) self.assertIsNone(options.argv)
def test_certificate_property_accepts_pem_and_path(self):
import tempfile
options = self._frida.RemoteDeviceOptions()
self.assertIsNone(options.certificate)
options.certificate = CERTIFICATE
self.assertEqual(options.certificate, CERTIFICATE)
with tempfile.NamedTemporaryFile("w", suffix=".pem") as f:
f.write(CERTIFICATE)
f.flush()
options.certificate = f.name
self.assertEqual(options.certificate, CERTIFICATE)
options.certificate = None
self.assertIsNone(options.certificate)
def test_certificate_property_rejects_missing_file(self):
options = self._frida.RemoteDeviceOptions()
with self.assertRaises(self._frida.InvalidArgumentError):
options.certificate = "/nonexistent.pem"
def test_vardict_property_round_trips(self): def test_vardict_property_round_trips(self):
options = self._frida.SpawnOptions() options = self._frida.SpawnOptions()
options.aux = {"name": "value", "count": 42, "flag": True} options.aux = {"name": "value", "count": 42, "flag": True}
@@ -646,6 +690,16 @@ class TestFacade(unittest.TestCase):
finally: finally:
session.detach() session.detach()
def test_attach_accepts_linker_notifier_offsets(self):
session = self.frida.get_local_device().attach(0, linker_notifier_offsets=[0x100])
session.detach()
def test_enable_debugger_defaults_to_any_port(self):
import inspect
for script in [self.frida.Script, self.frida.aio.Script]:
self.assertEqual(inspect.signature(script.enable_debugger).parameters["port"].default, 0)
def test_file_monitor_reports_changes(self): def test_file_monitor_reports_changes(self):
import os import os
import tempfile import tempfile