jvm_getter/lib.rs
1// Copyright (c) 2025 Gobley Contributors.
2
3//! > ⚠️ This library depends on implementation details of Android, not its public APIs. Use at your
4//! > own risk.
5//!
6//! A tiny `no_std` library for finding [`JNI_GetCreatedJavaVMs()`] on Android 24 to 30.
7//!
8//! [`JNI_GetCreatedJavaVMs()`] is a JNI function that returns the list of Java VM instances that
9//! have been created during runtime. Unfortunately, on Android API level 30 or lower,
10//! [`JNI_GetCreatedJavaVMs()`] is **not** one of the public APIs. Therefore, the recommended way by
11//! the official Android documentation is to use `JNI_OnLoad()`, which has a `JavaVM` parameter.
12//!
13//! This is painful for cross-platform library developers, especially when the OS feature they want
14//! to use is coupled with Java on Android, as they have to provide a way to pass `JNIEnv` to the
15//! library. By using [`JNI_GetCreatedJavaVMs()`], you can retrieve the `JavaVM` instance, and you
16//! can even create `JNIEnv` instances for threads created on the Rust side.
17//!
18//! With `jvm-getter`, libraries can provide cross-platform interfaces without demaning the
19//! consumers to manually handle Java-specific logic for Android. To learn about the strategy to
20//! find [`JNI_GetCreatedJavaVMs()`] used by `jvm-getter`, please refer to the documentation of
21//! [`find_jni_get_created_java_vms()`]. For compatibility, [`find_jni_get_created_java_vms()`] is
22//! also available on Desktop platforms, including Windows, macOS, and Linux.
23//!
24//! [`JNI_GetCreatedJavaVMs()`]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNI_GetCreatedJavaVMs
25
26#![no_std]
27
28#[cfg(all(target_os = "android", feature = "art-parsing"))]
29mod android;
30#[cfg(all(target_family = "unix", feature = "sym-search-unix"))]
31mod unix;
32#[cfg(all(target_os = "windows", feature = "sym-search-windows"))]
33mod windows;
34
35use jni_sys::{jint, jsize, JavaVM};
36
37/// The function pointer type of `JNI_GetCreatedJavaVMs()`.
38#[allow(non_camel_case_types, non_snake_case)]
39pub type JNI_GetCreatedJavaVMs =
40 unsafe extern "system" fn(vmBuf: *mut *mut JavaVM, bufLen: jsize, nVMs: *mut jsize) -> jint;
41
42/// Finds the current process's [JNI_GetCreatedJavaVMs]. For compatibility, this function is
43/// also available on Desktop platforms other than Android, including Windows, macOS, and Linux.
44///
45/// # Strategy
46///
47/// This function finds the address where the Android Runtime is loaded, parses `libart.so` using
48/// [goblin], finds the location of [JNI_GetCreatedJavaVMs] in `libart.so`, and computes its
49/// location in the memory by adding an offset to the address of `libart.so`.
50///
51/// On API level 24 or higher, `dlopen`ing private API results in an runtime error. Thus, we locate
52/// `libart.so` by iterating over the loaded shared object list of the current process using
53/// `dl_iterate_phdr`.
54///
55/// Since `dlsym` is also prohibited for private APIs, using [goblin] is necessary.
56///
57/// # Safety
58///
59/// This function depends on implementation details of Android, not its public APIs. Use at your
60/// own risk.
61pub unsafe fn find_jni_get_created_java_vms() -> Option<JNI_GetCreatedJavaVMs> {
62 // For API level 31 or higher, or level 23 or lower, where JNI_GetCreatedJavaVMs is a public
63 // API, we can just use `dlsym` to find the symbol.
64 #[cfg(all(target_family = "unix", feature = "sym-search-unix"))]
65 {
66 let symbol = unix::find_jni_get_created_java_vms_from_current_process();
67 if let Some(symbol) = symbol {
68 return Some(symbol);
69 }
70 }
71
72 #[cfg(all(target_os = "windows", feature = "sym-search-windows"))]
73 {
74 let symbol = windows::find_jni_get_created_java_vms_from_current_process();
75 if let Some(symbol) = symbol {
76 return Some(symbol);
77 }
78 }
79
80 #[cfg(all(target_os = "android", feature = "art-parsing"))]
81 {
82 use core::mem::MaybeUninit;
83
84 let mut art_library_filename = MaybeUninit::uninit();
85 let art_library_filename = android::get_art_library_filename(&mut art_library_filename);
86 let symbol =
87 android::find_jni_get_created_java_vms_from_library_filename(art_library_filename);
88 if let Some(symbol) = symbol {
89 return Some(symbol);
90 }
91 }
92
93 None
94}