Open DayChan opened 3 years ago
:+1:
Minimum reproducible example:
import frida
import logging
from multiprocessing import Process
from threading import Thread
logging.basicConfig(level=logging.INFO, format='%(processName)s %(threadName)s %(message)s')
def list_devs():
logging.info("listing devices...")
logging.info("got these devices: %s", frida.enumerate_devices())
list_devs()
p = Process(target=list_devs, name="ChildProcess")
p.start()
t = Thread(target=list_devs, name="ChildThread")
t.start()
t.join()
logging.info("thread joined")
p.join()
loggin.info("process joined")
output:
MainProcess MainThread listing devices...
MainProcess MainThread got these devices: [Device(id="local", name="Local System", type='local'), Device(id="socket", name="Local Socket", type='remote'), Device(id="**redacted**", name="iOS Device", type='usb')]
MainProcess ChildThread listing devices...
ChildProcess MainThread listing devices...
MainProcess ChildThread got these devices: [Device(id="local", name="Local System", type='local'), Device(id="socket", name="Local Socket", type='remote'), Device(id="**redacted**", name="iOS Device", type='usb')]
MainProcess MainThread thread joined
$ pip show frida
says Version: 15.1.22
. Also tested on Version: 16.0.8
I managed to get the stack trace of the hung process on 14.0.0
:
#1 0x00007ffff7296087 in g_cond_wait (cond=0xad0138, mutex=0xad0130) at ../../../glib/glib/gthread-posix.c:1637
#2 0x00007ffff4175b35 in frida_async_task_execute (self=self@entry=0xad0160, cancellable=cancellable@entry=0x0, error=error@entry=0x7fffffffc1d0) at ../../../frida-core/src/frida.vala:2591
#3 0x00007ffff417664b in frida_device_manager_enumerate_devices_sync (self=<optimized out>, cancellable=0x0, error=0x7fffffffc220) at ../../../frida-core/src/frida.vala:202
#4 0x00007ffff4145bc2 in PyDeviceManager_enumerate_devices (self=0x7ffff374c090) at ../../../frida-python/src/_frida.c:2002
Maybe the AsyncTask class isn't meant to be shared among processes but only threads.
EDIT Frida uses Vala and GLib. It extensively uses GLib's MainLoop and MainContext, with mutexes and threads. Those mutexes and threads get copied to the new process, messing everything up. ref: https://stackoverflow.com/questions/4223553/c-does-exec-have-to-immediately-follow-fork-in-a-multi-threaded-process
I think the "solution" here is to specify in the documentation that, as Frida is a multi-thread library, forking is not supported.
A (likely nasty) workaround for those getting here like me and finding they can't use Frida from a Python thread/process - even if you aren't doing any sort of multi-threaded work with it (i.e sharing Frida objects/devices/etc across threads).
It seems what makes things break here is that import frida
already initiates internal state at import time, and therefore you can't share/call the imported module from a different thread.
This will make things fail if you import a file on the main thread that itself imports Frida, yet you call the method that does all the Frida work from a children thread.
So if you do the import frida
within the method that is running on your thread instead, it will work as this global state will then not be shared.
So instead of:
import frida
def threaded_function():
device = frida.get_usb_device()
do:
def threaded_function():
import frida
device = frida.get_usb_device()
I haven't dug more on why this happens, if my theory is correct sounds like a fix for this could be to make a way for the bindings to not have this global state initiated implicitly on the import, but rather expose a way of initializing the "Frida context" separately somehow.
Caveat emptor, this only enabled my use of running a single Frida invocation within a thread of a single script, which I'd argue is not really multi-threaded usage from Frida's perspective as all of my Frida usage is happening from the same thread. It's likely more complex usecases, or even importing multiple times, will still break in fun ways.
Use get_device() in child thread is fine, however, if I call it in child process, it will be stuck without any log.