前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >硬核手写字节码实现HelloWorld

硬核手写字节码实现HelloWorld

作者头像
麒思妙想
发布2020-09-02 15:41:43
5860
发布2020-09-02 15:41:43
举报
文章被收录于专栏:麒思妙想麒思妙想麒思妙想

这是一篇硬(闲的)核(蛋疼)的文章,我们来通过手写字节码的方式,来完成一个Helloworld。起因很简单,最近在看《深入理解JVM字节码》,尝试了解读class文件,但是总觉得不过瘾,那么我们来试试手写一个class。

工具

所谓工欲善其事必先利其器,我们先说一下工具,写字节码我用了sublime,它可以直接将文件打开成16进制查看, 并且直接进行编辑,非常方便,并且sublime允许我们在编辑的时候加入空格,来方便阅读,这个功能真的太赞了。

同时我还使用了vscode的hexdump插件,来协助。

整活

先来看一下结果,一个经典的HelloWorld程序。

Class 结构

class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述, 由于class文件很灵活, 它甚至比Java源文件有着更强的描述能力。

class文件中的信息是一项一项排列的, 每项数据都有它的固定长度, 有的占一个字节, 有的占两个字节, 还有的占四个字节或8个字节, 数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节。可以把u1, u2, u3, u4看做class文件数据项的“类型” 。

类型

名称

介绍

数量

u4

magic

魔数,固定0xCAFEBABE

1

u2

minor_version

副版本号

1

u2

major_version

主版本

1

u2

constant_pool_count

常量池计数器

1

cp_info

constant_pool

常量池

constant_pool_count-1

u2

access_flags

类访问标识

1

u2

this_class

类索引

1

u2

super_class

父类索引

1

u2

interfaces_count

接口计数器

1

interface_info

interfaces

接口表

interfaces_count

u2

field_count

字段计数器

1

field_info

fields

字段表

field_count

u2

methods_count

方法计数器

1

method_info

methods

方法表

methods_count

u2

attribute_count

属性计数器

1

attribute_ino

attribute

属性表

attribute_count

初建

其实构建class,数量为1的字段都非常好办,麻烦的就是几个变长部分。我们先模拟一下极简场景

magic: CAFE BABE

major_version: 0000 0034 (52 jdk8)

constant_pool: 0000

access_flags:: 0021

this_class:0000

super_class: 0000

interfaces: 0000

fields: 0000

methods: 0000

attributes: 0000

cafe babe 0000 0034 0000 0021 0000 0000 0000 0000 0000 0000

添加类名

这里需要在常量池添加2个字段,一个是CONSTANT_Class_info(标识07)存放引用,另外一个CONSTANT_Utf8_info(标识01)存放二进制对应的字符串

类型

标志(tag)

描述

CONSTANT_Utf8_info

1

UTF-8编码的字符串

CONSTANT_Integer_info

3

整型字面量

CONSTANT_Float_info

4

浮点型字面量

CONSTANT_Long_info

5

长整型字面量

CONSTANT_Double_info

6

双精度浮点型字面量

CONSTANT_Class_info

7

类或接口的符号引用

CONSTANT_String_info

8

字符串类型字面量

CONSTANT_Fieldref_info

9

字段的符号引用

CONSTANT_Methodref_info

10

类中方法的符号引用

CONSTANT_InterfaceMethodref_info

11

接口中方法的符号引用

CONSTANT_NameAndType_info

12

字段或方法的部分符号引用

CONSTANT_MethodHandle_info

15

表示方法句柄

CONSTANT_MethodType_info

16

表示方法类型

CONSTANT_InvokeDynamic_info

18

表示一个动态方法调用点

-- Class 引用 #2
07 00 02-- UTF8 10 bytes    H  e  l  l  o  W  o  r  l  d
01 00 0a            48 65 6c 6c 6f 57 6f 72 6c 64

案例如下:

cafe babe 0000 0034 0003  -- 常量池 计数 30700 02
0100 0a  48 65 6c 6c 6f 57 6f 72 6c 640021 0001 0000
0000 0000 0000 0000

添加父类

添加父类 java/lang/Object

-- Class 引用 #4
07 00 04-- UTF8 10 bytes    J  a  v  a  /  l  a  n  g  /  O  b  j
01 00 10            6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a e  c  t65 63 74

案例如下:

cafe babe 0000 0034 0005 0700 02
0100 0a  48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10  6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740021 0001 0003
0000 0000 0000 0000

基础类完成了,我们的目标是完成下面的代码,所以还需要为常量池添加更多信息

static void main(String[] args) {
    System.out.println("HelloWorld");
}
  1. System.out的返回类型PrintStream
  2. 字符串HelloWorld ,这里其实是可以和类名复用的,但是为了就结构清晰,我们还是使用新的引用
  3. 2个utf8类型用于映射我们的方法前面main和([Ljava/lang/String;)V
  4. 表示特殊属性代码的UTF8字符串。这将需要指示main方法指令的主体。
cafe babe 0000 0034000b0700 02
0100 0a  48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10  6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10  6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13  6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b  48 65 6c 6c 6f 20 57 6f 72 6c 640021 0001 0003
0000 0000 0000 0000

添加引用字段和方法

这里主要构建System.out及其print所需相关常量池

0900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 56
cafe babe 0000 003400130700 02
0100 0a   48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13   6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b   48 65 6c 6c 6f 20 57 6f 72 6c 640900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 560021 0001 0003
0000 0000 0000 0000

main方法

cafe babe 0000 003400160700 02
0100 0a   48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13   6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b   48 65 6c 6c 6f 20 57 6f 72 6c 640900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 560100 04   6d 61 69 6e
0100 16   28 5b 4c 6a 61 76 61 2f 6c 61 6e
          67 2f 53 74 72 69 6e 67 3b 29 56
0100 04   43 6f 64 650021 0001 0003
0000 0000 0000 0000

添加方法体

接下来我们要为main添加实现

method_info {
    2 bytes        Methods access flags
    2 bytes        Name of method. UTF8 index in constant pool
    2 bytes        Type of method. UTF8 index in constant pool
    2 bytes        Number of attributes
    * bytes        Variable bytes describing attribute_info structs
}Note: the attribute we prepared for was Code. This will contain our byte code instructions.
cafe babe 0000 003400160700 02
0100 0a   48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13   6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b   48 65 6c 6c 6f 20 57 6f 72 6c 640900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 560100 04   6d 61 69 6e
0100 16   28 5b 4c 6a 61 76 61 2f 6c 61 6e
          67 2f 53 74 72 69 6e 67 3b 29 56
0100 04   43 6f 64 650021 0001 0003
0000 000000010009 0013 0014
00000000

上面我们添加了一个空方法,接下来就就是添加打印了。

cafe babe 0000 003400160700 02
0100 0a   48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13   6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b   48 65 6c 6c 6f 20 57 6f 72 6c 640900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 560100 04   6d 61 69 6e
0100 16   28 5b 4c 6a 61 76 61 2f 6c 61 6e
          67 2f 53 74 72 69 6e 67 3b 29 56
0100 04   43 6f 64 650021 0001 0003
0000 000000010009 0013 0014
0001
0015
0000 0015
0002 0001
0000 0009
b200 0b
1209
b600 0f
b1
0000
00000000
0009 - public static0013 0014 - main #19 ([Ljava/lang/String;) #200001 - attribute size = 1
0015 - Code Attribute ( this is index #21 in our constant pool )0000 0015 - Code Attribute size of 21 bytes. 21 bytes of code attribute:
0002 0001 - Max stack size of 2, and Max local var size of 10000 0009 - Size of code. 9 bytesThe actual machine instructions:
b200 0b - b2 = getstatic, 000b = index #11 in constant pool ( out )
1209 - 12 = ldc ( load constant ), 09 = index #19 ( Hello World )
b600 0f - b6 = invokevirtual, 000f = index #15 ( method println )
b1 - b1 = return void
0000 - Exception table of size 0
0000 - Attribute count for this attribute of 0

执行一下

到此,我们就正式完成了一个Helloworld。由于篇幅所限,未能完全展现字节码的魅力,后续也会找一些更有趣的选题跟大家分享。也欢迎大家与我交流。

完整清单

cafe babe 0000 003400160700 02
0100 0a   48 65 6c 6c 6f 57 6f 72 6c 640700 04
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 740700 06
0100 10   6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d0700 08
0100 13   6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d0800 0a
0100 0b   48 65 6c 6c 6f 20 57 6f 72 6c 640900 0500 0c
0c00 0d00 0e
0100 03   6f 75 74
0100 15   4c 6a 61 76 61 2f 69 6f 2f 50 72
          69 6e 74 53 74 72 65 61 6d 3b0a00 0700 10
0c00 1100 12
0100 07   70 72 69 6e 74 6c 6e
0100 15   28 4c 6a 61 76 61 2f 6c 61 6e 67
          2f 53 74 72 69 6e 67 3b 29 560100 04   6d 61 69 6e
0100 16   28 5b 4c 6a 61 76 61 2f 6c 61 6e
          67 2f 53 74 72 69 6e 67 3b 29 56
0100 04   43 6f 64 650021 0001 0003
0000 000000010009 0013 0014
0001
0015
0000 0015
0002 0001
0000 0009
b200 0b
1209
b600 0f
b1
0000
00000000

参考链接:

https://medium.com/@davethomas_9528/writing-hello-world-in-java-byte-code-34f75428e0ad

https://blog.csdn.net/qq_31350373/article/details/81512856

https://blog.csdn.net/justry_deng/article/details/86079756?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.edu_weight

历史文章导读

如果文章对您有那么一点点帮助,我将倍感荣幸

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 麒思妙想 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档