jvm开发笔记3—java虚拟机雏形

作者:王智通

 

一、背景

笔者希望通过自己动手编写一个简单的jvm来了解java虚拟机内部的工作细节毕竟hotsopt以及android的dalvik都有几十万行的c代码级别。 在前面的2篇开发笔记中已经实现了一个class文件解析器和一个java反汇编器 在这基础上 java虚拟机的雏形也已经写好。还没有内存管理功能 没有线程支持。它能解释执行的指令取决于我的java语法范围 在这之前我对java一无所知 通过写这个jvm顺便也把java学会了

它现在的功能如下

1、java反汇编器 山寨了javap的部分功能。
2、能解释执行如下jvm指令

iload_n, istore_n, aload_n, astore_n, iadd, isub, bipush,
invokespecail, invokestatic, invokevirtual, goto, return,
ireturn, if_icmpge, putfiled, new, dup

 

源码地址 http://www.cloud-sec.org/jvm.tgz
举2个测试例子

test.java
=========

class aa {
        int a = 6;

        int debug(int a, int b)
        {
                int sum;

                sum = a + b;

                return sum;
        }
}

public class test {
        public static void main(String args[]) {
                int a;

                aa bb = new aa();
                a = bb.debug(1, 2);
        }
}

test7.java
==========

public class test7 {
        static int sub(int value)
        {
                int a = 1;

                return value - 1;
        }

        static int add(int a, int b)
        {
                int sum = 0;
                int c;

                sum = a + b;

                c = sub(sum);

                return c;
        }

        public static void main(String args[]) {
                int a = 1, b = 2;
                int ret;

                ret = add(a, b);
                return ;
        }
}

二、JVM架构

2个核心文件:

classloader.c   – 从硬盘加载class文件并解析。
interp_engine.c – bytecode解释器。

运行时数据区

————————————————————–
| 方法区(method) | 堆栈(stack) | 程序计数器(pc) |
————————————————————–

注意这里缺少了heap, native stack 因为我们现在还不支持这些功能。
每个method都有自己对应的栈帧stack frame 在class文件解析的时候就已经创建好。

typedef struct jvm_stack_frame {
        u1 *local_var_table;        // 本地变量表的指针
        u1 *operand_stack;          // 操作数栈的指针
        u4 *method;
        u1 *return_addr;            // method调用函数的时候保存的返回地址
        u4 offset;                  // 操作数栈的偏移量
        u2 max_stack;               // 本地变量表中的变量数量
        u2 max_locals;              // 操作数栈的变量数量
        struct jvm_stack_frame *prev_stack;    // 指向前一个栈帧结构
}JVM_STACK_FRAME;

定义了一个叫curr_jvm_stack的全局变量 它用来保存当前解释器使用的栈帧结构 在jvm初始化的时候进行设置

int jvm_stack_init(void)
{
        curr_jvm_stack = (JVM_STACK_FRAME *)malloc(sizeof(JVM_STACK_FRAME));
        if (!curr_jvm_stack) {
                __error("malloc failed.");
                return -1;
        }
        memset(curr_jvm_stack, '', sizeof(JVM_STACK_FRAME));

        jvm_stack_depth = 0;

        return 0;
}

三、实现细节

1、 虚拟机执行过程

初始化jvm_init()
从磁盘加载class文件并解析在内存建立方法区数据结构 初始化内存堆栈 初始化jvm运行环境。

解释器运行 jvm_run()
初始化程序计数器pc, 从方法区中查找main函数开始解释执行。

退出 jvm_exit()
释放所有数据结构

2、class文件加载与解析

对于每一个class文件使用CLASS数据结构表示

typedef struct jvm_class {
        u4 class_magic;
        u2 access_flag;
        u2 this_class;
        u2 super_class;
        u2 minor_version;
        u2 major_version;
        u2 constant_pool_count;
        u2 interfaces_count;
        u2 fileds_count;
        u2 method_count;
        char class_file[1024];
        struct constant_info_st *constant_info;
        struct list_head interface_list_head;
        struct list_head filed_list_head;
        struct list_head method_list_head;
        struct list_head list;
}CLASS;

CLASS结构的前部分是按java虚拟机规范中对class文件结构的描述设置的。 class_file保存的是这个CLASS结构对应的磁盘class文件名。constant_info保存的是class文件常量池的字符串。utf8interface_list_headfiled_list_headmethod_list_head分别是接口字段 方法的链表头。

在解析class文件的时候 只解析了java虚拟机规范中规定的一个jvm最起码能解析的属性。 这个部分没什么好说的大家直接看源码 在对照java虚拟机规范就能看懂了。

3、解释器设计

java虚拟机规范中一共涉及了201条指令。没有使用switch case这种常用的算法。而是为每个jvm指令设计了一个数据结构

typedef int (*interp_func)(u2 opcode_len, char *symbol, void *base);

typedef struct bytecode_st {
        u2 opcode;
        u2 opcode_len;
        char symbol[OPCODE_SYMBOL_LEN];
        interp_func func;
}BYTECODE;

opcode是jvm指令的机器码 opcode_len是这条jvm指令的长度symbol指令的助记符func是具体的这条指令解释函数。事先建立了一个BYTECODE数组

BYTECODE jvm_byte_code[OPCODE_LEN] = {
                {0x00,  1,      "nop",                  jvm_interp_nop},
                {0x01,  1,      "aconst_null",          jvm_interp_aconst_null},
                {0x02,  1,      "iconst_m1",            jvm_interp_iconst_m1},
                {0x03,  1,      "iconst_0",             jvm_interp_iconst_0},
                {0x04,  1,      "iconst_1",             jvm_interp_iconst_1},
                {0x05,  1,      "iconst_2",             jvm_interp_iconst_2},
                {0x06,  1,      "iconst_3",             jvm_interp_iconst_3},
                {0x07,  1,      "iconst_4",             jvm_interp_iconst_4},
                {0x08,  1,      "iconst_5",             jvm_interp_iconst_5},
                {0x09,  1,      "lconst_0",             jvm_interp_lconst_0},
                {0x0a,  1,      "lconst_1",             jvm_interp_lconst_1},
                {0x0b,  1,      "fconst_0",             jvm_interp_fconst_0},
         ...
                {0xc5,  1,      "multianewarray",       jvm_interp_multianewarray},
                {0xc6,  1,      "ifnull",               jvm_interp_ifnull},
                {0xc7,  1,      "ifnonnull",            jvm_interp_ifnonnull},
                {0xc8,  1,      "goto_w",               jvm_interp_goto_w},
                {0xc9,  1,      "jsr_w",                jvm_interp_jsr_w},
                };

int jvm_interp_invokespecial(u2 len, char *symbol, void *base)
{
        u2 index;

        index = ((*(u1 *)(base + 1)) << 8) | (*(u1 *)(base + 2));
        printf("%s #%xn", symbol, index);
}

int jvm_interp_aload_0(u2 len, char *symbol, void *base)
{
        printf("%sn", symbol);
}

int jvm_interp_return(u2 len, char *symbol, void *base)
{
        printf("%sn", symbol);
}

对于一段bytecode0x2a0xb70x00x10xb1 手工解析如下

0x2a代表aload_0指令 它将本地局部变量中的第一个变量压入到堆栈里。这个指令本身长度就是一个字节没有参数 因此0x2a的解析就非常简单 直接在屏幕打印出aload_0即可

printf(“%sn”, symbol);

0xb7代表invokespecial 它用来调用超类构造方法实例初始化方法 私有方法。它的用法如下
invokespecial indexbyte1 indexbyte2indexbyte1和indexbyte2各占一个字节用(indexbyte1 << 8) | indexbyte2来构建一个常量池中的索引。每个jvm指令本身都占用一个字节加上它的两个参数 invokespecial语句它将占用3个字节空间。 所以它的解析算法如下

        u2 index;

        index = ((*(u1 *)(base + 1)) << 8) | (*(u1 *)(base + 2));
        printf("%s #%xn", symbol, index);

注意0xb7解析完后我们要跳过3个字节的地址那么就是0xb1了 它是return指令没有参数因此它的解析方法跟aload_0一样
printf(“%sn”, symbol);

用程序代码实现是

int interp_bytecode(CLASS_METHOD *method)
{
        jvm_stack_depth++;                    // 函数掉用计数加1
        curr_jvm_stack = &method->code_attr->stack_frame;    // 设置当前栈帧指针

        curr_jvm_interp_env->constant_info = method->class->constant_info;    // 设置当前运行环境
        curr_jvm_interp_env->prev_env = NULL;
        for (;;) {
                if (jvm_stack_depth == 0) {            // 为0代表所有函数执行完毕
                        printf("interpret bytecode done.n");
                        break;
                }

                index = *(u1 *)jvm_pc.pc;            // 设置程序计数器
                jvm_byte_code[index].func(jvm_byte_code[index].opcode_len, // 解释具体指令
                        jvm_byte_code[index].symbol, jvm_pc.pc);
                sleep(1);
        }
}

举个例子

int jvm_interp_iadd(u2 len, char *symbol, void *base)
{
        u4 tmp1, tmp2;

        printf("%sn", symbol);

        pop_operand_stack(int, tmp1)
        pop_operand_stack(int, tmp2)

        push_operand_stack(int, (tmp1 + tmp2))
        jvm_pc.pc += len;
}

jvm_interp_iadd用于解释执行iadd指令 首先从操作数栈中弹出2个int型变量tmp1, tmp2。
把tmp1 + tmp2相加后在压入到操作数栈里。

下面是test7.java的执行演示

public class test7 {
        static int sub(int value)
        {
                int a = 1;

                return value - 1;
        }

        static int add(int a, int b)
        {
                int sum = 0;
                int c;

                sum = a + b;

                c = sub(sum);

                return c;
        }

        public static void main(String args[]) {
                int a = 1, b = 2;
                int ret;

                ret = add(a, b);
                return ;
        }
}

 

 

时间: 2024-11-01 14:54:41

jvm开发笔记3&#8212;java虚拟机雏形的相关文章

jvm开发笔记2&amp;#8212;java反汇编器

作者:王智通   这两天在class文件解析器的基础上, 加上了java反汇编的功能, 反汇编器是指令解释器的基础,通过编写反汇编器可以熟悉jvm的指令系统, 不过jvm的指令一共有201个,反汇编过程基本就是个体力活.在<java虚拟机规范>中对每一条指令都有了详细的描述,下面说说我是如何解析bytecode的: 一个java文件经过javac编译后会生成class格式文件, 在class格式中method字段里会有Code属性,Code属性包含了java的指令码和长度. 首先用class解

jvm开发笔记4&amp;#8212;jvm crash信息处理

作者:王智通   ajvm是一个笔者正在开发中的java虚拟机, 用c和少量汇编语言编写, 目的在于探究一个可运行的java虚拟机是如何实现的, 目前整个jvm的source code代码量在5000行左右, 预计控制在1w行以内,只要能运行简单的java代码即可.笔者希望ajvm能变成一个教学用的简单java虚拟机实现, 帮助java程序员在陷入庞大的hotspot vm源码之前, 能对jvm的结构有个清晰的认识. ajvm是笔者利用业余时间编写的, 每次完成一个重要功能都会以笔记的形式发布到

jvm开发笔记1&amp;#8212;class文件解析器

作者:王智通   笔者最近对java虚拟机产生了浓厚的兴趣, 想了解下最简单的jvm是如何写出来的,于是看起了<java虚拟机规范>,这个规范如同intel开发手册一样,是每个jvm开发人员必须掌握的. 要想翻译执行java byte code, 首先得从java class文件中把Code属性解析出来才行. 在笔者看来, java的class文件结构着实比elf文件结构复杂很多,不过在复杂的结构, 只要耐心对照着手册中的结构一一解析即可, 经过几天的努力, 用c实现了一个class文件解析器

jvm开发笔记5 &amp;#8211; 虚拟机内存管理

作者:王智通   一. 前言 ajvm是笔者正在开发中的一个java虚拟机, 想通过编写这个jvm帮助程序员了解jvm的具体实现细节, 它是国内第一个开源的java虚拟机项目:https://github.com/cloudsec/ajvm, 同时笔者把它的开发笔记也分享到了ata上. 在前面4篇笔记中, 已经实现了class文件加载器, 反汇编器,jvm的crash信息处理, 同时它已经能运行简单的java代码了. 在今天的这篇笔记中, 将开始分享ajvm的内存管理模块是如何编写的. 二.内存

JVM学习笔记(三)——虚拟机类加载机制

在介绍完class文件格式后,我们来看下虚拟机是如何把一个由class文件描述的类加载到内存中的.具体来说java中类的加载涉及7个阶段:加载.校验.准备.解析.初始化.使用.卸载. 1.加载时机 并不是所有的类在程序启动时即被加载,为提升效率,虚拟机通常秉承的是按需加载的原则,即需要使用到相应的类时才加载对应的类.具体包括如下几个加载时机: 遇到new.getstatic.putstatic.invokestatic这4条指令时,如果对应的类没有被加载,虚拟机会首先加载对应的类.这4条指令对应

[看书笔记]《深入java虚拟机》——java体系结构(一)

java的这种适合网络环境的能力是由其体系结构决定的,它可以保证安全的.健壮的且和平台无关的程序通过网络传播,在很多不同的计算机和设备上运行. 体系结构包括四个独立但相关的技术:- java程序设计语言- java class文件- java应用编程接口(API)- java虚拟机 用java编程语言编写源代码,把它编译成java class文件,然后再在java虚拟机中运行class文件.当程序运行的时候,它通过调用class文件中实现了java API的方法来满足程序的java API调用.

深入理解Java虚拟机:JVM高级特性与最佳实践

目 录 [ - ] <深入理解Java虚拟机:JVM高级特性与最佳实践>前言 <深入理解Java虚拟机:JVM高级特性与最佳实践>内容特色 <深入理解Java虚拟机:JVM高级特性与最佳实践>目录 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 展望Java技术的未来 1.4.1 模块化 1.4.2 混合语言 1.4.3 多核并行 1.4.4 进一步丰富语法 1.4.5 64位虚拟机 1.5 实战:自己编译JDK 1.5.1

Java基础:JVM(Java 虚拟机)的详细讲解

可能有很多学习Java的朋友还不知道Java的运行原理.Java虚拟机是怎么工作的,本文将为你详细讲解(JVM)Java 虚拟机. 在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行.在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机.每一种平台的

JVM(Java虚拟机)详解

可能有很多学习Java的朋友还不知道Java的运行原理.Java虚拟机是怎么工作的,本文将为你详细讲解(JVM)Java虚拟机. 在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行.在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机.每一种平台的解