feat(session): wrap compile_script + create_script_from_bytes

frida-core has shipped a bytecode path for years:

  frida_session_compile_script_sync(session, source, opts) -> GBytes
  frida_session_create_script_from_bytes_sync(session, bytes, opts) -> FridaScript

The Python and Node bindings expose both. frida-rust 0.17.2 only
wraps the source-string path through `create_script`, leaving callers
to either drop into frida-sys themselves or recompile on every load.

This adds two thin methods on `Session`:

  fn compile_script(&self, source, &mut opts) -> Result<Vec<u8>>
  fn create_script_from_bytes<'b>(&self, bytes, &mut opts) -> Result<Script<'b>>

`compile_script` copies the returned GBytes into an owned `Vec<u8>`
and `g_bytes_unref`s the original. `create_script_from_bytes` wraps
`g_bytes_new` / `g_bytes_unref` around the C call.

Bytecode is runtime-specific — callers must compile and load with
matching `ScriptOption::set_runtime`. Documented inline.

Use cases this unblocks:
- Startup speedup: skip the JS parse pass at every attach.
- Soft script protection: ship .bin artifacts instead of readable JS.
- Self-attach `compile_script` workflows so build pipelines can emit
  bytecode without spinning up an extra process.
This commit is contained in:
yw committed 2026-05-13 17:17:36 +08:00
1 parent 9f72b70431
commit 6a92b72526
1 file changed
+65 -1
+65 -1
View File
@@ -4,7 +4,7 @@
* Licence: wxWindows Library Licence, Version 3.1
*/
use frida_sys::_FridaSession;
use frida_sys::{_FridaSession, g_bytes_get_data, g_bytes_new, g_bytes_unref, gsize};
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr::null_mut;
@@ -62,6 +62,70 @@ impl<'a> Session<'a> {
}
}
/// Creates a [`Script`] from a pre-compiled bytecode blob produced by
/// `frida_session_compile_script_sync` (or e.g. `session.compileScript`
/// in the Node binding).
///
/// Note: bytecode is runtime-specific. A blob produced with the QJS
/// runtime won't load under V8 and vice-versa — make sure the bytecode
/// was compiled with the same `ScriptRuntime` you pass here.
pub fn create_script_from_bytes<'b>(
&'a self,
bytes: &[u8],
option: &mut ScriptOption,
) -> Result<Script<'b>>
where
'a: 'b,
{
let mut error: *mut frida_sys::GError = std::ptr::null_mut();
let script = unsafe {
let g = g_bytes_new(bytes.as_ptr() as _, bytes.len() as _);
let s = frida_sys::frida_session_create_script_from_bytes_sync(
self.session_ptr,
g,
option.as_mut_ptr(),
null_mut(),
&mut error,
);
g_bytes_unref(g);
s
};
if error.is_null() {
Ok(Script::from_raw(script))
} else {
Err(Error::ScriptCreationError)
}
}
/// Compile JS source to V8/QJS bytecode. Runtime is taken from `option`
/// (default = QJS as of frida 16.x). The returned bytes can later be
/// loaded with [`create_script_from_bytes`](Self::create_script_from_bytes).
pub fn compile_script(&self, source: &str, option: &mut ScriptOption) -> Result<Vec<u8>> {
let source_c = CString::new(source).map_err(|_| Error::CStringFailed)?;
let mut error: *mut frida_sys::GError = std::ptr::null_mut();
unsafe {
let g = frida_sys::frida_session_compile_script_sync(
self.session_ptr,
source_c.as_ptr(),
option.as_mut_ptr(),
null_mut(),
&mut error,
);
if !error.is_null() {
return Err(Error::ScriptCreationError);
}
let mut len: gsize = 0;
let raw = g_bytes_get_data(g, &mut len) as *const u8;
let out = if raw.is_null() || len == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(raw, len as usize).to_vec()
};
g_bytes_unref(g);
Ok(out)
}
}
/// Detaches the current session.
pub fn detach(&self) -> Result<()> {
let mut error: *mut frida_sys::GError = std::ptr::null_mut();