use std::ffi::c_void;
use std::ptr;
use idakit_sys as sys;
struct MainClaim(*mut *mut c_void);
impl MainClaim {
fn locate() -> Result<Self, String> {
let g_main = decode_g_main(sys::is_main_thread as *const u8)?;
Ok(Self(g_main as *mut *mut c_void))
}
fn reclaim(self) -> Result<(), String> {
unsafe { self.0.write(ptr::null_mut()) };
if unsafe { sys::is_main_thread() } {
Ok(())
} else {
Err("re-claim did not take (located g_main address is wrong)".to_owned())
}
}
}
#[cfg(target_arch = "x86_64")]
fn decode_g_main(entry: *const u8) -> Result<*const u8, String> {
const WINDOW: usize = 32;
let entry = unsafe { follow_jmp_thunk(entry) };
let head: [u8; WINDOW] = unsafe { ptr::read(entry.cast()) };
for k in 0..=WINDOW - 7 {
if head[k] == 0x48 && head[k + 1] == 0x8b && head[k + 2] & 0xc7 == 0x05 {
let disp = i32::from_le_bytes([head[k + 3], head[k + 4], head[k + 5], head[k + 6]]);
return Ok(entry.wrapping_offset(k as isize + 7 + disp as isize));
}
}
Err(format!(
"no rip-relative g_main load in is_main_thread prologue {head:02x?}"
))
}
#[cfg(target_arch = "x86_64")]
unsafe fn follow_jmp_thunk(entry: *const u8) -> *const u8 {
let head: [u8; 6] = unsafe { ptr::read(entry.cast()) };
if head[0] == 0xff && head[1] == 0x25 {
let disp = i32::from_le_bytes([head[2], head[3], head[4], head[5]]);
let slot = entry.wrapping_offset(6 + disp as isize) as *const *const u8;
return unsafe { *slot };
}
entry
}
#[cfg(target_arch = "aarch64")]
fn decode_g_main(entry: *const u8) -> Result<*const u8, String> {
const WINDOW: usize = 8;
let insns: [u32; WINDOW] = unsafe { ptr::read(entry.cast()) };
for (i, &adrp) in insns.iter().enumerate() {
if adrp & 0x9f00_0000 != 0x9000_0000 {
continue;
}
let rd = adrp & 0x1f;
let imm = i64::from((((adrp >> 5) & 0x7_ffff) << 2) | ((adrp >> 29) & 0x3));
let page = (imm ^ 0x10_0000) - 0x10_0000; let adrp_pc = entry.wrapping_add(i * 4) as u64;
let base = ((adrp_pc & !0xfff) as i64 + (page << 12)) as u64;
for &ldr in &insns[i + 1..] {
if ldr & 0xffc0_0000 != 0xf940_0000 || (ldr >> 5) & 0x1f != rd {
continue;
}
let off = u64::from((ldr >> 10) & 0xfff) * 8; return Ok(base.wrapping_add(off) as *const u8);
}
}
Err("is_main_thread has no adrp+ldr g_main load in its prologue".to_owned())
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
fn decode_g_main(_entry: *const u8) -> Result<*const u8, String> {
compile_error!("g_main steal supports x86-64 and aarch64 only");
}
pub(crate) fn steal_main() -> Result<(), String> {
MainClaim::locate()?.reclaim()
}
#[inline]
pub(crate) fn ensure_kernel_thread() {
if !unsafe { sys::is_main_thread() } {
steal_main().expect("re-steal g_main after Database migrated to this thread");
}
}
#[cfg(all(test, target_arch = "x86_64"))]
mod x86_64_tests {
use assert2::assert;
use super::*;
fn code_at(offset: usize, disp: i32) -> [u8; 32] {
let mut buf = [0x90u8; 32];
buf[offset] = 0x48;
buf[offset + 1] = 0x8b;
buf[offset + 2] = 0x05;
buf[offset + 3..offset + 7].copy_from_slice(&disp.to_le_bytes());
buf
}
#[test]
fn decodes_load_at_prologue_start() {
let code = code_at(0, 0x1234);
let entry = code.as_ptr();
assert!(decode_g_main(entry).unwrap() == entry.wrapping_offset(7 + 0x1234));
}
#[test]
fn scans_past_a_stack_frame() {
let code = code_at(6, -0x40);
let entry = code.as_ptr();
assert!(decode_g_main(entry).unwrap() == entry.wrapping_offset(6 + 7 - 0x40));
}
#[test]
fn no_rip_load_in_window_is_err() {
let code = [0x90u8; 32];
assert!(decode_g_main(code.as_ptr()).is_err());
}
#[test]
fn follows_thunk_to_slot_target() {
#[repr(align(8))]
struct Aligned([u8; 16]);
let target = 0u8;
let body = &target as *const u8;
let mut buf = Aligned([0u8; 16]);
buf.0[0] = 0xff;
buf.0[1] = 0x25;
buf.0[2..6].copy_from_slice(&2i32.to_le_bytes()); buf.0[8..16].copy_from_slice(&(body as usize).to_le_bytes());
let entry = buf.0.as_ptr();
assert!(unsafe { follow_jmp_thunk(entry) } == body);
}
#[test]
fn non_thunk_entry_passes_through() {
let code = code_at(0, 0);
let entry = code.as_ptr();
assert!(unsafe { follow_jmp_thunk(entry) } == entry);
}
}
#[cfg(all(test, target_arch = "aarch64"))]
mod aarch64_tests {
use assert2::assert;
use super::*;
const ADRP_X0_PLUS1: u32 = 0xB000_0000;
const LDR_X0_X0_24: u32 = 0xF940_0C00;
fn window(offset: usize) -> [u32; 8] {
let mut w = [0u32; 8];
w[offset] = ADRP_X0_PLUS1;
w[offset + 1] = LDR_X0_X0_24;
w
}
fn expected(entry: *const u8, adrp_index: usize) -> *const u8 {
let adrp_pc = entry as usize + adrp_index * 4;
((adrp_pc & !0xfff) + 0x1000 + 24) as *const u8
}
#[test]
fn decodes_adrp_ldr_at_start() {
let w = window(0);
let entry = w.as_ptr().cast::<u8>();
assert!(decode_g_main(entry).unwrap() == expected(entry, 0));
}
#[test]
fn scans_past_a_stack_frame() {
let w = window(2);
let entry = w.as_ptr().cast::<u8>();
assert!(decode_g_main(entry).unwrap() == expected(entry, 2));
}
#[test]
fn no_adrp_ldr_in_window_is_err() {
let w = [0u32; 8];
assert!(decode_g_main(w.as_ptr().cast::<u8>()).is_err());
}
}