diff --git a/Cargo.toml b/Cargo.toml index 46a9f8c..054e844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,17 @@ -[package] -name = "jvm-rs" -version = "0.1.0" -edition = "2024" +[workspace] +members = [ + "crates/*" +] +resolver = "3" -[dependencies] +[workspace.dependencies] deku = { version = "0.20.0", features = ["logging"] } deku_derive = "0.20.0" log = "0.4.28" env_logger = "0.11.8" itertools = "0.14.0" sevenz-rust2 = { version = "0.19.3", features = ["brotli", "zstd"] } - -[build-dependencies] -bindgen = "0.72.1" - -[lints.rust] -missing_docs = "warn" \ No newline at end of file +dashmap = "7.0.0-rc2" +libloading = "0.8.9" +libffi = "5.0.0" +jni = "0.21.1" \ No newline at end of file diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..a40a538 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "jvm-rs-core" +version = "0.1.0" +edition = "2024" +publish = ["nexus"] + +[dependencies] +deku = { workspace = true } +deku_derive = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } +itertools = { workspace = true } +sevenz-rust2 = { workspace = true } +dashmap = { workspace = true } +libloading = { workspace = true } +libffi = { workspace = true } +jni = { workspace = true } + +[build-dependencies] +bindgen = "0.72.1" + +[lints.rust] +#missing_docs = "warn" \ No newline at end of file diff --git a/src/attributes.rs b/crates/core/src/attributes.rs similarity index 100% rename from src/attributes.rs rename to crates/core/src/attributes.rs diff --git a/src/bimage.rs b/crates/core/src/bimage.rs similarity index 55% rename from src/bimage.rs rename to crates/core/src/bimage.rs index 61c3e21..10f3437 100644 --- a/src/bimage.rs +++ b/crates/core/src/bimage.rs @@ -4,7 +4,6 @@ use std::fs::File; const DEFAULT_LOCATION: &str = "./lib/modules"; - pub struct Bimage { image: ArchiveReader, modules: Vec, @@ -12,13 +11,18 @@ pub struct Bimage { impl Default for Bimage { fn default() -> Self { - let reader = ArchiveReader::open(DEFAULT_LOCATION, Default::default()).expect("No image location given, and unable to open/locate default image"); + let reader = ArchiveReader::open(DEFAULT_LOCATION, Default::default()) + .expect("No image location given, and unable to open/locate default image"); - - let mut modules = reader.archive().files.iter().filter(|e|{ - e.is_directory && - e.name.split("/").count() == 1 - }).map(|e| { e.name.clone() }).collect::>().into_iter().collect::>(); + let mut modules = reader + .archive() + .files + .iter() + .filter(|e| e.is_directory && e.name.split("/").count() == 1) + .map(|e| e.name.clone()) + .collect::>() + .into_iter() + .collect::>(); modules.sort(); Self { image: reader, @@ -27,34 +31,39 @@ impl Default for Bimage { } } - impl Bimage { pub fn new(path: impl AsRef) -> Self { - let reader = ArchiveReader::open(path, Default::default()).expect("Unable to find specified bimage."); + let reader = ArchiveReader::open(path, Default::default()) + .expect("Unable to find specified bimage."); Self { image: reader, ..Default::default() } } - fn resolve_path(module: &str, class: &str) -> String { - let module = if module.is_empty() { "java.base" } else { module }; + let module = if module.is_empty() { + "java.base" + } else { + module + }; let class = Self::d2s(class); format!("{module}/classes/{class}.class") } - fn d2s( dots: &str) -> String { + fn d2s(dots: &str) -> String { dots.replace(".", "/") } - fn f2s ( slashes: &str) -> String { + fn f2s(slashes: &str) -> String { slashes.replace("/", ".") } - pub fn get_class(&mut self, module: &str, class: &str) -> Option> { let path = Self::resolve_path(module, class); - self.image.read_file(&path).map_err(|e| { - println!("{}", path); - }).ok() + self.image + .read_file(&path) + .map_err(|e| { + log::trace!("Class not found {}", path); + }) + .ok() } -} \ No newline at end of file +} diff --git a/src/class.rs b/crates/core/src/class.rs similarity index 88% rename from src/class.rs rename to crates/core/src/class.rs index 41fef98..49a1987 100644 --- a/src/class.rs +++ b/crates/core/src/class.rs @@ -4,6 +4,7 @@ use crate::class_file::{ MethodInfo, MethodRef, }; use crate::{FieldType, MethodDescriptor, VmError}; +use log::trace; use std::sync::{Arc, Mutex}; use std::thread::ThreadId; @@ -20,6 +21,7 @@ pub enum InitState { Error(String), } +#[derive(Debug)] pub struct RuntimeClass { pub constant_pool: Arc>, pub access_flags: ClassFlags, @@ -33,20 +35,23 @@ pub struct RuntimeClass { } impl RuntimeClass { - pub fn find_method(&self, name: &str, desc: MethodDescriptor) -> Result<&MethodData, VmError> { - println!("Finding method"); + pub fn find_method(&self, name: &str, desc: &MethodDescriptor) -> Result<&MethodData, VmError> { + trace!( + "Finding in {}, method Name: {name} desc:{desc},", + self.this_class + ); if let Some(method) = self.methods.iter().find(|e| { - println!("Method Name Needed: {name}, Checked:{}", e.name); - println!("Method type Needed: {desc:?}, Checked:{:?}", e.desc); let name_match = e.name.eq(name); let param_match = desc.parameters == e.desc.parameters; name_match && param_match }) { + trace!("Found method: {name}"); return Ok(method); }; // recurse super class if let Some(super_class) = &self.super_class { + trace!("Recursing for {name}"); return super_class.find_method(name, desc); } // No method found, and we must be Object, as we don't have a superclass diff --git a/src/class_file/class_file.rs b/crates/core/src/class_file/class_file.rs similarity index 98% rename from src/class_file/class_file.rs rename to crates/core/src/class_file/class_file.rs index c073848..833951b 100644 --- a/src/class_file/class_file.rs +++ b/crates/core/src/class_file/class_file.rs @@ -10,7 +10,7 @@ use std::fmt; use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::Chars; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; #[derive(Debug, PartialEq, DekuRead)] #[deku(magic = b"\xCA\xFE\xBA\xBE", endian = "big")] @@ -639,11 +639,12 @@ pub struct FieldRef { pub desc: FieldType, } +#[derive(Debug)] pub struct FieldData { pub name: String, pub flags: FieldFlags, pub desc: FieldType, - pub value: Option, + pub value: Arc>>, } #[derive(Clone, Debug, PartialEq)] @@ -655,6 +656,20 @@ pub enum Constant { String(String), } +impl From for Value { + fn from(value: Constant) -> Self { + match value { + Constant::Int(x) => Value::Int(x), + Constant::Long(x) => Value::Long(x), + Constant::Float(x) => Value::Float(x), + Constant::Double(x) => Value::Double(x), + Constant::String(x) => { + todo!("Constant string") + } + } + } +} + #[allow(non_snake_case)] #[derive(Debug, PartialEq, DekuRead, DekuWrite)] pub struct ClassFlags { diff --git a/src/class_file/constant_pool.rs b/crates/core/src/class_file/constant_pool.rs similarity index 88% rename from src/class_file/constant_pool.rs rename to crates/core/src/class_file/constant_pool.rs index 3215c32..3c1425c 100644 --- a/src/class_file/constant_pool.rs +++ b/crates/core/src/class_file/constant_pool.rs @@ -1,12 +1,20 @@ +use crate::attributes::{ + Attribute, AttributeInfo, CodeAttribute, LineNumberTableAttribute, LocalVariableTableAttribute, +}; +use crate::class_file::{ + ConstantClassInfo, ConstantDynamicInfo, ConstantFieldrefInfo, ConstantInterfaceMethodrefInfo, + ConstantInvokeDynamicInfo, ConstantMethodHandleInfo, ConstantMethodTypeInfo, + ConstantMethodrefInfo, ConstantModuleInfo, ConstantNameAndTypeInfo, ConstantPackageInfo, + ConstantPoolEntry, ConstantStringInfo, ConstantUtf8Info, DescParseError, FieldInfo, FieldRef, + MethodInfo, MethodRef, +}; +use crate::{pool_get_impl, FieldType, MethodDescriptor, VmError}; +use deku::DekuContainerRead; +use log::trace; use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; -use deku::DekuContainerRead; -use log::trace; -use crate::class_file::{ConstantClassInfo, ConstantFieldrefInfo, ConstantNameAndTypeInfo, ConstantPoolEntry, FieldRef, FieldInfo, MethodRef, MethodInfo, ConstantUtf8Info, ConstantStringInfo, ConstantMethodrefInfo, ConstantInterfaceMethodrefInfo, ConstantMethodHandleInfo, ConstantMethodTypeInfo, ConstantDynamicInfo, ConstantInvokeDynamicInfo, ConstantModuleInfo, ConstantPackageInfo, DescParseError}; -use crate::{pool_get_impl, FieldType, MethodDescriptor, VmError}; -use crate::attributes::{Attribute, AttributeInfo, CodeAttribute, LineNumberTableAttribute, LocalVariableTableAttribute}; pub type ConstantPoolSlice = [ConstantPoolEntry]; pub type ConstantPoolOwned = Vec; @@ -31,7 +39,7 @@ pub trait ConstantPoolExt: ConstantPoolGet { fn get_string(&self, index: u16) -> Result { let cp_entry = self.get_utf8_info(index)?; - String::from_utf8(cp_entry.bytes.clone()).map_err(|e| { e.to_string().into() }) + String::from_utf8(cp_entry.bytes.clone()).map_err(|e| e.to_string().into()) } // // fn get_field(&self, index: u16) -> Result<&ConstantFieldrefInfo, ()> { @@ -65,11 +73,7 @@ pub trait ConstantPoolExt: ConstantPoolGet { let name = self.get_string(name_and_type.name_index)?; let desc = self.get_string(name_and_type.descriptor_index)?; let desc = FieldType::parse(&desc)?; - Ok(FieldRef { - class, - name, - desc, - }) + Ok(FieldRef { class, name, desc }) } fn resolve_method_ref(&self, index: u16) -> Result { @@ -80,11 +84,7 @@ pub trait ConstantPoolExt: ConstantPoolGet { let name = self.get_string(name_and_type.name_index)?; let desc = self.get_string(name_and_type.descriptor_index)?; let desc = MethodDescriptor::parse(&desc)?; - Ok(MethodRef { - class, - name, - desc, - }) + Ok(MethodRef { class, name, desc }) } /*// (name, desc) @@ -111,12 +111,9 @@ pub trait ConstantPoolExt: ConstantPoolGet { }) } - - fn parse_attribute(&self, a: AttributeInfo) -> Result { let name = self.get_string(a.attribute_name_index)?; - trace!("Parsing attribute with name: {}", name); - + // trace!("Parsing attribute with name: {}", name); match name.as_ref() { "Code" => { @@ -131,15 +128,9 @@ pub trait ConstantPoolExt: ConstantPoolGet { let (_, lnt) = LineNumberTableAttribute::from_bytes((&a.info.as_slice(), 0))?; Ok(Attribute::LineNumberTable(lnt)) } - "StackMapTable" => { - Ok(Attribute::StackMapTable(a.info.clone())) - } - "Exceptions" => { - Ok(Attribute::Exceptions(a.info.clone())) - } - "InnerClasses" => { - Ok(Attribute::InnerClasses(a.info.clone())) - } + "StackMapTable" => Ok(Attribute::StackMapTable(a.info.clone())), + "Exceptions" => Ok(Attribute::Exceptions(a.info.clone())), + "InnerClasses" => Ok(Attribute::InnerClasses(a.info.clone())), "Signature" => { let signature_index = u16::from_be_bytes([a.info[0], a.info[1]]); Ok(Attribute::Signature(signature_index)) diff --git a/src/class_file/mod.rs b/crates/core/src/class_file/mod.rs similarity index 58% rename from src/class_file/mod.rs rename to crates/core/src/class_file/mod.rs index 68b97eb..440960b 100644 --- a/src/class_file/mod.rs +++ b/crates/core/src/class_file/mod.rs @@ -2,5 +2,5 @@ pub mod class_file; pub mod constant_pool; // Re-export items if you want them accessible directly from class_file:: -pub use class_file::*; // optional -// pub use constant_pool::*; // optional \ No newline at end of file +pub use class_file::*; // optional + // pub use constant_pool::*; // optional diff --git a/src/class_loader.rs b/crates/core/src/class_loader.rs similarity index 82% rename from src/class_loader.rs rename to crates/core/src/class_loader.rs index 618bcc7..2f531f2 100644 --- a/src/class_loader.rs +++ b/crates/core/src/class_loader.rs @@ -1,23 +1,26 @@ +use crate::attributes::Attribute; +use crate::bimage::Bimage; +use crate::class::RuntimeClass; +use crate::class_file::constant_pool::{ConstantPoolExt, ConstantPoolGet}; +use crate::class_file::{ + ClassFile, ClassFlags, ConstantClassInfo, ConstantPoolEntry, FieldData, FieldFlags, MethodData, + MethodFlags, +}; +use crate::native_libraries::NativeLibraries; +use crate::{FieldType, MethodDescriptor}; +use dashmap::DashMap; +use deku::DekuContainerRead; +use libloading::os::windows::Library; +use log::warn; use std::collections::hash_map::{Entry, Iter}; -use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use deku::DekuContainerRead; -use log::warn; -use crate::attributes::Attribute; -use crate::bimage::Bimage; -use crate::class::RuntimeClass; -use crate::class_file::{ClassFile, ClassFlags, ConstantClassInfo, ConstantPoolEntry, FieldData, FieldFlags, MethodData, MethodFlags}; -use crate::class_file::constant_pool::{ConstantPoolExt, ConstantPoolGet}; -use crate::{FieldType, MethodDescriptor}; pub type LoaderRef = Arc>; -#[deprecated( - note = "This method is deprecated and will be removed in future versions" -)] +#[deprecated(note = "This method is deprecated and will be removed in future versions")] pub fn resolve_path(what: &str) -> Result<(PathBuf, String), String> { let (module, fqn) = what.split_once("/").unwrap_or(("", what)); let module = dot_to_path(module); @@ -36,15 +39,22 @@ pub fn resolve_path(what: &str) -> Result<(PathBuf, String), String> { let classes_path = module_path.join("jmod/classes"); if !classes_path.exists() { - return Err(format!("Could not find jmod/classes directory in module: {}", module)); + return Err(format!( + "Could not find jmod/classes directory in module: {}", + module + )); } classes_path - } else { base.to_path_buf() }; - + } else { + base.to_path_buf() + }; let class_path = path.join(format!("{}.class", fqn)); if !class_path.exists() { - return Err(format!("Could not find class: {} in module: {}", fqn, module)); + return Err(format!( + "Could not find class: {} in module: {}", + fqn, module + )); } Ok((class_path, path_to_dot(&fqn))) @@ -77,9 +87,10 @@ pub fn resolve_path(what: &str) -> Result<(PathBuf, String), String> { /// ``` #[derive(Default)] pub struct ClassLoader { - classes: HashMap>, + classes: DashMap>, bimage: Bimage, - pub needs_init: Vec> + pub needs_init: Vec>, + native_libraries: NativeLibraries, } impl ClassLoader { @@ -98,7 +109,7 @@ impl ClassLoader { /// * `what` - A fully qualified class name (e.g. "java.base/java.lang.Object" or "java.lang.String") /// /// # Returns - /// * `Ok(Arc)` - A thread-safe reference-counted pointer to the requested `ClassFile` on success, + /// * `Ok(Arc)` - A thread-safe reference-counted pointer to the requested `ClassFile` on success, /// either retrieved from the storage or successfully loaded. /// * `Err(String)` - An error message string if the class could not be loaded due to some failure. /// @@ -133,14 +144,14 @@ impl ClassLoader { } /* pub fn classes(&self) -> HashMap { - self.classes.clone() - }*/ + self.classes.clone() + }*/ fn load_class(&mut self, what: &str) -> Result, String> { let (module, class_fqn) = ("", what); let bytes = self.bimage.get_class(module, class_fqn).unwrap_or_else(|| { let path = format!("./data/{what}.class"); - println!("{}", path); + log::info!("Loading class from path: {}", path); let mut class_file = File::open(path).unwrap(); let mut bytes = Vec::new(); class_file.read_to_end(&mut bytes).unwrap(); @@ -151,28 +162,33 @@ impl ClassLoader { let runtime = self.runtime_class(cf); let arced = Arc::new(runtime); let option = self.classes.insert(class_fqn.to_string(), arced.clone()); - if option.is_some() { warn!("Replaced loaded class: {}", class_fqn) } + if option.is_some() { + warn!("Replaced loaded class: {}", class_fqn) + } Ok(arced) } - fn runtime_class(&mut self, class_file: ClassFile) -> RuntimeClass { let constant_pool = class_file.constant_pool.clone(); let access_flags = ClassFlags::from(class_file.access_flags); let this_class = { - let cl = class_file.constant_pool.get_class_info(class_file.this_class).unwrap(); + let cl = class_file + .constant_pool + .get_class_info(class_file.this_class) + .unwrap(); let name = class_file.constant_pool.get_string(cl.name_index).unwrap(); name }; let super_class = { - if (this_class.eq("java/lang/Object")) - { + if (this_class.eq("java/lang/Object")) { debug_assert_eq!(this_class, "java/lang/Object"); debug_assert_eq!(class_file.super_class, 0u16); None } else { debug_assert_ne!(class_file.super_class, 0u16); - let super_info = constant_pool.get_class_info(class_file.super_class).unwrap(); + let super_info = constant_pool + .get_class_info(class_file.super_class) + .unwrap(); let name = constant_pool.get_string(**super_info).unwrap(); Some(self.get_or_load(&*name).unwrap()) } @@ -187,60 +203,73 @@ impl ClassLoader { } let interfaces = class_file - .interfaces.iter().copied() + .interfaces + .iter() + .copied() .map(|e| { let interface_info = constant_pool.get_class_info(e).unwrap(); let name = constant_pool.get_string(interface_info.name_index).unwrap(); self.get_or_load(&name).unwrap() - }).collect::>(); + }) + .collect::>(); let fields = class_file - .fields.iter() + .fields + .iter() .map(|e| { let name = constant_pool.get_string(e.name_index).unwrap(); let flags = FieldFlags::from(e.access_flags); - let desc = constant_pool.get_string(e.descriptor_index).map(|e|{ - FieldType::parse(&e) - }).unwrap().unwrap(); - let value = e.attributes.first() - .and_then(|x| { - if let Attribute::ConstantValue(val) = constant_pool.parse_attribute(x.clone()).unwrap() { - Some(val) - } else { - None - } - }); + let desc = constant_pool + .get_string(e.descriptor_index) + .map(|e| FieldType::parse(&e)) + .unwrap() + .unwrap(); + let value = e.attributes.first().and_then(|x| { + if let Attribute::ConstantValue(val) = + constant_pool.parse_attribute(x.clone()).unwrap() + { + Some(val.into()) + } else { + None + } + }); + let value = Arc::new(Mutex::new(value)); FieldData { name, flags, desc, value, } - }).collect::>(); + }) + .collect::>(); - - let methods = class_file.methods.iter().map(|e| { - let name = constant_pool.get_string(e.name_index).unwrap(); - let flags = MethodFlags::from(e.access_flags); - let desc = constant_pool.get_string(e.descriptor_index).map(|e|{ - MethodDescriptor::parse(&e) - }).unwrap().unwrap(); - let code = e.attributes.first() - .and_then(|x| { - if let Attribute::Code(val) = constant_pool.parse_attribute(x.clone()).unwrap() { + let methods = class_file + .methods + .iter() + .map(|e| { + let name = constant_pool.get_string(e.name_index).unwrap(); + let flags = MethodFlags::from(e.access_flags); + let desc = constant_pool + .get_string(e.descriptor_index) + .map(|e| MethodDescriptor::parse(&e)) + .unwrap() + .unwrap(); + let code = e.attributes.first().and_then(|x| { + if let Attribute::Code(val) = constant_pool.parse_attribute(x.clone()).unwrap() + { Some(val) } else { None } }); - MethodData { - name, - flags, - desc, - code, - } - }).collect::>(); - + MethodData { + name, + flags, + desc, + code, + } + }) + .collect::>(); RuntimeClass { constant_pool, @@ -253,6 +282,13 @@ impl ClassLoader { init_state: Mutex::new(crate::class::InitState::NotInitialized), } } + + unsafe fn find_native(&self, name: String) -> libloading::os::windows::Symbol { + // for (key, value) in self.native_libraries.iter() { + // // value.get() + // } + todo!("class_loader find native") + } } fn dot_to_path(s: &str) -> String { @@ -390,4 +426,4 @@ const VM_CLASSES: &[&str] = &[ // let (_, cf) = ClassFile::from_bytes_interpreted((bytes.as_ref(), 0)) // .map_err(|e| format!("failed to parse class file: {}", e))?; // Ok(cf) -// } \ No newline at end of file +// } diff --git a/crates/core/src/jni.rs b/crates/core/src/jni.rs new file mode 100644 index 0000000..eb0338b --- /dev/null +++ b/crates/core/src/jni.rs @@ -0,0 +1,257 @@ +use jni::sys::JNIEnv; +use jni::sys::{jint, JNINativeInterface_}; +use std::ptr; + +const JNI_VERSION_1_1: jint = 0x00010001; +const JNI_VERSION_1_2: jint = 0x00010002; +const JNI_VERSION_1_4: jint = 0x00010004; +const JNI_VERSION_1_6: jint = 0x00010006; +const JNI_VERSION_1_8: jint = 0x00010008; +const JNI_VERSION_9: jint = 0x00090000; +const JNI_VERSION_10: jint = 0x000a0000; +const JNI_VERSION_19: jint = 0x00130000; +const JNI_VERSION_20: jint = 0x00140000; +const JNI_VERSION_21: jint = 0x00150000; +const JNI_VERSION_24: jint = 0x00180000; + +pub fn create_jni_function_table() -> JNIEnv { + Box::into_raw(Box::new(JNINativeInterface_ { + reserved0: ptr::null_mut(), + reserved1: ptr::null_mut(), + reserved2: ptr::null_mut(), + reserved3: ptr::null_mut(), + GetVersion: Some(jni_get_version), + DefineClass: None, + FindClass: None, + FromReflectedMethod: None, + FromReflectedField: None, + ToReflectedMethod: None, + GetSuperclass: None, + IsAssignableFrom: None, + ToReflectedField: None, + Throw: None, + ThrowNew: None, + ExceptionOccurred: None, + ExceptionDescribe: None, + ExceptionClear: None, + FatalError: None, + PushLocalFrame: None, + PopLocalFrame: None, + NewGlobalRef: None, + DeleteGlobalRef: None, + DeleteLocalRef: None, + IsSameObject: None, + NewLocalRef: None, + EnsureLocalCapacity: None, + AllocObject: None, + NewObject: None, + NewObjectV: None, + NewObjectA: None, + GetObjectClass: None, + IsInstanceOf: None, + GetMethodID: None, + CallObjectMethod: None, + CallObjectMethodV: None, + CallObjectMethodA: None, + CallBooleanMethod: None, + CallBooleanMethodV: None, + CallBooleanMethodA: None, + CallByteMethod: None, + CallByteMethodV: None, + CallByteMethodA: None, + CallCharMethod: None, + CallCharMethodV: None, + CallCharMethodA: None, + CallShortMethod: None, + CallShortMethodV: None, + CallShortMethodA: None, + CallIntMethod: None, + CallIntMethodV: None, + CallIntMethodA: None, + CallLongMethod: None, + CallLongMethodV: None, + CallLongMethodA: None, + CallFloatMethod: None, + CallFloatMethodV: None, + CallFloatMethodA: None, + CallDoubleMethod: None, + CallDoubleMethodV: None, + CallDoubleMethodA: None, + CallVoidMethod: None, + CallVoidMethodV: None, + CallVoidMethodA: None, + CallNonvirtualObjectMethod: None, + CallNonvirtualObjectMethodV: None, + CallNonvirtualObjectMethodA: None, + CallNonvirtualBooleanMethod: None, + CallNonvirtualBooleanMethodV: None, + CallNonvirtualBooleanMethodA: None, + CallNonvirtualByteMethod: None, + CallNonvirtualByteMethodV: None, + CallNonvirtualByteMethodA: None, + CallNonvirtualCharMethod: None, + CallNonvirtualCharMethodV: None, + CallNonvirtualCharMethodA: None, + CallNonvirtualShortMethod: None, + CallNonvirtualShortMethodV: None, + CallNonvirtualShortMethodA: None, + CallNonvirtualIntMethod: None, + CallNonvirtualIntMethodV: None, + CallNonvirtualIntMethodA: None, + CallNonvirtualLongMethod: None, + CallNonvirtualLongMethodV: None, + CallNonvirtualLongMethodA: None, + CallNonvirtualFloatMethod: None, + CallNonvirtualFloatMethodV: None, + CallNonvirtualFloatMethodA: None, + CallNonvirtualDoubleMethod: None, + CallNonvirtualDoubleMethodV: None, + CallNonvirtualDoubleMethodA: None, + CallNonvirtualVoidMethod: None, + CallNonvirtualVoidMethodV: None, + CallNonvirtualVoidMethodA: None, + GetFieldID: None, + GetObjectField: None, + GetBooleanField: None, + GetByteField: None, + GetCharField: None, + GetShortField: None, + GetIntField: None, + GetLongField: None, + GetFloatField: None, + GetDoubleField: None, + SetObjectField: None, + SetBooleanField: None, + SetByteField: None, + SetCharField: None, + SetShortField: None, + SetIntField: None, + SetLongField: None, + SetFloatField: None, + SetDoubleField: None, + GetStaticMethodID: None, + CallStaticObjectMethod: None, + CallStaticObjectMethodV: None, + CallStaticObjectMethodA: None, + CallStaticBooleanMethod: None, + CallStaticBooleanMethodV: None, + CallStaticBooleanMethodA: None, + CallStaticByteMethod: None, + CallStaticByteMethodV: None, + CallStaticByteMethodA: None, + CallStaticCharMethod: None, + CallStaticCharMethodV: None, + CallStaticCharMethodA: None, + CallStaticShortMethod: None, + CallStaticShortMethodV: None, + CallStaticShortMethodA: None, + CallStaticIntMethod: None, + CallStaticIntMethodV: None, + CallStaticIntMethodA: None, + CallStaticLongMethod: None, + CallStaticLongMethodV: None, + CallStaticLongMethodA: None, + CallStaticFloatMethod: None, + CallStaticFloatMethodV: None, + CallStaticFloatMethodA: None, + CallStaticDoubleMethod: None, + CallStaticDoubleMethodV: None, + CallStaticDoubleMethodA: None, + CallStaticVoidMethod: None, + CallStaticVoidMethodV: None, + CallStaticVoidMethodA: None, + GetStaticFieldID: None, + GetStaticObjectField: None, + GetStaticBooleanField: None, + GetStaticByteField: None, + GetStaticCharField: None, + GetStaticShortField: None, + GetStaticIntField: None, + GetStaticLongField: None, + GetStaticFloatField: None, + GetStaticDoubleField: None, + SetStaticObjectField: None, + SetStaticBooleanField: None, + SetStaticByteField: None, + SetStaticCharField: None, + SetStaticShortField: None, + SetStaticIntField: None, + SetStaticLongField: None, + SetStaticFloatField: None, + SetStaticDoubleField: None, + NewString: None, + GetStringLength: None, + GetStringChars: None, + ReleaseStringChars: None, + NewStringUTF: None, + GetStringUTFLength: None, + GetStringUTFChars: None, + ReleaseStringUTFChars: None, + GetArrayLength: None, + NewObjectArray: None, + GetObjectArrayElement: None, + SetObjectArrayElement: None, + NewBooleanArray: None, + NewByteArray: None, + NewCharArray: None, + NewShortArray: None, + NewIntArray: None, + NewLongArray: None, + NewFloatArray: None, + NewDoubleArray: None, + GetBooleanArrayElements: None, + GetByteArrayElements: None, + GetCharArrayElements: None, + GetShortArrayElements: None, + GetIntArrayElements: None, + GetLongArrayElements: None, + GetFloatArrayElements: None, + GetDoubleArrayElements: None, + ReleaseBooleanArrayElements: None, + ReleaseByteArrayElements: None, + ReleaseCharArrayElements: None, + ReleaseShortArrayElements: None, + ReleaseIntArrayElements: None, + ReleaseLongArrayElements: None, + ReleaseFloatArrayElements: None, + ReleaseDoubleArrayElements: None, + GetBooleanArrayRegion: None, + GetByteArrayRegion: None, + GetCharArrayRegion: None, + GetShortArrayRegion: None, + GetIntArrayRegion: None, + GetLongArrayRegion: None, + GetFloatArrayRegion: None, + GetDoubleArrayRegion: None, + SetBooleanArrayRegion: None, + SetByteArrayRegion: None, + SetCharArrayRegion: None, + SetShortArrayRegion: None, + SetIntArrayRegion: None, + SetLongArrayRegion: None, + SetFloatArrayRegion: None, + SetDoubleArrayRegion: None, + RegisterNatives: None, + UnregisterNatives: None, + MonitorEnter: None, + MonitorExit: None, + GetJavaVM: None, + GetStringRegion: None, + GetStringUTFRegion: None, + GetPrimitiveArrayCritical: None, + ReleasePrimitiveArrayCritical: None, + GetStringCritical: None, + ReleaseStringCritical: None, + NewWeakGlobalRef: None, + DeleteWeakGlobalRef: None, + ExceptionCheck: None, + NewDirectByteBuffer: None, + GetDirectBufferAddress: None, + GetDirectBufferCapacity: None, + GetObjectRefType: None, + })) +} + +unsafe extern "system" fn jni_get_version(env: *mut JNIEnv) -> jint { + JNI_VERSION_24 +} diff --git a/src/lib.rs b/crates/core/src/lib.rs similarity index 86% rename from src/lib.rs rename to crates/core/src/lib.rs index 02c060c..eeda891 100644 --- a/src/lib.rs +++ b/crates/core/src/lib.rs @@ -21,9 +21,11 @@ use crate::class_file::constant_pool::{ConstantPoolError, ConstantPoolGet}; use crate::class_file::{Bytecode, ClassFile, ConstantPoolEntry, MethodData}; use crate::object::Object; use crate::thread::VmThread; +use crate::Value::Reference; use deku::{DekuContainerRead, DekuError}; use deku_derive::{DekuRead, DekuWrite}; -use log::warn; +use env_logger::Builder; +use log::{warn, LevelFilter}; use std::fmt::{Debug, Display, Formatter}; use std::fs::File; use std::io::Read; @@ -35,7 +37,9 @@ mod bimage; mod class; mod class_file; mod class_loader; +mod jni; mod macros; +mod native_libraries; mod object; mod rng; mod thread; @@ -46,7 +50,12 @@ const NULL: Value = Value::Reference(None); // include!(concat!(env!("OUT_DIR"), "/bindings.rs")); /// pseudo main pub fn run() { - env_logger::init(); + Builder::from_default_env() + .filter_level(LevelFilter::Trace) + .filter_module("deku", LevelFilter::Warn) + .filter_module("jvm_rs_core::class_file::class_file", LevelFilter::Info) + .filter_module("jvm_rs_core::attributes", LevelFilter::Info) + .init(); // let mut cl = ClassLoader::new().unwrap(); // cl.load_class("org.example.App").expect("TODO: panic message"); // let clazz = cl.get_or_load("org.example.App").unwrap(); @@ -54,11 +63,11 @@ pub fn run() { // std::fs::write(format!("./output/{}-{}.txt", i, class_loader::path_to_dot(k)), format!("{}\n{}", k, v)).unwrap(); // } - let mut class_file = File::open("./data/org/example/App.class").unwrap(); + /*let mut class_file = File::open("./data/org/example/Main.class").unwrap(); let mut bytes = Vec::new(); class_file.read_to_end(&mut bytes).unwrap(); let (_rest, clazz) = ClassFile::from_bytes((bytes.as_ref(), 0)).unwrap(); - let method = clazz.methods.iter().nth(1).unwrap().clone(); + let method = clazz.methods.get(1).unwrap().clone(); let code = method .attributes .iter() @@ -88,9 +97,9 @@ pub fn run() { } }) .unwrap(); - println!("{}", clazz); + println!("{}", clazz);*/ // let pool = clazz.constant_pool; - let mut vm = Vm::new("org/example/App"); + let mut vm = Vm::new("org/example/Main"); // println!("{:?}", ops); // println!("{:?}", var_table.local_variable_table); @@ -298,6 +307,45 @@ impl MethodDescriptor { } } +impl Display for BaseType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + BaseType::Byte => write!(f, "B"), + BaseType::Char => write!(f, "C"), + BaseType::Double => write!(f, "D"), + BaseType::Float => write!(f, "F"), + BaseType::Int => write!(f, "I"), + BaseType::Long => write!(f, "J"), + BaseType::Short => write!(f, "S"), + BaseType::Boolean => write!(f, "Z"), + } + } +} + +impl Display for FieldType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FieldType::Base(base) => write!(f, "{}", base), + FieldType::ClassType(name) => write!(f, "L{};", name), + FieldType::ArrayType(component) => write!(f, "[{}", component), + } + } +} + +impl Display for MethodDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + for param in &self.parameters { + write!(f, "{}", param)?; + } + write!(f, ")")?; + match &self.return_type { + Some(ret) => write!(f, "{}", ret), + None => write!(f, "V"), + } + } +} + /// Represents types that can be used for fields in the JVM. /// /// Field types can be: @@ -516,7 +564,13 @@ impl Frame { let result = init_class .find_field(&field_ref.name, field_ref.desc) .expect("TO hecken work"); - let constant = result.value.clone().unwrap(); + let constant = result + .value + .lock() + .unwrap() + .clone() + .expect("Static field was not initialised"); + self.stack.push(constant.into()); // let (code, pool) = { // let mut loader = self.vm.loader.lock().unwrap(); @@ -526,7 +580,6 @@ impl Frame { // (code, pool) // }; // println!("{:?}", field); - todo!("Finish get static"); Ok(ExecutionResult::Continue) } @@ -542,7 +595,7 @@ impl Frame { let class = loader.get_or_load(&meth.class).unwrap(); let pool = class.constant_pool.clone(); let code = class - .find_method(&meth.name, meth.desc) + .find_method(&meth.name, &meth.desc) .unwrap() .code .clone() @@ -576,6 +629,27 @@ impl Frame { Ok(ExecutionResult::Continue) } + Ops::aconst_null => { + self.stack.push(NULL); + Ok(ExecutionResult::Continue) + } + + Ops::putstatic(index) => { + let field_ref = self.pool.resolve_field(*index)?; + println!("Getting static field {field_ref:?}"); + + let init_class = self + .thread + .get_or_resolve_class(&field_ref.class, self.thread.clone()) + .expect("TO hecken work"); + let result = init_class + .find_field(&field_ref.name, field_ref.desc) + .expect("TO hecken work"); + let value = self.stack.pop().expect("stack to have value"); + *result.value.lock().unwrap() = Some(value); + Ok(ExecutionResult::Continue) + } + Ops::return_void => Ok(ExecutionResult::Return(())), _ => { todo!("Unimplemented op: {:?}", op) diff --git a/src/macros.rs b/crates/core/src/macros.rs similarity index 100% rename from src/macros.rs rename to crates/core/src/macros.rs diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs new file mode 100644 index 0000000..6c11e4a --- /dev/null +++ b/crates/core/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + jvm_rs_core::run() +} diff --git a/crates/core/src/native_libraries.rs b/crates/core/src/native_libraries.rs new file mode 100644 index 0000000..d471fa7 --- /dev/null +++ b/crates/core/src/native_libraries.rs @@ -0,0 +1,15 @@ +use crate::class_file::ConstantPoolEntry; +use dashmap::DashMap; +use libloading::os::windows::Library; +use std::collections::HashMap; + +pub type NativeLibraries = HashMap; +// impl NativeExt for NativeLibraries {} +// +// trait NativeExt: AsRef<[..]> { +// fn find(&self, name: String) -> () { +// for lib in self.iter() { +// lib +// } +// } +// } diff --git a/src/object.rs b/crates/core/src/object.rs similarity index 100% rename from src/object.rs rename to crates/core/src/object.rs diff --git a/src/rng.rs b/crates/core/src/rng.rs similarity index 100% rename from src/rng.rs rename to crates/core/src/rng.rs diff --git a/src/thread.rs b/crates/core/src/thread.rs similarity index 57% rename from src/thread.rs rename to crates/core/src/thread.rs index c579224..73856e3 100644 --- a/src/thread.rs +++ b/crates/core/src/thread.rs @@ -1,10 +1,19 @@ use crate::class::RuntimeClass; -use crate::class_file::{ClassFile, MethodRef}; +use crate::class_file::{ClassFile, MethodData, MethodRef}; use crate::class_loader::{ClassLoader, LoaderRef}; +use crate::jni::create_jni_function_table; use crate::vm::Vm; -use crate::{Frame, MethodDescriptor, Value, VmError}; +use crate::{BaseType, FieldType, Frame, MethodDescriptor, Value, VmError}; use deku::DekuError::Incomplete; +use jni::sys::jlong; +use jni::JNIEnv; +use libffi::low::call; +use libffi::middle::*; +use log::{trace, warn}; +use std::ops::Add; +use std::ptr::null_mut; use std::sync::{Arc, Mutex}; +use std::vec::IntoIter; type MethodCallResult = Result, VmError>; @@ -38,9 +47,9 @@ impl VmThread { .lock() .unwrap() .get_or_load(what) - .map_err(|e| VmError::LoaderError(e))?; + .map_err(VmError::LoaderError)?; - // Phase 2: Collect classes that need initialization (short lock) + // Phase 2: Collect classes that need initialisation (short lock) let classes_to_init = { let mut loader = self.loader.lock().unwrap(); let classes = loader.needs_init.clone(); @@ -48,7 +57,7 @@ impl VmThread { classes }; - // Phase 3: Initialize each class (NO lock held - allows recursion) + // Phase 3: Initialise each class (NO lock held - allows recursion) for class in classes_to_init { self.init(class, thread.clone())?; } @@ -74,7 +83,7 @@ impl VmThread { } InitState::Initializing(tid) if *tid == current_thread => { // JVM Spec 5.5: Recursive initialization by same thread is allowed - println!( + warn!( "Class {} already being initialized by this thread (recursive)", class.this_class ); @@ -102,7 +111,7 @@ impl VmThread { } // Perform actual initialization - println!("Initializing class: {}", class.this_class); + trace!("Initializing class: {}", class.this_class); let result = (|| { // Initialize superclass first (if any) if let Some(ref super_class) = class.super_class { @@ -110,7 +119,7 @@ impl VmThread { } // Run if present - let class_init_method = class.find_method("", MethodDescriptor::void()); + let class_init_method = class.find_method("", &MethodDescriptor::void()); if let Ok(method) = class_init_method { Frame::new( method.code.clone().unwrap(), @@ -130,7 +139,7 @@ impl VmThread { match result { Ok(_) => { *state = InitState::Initialized; - println!("Class {} initialized successfully", class.this_class); + trace!("Class {} initialized successfully", class.this_class); } Err(ref e) => { *state = InitState::Error(format!("{:?}", e)); @@ -142,9 +151,18 @@ impl VmThread { } pub fn invoke_main(&self, what: &str, thread: Arc) { + let method_ref = MethodRef { + class: what.to_string(), + name: "main".to_string(), + desc: MethodDescriptor::psvm(), + }; + + self.invoke(method_ref, thread).expect("Main method died"); + return (); + let class = self.get_or_resolve_class(what, thread.clone()).unwrap(); println!("invoking main: {}", class.this_class); - let main_method = class.find_method("main", MethodDescriptor::psvm()); + let main_method = class.find_method("main", &MethodDescriptor::psvm()); println!("{:?}", main_method); if let Ok(meth) = main_method { let mut frame = Frame::new( @@ -162,10 +180,13 @@ impl VmThread { pub fn invoke(&self, method_reference: MethodRef, thread: Arc) -> MethodCallResult { let class = self.get_or_resolve_class(&method_reference.class, thread.clone())?; let resolved_method = class - .find_method(&method_reference.name, method_reference.desc) + .find_method(&method_reference.name, &method_reference.desc) .unwrap(); + println!("invoking {}: {}", method_reference.name, class.this_class); if resolved_method.flags.ACC_NATIVE { - return self.invoke_native(); + unsafe { + return self.invoke_native(&method_reference); + } } let mut frame = Frame::new( resolved_method.code.clone().unwrap(), @@ -176,7 +197,79 @@ impl VmThread { frame.execute() } - pub fn invoke_native(&self) -> MethodCallResult { - todo!("Invoke native") + pub fn invoke_native(&self, method: &MethodRef) -> MethodCallResult { + let symbol_name = generate_jni_method_name(method); + println!("{:?}", &symbol_name); + + unsafe { + // manually load relevant library for poc + let lib = libloading::os::windows::Library::new( + "C:\\Program Files\\Java\\jdk-25\\bin\\jvm_rs.dll", + ) + .expect("load jvm_rs.dll"); + + // build pointer to native fn + let cp = CodePtr::from_ptr( + lib.get::<*const ()>(symbol_name.as_ref()) + .unwrap() + .as_raw_ptr(), + ); + // build actual JNI interface that forms the table of + // native functions that can be used to manipulate the JVM + let JNIEnv = create_jni_function_table(); + + // coerce my method descriptors into libffi C equivalents, then call + let l = method + .build_cif() + .call::(cp, &*vec![arg(&JNIEnv), arg(&null_mut::<()>())]); + + println!("{l}"); + } + + Ok(None) + // todo!("Invoke native") + } +} + +pub fn generate_jni_method_name(method_ref: &MethodRef) -> String { + let class_name = &method_ref.class.replace("/", "_"); + let method_name = &method_ref.name; + format!("Java_{class_name}_{method_name}") +} + +impl From for Type { + fn from(value: FieldType) -> Self { + match value { + FieldType::Base(v) => match v { + BaseType::Byte => Type::i8(), + BaseType::Char => Type::u16(), + BaseType::Double => Type::f64(), + BaseType::Float => Type::f32(), + BaseType::Int => Type::i32(), + BaseType::Long => Type::i64(), + BaseType::Short => Type::i16(), + BaseType::Boolean => Type::i8(), + }, + FieldType::ClassType(_) => Self::pointer(), + FieldType::ArrayType(_) => Self::pointer(), + } + } +} + +impl MethodRef { + fn build_cif(&self) -> Cif { + let mut args = vec![ + Type::pointer(), //JNIEnv* + Type::pointer(), //jclass + ]; + for v in self.desc.parameters.clone() { + args.push(v.into()) + } + let return_type = if let Some(x) = self.desc.return_type.clone() { + x.into() + } else { + Type::void() + }; + Builder::new().args(args).res(return_type).into_cif() } } diff --git a/src/vm.rs b/crates/core/src/vm.rs similarity index 77% rename from src/vm.rs rename to crates/core/src/vm.rs index 40cc248..e80cc72 100644 --- a/src/vm.rs +++ b/crates/core/src/vm.rs @@ -1,14 +1,17 @@ -use std::sync::{Arc, Mutex}; use crate::class_file::ClassFile; use crate::class_loader::ClassLoader; -use crate::Frame; use crate::thread::VmThread; +use crate::Frame; +use libloading::os::windows::Symbol; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; // struct AbstractObject<'a> {} pub struct Vm { // for now, model just a single thread pub thread: Mutex>>, - pub loader: Arc> + pub loader: Arc>, + pub native_methods: HashMap>, } impl Vm { @@ -17,10 +20,11 @@ impl Vm { let vm = Arc::new(Self { loader: Arc::new(Mutex::from(ClassLoader::default())), thread: Mutex::new(Vec::new()), + native_methods: Default::default(), }); let thread = Arc::new(VmThread::new(vm.clone(), None)); vm.thread.lock().unwrap().push(thread.clone()); thread.invoke_main(what, thread.clone()); vm.clone() } -} \ No newline at end of file +} diff --git a/crates/jvm-rs-sys/Cargo.toml b/crates/jvm-rs-sys/Cargo.toml new file mode 100644 index 0000000..773d20f --- /dev/null +++ b/crates/jvm-rs-sys/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jvm-rs-sys" +version = "0.1.0" +edition = "2024" +publish = ["nexus"] + +[dependencies] +jni = { workspace = true } + +[lib] +name = "jvm_rs" +crate-type = ["cdylib"] \ No newline at end of file diff --git a/crates/jvm-rs-sys/src/lib.rs b/crates/jvm-rs-sys/src/lib.rs new file mode 100644 index 0000000..506d2de --- /dev/null +++ b/crates/jvm-rs-sys/src/lib.rs @@ -0,0 +1,97 @@ +#![allow(non_snake_case)] + +use jni::objects::{JClass, JString}; +use jni::strings::JNIString; +use jni::sys::{jclass, jlong}; +use jni::{JNIEnv, NativeMethod}; +use std::ffi::c_void; +use std::io::Write; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[unsafe(no_mangle)] +pub extern "system" fn current_time_millis<'local>( + mut env: JNIEnv<'local>, + jclass: JClass<'local>, +) -> jlong { + println!("Sneaky hobitses has hijacked the native methods he has"); + + // SystemTime::now() + // .duration_since(UNIX_EPOCH) + // .unwrap() + // .as_millis() as jlong + + 1337i64 +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_example_MockIO_print<'local>( + mut env: JNIEnv<'local>, + jclass: JClass<'local>, + input: JString<'local>, +) { + let input: String = env + .get_string(&input) + .expect("Couldn't get java string!") + .into(); + std::io::stdout() + .write_all(input.as_bytes()) + .expect("Failed to emit to stdout"); + // println!("Yeetus bageetus! Im printing from rust!") +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_example_MockIO_registerNatives<'local>( + mut env: JNIEnv<'local>, + jclass: JClass<'local>, +) { + let system_methods = vec![NativeMethod { + name: JNIString::from("currentTimeMillis"), + sig: JNIString::from("()J"), + fn_ptr: current_time_millis as *mut c_void, + }]; + let system_class = env + .find_class("java/lang/System") + .expect("Failed to find system class"); + + env.register_native_methods(system_class, &system_methods) + .expect("failed to register method"); + + let library_methods = vec![NativeMethod { + name: JNIString::from("findEntry0"), + sig: JNIString::from("(JLjava/lang/String;)J"), + fn_ptr: findEntry0 as *mut c_void, + }]; + + let library_class = env + .find_class("jdk/internal/loader/NativeLibrary") + .expect("Failed to find system class"); + + // env.register_native_methods(library_class, &library_methods) + // .expect("failed to register method"); +} + +#[unsafe(no_mangle)] +pub extern "system" fn findEntry0<'local>( + mut env: JNIEnv<'local>, + jclass: JClass<'local>, + handle: jlong, + name: JString<'local>, +) -> jlong { + let name: String = env + .get_string(&name) + .expect("Couldn't get java string!") + .into(); + println!("Name: {}, Handle: {}", name, handle); + 0i64 +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_example_Main_getTime<'local>( + mut env: JNIEnv<'local>, + jclass: JClass<'local>, +) -> jlong { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as jlong +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 55081ba..0000000 --- a/src/main.rs +++ /dev/null @@ -1,5 +0,0 @@ - - -fn main() { - jvm_rs::run() -} \ No newline at end of file