Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why are there lifetimes on objects? #145

Open
ajwerner opened this issue Aug 28, 2024 · 12 comments
Open

Why are there lifetimes on objects? #145

ajwerner opened this issue Aug 28, 2024 · 12 comments

Comments

@ajwerner
Copy link
Contributor

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
Copy link
Contributor

s1341 commented Aug 29, 2024

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

@ajwerner
Copy link
Contributor Author

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
Copy link
Contributor

s1341 commented Sep 1, 2024

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
Copy link
Contributor Author

ajwerner commented Sep 6, 2024

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
Copy link
Contributor

s1341 commented Sep 8, 2024

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

@ajwerner
Copy link
Contributor Author

ajwerner commented Sep 9, 2024

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

@ajwerner
Copy link
Contributor Author

ajwerner commented Sep 9, 2024

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
Copy link
Contributor Author

ajwerner commented Sep 9, 2024

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
Copy link
Member

oleavr commented Sep 10, 2024

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
Copy link
Contributor Author

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
Copy link
Member

oleavr commented Sep 10, 2024

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
Copy link
Contributor

s1341 commented Sep 10, 2024

Yup sounds good to me too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants
@oleavr @ajwerner @s1341 and others