2个字节能存多少个16进制_硬核手写字节码实现HelloWorld

0cb490f52caee1ddbc95e3d427d4debb.png

5dc0457575e6efefd5e5f2c2993751f8.png

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

工具

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

7fdaf193cfb0d8d4187e1c8fb3c3eff6.png

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

fd5c2fc958e7707350ad53979bada4c7.png

整活

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

b3abbde8057d4a9eaedf762e08912cc4.png

Class 结构

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

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

类型名称介绍数量
u4magic魔数,固定0xCAFEBABE1
u2minor_version副版本号1
u2major_version主版本1
u2constant_pool_count常量池计数器1
cp_infoconstant_pool常量池constant_pool_count-1
u2access_flags类访问标识1
u2this_class类索引1
u2super_class父类索引1
u2interfaces_count接口计数器1
interface_infointerfaces接口表interfaces_count
u2field_count字段计数器1
field_infofields字段表field_count
u2methods_count方法计数器1
method_infomethods方法表methods_count
u2attribute_count属性计数器1
attribute_inoattribute属性表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

5201bb50491c5da987cd2092ebb1fedf.png

添加类名

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

类型标志(tag)描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16表示方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
-- 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

16aa0af6a6f43d7d3e8cd1eecb047ab5.png

添加父类

添加父类 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

c2af5af699ed4c331a8c7a2c6dc2a6cf.png

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

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

96639f63b16e55a1dec06ee20946c65e.png

添加引用字段和方法

这里主要构建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

a3c25d4be2ca68c8482a67142452677e.png

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

07d96e2b21dab9aa08bc92d44223d597.png

添加方法体

接下来我们要为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

752d63a7e792f33d840bbf051cbd9bf6.png

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

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

a1a214a0b9de07c9af56b69b76f52fbe.png

执行一下

ec3e772f1756184dd7b1f480fe1a8a9b.png

到此,我们就正式完成了一个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

历史文章导读

  • 抽象语法树为什么抽象
  • 基于Calcite自定义SQL解析器
  • 基于JDBC实现VPD:SQL解析篇
  • 如何成为一个成功的首席数据官
  • 数据库深度研究(100页PPT)
  • 基于Win10单机部署kubernetes应用
  • 浅谈基于JDBC实现虚拟专用数据库(VPD)

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

欢迎 关注、在看、点赞、转发

91d05dc356708c8a2a3fb9a21e48fe78.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值