use crate::class_file::ClassFile;
use crate::memory::{StackFrame, Value};
use log::{debug, info, trace, warn};
#[derive(Debug, Clone)]
pub struct DebugConfig {
pub trace_instructions: bool,
pub trace_memory: bool,
pub trace_classes: bool,
pub trace_methods: bool,
pub max_stack_frames: Option<usize>,
pub max_stack_entries: Option<usize>,
}
impl Default for DebugConfig {
fn default() -> Self {
Self {
trace_instructions: false,
trace_memory: false,
trace_classes: true,
trace_methods: true,
max_stack_frames: Some(5),
max_stack_entries: Some(10),
}
}
}
#[derive(Clone, Debug)]
pub struct JvmDebugger {
config: DebugConfig,
instruction_count: usize,
}
impl JvmDebugger {
pub fn new(config: DebugConfig) -> Self {
Self {
config,
instruction_count: 0,
}
}
pub fn log_method_entry(&self, class_name: &str, method_name: &str, method_descriptor: &str) {
if self.config.trace_methods {
info!(
"Entering method: {}.{}{}",
class_name, method_name, method_descriptor
);
}
}
pub fn log_method_exit(
&self,
class_name: &str,
method_name: &str,
method_descriptor: &str,
result: Option<&Value>,
) {
if self.config.trace_methods {
match result {
Some(value) => info!(
"Exiting method: {}.{}{} with return value: {:?}",
class_name, method_name, method_descriptor, value
),
None => info!(
"Exiting method: {}.{}{} (void)",
class_name, method_name, method_descriptor
),
}
}
}
pub fn log_instruction(&mut self, frame: &StackFrame, _class: &ClassFile, opcode: u8) {
if self.config.trace_instructions {
self.instruction_count += 1;
trace!(
"Instruction #{}: 0x{:02x} ({})",
self.instruction_count,
opcode,
self.get_opcode_name(opcode)
);
trace!(
" Frame: {} (PC: {}, Local vars: {}, Stack depth: {})",
frame.method_name,
frame.pc,
frame.locals.len(),
frame.stack.len()
);
if self.config.trace_memory {
self.log_operand_stack(frame);
self.log_local_variables(frame);
}
}
}
fn log_operand_stack(&self, frame: &StackFrame) {
let max_entries = self.config.max_stack_entries.unwrap_or(frame.stack.len());
let to_show = std::cmp::min(max_entries, frame.stack.len());
trace!(
" Operand Stack (showing {}/{}):",
to_show,
frame.stack.len()
);
for (i, value) in frame.stack.iter().rev().take(to_show).enumerate() {
trace!(" [{}]: {:?}", i, value);
}
}
fn log_local_variables(&self, frame: &StackFrame) {
trace!(" Local Variables ({}):", frame.locals.len());
for (i, value) in frame.locals.iter().enumerate() {
trace!(" [{}]: {:?}", i, value);
}
}
pub fn log_class_loaded(&self, class_name: &str, source: &str) {
if self.config.trace_classes {
info!("Loaded class '{}' from {}", class_name, source);
}
}
pub fn log_memory_allocation(&self, address: u32, size: usize, object_type: &str) {
if self.config.trace_memory {
debug!(
"Allocated {} bytes at address 0x{:08x} for {}",
size, address, object_type
);
}
}
pub fn log_memory_access(&self, address: u32, operation: &str, value: Option<&Value>) {
if self.config.trace_memory {
match value {
Some(v) => trace!("Memory {}: address 0x{:08x} = {:?}", operation, address, v),
None => trace!("Memory {}: address 0x{:08x}", operation, address),
}
}
}
pub fn log_exception(&self, exception: &str, handler_pc: Option<usize>) {
warn!("Exception thrown: {}", exception);
if let Some(pc) = handler_pc {
info!("Handling exception at PC {}", pc);
}
}
pub fn log_gc(&self, freed_objects: usize, freed_bytes: usize) {
info!(
"GC completed: freed {} objects ({} bytes)",
freed_objects, freed_bytes
);
}
fn get_opcode_name(&self, opcode: u8) -> &'static str {
match opcode {
0x00 => "nop",
0x01 => "aconst_null",
0x02 => "iconst_m1",
0x03 => "iconst_0",
0x04 => "iconst_1",
0x05 => "iconst_2",
0x06 => "iconst_3",
0x07 => "iconst_4",
0x08 => "iconst_5",
0x09 => "lconst_0",
0x0a => "lconst_1",
0x0b => "fconst_0",
0x0c => "fconst_1",
0x0d => "fconst_2",
0x0e => "dconst_0",
0x0f => "dconst_1",
0x10 => "bipush",
0x11 => "sipush",
0x12 => "ldc",
0x13 => "ldc_w",
0x14 => "ldc2_w",
0x15 => "iload",
0x16 => "lload",
0x17 => "fload",
0x18 => "dload",
0x19 => "aload",
0x1a => "iload_0",
0x1b => "iload_1",
0x1c => "iload_2",
0x1d => "iload_3",
0x1e => "lload_0",
0x1f => "lload_1",
0x20 => "lload_2",
0x21 => "lload_3",
0x22 => "fload_0",
0x23 => "fload_1",
0x24 => "fload_2",
0x25 => "fload_3",
0x26 => "dload_0",
0x27 => "dload_1",
0x28 => "dload_2",
0x29 => "dload_3",
0x2a => "aload_0",
0x2b => "aload_1",
0x2c => "aload_2",
0x2d => "aload_3",
0x2e => "iaload",
0x2f => "laload",
0x30 => "faload",
0x31 => "daload",
0x32 => "aaload",
0x33 => "baload",
0x34 => "caload",
0x35 => "saload",
0x36 => "istore",
0x37 => "lstore",
0x38 => "fstore",
0x39 => "dstore",
0x3a => "astore",
0x3b => "istore_0",
0x3c => "istore_1",
0x3d => "istore_2",
0x3e => "istore_3",
0x3f => "lstore_0",
0x40 => "lstore_1",
0x41 => "lstore_2",
0x42 => "lstore_3",
0x43 => "fstore_0",
0x44 => "fstore_1",
0x45 => "fstore_2",
0x46 => "fstore_3",
0x47 => "dstore_0",
0x48 => "dstore_1",
0x49 => "dstore_2",
0x4a => "dstore_3",
0x4b => "astore_0",
0x4c => "astore_1",
0x4d => "astore_2",
0x4e => "astore_3",
0x4f => "iastore",
0x50 => "lastore",
0x51 => "fastore",
0x52 => "dastore",
0x53 => "aastore",
0x54 => "bastore",
0x55 => "castore",
0x56 => "sastore",
0x57 => "pop",
0x58 => "pop2",
0x59 => "dup",
0x5a => "dup_x1",
0x5b => "dup_x2",
0x5c => "dup2",
0x5d => "dup2_x1",
0x5e => "dup2_x2",
0x5f => "swap",
0x60 => "iadd",
0x61 => "ladd",
0x62 => "fadd",
0x63 => "dadd",
0x64 => "isub",
0x65 => "lsub",
0x66 => "fsub",
0x67 => "dsub",
0x68 => "imul",
0x69 => "lmul",
0x6a => "fmul",
0x6b => "dmul",
0x6c => "idiv",
0x6d => "ldiv",
0x6e => "fdiv",
0x6f => "ddiv",
0x70 => "irem",
0x71 => "lrem",
0x72 => "frem",
0x73 => "drem",
0x74 => "ineg",
0x75 => "lneg",
0x76 => "fneg",
0x77 => "dneg",
0x78 => "ishl",
0x79 => "lshl",
0x7a => "ishr",
0x7b => "lshr",
0x7c => "iushr",
0x7d => "lushr",
0x7e => "iand",
0x7f => "land",
0x80 => "ior",
0x81 => "lor",
0x82 => "ixor",
0x83 => "lxor",
0x84 => "iinc",
0x85 => "i2l",
0x86 => "i2f",
0x87 => "i2d",
0x88 => "l2i",
0x89 => "l2f",
0x8a => "l2d",
0x8b => "f2i",
0x8c => "f2l",
0x8d => "f2d",
0x8e => "d2i",
0x8f => "d2l",
0x90 => "d2f",
0x91 => "i2b",
0x92 => "i2c",
0x93 => "i2s",
0x94 => "lcmp",
0x95 => "fcmpl",
0x96 => "fcmpg",
0x97 => "dcmpl",
0x98 => "dcmpg",
0x99 => "ifeq",
0x9a => "ifne",
0x9b => "iflt",
0x9c => "ifge",
0x9d => "ifgt",
0x9e => "ifle",
0x9f => "if_icmpeq",
0xa0 => "if_icmpne",
0xa1 => "if_icmplt",
0xa2 => "if_icmpge",
0xa3 => "if_icmpgt",
0xa4 => "if_icmple",
0xa5 => "if_acmpeq",
0xa6 => "if_acmpne",
0xa7 => "goto",
0xa8 => "jsr",
0xa9 => "ret",
0xaa => "tableswitch",
0xab => "lookupswitch",
0xac => "ireturn",
0xad => "lreturn",
0xae => "freturn",
0xaf => "dreturn",
0xb0 => "areturn",
0xb1 => "return",
0xb2 => "getstatic",
0xb3 => "putstatic",
0xb4 => "getfield",
0xb5 => "putfield",
0xb6 => "invokevirtual",
0xb7 => "invokespecial",
0xb8 => "invokestatic",
0xb9 => "invokeinterface",
0xba => "invokedynamic",
0xbb => "new",
0xbc => "newarray",
0xbd => "anewarray",
0xbe => "arraylength",
0xbf => "athrow",
0xc0 => "checkcast",
0xc1 => "instanceof",
0xc2 => "monitorenter",
0xc3 => "monitorexit",
0xc4 => "wide",
0xc5 => "multianewarray",
0xc6 => "ifnull",
0xc7 => "ifnonnull",
0xc8 => "goto_w",
0xc9 => "jsr_w",
_ => "unknown",
}
}
pub fn instruction_count(&self) -> usize {
self.instruction_count
}
pub fn reset_instruction_count(&mut self) {
self.instruction_count = 0;
}
}
pub fn init_logging(level: log::LevelFilter) {
env_logger::Builder::from_default_env()
.filter_level(level)
.init();
}
pub fn debug_config_from_env() -> DebugConfig {
DebugConfig {
trace_instructions: std::env::var("JVMRS_TRACE_INSTRUCTIONS").is_ok(),
trace_memory: std::env::var("JVMRS_TRACE_MEMORY").is_ok(),
trace_classes: !std::env::var("JVMRS_TRACE_CLASSES").is_ok_and(|v| v == "false"),
trace_methods: !std::env::var("JVMRS_TRACE_METHODS").is_ok_and(|v| v == "false"),
max_stack_frames: std::env::var("JVMRS_MAX_STACK_FRAMES")
.ok()
.and_then(|s| s.parse().ok()),
max_stack_entries: std::env::var("JVMRS_MAX_STACK_ENTRIES")
.ok()
.and_then(|s| s.parse().ok()),
}
}