Sign in Sign up
kretrod/lodestone Public
Branches
master
217 lines (197 loc) · 8.4 KB Raw
//! HotSpot's self-description: the VMStructs table.
//!
//! Every HotSpot ships an exported table describing the layout of its own C++
//! structures — the same table `jhsdb` uses to debug a live JVM from outside.
//! Because we read it at runtime instead of hardcoding offsets, this works
//! across JDK builds and versions without a single magic number.
//!
//!     jvm.dll exports:
//!       gHotSpotVMStructs            -> VMStructEntry[]   (type, field, offset)
//!       gHotSpotVMTypes              -> VMTypeEntry[]     (type, super, size)
//!       gHotSpotVMIntConstants       -> VMIntConstantEntry[]
//!       gHotSpotVMLongConstants      -> VMLongConstantEntry[]
//!       gHotSpotVM*EntryArrayStride  -> element size of each array
//!       gHotSpotVM*Entry*Offset      -> field offsets inside each element

use std::collections::HashMap;

use crate::win::{Module, Process, Result};

#[derive(Debug, Clone)]
pub struct FieldEntry {
    pub type_name: String,
    pub field_name: String,
    /// C++ type of the field, verbatim ("Symbol*", "jint", "OopHandle", ...).
    pub type_string: String,
    pub is_static: bool,
    /// Byte offset within the struct (instance fields).
    pub offset: u64,
    /// Absolute address in the target (static fields).
    pub address: u64,
}

#[derive(Debug, Clone)]
pub struct TypeInfo {
    pub name: String,
    pub superclass: String,
    pub is_oop_type: bool,
    pub is_integer: bool,
    pub is_unsigned: bool,
    pub size: u64,
}

pub struct VmDb {
    pub types: HashMap<String, TypeInfo>,
    pub fields: HashMap<(String, String), FieldEntry>,
    pub int_consts: HashMap<String, i32>,
    pub long_consts: HashMap<String, u64>,
}

/// Read one exported `uint64_t` global.
fn export_u64(p: &Process, exports: &HashMap<String, u64>, name: &str) -> Result<u64> {
    let a = *exports
        .get(name)
        .ok_or_else(|| format!("jvm.dll does not export {name}"))?;
    p.u64(a)
}

impl VmDb {
    pub fn load(p: &Process, jvm: &Module) -> Result<Self> {
        let exports: HashMap<String, u64> = p.exports(jvm)?.into_iter().collect();

        // --- struct entries -------------------------------------------------
        let structs = export_u64(p, &exports, "gHotSpotVMStructs")?;
        let s_stride = export_u64(p, &exports, "gHotSpotVMStructEntryArrayStride")?;
        let o_type = export_u64(p, &exports, "gHotSpotVMStructEntryTypeNameOffset")?;
        let o_field = export_u64(p, &exports, "gHotSpotVMStructEntryFieldNameOffset")?;
        let o_tstr = export_u64(p, &exports, "gHotSpotVMStructEntryTypeStringOffset")?;
        let o_static = export_u64(p, &exports, "gHotSpotVMStructEntryIsStaticOffset")?;
        let o_off = export_u64(p, &exports, "gHotSpotVMStructEntryOffsetOffset")?;
        let o_addr = export_u64(p, &exports, "gHotSpotVMStructEntryAddressOffset")?;

        let mut fields = HashMap::new();
        let mut e = structs;
        loop {
            // The table terminates on the first entry with a null fieldName.
            let field_ptr = p.u64(e + o_field)?;
            if field_ptr == 0 {
                break;
            }
            let type_name = p.cstring(p.u64(e + o_type)?, 256)?;
            let field_name = p.cstring(field_ptr, 256)?;
            let type_string_ptr = p.u64(e + o_tstr)?;
            let type_string = if type_string_ptr == 0 {
                String::new()
            } else {
                p.cstring(type_string_ptr, 256)?
            };
            let is_static = p.i32(e + o_static)? != 0;
            let entry = FieldEntry {
                type_string,
                is_static,
                offset: if is_static { 0 } else { p.u64(e + o_off)? },
                address: if is_static { p.u64(e + o_addr)? } else { 0 },
                type_name: type_name.clone(),
                field_name: field_name.clone(),
            };
            fields.insert((type_name, field_name), entry);
            e += s_stride;
        }

        // --- type entries ---------------------------------------------------
        let types_base = export_u64(p, &exports, "gHotSpotVMTypes")?;
        let t_stride = export_u64(p, &exports, "gHotSpotVMTypeEntryArrayStride")?;
        let t_name = export_u64(p, &exports, "gHotSpotVMTypeEntryTypeNameOffset")?;
        let t_super = export_u64(p, &exports, "gHotSpotVMTypeEntrySuperclassNameOffset")?;
        let t_oop = export_u64(p, &exports, "gHotSpotVMTypeEntryIsOopTypeOffset")?;
        let t_int = export_u64(p, &exports, "gHotSpotVMTypeEntryIsIntegerTypeOffset")?;
        let t_uns = export_u64(p, &exports, "gHotSpotVMTypeEntryIsUnsignedOffset")?;
        let t_size = export_u64(p, &exports, "gHotSpotVMTypeEntrySizeOffset")?;

        let mut types = HashMap::new();
        let mut e = types_base;
        loop {
            let name_ptr = p.u64(e + t_name)?;
            if name_ptr == 0 {
                break;
            }
            let name = p.cstring(name_ptr, 256)?;
            let sup_ptr = p.u64(e + t_super)?;
            let superclass = if sup_ptr == 0 {
                String::new()
            } else {
                p.cstring(sup_ptr, 256)?
            };
            types.insert(
                name.clone(),
                TypeInfo {
                    name,
                    superclass,
                    is_oop_type: p.i32(e + t_oop)? != 0,
                    is_integer: p.i32(e + t_int)? != 0,
                    is_unsigned: p.i32(e + t_uns)? != 0,
                    size: p.u64(e + t_size)?,
                },
            );
            e += t_stride;
        }

        // --- constants ------------------------------------------------------
        let mut int_consts = HashMap::new();
        let ic = export_u64(p, &exports, "gHotSpotVMIntConstants")?;
        let ic_stride = export_u64(p, &exports, "gHotSpotVMIntConstantEntryArrayStride")?;
        let ic_name = export_u64(p, &exports, "gHotSpotVMIntConstantEntryNameOffset")?;
        let ic_val = export_u64(p, &exports, "gHotSpotVMIntConstantEntryValueOffset")?;
        let mut e = ic;
        loop {
            let np = p.u64(e + ic_name)?;
            if np == 0 {
                break;
            }
            int_consts.insert(p.cstring(np, 256)?, p.i32(e + ic_val)?);
            e += ic_stride;
        }

        let mut long_consts = HashMap::new();
        let lc = export_u64(p, &exports, "gHotSpotVMLongConstants")?;
        let lc_stride = export_u64(p, &exports, "gHotSpotVMLongConstantEntryArrayStride")?;
        let lc_name = export_u64(p, &exports, "gHotSpotVMLongConstantEntryNameOffset")?;
        let lc_val = export_u64(p, &exports, "gHotSpotVMLongConstantEntryValueOffset")?;
        let mut e = lc;
        loop {
            let np = p.u64(e + lc_name)?;
            if np == 0 {
                break;
            }
            long_consts.insert(p.cstring(np, 256)?, p.u64(e + lc_val)?);
            e += lc_stride;
        }

        Ok(Self { types, fields, int_consts, long_consts })
    }

    pub fn field(&self, ty: &str, field: &str) -> Result<&FieldEntry> {
        self.fields
            .get(&(ty.to_string(), field.to_string()))
            .ok_or_else(|| format!("VMStructs has no {ty}::{field}"))
    }

    /// Byte offset of an instance field within its struct.
    pub fn off(&self, ty: &str, field: &str) -> Result<u64> {
        let f = self.field(ty, field)?;
        if f.is_static {
            return Err(format!("{ty}::{field} is static, not an offset"));
        }
        Ok(f.offset)
    }

    /// Absolute address of a static field in the target process.
    pub fn static_addr(&self, ty: &str, field: &str) -> Result<u64> {
        let f = self.field(ty, field)?;
        if !f.is_static {
            return Err(format!("{ty}::{field} is an instance field"));
        }
        Ok(f.address)
    }

    pub fn type_size(&self, ty: &str) -> Result<u64> {
        self.types
            .get(ty)
            .map(|t| t.size)
            .ok_or_else(|| format!("VMStructs has no type {ty}"))
    }

    pub fn int_const(&self, name: &str) -> Result<i32> {
        self.int_consts
            .get(name)
            .copied()
            .ok_or_else(|| format!("no int constant {name}"))
    }

    /// All fields declared on one type, sorted by offset.
    pub fn fields_of(&self, ty: &str) -> Vec<&FieldEntry> {
        let mut v: Vec<&FieldEntry> = self.fields.values().filter(|f| f.type_name == ty).collect();
        v.sort_by_key(|f| (f.is_static, f.offset));
        v
    }
}