mirror of
https://github.com/frida/frida-core.git
synced 2026-11-13 14:31:32 -10:00
Compare commits
No files matched your search
+1
-1
Submodule releng updated: d6b56b460b...4e51c61880.
Binary file not shown.
@@ -18,7 +18,6 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -982,10 +981,10 @@ public class HelperBackend {
|
||||
icon.setBounds(0, 0, width, height);
|
||||
icon.draw(canvas);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
|
||||
ByteArrayOutputStream png = new ByteArrayOutputStream();
|
||||
bitmap.compress(CompressFormat.PNG, 100, png);
|
||||
|
||||
return output.toString();
|
||||
return Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
|
||||
|
||||
@@ -29,7 +29,9 @@ use crate::bindings::{
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
use crate::gum_modules::enumerate_symbols_in_range;
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
use crate::gum_windows::enumerate_exports_in_range;
|
||||
use crate::gum_windows::{enumerate_exports_in_range, enumerate_imports_in_module};
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
use crate::bindings::{GumFoundImportFunc, GumImportDetails, GumImportType_GUM_IMPORT_FUNCTION};
|
||||
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
|
||||
use crate::gum_injected::enumerate_exports_in_range;
|
||||
|
||||
@@ -184,6 +186,11 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
|
||||
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
|
||||
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
|
||||
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
{
|
||||
(*iface).enumerate_imports =
|
||||
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_imports, 0));
|
||||
}
|
||||
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
{
|
||||
@@ -317,6 +324,32 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_imports(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundImportFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let base = (*module).range.base_address;
|
||||
|
||||
let mut on_import = |library: *const gchar, name: *const gchar, slot: u64, address: u64| {
|
||||
let details = GumImportDetails {
|
||||
type_: GumImportType_GUM_IMPORT_FUNCTION,
|
||||
name,
|
||||
module: library,
|
||||
address,
|
||||
slot,
|
||||
};
|
||||
|
||||
func.unwrap()(&details as *const GumImportDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_imports_in_module(base, &mut on_import);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_symbols(
|
||||
self_: *mut GumModule,
|
||||
@@ -368,6 +401,9 @@ const SYMBOL_IS_FUNCTION: u8 = 2;
|
||||
/// it returns false.
|
||||
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
pub(crate) type FoundImportCallback<'a> = dyn FnMut(*const gchar, *const gchar, u64, u64) -> bool + 'a;
|
||||
|
||||
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
|
||||
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
|
||||
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
|
||||
@@ -469,6 +505,11 @@ mod symbolication {
|
||||
return crate::xnu_mapped::export_named(name);
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if crate::kernel::in_copy() {
|
||||
return crate::kernel::export_named(name);
|
||||
}
|
||||
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
|
||||
match table.find_symbol_by_name(name) {
|
||||
|
||||
@@ -23,7 +23,10 @@ use crate::{
|
||||
#[cfg(feature = "xnu-core")]
|
||||
use crate::bindings::{g_object_unref, gum_barebone_register_module};
|
||||
#[cfg(feature = "linux-injected")]
|
||||
use crate::bindings::gum_barebone_unregister_thread;
|
||||
use crate::bindings::{
|
||||
GError, GumBareboneStreamOps, g_io_error_from_errno, g_io_error_quark, g_set_error_literal,
|
||||
g_strerror, gint, gssize, gum_barebone_unregister_thread,
|
||||
};
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
use crate::bindings::{
|
||||
g_variant_get_boolean, g_variant_new,
|
||||
@@ -176,6 +179,51 @@ fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpoin
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
const GUM_PAGE_RWX: u32 = 7;
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
|
||||
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
|
||||
poll: Some(crate::linux::user::frida_poll),
|
||||
read: Some(read_descriptor),
|
||||
write: Some(write_descriptor),
|
||||
close: Some(close_descriptor),
|
||||
};
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn read_descriptor(handle: gpointer, buffer: gpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
transferred(crate::linux::user::read(handle as i32, buffer as *mut u8, count as usize), error)
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn write_descriptor(handle: gpointer, buffer: gconstpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
transferred(crate::linux::user::write(handle as i32, buffer as *const u8, count as usize),
|
||||
error)
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn close_descriptor(handle: gpointer, error: *mut *mut GError) -> gboolean {
|
||||
(transferred(crate::linux::user::close(handle as i32), error) >= 0) as gboolean
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn transferred(result: isize, error: *mut *mut GError) -> gssize {
|
||||
if result < 0 {
|
||||
let errno = -result as gint;
|
||||
unsafe {
|
||||
g_set_error_literal(error, g_io_error_quark(), g_io_error_from_errno(errno) as gint,
|
||||
g_strerror(errno))
|
||||
};
|
||||
return -1;
|
||||
}
|
||||
result as gssize
|
||||
}
|
||||
|
||||
unsafe fn what_changed(first_page: u64, shadow: *const u8, total: usize) -> Option<(usize, usize)> {
|
||||
let live = unsafe { core::slice::from_raw_parts(first_page as *const u8, total) };
|
||||
let ours = unsafe { core::slice::from_raw_parts(shadow, total) };
|
||||
@@ -937,3 +985,47 @@ pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_get_system_error() -> gint {
|
||||
match errno_slot() {
|
||||
Some(slot) => unsafe { *slot },
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_set_system_error(value: gint) {
|
||||
if let Some(slot) = errno_slot() {
|
||||
unsafe { *slot = value };
|
||||
}
|
||||
}
|
||||
|
||||
// errno is thread-local behind a function that hands back its address:
|
||||
// __errno_location on glibc and musl, __errno on Bionic.
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn errno_slot() -> Option<*mut gint> {
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
if !crate::kernel::in_copy() {
|
||||
return None;
|
||||
}
|
||||
|
||||
static LOCATE: AtomicUsize = AtomicUsize::new(0);
|
||||
let mut locate = LOCATE.load(Ordering::Relaxed);
|
||||
if locate == 0 {
|
||||
locate = crate::kernel::export_named("__errno_location") as usize;
|
||||
if locate == 0 {
|
||||
locate = crate::kernel::export_named("__errno") as usize;
|
||||
}
|
||||
if locate == 0 {
|
||||
return None;
|
||||
}
|
||||
LOCATE.store(locate, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let locate: unsafe extern "C" fn() -> *mut gint = unsafe { core::mem::transmute(locate) };
|
||||
Some(unsafe { locate() })
|
||||
}
|
||||
@@ -3,26 +3,240 @@
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GumMemoryRange,
|
||||
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_object_unref, gboolean, gpointer,
|
||||
gsize, guint, gum_barebone_register_module, gum_mprotect, GumFoundRangeFunc,
|
||||
GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc, GumRangeDetails,
|
||||
GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GError,
|
||||
GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE, GIOErrorEnum_G_IO_ERROR_FAILED,
|
||||
GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT, GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
|
||||
GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED, GumBareboneStreamOps, GumMemoryRange,
|
||||
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_free, g_io_error_quark, g_malloc,
|
||||
g_object_unref, g_set_error_literal, g_utf8_to_utf16, g_utf16_to_utf8, gboolean, gchar,
|
||||
gint, gpointer, gsize, gssize, guint, gum_barebone_register_module, gum_mprotect,
|
||||
GumFoundRangeFunc, GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc,
|
||||
GumRangeDetails, GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
|
||||
gconstpointer, gum_thread_details_copy,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
gum::{self, FoundExportCallback, FoundImportCallback},
|
||||
kernel,
|
||||
};
|
||||
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
|
||||
use alloc::ffi::CString;
|
||||
#[cfg(not(feature = "winnt"))]
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "stdcall" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "win64" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "C" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use windows_fn;
|
||||
|
||||
pub struct FileApi {
|
||||
pub read_file: windows_fn!(usize, *mut u8, u32, *mut u32, usize => i32),
|
||||
pub write_file: windows_fn!(usize, *const u8, u32, *mut u32, usize => i32),
|
||||
pub close_handle: windows_fn!(usize => i32),
|
||||
pub format_message: windows_fn!(u32, usize, u32, u32, *mut u8, u32, usize => u32),
|
||||
}
|
||||
|
||||
pub struct TextApi {
|
||||
pub multi_byte_to_wide_char: windows_fn!(u32, u32, *const u8, i32, *mut u16, i32 => i32),
|
||||
pub wide_char_to_multi_byte:
|
||||
windows_fn!(u32, u32, *const u16, i32, *mut u8, i32, usize, usize => i32),
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"windows".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_get_system_error() -> crate::bindings::gint {
|
||||
kernel::thread_error() as crate::bindings::gint
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_set_system_error(value: crate::bindings::gint) {
|
||||
kernel::set_thread_error(value as u32)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_ansi_string_to_utf8(str_ansi: *const gchar, length: gint) -> *mut gchar {
|
||||
let ansi = unsafe { bytes_of(str_ansi as *const u8, length) };
|
||||
match kernel::text_api() {
|
||||
Some(api) => ansi_to_utf8(api, ansi),
|
||||
None => handed_to_glib(ansi.iter().map(|&byte| byte as char).collect::<String>().as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_ansi_string_from_utf8(str_utf8: *const gchar) -> *mut gchar {
|
||||
match kernel::text_api() {
|
||||
Some(api) => ansi_from_utf8(api, str_utf8),
|
||||
None => {
|
||||
let utf8 = unsafe { core::ffi::CStr::from_ptr(str_utf8) }.to_str().unwrap();
|
||||
let latin1: Vec<u8> =
|
||||
utf8.chars().map(|c| if (c as u32) <= 0xff { c as u8 } else { b'?' }).collect();
|
||||
handed_to_glib(&latin1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn bytes_of<'a>(str: *const u8, length: gint) -> &'a [u8] {
|
||||
let length = if length < 0 {
|
||||
unsafe { core::ffi::CStr::from_ptr(str as *const core::ffi::c_char) }.to_bytes().len()
|
||||
} else {
|
||||
length as usize
|
||||
};
|
||||
unsafe { core::slice::from_raw_parts(str, length) }
|
||||
}
|
||||
|
||||
fn ansi_to_utf8(api: &TextApi, ansi: &[u8]) -> *mut gchar {
|
||||
let count = unsafe {
|
||||
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
|
||||
ptr::null_mut(), 0)
|
||||
};
|
||||
let mut wide = alloc::vec![0u16; count as usize];
|
||||
unsafe {
|
||||
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
|
||||
wide.as_mut_ptr(), count);
|
||||
g_utf16_to_utf8(wide.as_ptr(), count as _, ptr::null_mut(), ptr::null_mut(),
|
||||
ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
fn ansi_from_utf8(api: &TextApi, utf8: *const gchar) -> *mut gchar {
|
||||
unsafe {
|
||||
let wide = g_utf8_to_utf16(utf8, -1, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
let size = (api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ptr::null_mut(), 0, 0, 0);
|
||||
let ansi = g_malloc(size as gsize) as *mut u8;
|
||||
(api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ansi, size, 0, 0);
|
||||
g_free(wide as gpointer);
|
||||
ansi as *mut gchar
|
||||
}
|
||||
}
|
||||
|
||||
fn handed_to_glib(bytes: &[u8]) -> *mut gchar {
|
||||
unsafe {
|
||||
let copy = g_malloc((bytes.len() + 1) as gsize) as *mut u8;
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), copy, bytes.len());
|
||||
*copy.add(bytes.len()) = 0;
|
||||
copy as *mut gchar
|
||||
}
|
||||
}
|
||||
|
||||
const CP_ACP: u32 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
|
||||
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
|
||||
}
|
||||
|
||||
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
|
||||
poll: Some(kernel::poll_handles),
|
||||
read: Some(read_handle),
|
||||
write: Some(write_handle),
|
||||
close: Some(close_handle),
|
||||
};
|
||||
|
||||
unsafe extern "C" fn read_handle(handle: gpointer, buffer: gpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
let mut transferred = 0u32;
|
||||
let succeeded = unsafe {
|
||||
(kernel::file_api().read_file)(handle as usize, buffer as *mut u8, count as u32,
|
||||
&mut transferred, 0)
|
||||
};
|
||||
if succeeded == 0 {
|
||||
return failed(error);
|
||||
}
|
||||
transferred as gssize
|
||||
}
|
||||
|
||||
unsafe extern "C" fn write_handle(handle: gpointer, buffer: gconstpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
let mut transferred = 0u32;
|
||||
let succeeded = unsafe {
|
||||
(kernel::file_api().write_file)(handle as usize, buffer as *const u8, count as u32,
|
||||
&mut transferred, 0)
|
||||
};
|
||||
if succeeded == 0 {
|
||||
return failed(error);
|
||||
}
|
||||
transferred as gssize
|
||||
}
|
||||
|
||||
unsafe extern "C" fn close_handle(handle: gpointer, error: *mut *mut GError) -> gboolean {
|
||||
let succeeded = unsafe { (kernel::file_api().close_handle)(handle as usize) };
|
||||
if succeeded == 0 {
|
||||
failed(error);
|
||||
return 0;
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn failed(error: *mut *mut GError) -> gssize {
|
||||
let code = kernel::thread_error();
|
||||
let message = CString::new(describe_error(code)).unwrap();
|
||||
unsafe {
|
||||
g_set_error_literal(error, g_io_error_quark(), io_error_from_win32(code) as gint,
|
||||
message.as_ptr())
|
||||
};
|
||||
-1
|
||||
}
|
||||
|
||||
fn describe_error(code: u32) -> String {
|
||||
let mut ansi = [0u8; 512];
|
||||
let length = unsafe {
|
||||
(kernel::file_api().format_message)(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, code, 0,
|
||||
ansi.as_mut_ptr(), ansi.len() as u32, 0)
|
||||
};
|
||||
let utf8 = ansi_to_utf8(kernel::text_api().unwrap(), &ansi[..length as usize]);
|
||||
let message = unsafe { core::ffi::CStr::from_ptr(utf8) }.to_str().unwrap().trim_end();
|
||||
let described = String::from(message);
|
||||
unsafe { g_free(utf8 as gpointer) };
|
||||
described
|
||||
}
|
||||
|
||||
fn io_error_from_win32(code: u32) -> u32 {
|
||||
match code {
|
||||
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
|
||||
ERROR_ACCESS_DENIED => GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED,
|
||||
ERROR_INVALID_HANDLE | ERROR_INVALID_PARAMETER => GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT,
|
||||
ERROR_BROKEN_PIPE => GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE,
|
||||
_ => GIOErrorEnum_G_IO_ERROR_FAILED,
|
||||
}
|
||||
}
|
||||
|
||||
const FORMAT_MESSAGE_IGNORE_INSERTS: u32 = 0x200;
|
||||
const FORMAT_MESSAGE_FROM_SYSTEM: u32 = 0x1000;
|
||||
const ERROR_FILE_NOT_FOUND: u32 = 2;
|
||||
const ERROR_PATH_NOT_FOUND: u32 = 3;
|
||||
const ERROR_ACCESS_DENIED: u32 = 5;
|
||||
const ERROR_INVALID_HANDLE: u32 = 6;
|
||||
const ERROR_INVALID_PARAMETER: u32 = 87;
|
||||
const ERROR_BROKEN_PIPE: u32 = 109;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
if crate::on_js_thread() {
|
||||
@@ -653,6 +867,14 @@ const INTEGER_OVERFLOW: u32 = 0xc000_0095;
|
||||
const BREAKPOINT: u32 = 0x8000_0003;
|
||||
const SINGLE_STEP: u32 = 0x8000_0004;
|
||||
|
||||
pub(crate) unsafe fn enumerate_imports_in_module(base: u64, callback: &mut FoundImportCallback) {
|
||||
if crate::running_in_a_process() {
|
||||
kernel::enumerate_imports(base as _, &mut |library, name, slot, address| {
|
||||
callback(library as *const crate::bindings::gchar, name as *const crate::bindings::gchar, slot, address)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![no_std]
|
||||
#![cfg_attr(feature = "linux", feature(linkage))]
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "xnu-core",
|
||||
@@ -1120,13 +1121,42 @@ unsafe fn init_gum() {
|
||||
// do. A copy in a process would take it from the half that uses it.
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
pub(crate) unsafe fn init_gum_without_exceptor() {
|
||||
unsafe { init_gum_with_exceptor(false) };
|
||||
unsafe {
|
||||
init_gum_with_exceptor(false);
|
||||
warm_async_io();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn warm_async_io() {
|
||||
unsafe {
|
||||
let task = bindings::g_task_new(core::ptr::null_mut(), core::ptr::null_mut(), None,
|
||||
core::ptr::null_mut());
|
||||
bindings::g_object_unref(task as bindings::gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
// TEMPORARY: g_io_watch_funcs lives in GLib's giounix.c / giowin32.c, and a
|
||||
// freestanding GLib builds neither, so gobject's gsourceclosure.c is left taking the
|
||||
// address of a symbol nothing defines. The kernel module must resolve every symbol,
|
||||
// and the agent never creates a GIOChannel watch, so stand in with a placeholder that
|
||||
// GLib's own definition overrides once the freestanding build ships one.
|
||||
#[cfg(feature = "linux")]
|
||||
#[unsafe(no_mangle)]
|
||||
#[linkage = "weak"]
|
||||
pub static g_io_watch_funcs: bindings::GSourceFuncs = bindings::GSourceFuncs {
|
||||
prepare: None,
|
||||
check: None,
|
||||
dispatch: None,
|
||||
finalize: None,
|
||||
closure_callback: None,
|
||||
closure_marshal: None,
|
||||
};
|
||||
|
||||
unsafe fn init_gum_with_exceptor(exceptor: bool) {
|
||||
unsafe {
|
||||
bindings::g_set_panic_handler(Some(signed_to_be_called_back(frida_panic_handler, 0)), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
bindings::glib_enable_io_features();
|
||||
if exceptor {
|
||||
bindings::gum_exceptor_obtain();
|
||||
}
|
||||
@@ -1380,11 +1410,11 @@ pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> boo
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
static mut WORK_SOURCE: *mut GSource = ptr::null_mut();
|
||||
|
||||
#[cfg(any(feature = "winnt", feature = "linux-injected"))]
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
|
||||
static mut WAKEUP_POLLFD: bindings::GPollFD =
|
||||
bindings::GPollFD { fd: -1, events: 0, revents: 0, user_data: ptr::null_mut() };
|
||||
|
||||
#[cfg(any(feature = "winnt", feature = "linux-injected"))]
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
|
||||
pub(crate) fn watch_a_descriptor(context: *mut GMainContext, fd: i32,
|
||||
poll: unsafe extern "C" fn(*mut bindings::GPollFD, u32, i32) -> i32) {
|
||||
const G_IO_IN: u16 = 1;
|
||||
|
||||
@@ -25,14 +25,6 @@ impl Arena {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn said(&self) -> &str {
|
||||
let bytes =
|
||||
unsafe { core::slice::from_raw_parts((self.begins + SAID) as *const u8, SAID_SIZE) };
|
||||
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(SAID_SIZE);
|
||||
|
||||
core::str::from_utf8(&bytes[..length]).unwrap_or("")
|
||||
}
|
||||
|
||||
pub fn page_size(&self) -> u32 {
|
||||
self.word(PAGE_SIZE).load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
use crate::bindings::{
|
||||
@@ -857,31 +856,10 @@ pub fn serve_register_requests() {
|
||||
|
||||
pub fn report_what_the_copies_hit() {
|
||||
for placed in unsafe { placements() }.values() {
|
||||
let Some(hit) = what_it_says(placed) else {
|
||||
if what_it_says(placed).is_none() {
|
||||
continue;
|
||||
};
|
||||
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
|
||||
|
||||
let arena = Arena::at(placed.arena);
|
||||
let said = arena.said();
|
||||
if hit == super::user::SPOKE {
|
||||
native::log(&alloc::format!("copy in {}: {}\n", pid_reported_by(placed), said));
|
||||
} else if hit == super::user::PANICKED {
|
||||
native::log(&alloc::format!("copy in {} gave up: {}\n", pid_reported_by(placed), said));
|
||||
} else {
|
||||
let kind = read_address(placed.arena + super::arena::FAULT_KIND);
|
||||
native::log(&alloc::format!(
|
||||
"copy in {} died on signal {} ({}) at {:#x}, pc {}, lr {}, image at {:#x}, {}\n",
|
||||
pid_reported_by(placed),
|
||||
kind as u32,
|
||||
(kind >> 32) as u32,
|
||||
read_address(placed.arena + super::arena::FAULT_ADDRESS),
|
||||
relative_to_image(read_address(placed.arena + super::arena::FAULT_PC), placed),
|
||||
relative_to_image(read_address(placed.arena + super::arena::FAULT_LR), placed),
|
||||
placed.base,
|
||||
said
|
||||
));
|
||||
}
|
||||
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,16 +877,6 @@ fn bump_to(address: usize, value: u32) {
|
||||
unsafe { (address as *mut u32).write_volatile(value) };
|
||||
}
|
||||
|
||||
fn relative_to_image(address: u64, placed: &Placement) -> String {
|
||||
let base = placed.base as u64;
|
||||
let size = crate::own_range().1 as u64;
|
||||
if address >= base && address - base < size {
|
||||
alloc::format!("image+{:#x}", address - base)
|
||||
} else {
|
||||
alloc::format!("{:#x}", address)
|
||||
}
|
||||
}
|
||||
|
||||
const NOTHING_MORE: u32 = 0;
|
||||
|
||||
fn watch_the_threads() {
|
||||
@@ -1134,10 +1102,6 @@ fn wake_the_copy(placed: &Placement) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_address(address: usize) -> u64 {
|
||||
unsafe { (address as *const u64).read_volatile() }
|
||||
}
|
||||
|
||||
fn read_word(address: usize) -> u32 {
|
||||
unsafe { (address as *const u32).read_volatile() }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::ffi::c_void;
|
||||
use core::ffi::{CStr, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{
|
||||
@@ -112,6 +112,22 @@ pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCal
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_named(wanted: &str) -> u64 {
|
||||
let mut found: u64 = 0;
|
||||
for image in mapped_images() {
|
||||
enumerate_exports_in_range(image.base, image.base + image.size, &mut |name, address| {
|
||||
if !name.is_null() && unsafe { CStr::from_ptr(name as *const _) }.to_bytes() == wanted.as_bytes() {
|
||||
found = address;
|
||||
}
|
||||
found == 0
|
||||
});
|
||||
if found != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
pub fn enumerate_symbols_in_range(from: u64, to: u64, found: &mut FoundSymbolCallback<'_>) {
|
||||
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
|
||||
return;
|
||||
|
||||
@@ -595,7 +595,7 @@ struct TimeSpec {
|
||||
nanoseconds: i64,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_poll(ufds: *mut crate::bindings::GPollFD, nfds: u32,
|
||||
pub(crate) unsafe extern "C" fn frida_poll(ufds: *mut crate::bindings::GPollFD, nfds: u32,
|
||||
timeout: i32) -> i32 {
|
||||
const MAX_FDS: usize = 16;
|
||||
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
|
||||
@@ -1204,6 +1204,18 @@ pub fn names_in(directory: &core::ffi::CStr) -> Vec<String> {
|
||||
names
|
||||
}
|
||||
|
||||
pub(crate) fn read(fd: i32, buffer: *mut u8, count: usize) -> isize {
|
||||
syscall(READ, fd as usize, buffer as usize, count, 0, 0, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn write(fd: i32, buffer: *const u8, count: usize) -> isize {
|
||||
syscall(WRITE, fd as usize, buffer as usize, count, 0, 0, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn close(fd: i32) -> isize {
|
||||
syscall(CLOSE, fd as usize, 0, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
pub fn contents_of(path: &core::ffi::CStr) -> Vec<u8> {
|
||||
let file = syscall(OPENAT, WORKING_DIRECTORY, path.as_ptr() as usize, READ_ONLY, 0, 0, 0);
|
||||
if file < 0 {
|
||||
|
||||
+124
-12
@@ -192,7 +192,11 @@ fn block_until_signalled_or_timed_out(semaphore: u32, timeout_us: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
pub fn poke_loop_wakeup() {
|
||||
if in_copy() {
|
||||
crate::win9x_user::poke_loop_wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wake(token: *const u8) {
|
||||
(primitives().wake)(token)
|
||||
@@ -220,7 +224,8 @@ pub fn inject_agent(pid: u32) -> u32 {
|
||||
0xff, 0x74, 0x24, 0x04,
|
||||
0xba, 0, 0, 0, 0,
|
||||
0xff, 0xd2,
|
||||
0xeb, 0xfe,
|
||||
0x59,
|
||||
0xc3,
|
||||
];
|
||||
payload[5..9].copy_from_slice(&entry.to_le_bytes());
|
||||
|
||||
@@ -244,6 +249,7 @@ pub fn inject_agent(pid: u32) -> u32 {
|
||||
unsafe {
|
||||
targets().insert(observed, Target {
|
||||
arena: injection.arena,
|
||||
thread: injection.thread,
|
||||
stack: injection.stack,
|
||||
image_base,
|
||||
})
|
||||
@@ -532,15 +538,15 @@ pub fn detach_from_process(pid: u32) -> bool {
|
||||
};
|
||||
let arena = target.arena;
|
||||
|
||||
unsafe { DEPARTING_THREAD = target.thread };
|
||||
unsafe { ((arena + STOP_REQUEST) as *mut u32).write_volatile(1) };
|
||||
if !await_flag(arena + WORKER_STOPPED) || !await_flag(arena + MAIN_STOPPED) {
|
||||
wake_copy(arena);
|
||||
if !await_flag(arena + WORKER_STOPPED) || !await_flag(arena + MAIN_STOPPED)
|
||||
|| !await_departure()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A thread sets the flag before it leaves. Thus wait for the time it needs to run its last
|
||||
// instructions and return into KERNEL32.
|
||||
wait(core::ptr::addr_of!(TEARDOWN_TOKEN), Some(TEARDOWN_GRACE_US), &mut || false);
|
||||
|
||||
forget_hold(arena);
|
||||
|
||||
unsafe {
|
||||
@@ -556,13 +562,26 @@ pub fn detach_from_process(pid: u32) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn await_departure() -> bool {
|
||||
for _ in 0..INJECTION_ATTEMPTS {
|
||||
if unsafe { (&raw const DEPARTING_THREAD).read_volatile() } == 0 {
|
||||
return true;
|
||||
}
|
||||
wait(core::ptr::addr_of!(DEPARTURE_TOKEN), Some(INJECTION_POLL_US), &mut || false);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
struct Target {
|
||||
arena: u32,
|
||||
thread: u32,
|
||||
stack: u32,
|
||||
image_base: u32,
|
||||
}
|
||||
|
||||
static mut TEARDOWN_TOKEN: u8 = 0;
|
||||
static mut DEPARTING_THREAD: u32 = 0;
|
||||
static mut DEPARTURE_TOKEN: u8 = 0;
|
||||
|
||||
pub fn arena_for_pid(pid: u32) -> Option<u64> {
|
||||
unsafe { targets().get(&pid).map(|t| t.arena as u64) }
|
||||
@@ -784,12 +803,13 @@ pub fn inject(process: u32, payload: &[u8]) -> Injection {
|
||||
|
||||
let database = unsafe { ((arena + THREAD_DATABASE) as *const u32).read_volatile() };
|
||||
let thread = unsafe { (database as *const u32).byte_add(TDB_CONTROL_BLOCK).read() };
|
||||
redirect_to_trampoline(arena, thread);
|
||||
let start_stack = redirect_to_trampoline(arena, thread);
|
||||
|
||||
resume_thread(arena + RESUME_STUB_OFFSET, arena);
|
||||
if !await_flag(arena + RUNNING_FLAG) {
|
||||
return Injection { arena, thread: 0, stack };
|
||||
}
|
||||
free_shared(start_stack);
|
||||
unsafe {
|
||||
((arena + GATING) as *mut u32).write_volatile(spawns_are_gated() as u32);
|
||||
((arena + GO_FLAG) as *mut u32).write_volatile(1);
|
||||
@@ -1030,18 +1050,24 @@ fn write_trampoline(trampoline: u32, entry: u32, stack_top: u32) {
|
||||
0x50,
|
||||
0xba, 0, 0, 0, 0,
|
||||
0xff, 0xd2,
|
||||
0x64, 0xa1, 0x18, 0x00, 0x00, 0x00,
|
||||
0x89, 0x60, 0x58,
|
||||
0x89, 0x60, 0x04,
|
||||
0xc7, 0x40, 0x08, 0, 0, 0, 0,
|
||||
0x6a, 0x00,
|
||||
0xba, 0, 0, 0, 0,
|
||||
0xff, 0xd2,
|
||||
];
|
||||
let stack_bottom = stack_top + 0x40 - INJECTION_STACK_SIZE as u32;
|
||||
code[5..9].copy_from_slice(&stack_top.to_le_bytes());
|
||||
code[11..15].copy_from_slice(&entry.to_le_bytes());
|
||||
code[20..24].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
|
||||
code[32..36].copy_from_slice(&stack_bottom.to_le_bytes());
|
||||
code[39..43].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
|
||||
|
||||
unsafe { core::ptr::copy_nonoverlapping(code.as_ptr(), trampoline as *mut u8, code.len()) };
|
||||
}
|
||||
|
||||
fn redirect_to_trampoline(arena: u32, thread: u32) {
|
||||
fn redirect_to_trampoline(arena: u32, thread: u32) -> u32 {
|
||||
let mut context = [0u8; CONTEXT_SIZE];
|
||||
let base = context.as_mut_ptr();
|
||||
unsafe {
|
||||
@@ -1065,6 +1091,8 @@ fn redirect_to_trampoline(arena: u32, thread: u32) {
|
||||
base.add(CONTEXT_ESP).cast::<u32>().write_unaligned(top);
|
||||
__VWIN32_Set_Thread_Context(thread, base);
|
||||
}
|
||||
|
||||
stack
|
||||
}
|
||||
|
||||
fn await_flag(address: u32) -> bool {
|
||||
@@ -1128,6 +1156,26 @@ pub fn in_copy() -> bool {
|
||||
core::ptr::eq(primitives(), &crate::win9x_user::USER)
|
||||
}
|
||||
|
||||
pub fn thread_error() -> u32 {
|
||||
if in_copy() { crate::win9x_user::last_error() } else { 0 }
|
||||
}
|
||||
|
||||
pub fn set_thread_error(value: u32) {
|
||||
if in_copy() {
|
||||
crate::win9x_user::set_last_error(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_api() -> &'static crate::gum_windows::FileApi {
|
||||
crate::win9x_user::file_api()
|
||||
}
|
||||
|
||||
pub fn text_api() -> Option<&'static crate::gum_windows::TextApi> {
|
||||
if in_copy() { Some(crate::win9x_user::text_api()) } else { None }
|
||||
}
|
||||
|
||||
pub(crate) use crate::win9x_user::poll_handles;
|
||||
|
||||
fn primitives() -> &'static Primitives {
|
||||
unsafe { ACTIVE }
|
||||
}
|
||||
@@ -1462,6 +1510,48 @@ pub(crate) fn enumerate_exports(base: u32, found: &mut dyn FnMut(*const u8, u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enumerate_imports(base: u32,
|
||||
found: &mut dyn FnMut(*const u8, *const u8, u64, u64) -> bool) {
|
||||
let headers = base + read_u32(base + DOS_HEADERS_OFFSET);
|
||||
let directory_rva = read_u32(headers + IMPORT_DIRECTORY_OFFSET);
|
||||
if directory_rva == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut descriptor = base + directory_rva;
|
||||
loop {
|
||||
let library_rva = read_u32(descriptor + IMPORT_NAME_OFFSET);
|
||||
if library_rva == 0 {
|
||||
return;
|
||||
}
|
||||
let library = (base + library_rva) as *const u8;
|
||||
let thunks = base + read_u32(descriptor + IMPORT_THUNKS_OFFSET);
|
||||
let lookups = match read_u32(descriptor + IMPORT_LOOKUPS_OFFSET) {
|
||||
0 => thunks,
|
||||
rva => base + rva,
|
||||
};
|
||||
|
||||
let mut index = 0;
|
||||
loop {
|
||||
let lookup = read_u32(lookups + index * 4);
|
||||
if lookup == 0 {
|
||||
break;
|
||||
}
|
||||
let slot = thunks + index * 4;
|
||||
let imported_by_name = lookup & IMPORT_BY_ORDINAL == 0;
|
||||
if imported_by_name {
|
||||
let name = (base + lookup + IMPORT_HINT_SIZE) as *const u8;
|
||||
if !found(library, name, slot as u64, read_u32(slot) as u64) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
descriptor += IMPORT_DESCRIPTOR_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel32_export(wanted: &[u8]) -> u32 {
|
||||
let base = unsafe { core::ptr::addr_of!(_KERNEL32_Base).read() };
|
||||
let headers = base + read_u32(base + DOS_HEADERS_OFFSET);
|
||||
@@ -1512,6 +1602,13 @@ fn read_u8(address: u32) -> u8 {
|
||||
}
|
||||
|
||||
const EXPORT_DIRECTORY_OFFSET: u32 = 0x78;
|
||||
const IMPORT_DIRECTORY_OFFSET: u32 = 0x80;
|
||||
const IMPORT_DESCRIPTOR_SIZE: u32 = 20;
|
||||
const IMPORT_LOOKUPS_OFFSET: u32 = 0;
|
||||
const IMPORT_NAME_OFFSET: u32 = 12;
|
||||
const IMPORT_THUNKS_OFFSET: u32 = 16;
|
||||
const IMPORT_BY_ORDINAL: u32 = 0x8000_0000;
|
||||
const IMPORT_HINT_SIZE: u32 = 2;
|
||||
|
||||
pub(crate) const MEM_RESERVE: u32 = 0x0000_2000;
|
||||
pub(crate) const MEM_RELEASE: u32 = 0x0000_8000;
|
||||
@@ -1553,7 +1650,6 @@ const PATCH_SIZE: usize = 2;
|
||||
const PE_SIGNATURE_OFFSET: usize = 0x3c;
|
||||
const PE_ENTRY_POINT_OFFSET: usize = 0x28;
|
||||
const FRAME_BUFFER_SIZE: usize = 0x4000;
|
||||
const TEARDOWN_GRACE_US: u64 = 500_000;
|
||||
const TDB_CONTROL_BLOCK: usize = 0x5c;
|
||||
pub(crate) const PARKED_SLEEP_MS: u32 = 200;
|
||||
const STUB_OFFSET: u32 = 0x100;
|
||||
@@ -2291,6 +2387,10 @@ extern "C" fn frida_win9x_on_control(message: u32, thread: u32) {
|
||||
tell_the_copy_of_a_departure(thread);
|
||||
}
|
||||
|
||||
if message == DESTROY_THREAD && thread == unsafe { DEPARTING_THREAD } {
|
||||
unsafe { (&raw mut DEPARTING_THREAD).write_volatile(0) };
|
||||
}
|
||||
|
||||
let told = match message {
|
||||
THREAD_INIT => unsafe { THREAD_APPEARED },
|
||||
DESTROY_THREAD => unsafe { THREAD_VANISHED },
|
||||
@@ -2436,6 +2536,10 @@ extern "C" fn frida_win9x_on_fault(fault: u32, frame: *mut u32) -> u32 {
|
||||
)
|
||||
};
|
||||
if handled == 0 {
|
||||
if fault == PAGE_FAULT && page_is_absent(registers) {
|
||||
return unsafe { FAULT_CHAIN[fault as usize] };
|
||||
}
|
||||
|
||||
report_unhandled_fault(fault, cpu_context.eip);
|
||||
cpu_context.eip = frida_win9x_park as *const () as u32;
|
||||
}
|
||||
@@ -2472,6 +2576,12 @@ fn recovery_stack(frame: *mut u32) -> u32 {
|
||||
unsafe { frame.add(THUNK_FRAME_ESP).read() }
|
||||
}
|
||||
|
||||
fn page_is_absent(registers: *const u32) -> bool {
|
||||
let error_code = unsafe { registers.byte_add(FAULT_FRAME_ERROR_CODE).read() };
|
||||
|
||||
error_code & PAGE_FAULT_PRESENT == 0
|
||||
}
|
||||
|
||||
fn report_unhandled_fault(fault: u32, eip: u32) {
|
||||
let address = faulting_address();
|
||||
|
||||
@@ -2524,8 +2634,10 @@ const SIMD_FLOATING_POINT: u32 = 19;
|
||||
|
||||
const THUNK_FRAME_EBP: usize = 2;
|
||||
const THUNK_FRAME_ESP: usize = 3;
|
||||
const FAULT_FRAME_ERROR_CODE: usize = 0x20;
|
||||
const FAULT_FRAME_EIP: usize = 0x24;
|
||||
const FAULT_FRAME_CS: usize = 0x28;
|
||||
const PAGE_FAULT_PRESENT: u32 = 1 << 0;
|
||||
const PARK_SLICE_US: u64 = 1_000_000;
|
||||
|
||||
fn faulting_address() -> u32 {
|
||||
|
||||
@@ -8,6 +8,7 @@ use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::gum_windows::{FileApi, TextApi};
|
||||
use crate::kernel::{MemoryRegion, ThreadEntry};
|
||||
use crate::win9x::*;
|
||||
|
||||
@@ -48,9 +49,6 @@ pub extern "C" fn frida_win9x_user_main(arena: u32) {
|
||||
|
||||
stop_hearing_about_faults();
|
||||
unsafe { ((arena + MAIN_STOPPED) as *mut u32).write_volatile(1) };
|
||||
loop {
|
||||
unsafe { sleep(PARKED_SLEEP_MS) };
|
||||
}
|
||||
}
|
||||
|
||||
// The group that the user-mode half runs on. The order is the order in Primitives.
|
||||
@@ -166,6 +164,97 @@ fn copy_has_work() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
static LOOP_WAKE_EVENT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
|
||||
|
||||
fn loop_wake_event() -> u32 {
|
||||
let existing = LOOP_WAKE_EVENT.load(Ordering::Acquire);
|
||||
if existing != 0 {
|
||||
return existing;
|
||||
}
|
||||
|
||||
let create_event: unsafe extern "stdcall" fn(u32, u32, u32, u32) -> u32 =
|
||||
unsafe { core::mem::transmute(user_api().create_event as usize) };
|
||||
let created = unsafe { create_event(0, 0, 0, 0) };
|
||||
|
||||
match LOOP_WAKE_EVENT.compare_exchange(0, created, Ordering::AcqRel, Ordering::Acquire) {
|
||||
Ok(_) => created,
|
||||
Err(raced) => {
|
||||
let close_handle: unsafe extern "stdcall" fn(u32) -> u32 =
|
||||
unsafe { core::mem::transmute(user_api().close_handle as usize) };
|
||||
unsafe { close_handle(created) };
|
||||
raced
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn poke_loop_wakeup() {
|
||||
let set_event: unsafe extern "stdcall" fn(u32) -> u32 =
|
||||
unsafe { core::mem::transmute(user_api().set_event as usize) };
|
||||
unsafe { set_event(loop_wake_event()) };
|
||||
}
|
||||
|
||||
pub(crate) unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
|
||||
timeout: i32) -> i32 {
|
||||
const MAX_HANDLES: usize = 16;
|
||||
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
|
||||
|
||||
let wake = loop_wake_event();
|
||||
|
||||
let mut handles = [0u32; MAX_HANDLES];
|
||||
let mut origin = [usize::MAX; MAX_HANDLES];
|
||||
handles[0] = wake;
|
||||
let mut count = 1usize;
|
||||
let mut ready = 0i32;
|
||||
|
||||
for (index, fd) in fds.iter_mut().enumerate() {
|
||||
fd.revents = 0;
|
||||
|
||||
if fd.fd == G_WAIT_WAKEUP_HANDLE {
|
||||
if !fd.user_data.is_null() {
|
||||
if unsafe { (fd.user_data as *const i32).read_volatile() } != 0 {
|
||||
fd.revents = POLL_IN;
|
||||
ready += 1;
|
||||
}
|
||||
let token = (fd.user_data as usize + GWAKEUP_TOKEN) as *mut *mut c_void;
|
||||
unsafe { token.write(fd.user_data) };
|
||||
}
|
||||
} else if fd.fd != -1 && fd.fd as u32 != wake && count < MAX_HANDLES {
|
||||
handles[count] = fd.fd as u32;
|
||||
origin[count] = index;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ready == 0 {
|
||||
let wait_for_multiple_objects_ex: unsafe extern "stdcall" fn(u32, *const u32, u32, u32, u32) -> u32 =
|
||||
unsafe { core::mem::transmute(user_api().wait_for_multiple_objects_ex as usize) };
|
||||
let timeout_ms = if timeout < 0 { INFINITE } else { timeout as u32 };
|
||||
let status = unsafe {
|
||||
wait_for_multiple_objects_ex(count as u32, handles.as_ptr(), 0, timeout_ms, 1)
|
||||
};
|
||||
if (status as usize) < count {
|
||||
let slot = origin[status as usize];
|
||||
if slot != usize::MAX {
|
||||
fds[slot].revents = POLL_IN;
|
||||
ready += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for fd in fds.iter() {
|
||||
if fd.fd == G_WAIT_WAKEUP_HANDLE && !fd.user_data.is_null() {
|
||||
let token = (fd.user_data as usize + GWAKEUP_TOKEN) as *mut *mut c_void;
|
||||
unsafe { token.write(core::ptr::null_mut()) };
|
||||
}
|
||||
}
|
||||
|
||||
ready
|
||||
}
|
||||
|
||||
const G_WAIT_WAKEUP_HANDLE: i32 = -43;
|
||||
const GWAKEUP_TOKEN: usize = 4;
|
||||
const POLL_IN: u16 = 1;
|
||||
|
||||
fn serve_the_copy() {
|
||||
let arena = unsafe { ARENA };
|
||||
|
||||
@@ -224,6 +313,7 @@ unsafe extern "C" fn user_worker(parameter: *mut c_void, _wait_result: i32) {
|
||||
crate::gum_windows::watch_the_loader();
|
||||
crate::glib::own_the_loop();
|
||||
crate::watch_for_work(context, copy_has_work, serve_the_copy);
|
||||
crate::watch_a_descriptor(context, loop_wake_event() as i32, poll_handles);
|
||||
|
||||
while unsafe { ((arena + STOP_REQUEST) as *const u32).read_volatile() } == 0 {
|
||||
unsafe { crate::dispatch_pending_work(context) };
|
||||
@@ -331,19 +421,11 @@ const GUM_PAGE_WRITE: u32 = 0x2;
|
||||
const GUM_PAGE_EXECUTE: u32 = 0x4;
|
||||
|
||||
pub unsafe extern "stdcall" fn on_module_load(name: *const u8) -> u32 {
|
||||
unsafe {
|
||||
let n = (ARENA + 0xb0) as *mut u32;
|
||||
n.write_volatile(n.read_volatile() + 1);
|
||||
}
|
||||
let original: extern "stdcall" fn(*const u8) -> u32 =
|
||||
unsafe { core::mem::transmute(crate::gum_windows::loader_load()) };
|
||||
|
||||
let handle = original(name);
|
||||
note_module(handle, name);
|
||||
unsafe {
|
||||
let n = (ARENA + 0xb4) as *mut u32;
|
||||
n.write_volatile(n.read_volatile() + 1);
|
||||
}
|
||||
|
||||
handle
|
||||
}
|
||||
@@ -514,13 +596,20 @@ fn resolve_user_api() {
|
||||
create_event: kernel32_export(b"CreateEventA"),
|
||||
set_event: kernel32_export(b"SetEvent"),
|
||||
wait_for_single_object_ex: kernel32_export(b"WaitForSingleObjectEx"),
|
||||
wait_for_multiple_objects_ex: kernel32_export(b"WaitForMultipleObjectsEx"),
|
||||
close_handle: kernel32_export(b"CloseHandle"),
|
||||
set_file_pointer: kernel32_export(b"SetFilePointer"),
|
||||
create_file: kernel32_export(b"CreateFileA"),
|
||||
read_file: kernel32_export(b"ReadFile"),
|
||||
write_file: kernel32_export(b"WriteFile"),
|
||||
find_first_file: kernel32_export(b"FindFirstFileA"),
|
||||
find_next_file: kernel32_export(b"FindNextFileA"),
|
||||
find_close: kernel32_export(b"FindClose"),
|
||||
get_last_error: kernel32_export(b"GetLastError"),
|
||||
set_last_error: kernel32_export(b"SetLastError"),
|
||||
format_message: kernel32_export(b"FormatMessageA"),
|
||||
multi_byte_to_wide_char: kernel32_export(b"MultiByteToWideChar"),
|
||||
wide_char_to_multi_byte: kernel32_export(b"WideCharToMultiByte"),
|
||||
set_unhandled_exception_filter: kernel32_export(b"SetUnhandledExceptionFilter"),
|
||||
tls_alloc: kernel32_export(b"TlsAlloc"),
|
||||
tls_get_value: kernel32_export(b"TlsGetValue"),
|
||||
@@ -533,6 +622,50 @@ fn user_api() -> &'static UserApi {
|
||||
unsafe { &*core::ptr::addr_of!(USER_API) }
|
||||
}
|
||||
|
||||
pub fn last_error() -> u32 {
|
||||
let get_last_error: unsafe extern "stdcall" fn() -> u32 =
|
||||
unsafe { core::mem::transmute(user_api().get_last_error as usize) };
|
||||
unsafe { get_last_error() }
|
||||
}
|
||||
|
||||
pub fn set_last_error(value: u32) {
|
||||
let set_last_error: unsafe extern "stdcall" fn(u32) =
|
||||
unsafe { core::mem::transmute(user_api().set_last_error as usize) };
|
||||
unsafe { set_last_error(value) }
|
||||
}
|
||||
|
||||
pub fn file_api() -> &'static FileApi {
|
||||
unsafe {
|
||||
if (*core::ptr::addr_of!(FILE_API)).is_none() {
|
||||
let api = user_api();
|
||||
FILE_API = Some(FileApi {
|
||||
read_file: core::mem::transmute(api.read_file as usize),
|
||||
write_file: core::mem::transmute(api.write_file as usize),
|
||||
close_handle: core::mem::transmute(api.close_handle as usize),
|
||||
format_message: core::mem::transmute(api.format_message as usize),
|
||||
});
|
||||
}
|
||||
(*core::ptr::addr_of!(FILE_API)).as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
static mut FILE_API: Option<FileApi> = None;
|
||||
|
||||
pub fn text_api() -> &'static TextApi {
|
||||
unsafe {
|
||||
if (*core::ptr::addr_of!(TEXT_API)).is_none() {
|
||||
let api = user_api();
|
||||
TEXT_API = Some(TextApi {
|
||||
multi_byte_to_wide_char: core::mem::transmute(api.multi_byte_to_wide_char as usize),
|
||||
wide_char_to_multi_byte: core::mem::transmute(api.wide_char_to_multi_byte as usize),
|
||||
});
|
||||
}
|
||||
(*core::ptr::addr_of!(TEXT_API)).as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
static mut TEXT_API: Option<TextApi> = None;
|
||||
|
||||
struct UserApi {
|
||||
sleep: u32,
|
||||
get_tick_count: u32,
|
||||
@@ -553,13 +686,20 @@ struct UserApi {
|
||||
create_event: u32,
|
||||
set_event: u32,
|
||||
wait_for_single_object_ex: u32,
|
||||
wait_for_multiple_objects_ex: u32,
|
||||
close_handle: u32,
|
||||
set_file_pointer: u32,
|
||||
create_file: u32,
|
||||
read_file: u32,
|
||||
write_file: u32,
|
||||
find_first_file: u32,
|
||||
find_next_file: u32,
|
||||
find_close: u32,
|
||||
get_last_error: u32,
|
||||
set_last_error: u32,
|
||||
format_message: u32,
|
||||
multi_byte_to_wide_char: u32,
|
||||
wide_char_to_multi_byte: u32,
|
||||
set_unhandled_exception_filter: u32,
|
||||
tls_alloc: u32,
|
||||
tls_get_value: u32,
|
||||
@@ -586,13 +726,20 @@ static mut USER_API: UserApi = UserApi {
|
||||
create_event: 0,
|
||||
set_event: 0,
|
||||
wait_for_single_object_ex: 0,
|
||||
wait_for_multiple_objects_ex: 0,
|
||||
close_handle: 0,
|
||||
set_file_pointer: 0,
|
||||
create_file: 0,
|
||||
read_file: 0,
|
||||
write_file: 0,
|
||||
find_first_file: 0,
|
||||
find_next_file: 0,
|
||||
find_close: 0,
|
||||
get_last_error: 0,
|
||||
set_last_error: 0,
|
||||
format_message: 0,
|
||||
multi_byte_to_wide_char: 0,
|
||||
wide_char_to_multi_byte: 0,
|
||||
set_unhandled_exception_filter: 0,
|
||||
tls_alloc: 0,
|
||||
tls_get_value: 0,
|
||||
|
||||
@@ -28,31 +28,7 @@ type ThreadChangeRoutine = unsafe extern "win64" fn(usize, usize, u8);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
type ThreadChangeRoutine = unsafe extern "C" fn(usize, usize, u8);
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "stdcall" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "win64" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "C" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use windows_fn;
|
||||
pub(crate) use crate::gum_windows::windows_fn;
|
||||
|
||||
pub const MODULE_DIRECTORY: &str = "/WINDOWS/system32/";
|
||||
|
||||
@@ -3447,6 +3423,26 @@ pub fn in_copy() -> bool {
|
||||
core::ptr::eq(primitives(), &crate::winnt_user::USER)
|
||||
}
|
||||
|
||||
pub fn thread_error() -> u32 {
|
||||
if in_copy() { crate::winnt_user::last_error() } else { 0 }
|
||||
}
|
||||
|
||||
pub fn set_thread_error(value: u32) {
|
||||
if in_copy() {
|
||||
crate::winnt_user::set_last_error(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_api() -> &'static crate::gum_windows::FileApi {
|
||||
crate::winnt_user::file_api()
|
||||
}
|
||||
|
||||
pub fn text_api() -> Option<&'static crate::gum_windows::TextApi> {
|
||||
if in_copy() { Some(crate::winnt_user::text_api()) } else { None }
|
||||
}
|
||||
|
||||
pub(crate) use crate::winnt_user::poll_handles;
|
||||
|
||||
fn primitives() -> &'static Primitives {
|
||||
unsafe { ACTIVE }
|
||||
}
|
||||
@@ -3749,19 +3745,83 @@ pub(crate) fn enumerate_exports(base: usize, found: &mut dyn FnMut(*const u8, u6
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enumerate_imports(base: usize,
|
||||
found: &mut dyn FnMut(*const u8, *const u8, u64, u64) -> bool) {
|
||||
unsafe {
|
||||
let directories = data_directories(base);
|
||||
let directory_rva = read_u32(directories + IMPORT_DIRECTORY_INDEX * 8) as usize;
|
||||
if directory_rva == 0 {
|
||||
return;
|
||||
}
|
||||
let wide = is_pe32_plus(base);
|
||||
let thunk_size = if wide { 8 } else { 4 };
|
||||
let by_ordinal: u64 = if wide { 1 << 63 } else { 1 << 31 };
|
||||
|
||||
let mut descriptor = base + directory_rva;
|
||||
loop {
|
||||
let library_rva = read_u32(descriptor + IMPORT_NAME_OFFSET) as usize;
|
||||
if library_rva == 0 {
|
||||
return;
|
||||
}
|
||||
let library = (base + library_rva) as *const u8;
|
||||
let thunks = base + read_u32(descriptor + IMPORT_THUNKS_OFFSET) as usize;
|
||||
let lookups = match read_u32(descriptor + IMPORT_LOOKUPS_OFFSET) as usize {
|
||||
0 => thunks,
|
||||
rva => base + rva,
|
||||
};
|
||||
|
||||
let mut index = 0;
|
||||
loop {
|
||||
let lookup = read_thunk(lookups + index * thunk_size, wide);
|
||||
if lookup == 0 {
|
||||
break;
|
||||
}
|
||||
let slot = thunks + index * thunk_size;
|
||||
if lookup & by_ordinal == 0 {
|
||||
let name = (base + lookup as usize + IMPORT_HINT_SIZE) as *const u8;
|
||||
if !found(library, name, slot as u64, read_thunk(slot, wide)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
descriptor += IMPORT_DESCRIPTOR_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_thunk(address: usize, wide: bool) -> u64 {
|
||||
unsafe {
|
||||
if wide {
|
||||
(address as *const u64).read()
|
||||
} else {
|
||||
read_u32(address) as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn export_directory(base: usize) -> usize {
|
||||
unsafe { base + read_u32(data_directories(base)) as usize }
|
||||
}
|
||||
|
||||
unsafe fn is_pe32_plus(base: usize) -> bool {
|
||||
unsafe {
|
||||
let headers = base + read_u32(base + PE_HEADERS_OFFSET) as usize;
|
||||
let magic = (headers + OPTIONAL_HEADER_OFFSET) as *const u16;
|
||||
let directories = headers
|
||||
((headers + OPTIONAL_HEADER_OFFSET) as *const u16).read() == PE32_PLUS_MAGIC
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn data_directories(base: usize) -> usize {
|
||||
unsafe {
|
||||
let headers = base + read_u32(base + PE_HEADERS_OFFSET) as usize;
|
||||
headers
|
||||
+ OPTIONAL_HEADER_OFFSET
|
||||
+ if magic.read() == PE32_PLUS_MAGIC {
|
||||
+ if is_pe32_plus(base) {
|
||||
DATA_DIRECTORIES_OFFSET_64
|
||||
} else {
|
||||
DATA_DIRECTORIES_OFFSET_32
|
||||
};
|
||||
|
||||
base + read_u32(directories) as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3840,6 +3900,12 @@ const PE32_PLUS_MAGIC: u16 = 0x20b;
|
||||
const DATA_DIRECTORIES_OFFSET_32: usize = 0x60;
|
||||
const DATA_DIRECTORIES_OFFSET_64: usize = 0x70;
|
||||
const EXPORT_NAME_COUNT_OFFSET: usize = 0x18;
|
||||
const IMPORT_DIRECTORY_INDEX: usize = 1;
|
||||
const IMPORT_DESCRIPTOR_SIZE: usize = 20;
|
||||
const IMPORT_LOOKUPS_OFFSET: usize = 0;
|
||||
const IMPORT_NAME_OFFSET: usize = 12;
|
||||
const IMPORT_THUNKS_OFFSET: usize = 16;
|
||||
const IMPORT_HINT_SIZE: usize = 2;
|
||||
const EXPORT_FUNCTIONS_OFFSET: usize = 0x1c;
|
||||
const EXPORT_NAMES_OFFSET: usize = 0x20;
|
||||
const EXPORT_ORDINALS_OFFSET: usize = 0x24;
|
||||
|
||||
@@ -8,6 +8,7 @@ use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::gum_windows::{FileApi, TextApi};
|
||||
use crate::kernel::MemoryRegion;
|
||||
use crate::winnt::{
|
||||
BLOCK_PROCESS_ID, BLOCK_THREAD_ID, CURRENT_PROCESS, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE,
|
||||
@@ -788,6 +789,68 @@ fn user_api() -> &'static UserApi {
|
||||
unsafe { (*core::ptr::addr_of!(USER_API)).as_ref().unwrap() }
|
||||
}
|
||||
|
||||
pub fn last_error() -> u32 {
|
||||
unsafe { (error_api().get_last_error)() }
|
||||
}
|
||||
|
||||
pub fn set_last_error(value: u32) {
|
||||
unsafe { (error_api().set_last_error)(value) }
|
||||
}
|
||||
|
||||
fn error_api() -> &'static ErrorApi {
|
||||
unsafe {
|
||||
if (*core::ptr::addr_of!(ERROR_API)).is_none() {
|
||||
let library = module_base(peb(), b"kernel32.dll");
|
||||
ERROR_API = Some(ErrorApi {
|
||||
get_last_error: core::mem::transmute(export(library, b"GetLastError")),
|
||||
set_last_error: core::mem::transmute(export(library, b"SetLastError")),
|
||||
});
|
||||
}
|
||||
(*core::ptr::addr_of!(ERROR_API)).as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct ErrorApi {
|
||||
get_last_error: windows_fn!(=> u32),
|
||||
set_last_error: windows_fn!(u32),
|
||||
}
|
||||
|
||||
static mut ERROR_API: Option<ErrorApi> = None;
|
||||
|
||||
pub fn file_api() -> &'static FileApi {
|
||||
unsafe {
|
||||
if (*core::ptr::addr_of!(FILE_API)).is_none() {
|
||||
let library = module_base(peb(), b"kernel32.dll");
|
||||
FILE_API = Some(FileApi {
|
||||
read_file: core::mem::transmute(export(library, b"ReadFile")),
|
||||
write_file: core::mem::transmute(export(library, b"WriteFile")),
|
||||
close_handle: core::mem::transmute(export(library, b"CloseHandle")),
|
||||
format_message: core::mem::transmute(export(library, b"FormatMessageA")),
|
||||
});
|
||||
}
|
||||
(*core::ptr::addr_of!(FILE_API)).as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
static mut FILE_API: Option<FileApi> = None;
|
||||
|
||||
pub fn text_api() -> &'static TextApi {
|
||||
unsafe {
|
||||
if (*core::ptr::addr_of!(TEXT_API)).is_none() {
|
||||
let library = module_base(peb(), b"kernel32.dll");
|
||||
TEXT_API = Some(TextApi {
|
||||
multi_byte_to_wide_char: core::mem::transmute(
|
||||
export(library, b"MultiByteToWideChar")),
|
||||
wide_char_to_multi_byte: core::mem::transmute(
|
||||
export(library, b"WideCharToMultiByte")),
|
||||
});
|
||||
}
|
||||
(*core::ptr::addr_of!(TEXT_API)).as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
static mut TEXT_API: Option<TextApi> = None;
|
||||
|
||||
struct UserApi {
|
||||
allocate_heap: windows_fn!(usize, u32, usize => *mut u8),
|
||||
free_heap: windows_fn!(usize, u32, *mut u8 => u8),
|
||||
@@ -969,7 +1032,7 @@ fn serve_the_copy() {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
|
||||
pub(crate) unsafe extern "C" fn poll_handles(ufds: *mut crate::bindings::GPollFD, nfds: u32,
|
||||
timeout: i32) -> i32 {
|
||||
const MAX_HANDLES: usize = 16;
|
||||
let fds = unsafe { core::slice::from_raw_parts_mut(ufds, nfds as usize) };
|
||||
|
||||
+77
-49
@@ -553,6 +553,7 @@ namespace Frida {
|
||||
string project_root = compute_project_root (entrypoint, opts.project_root);
|
||||
string platform = (opts.platform != null) ? opts.platform : "";
|
||||
string arch = (opts.arch != null) ? opts.arch : "";
|
||||
string? defines = PatternJson.serialize_dict (opts.defines);
|
||||
|
||||
string? result = null;
|
||||
string? error_message = null;
|
||||
@@ -562,13 +563,13 @@ namespace Frida {
|
||||
schedule_on_frida_thread (compile.callback);
|
||||
};
|
||||
|
||||
CompilerBackend.Patterns.describe (project_root, entrypoint, platform, arch, (owned) on_result);
|
||||
CompilerBackend.Patterns.describe (project_root, entrypoint, platform, arch, defines, (owned) on_result);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", error_message);
|
||||
|
||||
return PatternJson.parse_module (result, this, project_root, entrypoint, platform, arch);
|
||||
return PatternJson.parse_module (result, this, project_root, entrypoint, platform, arch, defines);
|
||||
}
|
||||
|
||||
public PatternModule compile_sync (string entrypoint, PatternCompileOptions? options = null, Cancellable? cancellable = null)
|
||||
@@ -589,8 +590,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
internal async PatternValue decode_value (string project_root, string entrypoint, string platform, string arch,
|
||||
string type_name, Bytes data, uint64 address, PatternDecodeOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
string? defines, string type_name, Bytes data, uint64 address, PatternDecodeOptions? options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
string? result = null;
|
||||
@@ -602,7 +603,7 @@ namespace Frida {
|
||||
};
|
||||
|
||||
CompilerBackend.Patterns.decode (project_root, entrypoint, type_name, data.get_data (), address, platform, arch,
|
||||
PatternJson.serialize_inputs (options), (owned) on_result);
|
||||
defines, PatternJson.serialize_inputs (options), (owned) on_result);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
@@ -612,8 +613,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
internal async string call_value_function (string project_root, string entrypoint, string platform, string arch,
|
||||
string type_name, Bytes data, uint64 address, uint pattern, string function, PatternDecodeOptions? options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
string? defines, string type_name, Bytes data, uint64 address, uint pattern, string function,
|
||||
PatternDecodeOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
string? result = null;
|
||||
@@ -625,7 +626,7 @@ namespace Frida {
|
||||
};
|
||||
|
||||
CompilerBackend.Patterns.call (project_root, entrypoint, type_name, data.get_data (), address, platform, arch,
|
||||
PatternJson.serialize_inputs (options), pattern, function, (owned) on_result);
|
||||
defines, PatternJson.serialize_inputs (options), pattern, function, (owned) on_result);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
@@ -681,6 +682,16 @@ namespace Frida {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocessor macros to define before the source is read, by name,
|
||||
* the way a `#define` at the top of the entrypoint would.
|
||||
*/
|
||||
public HashTable<string, Variant> defines {
|
||||
get;
|
||||
set;
|
||||
default = make_parameters_dict ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -727,9 +738,11 @@ namespace Frida {
|
||||
private string entrypoint;
|
||||
private string platform;
|
||||
private string arch;
|
||||
private string? defines;
|
||||
|
||||
internal PatternModule (PatternCompiler compiler, string project_root, string entrypoint, string platform, string arch,
|
||||
PatternTypeList types, string? root_type, PatternInputList inputs, PatternDiagnosticList diagnostics) {
|
||||
string? defines, PatternTypeList types, string? root_type, PatternInputList inputs,
|
||||
PatternDiagnosticList diagnostics) {
|
||||
Object (types: types, root_type: root_type, inputs: inputs, diagnostics: diagnostics);
|
||||
|
||||
this.compiler = compiler;
|
||||
@@ -737,6 +750,7 @@ namespace Frida {
|
||||
this.entrypoint = entrypoint;
|
||||
this.platform = platform;
|
||||
this.arch = arch;
|
||||
this.defines = defines;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,8 +774,8 @@ namespace Frida {
|
||||
*/
|
||||
public async PatternValue decode (string type_name, Bytes data, uint64 address, PatternDecodeOptions? options = null,
|
||||
Cancellable? cancellable = null) throws Error, IOError {
|
||||
return yield compiler.decode_value (project_root, entrypoint, platform, arch, type_name, data, address, options,
|
||||
cancellable);
|
||||
return yield compiler.decode_value (project_root, entrypoint, platform, arch, defines, type_name, data, address,
|
||||
options, cancellable);
|
||||
}
|
||||
|
||||
public PatternValue decode_sync (string type_name, Bytes data, uint64 address, PatternDecodeOptions? options = null,
|
||||
@@ -800,8 +814,8 @@ namespace Frida {
|
||||
*/
|
||||
public async string call_function (string type_name, Bytes data, uint64 address, uint pattern, string function,
|
||||
PatternDecodeOptions? options = null, Cancellable? cancellable = null) throws Error, IOError {
|
||||
return yield compiler.call_value_function (project_root, entrypoint, platform, arch, type_name, data, address, pattern,
|
||||
function, options, cancellable);
|
||||
return yield compiler.call_value_function (project_root, entrypoint, platform, arch, defines, type_name, data, address,
|
||||
pattern, function, options, cancellable);
|
||||
}
|
||||
|
||||
public string call_function_sync (string type_name, Bytes data, uint64 address, uint pattern, string function,
|
||||
@@ -2106,7 +2120,7 @@ namespace Frida {
|
||||
|
||||
namespace PatternJson {
|
||||
private PatternModule parse_module (string json, PatternCompiler compiler, string project_root, string entrypoint,
|
||||
string platform, string arch) throws Error {
|
||||
string platform, string arch, string? defines) throws Error {
|
||||
var reader = make_reader (json);
|
||||
|
||||
var types = new Gee.ArrayList<PatternType> ();
|
||||
@@ -2148,17 +2162,23 @@ namespace Frida {
|
||||
}
|
||||
reader.end_member ();
|
||||
|
||||
return new PatternModule (compiler, project_root, entrypoint, platform, arch, new PatternTypeList (types),
|
||||
return new PatternModule (compiler, project_root, entrypoint, platform, arch, defines, new PatternTypeList (types),
|
||||
read_optional_string (reader, "root"), new PatternInputList (inputs), new PatternDiagnosticList (diagnostics));
|
||||
}
|
||||
|
||||
internal string? serialize_inputs (PatternDecodeOptions? options) {
|
||||
if (options == null || options.inputs.size () == 0)
|
||||
if (options == null)
|
||||
return null;
|
||||
return serialize_dict (options.inputs);
|
||||
}
|
||||
|
||||
internal string? serialize_dict (HashTable<string, Variant> dict) {
|
||||
if (dict.size () == 0)
|
||||
return null;
|
||||
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
options.inputs.for_each ((name, value) => {
|
||||
dict.for_each ((name, value) => {
|
||||
builder.set_member_name (name);
|
||||
if (value.is_of_type (VariantType.INT64))
|
||||
builder.add_int_value (value.get_int64 ());
|
||||
@@ -2675,15 +2695,16 @@ namespace Frida {
|
||||
namespace Patterns {
|
||||
[CCode (has_target = false)]
|
||||
private delegate void DescribeFunc (string project_root, string entrypoint, string platform, string arch,
|
||||
owned PatternResultFunc on_result);
|
||||
string? defines, owned PatternResultFunc on_result);
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate void DecodeFunc (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
|
||||
string platform, string arch, string? inputs, owned PatternResultFunc on_result);
|
||||
string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result);
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate void CallFunc (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
|
||||
string platform, string arch, string? inputs, uint pattern, string function, owned PatternResultFunc on_result);
|
||||
string platform, string arch, string? defines, string? inputs, uint pattern, string function,
|
||||
owned PatternResultFunc on_result);
|
||||
|
||||
private DescribeFunc? describe;
|
||||
private DecodeFunc? decode;
|
||||
@@ -2763,21 +2784,21 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private static void executable_patterns_describe (string project_root, string entrypoint, string platform, string arch,
|
||||
owned PatternResultFunc on_result) {
|
||||
backend_process.describe_patterns (project_root, entrypoint, platform, arch, (owned) on_result);
|
||||
string? defines, owned PatternResultFunc on_result) {
|
||||
backend_process.describe_patterns (project_root, entrypoint, platform, arch, defines, (owned) on_result);
|
||||
}
|
||||
|
||||
private static void executable_patterns_decode (string project_root, string entrypoint, string type_name, uint8[] data,
|
||||
uint64 address, string platform, string arch, string? inputs, owned PatternResultFunc on_result) {
|
||||
backend_process.decode_pattern (project_root, entrypoint, type_name, data, address, platform, arch, inputs,
|
||||
uint64 address, string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result) {
|
||||
backend_process.decode_pattern (project_root, entrypoint, type_name, data, address, platform, arch, defines, inputs,
|
||||
(owned) on_result);
|
||||
}
|
||||
|
||||
private static void executable_patterns_call (string project_root, string entrypoint, string type_name, uint8[] data,
|
||||
uint64 address, string platform, string arch, string? inputs, uint pattern, string function,
|
||||
uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern, string function,
|
||||
owned PatternResultFunc on_result) {
|
||||
backend_process.call_pattern_function (project_root, entrypoint, type_name, data, address, platform, arch, inputs,
|
||||
pattern, function, (owned) on_result);
|
||||
backend_process.call_pattern_function (project_root, entrypoint, type_name, data, address, platform, arch, defines,
|
||||
inputs, pattern, function, (owned) on_result);
|
||||
}
|
||||
|
||||
private class BackendProcess : Object {
|
||||
@@ -3072,7 +3093,7 @@ namespace Frida {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void describe_patterns (string project_root, string entrypoint, string platform, string arch,
|
||||
public void describe_patterns (string project_root, string entrypoint, string platform, string arch, string? defines,
|
||||
owned PatternResultFunc on_result) {
|
||||
try {
|
||||
ensure_started ();
|
||||
@@ -3085,11 +3106,11 @@ namespace Frida {
|
||||
|
||||
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
|
||||
|
||||
post_message (make_patterns_describe_request (request_id, project_root, entrypoint, platform, arch));
|
||||
post_message (make_patterns_describe_request (request_id, project_root, entrypoint, platform, arch, defines));
|
||||
}
|
||||
|
||||
public void decode_pattern (string project_root, string entrypoint, string type_name, uint8[] data, uint64 address,
|
||||
string platform, string arch, string? inputs, owned PatternResultFunc on_result) {
|
||||
string platform, string arch, string? defines, string? inputs, owned PatternResultFunc on_result) {
|
||||
try {
|
||||
ensure_started ();
|
||||
} catch (GLib.Error e) {
|
||||
@@ -3102,11 +3123,11 @@ namespace Frida {
|
||||
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
|
||||
|
||||
post_message (make_patterns_decode_request (request_id, project_root, entrypoint, type_name, data, address, platform,
|
||||
arch, inputs));
|
||||
arch, defines, inputs));
|
||||
}
|
||||
|
||||
public void call_pattern_function (string project_root, string entrypoint, string type_name, uint8[] data,
|
||||
uint64 address, string platform, string arch, string? inputs, uint pattern, string function,
|
||||
uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern, string function,
|
||||
owned PatternResultFunc on_result) {
|
||||
try {
|
||||
ensure_started ();
|
||||
@@ -3120,7 +3141,7 @@ namespace Frida {
|
||||
pending_pattern_requests[request_id] = new PendingPatternRequest ((owned) on_result);
|
||||
|
||||
post_message (make_patterns_call_request (request_id, project_root, entrypoint, type_name, data, address, platform,
|
||||
arch, inputs, pattern, function));
|
||||
arch, defines, inputs, pattern, function));
|
||||
}
|
||||
|
||||
private class PendingPatternRequest {
|
||||
@@ -3266,7 +3287,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private static string make_patterns_describe_request (uint id, string project_root, string entrypoint, string platform,
|
||||
string arch) {
|
||||
string arch, string? defines) {
|
||||
var builder = new Json.Builder ();
|
||||
|
||||
builder
|
||||
@@ -3282,25 +3303,27 @@ namespace Frida {
|
||||
.set_member_name ("platform")
|
||||
.add_string_value (platform)
|
||||
.set_member_name ("arch")
|
||||
.add_string_value (arch)
|
||||
.end_object ();
|
||||
.add_string_value (arch);
|
||||
add_patterns_dict (builder, "defines", defines);
|
||||
builder.end_object ();
|
||||
|
||||
return Json.to_string (builder.get_root (), false);
|
||||
}
|
||||
|
||||
private static string make_patterns_decode_request (uint id, string project_root, string entrypoint, string type_name,
|
||||
uint8[] data, uint64 address, string platform, string arch, string? inputs) {
|
||||
uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs) {
|
||||
var builder = begin_patterns_data_request ("patterns:decode", id, project_root, entrypoint, type_name, data, address,
|
||||
platform, arch, inputs);
|
||||
platform, arch, defines, inputs);
|
||||
builder.end_object ();
|
||||
|
||||
return Json.to_string (builder.get_root (), false);
|
||||
}
|
||||
|
||||
private static string make_patterns_call_request (uint id, string project_root, string entrypoint, string type_name,
|
||||
uint8[] data, uint64 address, string platform, string arch, string? inputs, uint pattern, string function) {
|
||||
uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs, uint pattern,
|
||||
string function) {
|
||||
var builder = begin_patterns_data_request ("patterns:call", id, project_root, entrypoint, type_name, data, address,
|
||||
platform, arch, inputs);
|
||||
platform, arch, defines, inputs);
|
||||
builder
|
||||
.set_member_name ("pattern")
|
||||
.add_int_value (pattern)
|
||||
@@ -3312,7 +3335,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private static Json.Builder begin_patterns_data_request (string type, uint id, string project_root, string entrypoint,
|
||||
string type_name, uint8[] data, uint64 address, string platform, string arch, string? inputs) {
|
||||
string type_name, uint8[] data, uint64 address, string platform, string arch, string? defines, string? inputs) {
|
||||
var builder = new Json.Builder ();
|
||||
|
||||
builder
|
||||
@@ -3335,18 +3358,23 @@ namespace Frida {
|
||||
.add_string_value (platform)
|
||||
.set_member_name ("arch")
|
||||
.add_string_value (arch);
|
||||
if (inputs != null) {
|
||||
try {
|
||||
builder
|
||||
.set_member_name ("inputs")
|
||||
.add_value (Json.from_string (inputs));
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
add_patterns_dict (builder, "defines", defines);
|
||||
add_patterns_dict (builder, "inputs", inputs);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void add_patterns_dict (Json.Builder builder, string name, string? json) {
|
||||
if (json == null)
|
||||
return;
|
||||
try {
|
||||
builder
|
||||
.set_member_name (name)
|
||||
.add_value (Json.from_string (json));
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static string make_dispose_request (uint session_id) {
|
||||
var builder = new Json.Builder ();
|
||||
|
||||
|
||||
@@ -361,9 +361,9 @@ func _frida_compiler_backend_language_server_post(h uintptr, cText *C.char) *C.c
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_patterns_describe
|
||||
func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatform, cArch *C.char,
|
||||
func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines *C.char,
|
||||
onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
|
||||
onResult := NewCDelegate(onResultFn, onResultData, onResultDataDestroy)
|
||||
|
||||
go func() {
|
||||
@@ -376,9 +376,9 @@ func _frida_compiler_backend_patterns_describe(cProjectRoot, cEntrypoint, cPlatf
|
||||
|
||||
//export _frida_compiler_backend_patterns_decode
|
||||
func _frida_compiler_backend_patterns_decode(cProjectRoot, cEntrypoint, cTypeName *C.char, cData *C.uint8_t, dataLength C.int,
|
||||
address C.uint64_t, cPlatform, cArch, cInputs *C.char, onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer,
|
||||
onResultDataDestroy C.FridaDestroyFunc) {
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
|
||||
address C.uint64_t, cPlatform, cArch, cDefines, cInputs *C.char, onResultFn C.FridaPatternResultFunc,
|
||||
onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
|
||||
typeName := C.GoString(cTypeName)
|
||||
data := C.GoBytes(unsafe.Pointer(cData), dataLength)
|
||||
inputs := parsePatternInputs(cInputs)
|
||||
@@ -394,9 +394,9 @@ func _frida_compiler_backend_patterns_decode(cProjectRoot, cEntrypoint, cTypeNam
|
||||
|
||||
//export _frida_compiler_backend_patterns_call
|
||||
func _frida_compiler_backend_patterns_call(cProjectRoot, cEntrypoint, cTypeName *C.char, cData *C.uint8_t, dataLength C.int,
|
||||
address C.uint64_t, cPlatform, cArch, cInputs *C.char, patternID C.uint, cFunctionName *C.char,
|
||||
address C.uint64_t, cPlatform, cArch, cDefines, cInputs *C.char, patternID C.uint, cFunctionName *C.char,
|
||||
onResultFn C.FridaPatternResultFunc, onResultData unsafe.Pointer, onResultDataDestroy C.FridaDestroyFunc) {
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch)
|
||||
query := patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines)
|
||||
typeName := C.GoString(cTypeName)
|
||||
data := C.GoBytes(unsafe.Pointer(cData), dataLength)
|
||||
inputs := parsePatternInputs(cInputs)
|
||||
@@ -411,24 +411,29 @@ func _frida_compiler_backend_patterns_call(cProjectRoot, cEntrypoint, cTypeName
|
||||
}()
|
||||
}
|
||||
|
||||
func patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch *C.char) patternQuery {
|
||||
func patternQueryFromC(cProjectRoot, cEntrypoint, cPlatform, cArch, cDefines *C.char) patternQuery {
|
||||
return patternQuery{
|
||||
projectRoot: C.GoString(cProjectRoot),
|
||||
entrypoint: C.GoString(cEntrypoint),
|
||||
platform: C.GoString(cPlatform),
|
||||
arch: C.GoString(cArch),
|
||||
defines: parsePatternDict(cDefines),
|
||||
}
|
||||
}
|
||||
|
||||
func parsePatternInputs(cInputs *C.char) map[string]any {
|
||||
if cInputs == nil {
|
||||
return parsePatternDict(cInputs)
|
||||
}
|
||||
|
||||
func parsePatternDict(cJSON *C.char) map[string]any {
|
||||
if cJSON == nil {
|
||||
return nil
|
||||
}
|
||||
var inputs map[string]any
|
||||
if err := json.Unmarshal([]byte(C.GoString(cInputs)), &inputs); err != nil {
|
||||
var dict map[string]any
|
||||
if err := json.Unmarshal([]byte(C.GoString(cJSON)), &dict); err != nil {
|
||||
return nil
|
||||
}
|
||||
return inputs
|
||||
return dict
|
||||
}
|
||||
|
||||
func invokePatternResultFunc(onResult *CDelegate[C.FridaPatternResultFunc], result string, err error) {
|
||||
|
||||
@@ -28,6 +28,7 @@ type BackendRequest struct {
|
||||
SourceMap bool `json:"source_map,omitempty"`
|
||||
Compress bool `json:"compress,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Defines map[string]any `json:"defines,omitempty"`
|
||||
Inputs map[string]any `json:"inputs,omitempty"`
|
||||
Externals []string `json:"externals,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
@@ -355,7 +356,8 @@ func patternRequestData(req BackendRequest) ([]byte, uint64, error) {
|
||||
}
|
||||
|
||||
func patternQueryFromRequest(req BackendRequest) patternQuery {
|
||||
return patternQuery{projectRoot: req.ProjectRoot, entrypoint: req.Entrypoint, platform: req.Platform, arch: req.Arch}
|
||||
return patternQuery{projectRoot: req.ProjectRoot, entrypoint: req.Entrypoint, platform: req.Platform, arch: req.Arch,
|
||||
defines: req.Defines}
|
||||
}
|
||||
|
||||
func libraryOptionsFromRequest(req BackendRequest) LibraryOptions {
|
||||
|
||||
@@ -27,13 +27,7 @@ export function describe(hitpoints: number): string {
|
||||
}
|
||||
`)
|
||||
writeFile(t, filepath.Join(dir, "lib", "patterns", "player.pat"), "import geometry;\n"+playerPattern)
|
||||
writeFile(t, filepath.Join(dir, "lib", "patterns", "geometry.pat"), `
|
||||
struct Vec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
`)
|
||||
writeFile(t, filepath.Join(dir, "lib", "patterns", "geometry.pat"), vec3Pattern)
|
||||
|
||||
var diagnostics []Diagnostic
|
||||
err := buildLibrary(LibraryOptions{ProjectRoot: dir, Entrypoint: "lib/index.ts", OutputDir: "dist", SourceMap: true}, func(d Diagnostic) {
|
||||
|
||||
Generated
+4
-4
@@ -35,7 +35,7 @@
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "^19.12.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
"frida-fs": "7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/assert": {
|
||||
@@ -283,9 +283,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/frida-fs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-7.0.0.tgz",
|
||||
"integrity": "sha512-95m7LCjuFMZObrR/37tlEUHAKC0UP82vWBtcaFntUSFr5MuqhoZG6FDCXKl58FhTf0SwOCYvwctCYPLV3/pHgw==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-7.1.1.tgz",
|
||||
"integrity": "sha512-2aiPVKD3+a1pgWCbAuSyDx9QsmK6JMX5aw5iukkE9lGGYeYkj1FSajaMGlRk/s9MynXm+jTVmuY6t0a2oBptOg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-parser-js": {
|
||||
|
||||
@@ -31,6 +31,6 @@
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "^19.12.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
"frida-fs": "7.1.1"
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,14 @@ type routedMessage struct {
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
type configurationParams struct {
|
||||
Settings struct {
|
||||
Patterns struct {
|
||||
Defines map[string]string `json:"defines"`
|
||||
} `json:"patterns"`
|
||||
} `json:"settings"`
|
||||
}
|
||||
|
||||
type documentParams struct {
|
||||
TextDocument struct {
|
||||
URI string `json:"uri"`
|
||||
@@ -47,6 +55,12 @@ func (r *patternRouter) route(text string) bool {
|
||||
r.initializeID = message.ID
|
||||
return false
|
||||
}
|
||||
if message.Method == "workspace/didChangeConfiguration" {
|
||||
var params configurationParams
|
||||
json.Unmarshal(message.Params, ¶ms)
|
||||
r.service.Define(params.Settings.Patterns.Defines)
|
||||
return false
|
||||
}
|
||||
var params documentParams
|
||||
if !strings.HasPrefix(message.Method, "textDocument/") || json.Unmarshal(message.Params, ¶ms) != nil {
|
||||
return false
|
||||
|
||||
@@ -64,3 +64,16 @@ func TestPatternRouterAdjustsInitializeResponse(t *testing.T) {
|
||||
t.Fatalf("unexpected tokens: %s", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatternRouterTakesDefinesFromConfiguration(t *testing.T) {
|
||||
var emitted []string
|
||||
router := newPatternRouter(func(text string) { emitted = append(emitted, text) })
|
||||
if router.route(`{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"patterns":{"defines":{"LIVE":""}}}}}`) {
|
||||
t.Fatal("configuration changes belong to both servers")
|
||||
}
|
||||
router.route(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///p.hexpat","text":"#ifdef LIVE\nstruct A { u8 x; };\n#else\nstruct A { u8 x[missing]; };\n#endif\n"}}}`)
|
||||
router.route(`{"jsonrpc":"2.0","id":4,"method":"textDocument/diagnostic","params":{"textDocument":{"uri":"file:///p.hexpat"}}}`)
|
||||
if len(emitted) != 1 || !strings.Contains(emitted[0], `"items":[]`) {
|
||||
t.Fatalf("the defined branch should be compiled: %v", emitted)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ type patternQuery struct {
|
||||
entrypoint string
|
||||
platform string
|
||||
arch string
|
||||
defines map[string]any
|
||||
}
|
||||
|
||||
type patternCompilation struct {
|
||||
@@ -63,13 +64,25 @@ func (q patternQuery) compile() (*patternCompilation, error) {
|
||||
return nil, fmt.Errorf("Failed to resolve entrypoint: %w", err)
|
||||
}
|
||||
|
||||
module, diagnostics, err := patterns.CompileFile(entrypoint, nil)
|
||||
module, diagnostics, err := patterns.CompileFile(entrypoint, nil, defineBodies(q.defines))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &patternCompilation{module: module, diagnostics: diagnostics, target: targetFor(q.platform, q.arch), projectRoot: projectRoot}, nil
|
||||
}
|
||||
|
||||
func defineBodies(defines map[string]any) map[string]string {
|
||||
bodies := make(map[string]string, len(defines))
|
||||
for name, value := range defines {
|
||||
if text, isText := value.(string); isText {
|
||||
bodies[name] = text
|
||||
} else {
|
||||
bodies[name] = fmt.Sprint(value)
|
||||
}
|
||||
}
|
||||
return bodies
|
||||
}
|
||||
|
||||
func targetFor(platform string, arch string) patterns.Target {
|
||||
if platform == "" {
|
||||
platform = hostPlatform()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodePatternWithDefines(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeFile(t, filepath.Join(dir, "layout.hexpat"), "#ifdef WIDE\nstruct Layout { u16 value; };\n#else\nstruct Layout { u8 value; };\n#endif\n")
|
||||
query := patternQuery{projectRoot: dir, entrypoint: "layout.hexpat", defines: map[string]any{"WIDE": ""}}
|
||||
result, err := decodePattern(query, "Layout", []byte{1, 2}, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(result, `"size":2`) {
|
||||
t.Fatalf("the define should select the wide branch: %s", result)
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,21 @@ func init() {
|
||||
defineHashBuiltins()
|
||||
}
|
||||
|
||||
var patternArguments = map[string]bool{
|
||||
"std::core::formatted_value": true, "std::core::set_display_name": true, "std::core::member_count": true, "std::core::has_member": true,
|
||||
}
|
||||
|
||||
func (f *frame) builtin(call *Builtin) (runtimeValue, error) {
|
||||
arguments := make([]runtimeValue, len(call.Arguments))
|
||||
for i, argument := range call.Arguments {
|
||||
if patternArguments[call.Name] && isReference(argument) {
|
||||
node, err := f.target(argument)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arguments[i] = node
|
||||
continue
|
||||
}
|
||||
result, err := f.eval(argument)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -301,7 +313,7 @@ func (f *frame) findBytes(occurrenceValue runtimeValue, fromValue runtimeValue,
|
||||
|
||||
func defineCoreBuiltins() {
|
||||
defineBuiltin("std::core::array_index", 0, "$std_core_array_index", func(f *frame, args []runtimeValue) (runtimeValue, error) {
|
||||
return int64(f.cursorOwner().arrayIndex), nil
|
||||
return int64(f.arrayIndexInScope()), nil
|
||||
})
|
||||
defineBuiltin("std::core::member_count", 1, "$std_core_member_count", func(f *frame, args []runtimeValue) (runtimeValue, error) {
|
||||
if elements, isArray := args[0].([]runtimeValue); isArray {
|
||||
@@ -343,7 +355,7 @@ func defineCoreBuiltins() {
|
||||
if node.Formatted != "" {
|
||||
return node.Formatted, nil
|
||||
}
|
||||
return display(node.raw), nil
|
||||
return display(node.runtimeValue()), nil
|
||||
}
|
||||
return display(args[0]), nil
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ package patterns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -626,3 +628,52 @@ func TestImportedDeclarationsAreShared(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileReportsEndlessTemplateRecursion(t *testing.T) {
|
||||
_, diagnostics := Compile(`
|
||||
struct Level<auto Depth> {
|
||||
u8 value;
|
||||
Level<Depth + 1> next;
|
||||
};
|
||||
Level<0> root @ 0;
|
||||
`)
|
||||
if len(diagnostics) == 0 || !strings.Contains(diagnostics[0].Message, "recursively") {
|
||||
t.Fatalf("unexpected diagnostics: %v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileFileWithDefines(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "layout.hexpat")
|
||||
if err := os.WriteFile(path, []byte(`
|
||||
#ifdef WIDE
|
||||
struct Layout { u16 value; };
|
||||
#else
|
||||
struct Layout { u8 value; };
|
||||
#endif
|
||||
|
||||
struct Scaled { u8 bytes[SCALE * 2]; };
|
||||
`), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
narrow, diagnostics, err := CompileFile(path, nil, map[string]string{"SCALE": "3"})
|
||||
if err != nil || len(diagnostics) > 0 {
|
||||
t.Fatalf("compile failed: %v %v", err, diagnostics)
|
||||
}
|
||||
wide, diagnostics, err := CompileFile(path, nil, map[string]string{"WIDE": "", "SCALE": "4"})
|
||||
if err != nil || len(diagnostics) > 0 {
|
||||
t.Fatalf("compile failed: %v %v", err, diagnostics)
|
||||
}
|
||||
sizeOf := func(module *Module, name string) int {
|
||||
return module.Layout(Targets[0]).Of(findType(module, name)).Size
|
||||
}
|
||||
if sizeOf(narrow, "Layout") != 1 || sizeOf(wide, "Layout") != 2 {
|
||||
t.Fatalf("the define should select the branch: %d %d", sizeOf(narrow, "Layout"), sizeOf(wide, "Layout"))
|
||||
}
|
||||
if sizeOf(narrow, "Scaled") != 6 || sizeOf(wide, "Scaled") != 8 {
|
||||
t.Fatalf("the define's body should expand: %d %d", sizeOf(narrow, "Scaled"), sizeOf(wide, "Scaled"))
|
||||
}
|
||||
again, _, _ := CompileFile(path, nil, map[string]string{"SCALE": "3"})
|
||||
if again != narrow {
|
||||
t.Fatal("the cache should keep one compilation per set of defines")
|
||||
}
|
||||
}
|
||||
@@ -363,6 +363,7 @@ type frame struct {
|
||||
caller *frame
|
||||
depth int
|
||||
arrayIndex int
|
||||
inArray bool
|
||||
bits bool
|
||||
bitfield *Bitfield
|
||||
bitCursor int
|
||||
@@ -399,6 +400,9 @@ func (f *frame) node(path []*Field) (*DecodedValue, error) {
|
||||
for i, field := range path {
|
||||
node, isDecoded := scope.nodes[field]
|
||||
if !isDecoded {
|
||||
node = scope.nodeNamed(field.Name)
|
||||
}
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("%s is not available", field.Name)
|
||||
}
|
||||
if node.pointee != nil {
|
||||
@@ -437,6 +441,21 @@ func (f *frame) parentNode(depth int, path []string) (*DecodedValue, error) {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (f *frame) iterate() func() {
|
||||
saved := f.inArray
|
||||
f.inArray = true
|
||||
return func() { f.inArray = saved }
|
||||
}
|
||||
|
||||
func (f *frame) arrayIndexInScope() int {
|
||||
for scope := f.cursorOwner(); scope != nil; scope = scope.parent {
|
||||
if scope.inArray {
|
||||
return scope.arrayIndex
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *frame) nodeNamed(name string) *DecodedValue {
|
||||
for field, node := range f.nodes {
|
||||
if field.Name == name {
|
||||
@@ -840,6 +859,7 @@ func (d *decoder) decodeBitfield(value *DecodedValue, t *Bitfield, offset int, p
|
||||
field.Value, field.ValueKind, field.raw = integerJSON(bits, false), "uint", bits
|
||||
if member.Enum != nil {
|
||||
field.Label = enumLabel(member.Enum, int64(bits))
|
||||
field.typ = member.Enum
|
||||
}
|
||||
}
|
||||
value.Fields = append(value.Fields, field)
|
||||
@@ -925,6 +945,7 @@ func (f *frame) placeBits(member *BitfieldMember) error {
|
||||
node.Value, node.ValueKind, node.raw = integerJSON(bits, false), "uint", bits
|
||||
if member.Enum != nil {
|
||||
node.Label = enumLabel(member.Enum, int64(bits))
|
||||
node.typ = member.Enum
|
||||
}
|
||||
}
|
||||
f.value.Fields = append(f.value.Fields, node)
|
||||
@@ -1514,6 +1535,7 @@ func (d *decoder) decodeArray(value *DecodedValue, t *Array, offset int, scope *
|
||||
value.Count = &count
|
||||
value.Elements = []*DecodedValue{}
|
||||
end := offset
|
||||
defer scope.iterate()()
|
||||
for i := 0; i != count; i++ {
|
||||
scope.arrayIndex = i
|
||||
elementValue := d.decode("", t.Element, end, scope)
|
||||
@@ -1539,11 +1561,13 @@ func (d *decoder) decodeWhileArray(value *DecodedValue, t *Array, offset int, sc
|
||||
value.Elements = []*DecodedValue{}
|
||||
end := offset
|
||||
count := 0
|
||||
defer scope.iterate()()
|
||||
for {
|
||||
if err := d.step(); err != nil {
|
||||
value.fail(err)
|
||||
return
|
||||
}
|
||||
scope.arrayIndex = count
|
||||
owner := scope.cursorOwner()
|
||||
saved := owner.cursor
|
||||
owner.cursor = end
|
||||
@@ -1556,7 +1580,6 @@ func (d *decoder) decodeWhileArray(value *DecodedValue, t *Array, offset int, sc
|
||||
if !proceed {
|
||||
break
|
||||
}
|
||||
scope.arrayIndex = count
|
||||
elementValue := d.decode("", t.Element, end, scope)
|
||||
if elementValue.ending != flowContinue {
|
||||
if count < maxDecodedElements {
|
||||
|
||||
@@ -324,3 +324,190 @@ struct Header {
|
||||
t.Errorf("the enum should read through VarInt and take its size: %s", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTemplateFieldArgumentAcrossInstantiations(t *testing.T) {
|
||||
const source = `
|
||||
struct Payload<auto Size> {
|
||||
u8 data[Size];
|
||||
};
|
||||
struct Record<Addr> {
|
||||
u8 size;
|
||||
u8 tag = std::mem::read_unsigned($, 1);
|
||||
Payload<size> payload;
|
||||
};
|
||||
struct File {
|
||||
u8 wide;
|
||||
if (wide == 1) {
|
||||
Record<u32> record;
|
||||
} else {
|
||||
Record<u64> record;
|
||||
}
|
||||
};
|
||||
`
|
||||
for _, wide := range []byte{1, 2} {
|
||||
decoded, err := DecodeSource(source, "File", []byte{wide, 2, 0xaa, 0xbb}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(decoded, "is not available") || !strings.Contains(decoded, `"count":2`) {
|
||||
t.Errorf("wide=%d: the template argument should bind in every instantiation: %s", wide, decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEnumsKeepTheirNames(t *testing.T) {
|
||||
const source = `
|
||||
enum Kind : u8 {
|
||||
Circle = 1,
|
||||
Square = 2,
|
||||
};
|
||||
bitfield Packed {
|
||||
Kind kind : 4;
|
||||
padding : 4;
|
||||
};
|
||||
fn describe(ref auto shape) {
|
||||
return std::format("{} {}", shape.inner.kind, std::core::formatted_value(shape.packed.kind));
|
||||
};
|
||||
struct Inner {
|
||||
Kind kind;
|
||||
};
|
||||
struct Shape {
|
||||
Inner inner;
|
||||
Packed packed;
|
||||
str nested = std::format("{}", inner.kind) [[export]];
|
||||
str bits = std::format("{}", packed.kind) [[export]];
|
||||
str formatted = std::core::formatted_value(packed.kind) [[export]];
|
||||
} [[format("describe")]];
|
||||
struct Shapes {
|
||||
Shape shapes[1];
|
||||
};
|
||||
`
|
||||
decoded, err := DecodeSource(source, "Shapes", []byte{2, 1}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var value DecodedValue
|
||||
if err := json.Unmarshal([]byte(decoded), &value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
shape := value.Fields[0].Elements[0]
|
||||
if shape.Formatted != "Kind::Square Kind::Circle" {
|
||||
t.Errorf("a ref parameter should see enum names: %q", shape.Formatted)
|
||||
}
|
||||
if shape.Fields[2].Value != "Kind::Square" || shape.Fields[3].Value != "Kind::Circle" || shape.Fields[4].Value != "Kind::Circle" {
|
||||
t.Errorf("nested and bitfield members should keep their enum names: %s", shape)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeFormattedValueOfScalar(t *testing.T) {
|
||||
const source = `
|
||||
fn seconds(u32 millis) {
|
||||
return std::format("{}s", millis / 1000);
|
||||
};
|
||||
bitfield Prot {
|
||||
bool read : 1;
|
||||
bool write : 1;
|
||||
padding : 6;
|
||||
} [[format("describeProt")]];
|
||||
fn describeProt(ref auto prot) {
|
||||
return prot.read ? "r" : "-";
|
||||
};
|
||||
struct Timing {
|
||||
u32 elapsed [[format("seconds")]];
|
||||
Prot prot;
|
||||
str summary = std::format("{} {}", std::core::formatted_value(elapsed), std::core::formatted_value(prot)) [[export]];
|
||||
};
|
||||
`
|
||||
decoded, err := DecodeSource(source, "Timing", []byte{0xd0, 0x07, 0, 0, 1}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(decoded, `"value":"2s r"`) {
|
||||
t.Errorf("formatted_value should format scalar and bitfield fields: %s", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeArrayIndexInsideElement(t *testing.T) {
|
||||
const source = `
|
||||
struct Entry {
|
||||
u8 value;
|
||||
u32 index = std::core::array_index() [[export]];
|
||||
};
|
||||
struct Table {
|
||||
Entry entries[3];
|
||||
};
|
||||
`
|
||||
decoded, err := DecodeSource(source, "Table", []byte{10, 20, 30}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var value DecodedValue
|
||||
if err := json.Unmarshal([]byte(decoded), &value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, element := range value.Fields[0].Elements {
|
||||
if element.Fields[1].Value != float64(i) {
|
||||
t.Errorf("element %d reports index %v", i, element.Fields[1].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeTemplateLocalArgumentAcrossInstantiations(t *testing.T) {
|
||||
const source = `
|
||||
struct Leaf<auto Kind> {
|
||||
u8 value;
|
||||
str summary = std::format("kind {}", Kind) [[export]];
|
||||
};
|
||||
struct First<auto Kind> {
|
||||
Leaf<Kind> leaf;
|
||||
};
|
||||
struct Second<auto Kind> {
|
||||
Leaf<Kind> leaf;
|
||||
};
|
||||
struct Top {
|
||||
u8 kind;
|
||||
First<kind> first;
|
||||
Second<kind + 1> second;
|
||||
};
|
||||
`
|
||||
decoded, err := DecodeSource(source, "Top", []byte{7, 1, 2}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(decoded, `"value":"kind 7"`) || !strings.Contains(decoded, `"value":"kind 8"`) {
|
||||
t.Errorf("each instantiation should see its own argument: %s", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeRefParametersReceivePatterns(t *testing.T) {
|
||||
const source = `
|
||||
bitfield Bits {
|
||||
bool read : 1;
|
||||
bool write : 1;
|
||||
padding : 6;
|
||||
};
|
||||
fn describeBits(ref auto bits) {
|
||||
return (bits.read ? "r" : "-") + (bits.write ? "w" : "-");
|
||||
};
|
||||
fn describePair(ref auto pair) {
|
||||
return std::format("{}/{}", pair.a, pair.b);
|
||||
};
|
||||
struct Pair {
|
||||
u8 a;
|
||||
u8 b;
|
||||
};
|
||||
struct Root {
|
||||
Bits bits;
|
||||
Pair pair;
|
||||
str viaBitfield = describeBits(bits) [[export]];
|
||||
str viaStruct = describePair(pair) [[export]];
|
||||
};
|
||||
`
|
||||
decoded, err := DecodeSource(source, "Root", []byte{3, 5, 6}, 0, Targets[0], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(decoded, `"value":"rw"`) || !strings.Contains(decoded, `"value":"5/6"`) {
|
||||
t.Errorf("ref parameters should receive the pattern: %s", decoded)
|
||||
}
|
||||
}
|
||||
@@ -360,6 +360,21 @@ func (f *frame) element(v *Index) (*DecodedValue, error) {
|
||||
return nil, fmt.Errorf("value cannot be indexed")
|
||||
}
|
||||
|
||||
func (v *DecodedValue) runtimeValue() runtimeValue {
|
||||
if v.typ == nil {
|
||||
return v.raw
|
||||
}
|
||||
enum, isEnum := Unalias(v.typ).(*Enum)
|
||||
if !isEnum {
|
||||
return v.raw
|
||||
}
|
||||
number, err := toInt(v.raw)
|
||||
if err != nil {
|
||||
return v.raw
|
||||
}
|
||||
return enumValue{enum: enum, value: number}
|
||||
}
|
||||
|
||||
func (f *frame) memberOf(v *MemberOf) (*DecodedValue, error) {
|
||||
owner, err := f.memberOwner(v)
|
||||
if err != nil {
|
||||
@@ -442,6 +457,13 @@ func (f *frame) lookupLocal(local *Local) (runtimeValue, error) {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
for scope := f; scope != nil; scope = scope.parent {
|
||||
for bound, value := range scope.locals {
|
||||
if bound.Name == local.Name {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s is not available", local.Name)
|
||||
}
|
||||
|
||||
@@ -582,13 +604,13 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node.raw, nil
|
||||
return node.runtimeValue(), nil
|
||||
case *ParentFieldRef:
|
||||
node, err := f.parentNode(v.Depth, v.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node.raw, nil
|
||||
return node.runtimeValue(), nil
|
||||
case *LocalRef:
|
||||
return f.lookupLocal(v.Local)
|
||||
case *BitRef:
|
||||
@@ -596,7 +618,7 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
|
||||
if !isDecoded {
|
||||
return nil, fmt.Errorf("%s is not available", v.Member.Name)
|
||||
}
|
||||
return node.raw, nil
|
||||
return node.runtimeValue(), nil
|
||||
case *GlobalRef:
|
||||
return f.global(v)
|
||||
case *ArrayValue:
|
||||
@@ -614,13 +636,13 @@ func (f *frame) eval(v Value) (runtimeValue, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return element.raw, nil
|
||||
return element.runtimeValue(), nil
|
||||
case *MemberOf:
|
||||
member, err := f.memberOf(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return member.raw, nil
|
||||
return member.runtimeValue(), nil
|
||||
case *Cursor:
|
||||
owner := f.cursorOwner()
|
||||
return int64(owner.cursor - owner.base), nil
|
||||
@@ -1291,7 +1313,7 @@ func (f *frame) call(c *FunctionCall) (runtimeValue, error) {
|
||||
return nil, err
|
||||
}
|
||||
for i, param := range c.Function.Params {
|
||||
if i < len(c.Arguments) && isComposite(param.Type) && denotesPattern(c.Arguments[i]) {
|
||||
if i < len(c.Arguments) && (param.Ref || isComposite(param.Type)) && denotesPattern(c.Arguments[i]) {
|
||||
if arguments[i], err = f.target(c.Arguments[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ type fileCompilation struct {
|
||||
|
||||
var fileCompilations = newCache[string, *fileCompilation](16)
|
||||
|
||||
func CompileFile(path string, overrides map[string]string) (*Module, []Diagnostic, error) {
|
||||
if cached, isCached := fileCompilations.get(path); isCached && cached.resolver.Fresh(overrides) {
|
||||
func CompileFile(path string, overrides map[string]string, defines map[string]string) (*Module, []Diagnostic, error) {
|
||||
key := path + "\x00" + definedMacros(defines).signature()
|
||||
if cached, isCached := fileCompilations.get(key); isCached && cached.resolver.Fresh(overrides) {
|
||||
return cached.module, cached.diagnostics, nil
|
||||
}
|
||||
resolver := NewFileResolver(overrides)
|
||||
@@ -36,8 +37,8 @@ func CompileFile(path string, overrides map[string]string) (*Module, []Diagnosti
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
module, diagnostics := CompileSource(main, resolver)
|
||||
fileCompilations.put(path, &fileCompilation{module: module, diagnostics: diagnostics, resolver: resolver})
|
||||
module, diagnostics := compileUnit(main, definedMacros(defines), newUnitRegistry(resolver))
|
||||
fileCompilations.put(key, &fileCompilation{module: module, diagnostics: diagnostics, resolver: resolver})
|
||||
return module, diagnostics, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
type LanguageService struct {
|
||||
documents map[string]*serviceDocument
|
||||
defines map[string]string
|
||||
}
|
||||
|
||||
type serviceDocument struct {
|
||||
@@ -147,6 +148,13 @@ func (s *LanguageService) Handles(uri string) bool {
|
||||
return strings.HasSuffix(uri, ".hexpat") || strings.HasSuffix(uri, ".pat")
|
||||
}
|
||||
|
||||
func (s *LanguageService) Define(defines map[string]string) {
|
||||
s.defines = defines
|
||||
for uri, d := range s.documents {
|
||||
s.documents[uri] = s.newDocument(uri, d.text)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LanguageService) Open(uri string, text string) {
|
||||
s.documents[uri] = s.newDocument(uri, text)
|
||||
}
|
||||
@@ -163,7 +171,7 @@ func (s *LanguageService) newDocument(uri string, text string) *serviceDocument
|
||||
path := documentPath(uri)
|
||||
overlays := s.overlays()
|
||||
overlays[path] = text
|
||||
parsed := parseShared(Source{Path: path, Text: text}, nil, NewFileResolver(overlays))
|
||||
parsed := parseShared(Source{Path: path, Text: text}, definedMacros(s.defines), NewFileResolver(overlays))
|
||||
return &serviceDocument{service: s, uri: uri, path: path, text: text, lines: lineStarts(text), file: parsed.file,
|
||||
diags: parsed.diagnostics, toks: parsed.tokens}
|
||||
}
|
||||
@@ -202,7 +210,7 @@ func (s *LanguageService) Diagnostics(uri string) []LSPDiagnostic {
|
||||
}
|
||||
|
||||
func (d *serviceDocument) compile() (*Module, []Diagnostic) {
|
||||
module, diagnostics, _ := CompileFile(d.path, d.service.overlays())
|
||||
module, diagnostics, _ := CompileFile(d.path, d.service.overlays(), d.service.defines)
|
||||
return module, diagnostics
|
||||
}
|
||||
|
||||
|
||||
@@ -243,3 +243,15 @@ func TestServiceCompletionsFollowContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDefines(t *testing.T) {
|
||||
service := NewLanguageService()
|
||||
service.Open("file:///layout.hexpat", "#ifdef LIVE\nstruct Layout { u8 live; };\n#else\nstruct Layout { u8 file[missing]; };\n#endif\n")
|
||||
if diagnostics := service.Diagnostics("file:///layout.hexpat"); len(diagnostics) != 1 {
|
||||
t.Fatalf("the undefined branch should be compiled: %+v", diagnostics)
|
||||
}
|
||||
service.Define(map[string]string{"LIVE": ""})
|
||||
if diagnostics := service.Diagnostics("file:///layout.hexpat"); len(diagnostics) != 0 {
|
||||
t.Fatalf("the defined branch should be compiled: %+v", diagnostics)
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,15 @@ func preprocess(tokens []token, diagnostics []Diagnostic, incoming macros, inclu
|
||||
return p.output, append(diagnostics, p.diagnostics...), p.defines
|
||||
}
|
||||
|
||||
func definedMacros(defines map[string]string) macros {
|
||||
defined := make(macros, len(defines))
|
||||
for name, body := range defines {
|
||||
tokens, _ := lex("", body)
|
||||
defined[name] = tokens[:len(tokens)-1]
|
||||
}
|
||||
return defined
|
||||
}
|
||||
|
||||
func (m macros) clone() macros {
|
||||
cloned := make(macros, len(m))
|
||||
for name, body := range m {
|
||||
|
||||
@@ -293,6 +293,8 @@ func structLocals(t *Struct) []*Local {
|
||||
return locals
|
||||
}
|
||||
|
||||
const maxInstantiationDepth = 64
|
||||
|
||||
func (r *resolver) instantiate(ref TypeRef, scope *fieldScope) Type {
|
||||
decl, isTemplate := r.lookupTemplate(ref.Name)
|
||||
if !isTemplate {
|
||||
@@ -307,6 +309,12 @@ func (r *resolver) instantiate(ref TypeRef, scope *fieldScope) Type {
|
||||
r.report(ref.Position, fmt.Sprintf("%s takes %d template arguments", ref.Name, len(params)))
|
||||
return nil
|
||||
}
|
||||
if r.instantiations >= maxInstantiationDepth {
|
||||
r.report(ref.Position, fmt.Sprintf("%s is instantiated recursively without end", ref.Name))
|
||||
return nil
|
||||
}
|
||||
r.instantiations++
|
||||
defer func() { r.instantiations-- }()
|
||||
|
||||
subst := &substitution{types: map[string]Type{}, values: map[string]Value{}, namings: map[string][]NamePart{}, outer: r.subst}
|
||||
keys := make([]string, len(params))
|
||||
|
||||
@@ -34,7 +34,11 @@ func Compile(source string) (*Module, []Diagnostic) {
|
||||
}
|
||||
|
||||
func CompileSource(main Source, resolver Resolver) (*Module, []Diagnostic) {
|
||||
return compileUnit(main, nil, &unitRegistry{resolver: resolver, byKey: map[string]*sharedUnit{}})
|
||||
return compileUnit(main, nil, newUnitRegistry(resolver))
|
||||
}
|
||||
|
||||
func newUnitRegistry(resolver Resolver) *unitRegistry {
|
||||
return &unitRegistry{resolver: resolver, byKey: map[string]*sharedUnit{}}
|
||||
}
|
||||
|
||||
func compileUnit(main Source, incoming macros, registry *unitRegistry) (*Module, []Diagnostic) {
|
||||
|
||||
@@ -7,14 +7,16 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const playerPattern = `
|
||||
#pragma abi native
|
||||
|
||||
const vec3Pattern = `
|
||||
struct Vec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
`
|
||||
|
||||
const playerPattern = `
|
||||
#pragma abi native
|
||||
|
||||
struct Player {
|
||||
u32 hitpoints;
|
||||
@@ -26,7 +28,7 @@ struct Player {
|
||||
|
||||
func TestBuildAgentWithPatterns(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeFile(t, filepath.Join(dir, "player.pat"), playerPattern)
|
||||
writeFile(t, filepath.Join(dir, "player.pat"), vec3Pattern+playerPattern)
|
||||
writeFile(t, filepath.Join(dir, "agent.ts"), `
|
||||
import { Player } from "./player.pat";
|
||||
|
||||
@@ -125,8 +127,8 @@ console.log(isRed, position.x);
|
||||
if err != nil {
|
||||
t.Fatalf("build failed: %v\n%+v", err, diagnostics)
|
||||
}
|
||||
if !strings.Contains(bundle, "geo.Vec2 = class Vec2") {
|
||||
t.Errorf("bundle lacks the imported type:\n%s", bundle)
|
||||
if !strings.Contains(bundle, "geometry.Vec2 = class Vec2") || !strings.Contains(bundle, "geo.Vec2 = geometry.Vec2") {
|
||||
t.Errorf("bundle lacks the imported type under its alias:\n%s", bundle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -2416,9 +2416,12 @@ namespace Frida {
|
||||
|
||||
var config = (BareboneLinuxKernelConfig) Json.gobject_deserialize (
|
||||
typeof (BareboneLinuxKernelConfig), node);
|
||||
config.kernel = (kind == "linux-system-map")
|
||||
? (LinuxKernelSymbols) LinuxSystemMap.open (file)
|
||||
: (LinuxKernelSymbols) LinuxKernelImage.open (file);
|
||||
// FIXME: Work around Vala compiler bug where upcasting the result inside the
|
||||
// ?: drops its ownership, freeing it before set_kernel () stores it.
|
||||
if (kind == "linux-system-map")
|
||||
config.kernel = LinuxSystemMap.open (file);
|
||||
else
|
||||
config.kernel = LinuxKernelImage.open (file);
|
||||
return config;
|
||||
} catch (Error e) {
|
||||
return new BareboneInvalidImageConfig (e.message);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/frida-gum.git
|
||||
revision = 17.22.2
|
||||
revision = 17.23.2
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -190,6 +190,17 @@ struct Image { u8 magic[2]; u8 width; };
|
||||
var scaled = yield configurable.decode ("Configurable", new Bytes ({ 7 }), 0, decode_options);
|
||||
assert (scaled.fields.get (0).fields.get (1).value.get_int64 () == 21);
|
||||
|
||||
write_pattern (project_dir, "layout.hexpat",
|
||||
"#ifdef WIDE\nstruct Layout { u16 value; };\n#else\nstruct Layout { u8 value; };\n#endif\n");
|
||||
var narrow = yield compiler.compile ("layout.hexpat", options);
|
||||
assert (narrow.lookup ("Layout").size == 1);
|
||||
var wide_options = make_pattern_options (project_dir);
|
||||
wide_options.defines["WIDE"] = new Variant.string ("");
|
||||
var wide = yield compiler.compile ("layout.hexpat", wide_options);
|
||||
assert (wide.lookup ("Layout").size == 2);
|
||||
var wide_value = yield wide.decode ("Layout", new Bytes ({ 1, 2 }), 0);
|
||||
assert (wide_value.fields.get (0).value.get_uint64 () == 0x201);
|
||||
|
||||
var data = new uint8[32];
|
||||
data[0] = 94;
|
||||
data[4] = 7;
|
||||
|
||||
Reference in new issue
Block a user