frida / frida-rust

Frida Rust bindings
Other
176 stars 46 forks source link

Why are there lifetimes on objects? #145

Open ajwerner opened 2 weeks ago

ajwerner commented 2 weeks ago

I don't understand the lifetimes on things like Device, Injector etc. They aren't borrowing from a stack, they own the relevant heap allocations make to construct them. The lifetimes can be literally whatever. For example:

 let mut injector: frida::Injector<'static> = frida::Injector::new();

These lifetimes makes the APIs harder to both use and understand. Is there some purpose they serve, or were they just cargo-culted from the start?

s1341 commented 2 weeks ago

I took a look and you are right, I can't see why these types have explicit lifetimes. Perhaps @fabianfreyer can comment?

ajwerner commented 2 weeks ago

I guess the idea in some cases may have been to tie the lifetime to a prerequisite object. Like if you look in frida-gum, the Iterceptor::obtain indeed ensures that the returned interceptor holds a lifetime that is shorter than that of the reference to the Gum. That seems legit enough. I suppose a similar thing could be done for the things that require a Frida.

s1341 commented 2 weeks ago

I think that that's the right way to go. We should replace *::new() with *::obtain(&'b Frida) (or whatever), to tie them to the lifetime of the Frida object.

ajwerner commented 1 week ago

Another approach here, that I think I prefer because it'll simplify the programming model would be to wrap the Frida object as a process-global singleton and track its references with refcounts. Then any derived object that assumes Frida is live should hold onto one of these owned references. That way it'll become impossible to mess up, and easier to program with because the objects will be static.

s1341 commented 1 week ago

I can live with that. But it would have to be done for both Frida and Gum.

ajwerner commented 5 days ago

What do other Frida bindings do with regards to getting a handle to Frida or gum?

ajwerner commented 5 days ago

Looking at this more, gobject is already doing atomic refcounting for us. I think rather than rust having its own refcounts, we should use g_object_ref and g_object_unref and have a clone impl to increment the ref counts. Then for objects in rust which depend on a reference existing, we can have them hold refs.

One thing I can't easily figure out is which APIs are thread safe and which aren't. It looks to me like the gum APIs are all thread safe whereas the Frida-core APIs seem to not be thread-safe, does that seem right?

ajwerner commented 5 days ago

I'm realizing I don't know enough to know what makes sense. I see now that g_object_unref is only exposed on when #[cfg(not(any(target_os = "windows", target_os = "android", target_vendor = "apple",)))]. What's that about?

oleavr commented 5 days ago

I'm realizing I don't know enough to know what makes sense. I see now that g_object_unref is only exposed on when #[cfg(not(any(target_os = "windows", target_os = "android", target_vendor = "apple",)))]. What's that about?

Our releng repo's devkit.py, which generates the devkits, renames all symbols not prefixed with frida_ so they're prefixed with _frida. The motivation is to avoid conflicts in situations where the user builds a shared library with all symbols visible and this gets loaded into a process that's already got e.g. a GLib of its own loaded. However, we haven't yet implemented this renaming logic for Microsoft and Apple toolchains, so that's why we have some weird-looking code like the above to work around this (hopefully temporary) misery 😅

ajwerner commented 5 days ago

Thanks for the hint! Given that, it seems like we should use the gobject refcounts data types an instance of a gobject, and cloning in rust should increment the refcount of the underlying gobject.

The two special cases are the "namespaces" Gum and Frida which aren't objects, but rather are magical handles to ensure the relevant runtime has been initialized. For those, we should have rust-level reference counting.

Any rust handle to a gobject pointer then should also hold onto the namespace handle to make sure we don't accidentally deinit too early.

How does that sound to folks?

oleavr commented 5 days ago

Thanks for the hint! Given that, it seems like we should use the gobject refcounts data types an instance of a gobject, and cloning in rust should increment the refcount of the underlying gobject.

The two special cases are the "namespaces" Gum and Frida which aren't objects, but rather are magical handles to ensure the relevant runtime has been initialized. For those, we should have rust-level reference counting.

Any rust handle to a gobject pointer then should also hold onto the namespace handle to make sure we don't accidentally deinit too early.

How does that sound to folks?

Sounds great to me!

s1341 commented 5 days ago

Yup sounds good to me too.