专栏首页麒思妙想硬核手写字节码实现HelloWorld

硬核手写字节码实现HelloWorld

这是一篇硬(闲的)核(蛋疼)的文章,我们来通过手写字节码的方式,来完成一个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

历史文章导读

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

本文分享自微信公众号 - 麒思妙想(qicai1612),作者:dafei1288

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 再谈Flink

    前一阵痴迷于calcite,打算写一些streaming sql相关的东西,正好时逢置办年货,就买了本书《Flink基础教程》,打开看了一下,就放不下了,一口气...

    麒思妙想
  • 使用Spark轻松做数据透视(Pivot)

    spark从1.6开始引入,到现在2.4版本,pivot算子有了进一步增强,这使得后续无论是交给pandas继续做处理,还是交给R继续分析,都简化了不少。大家无...

    麒思妙想
  • MySQL的sql执行顺序

    在SQL语句中每个关键字都会按照顺序往下执行,而每一步操作,会生成一个虚拟表,最后产生的虚拟表会作为执行的最终结果返回。下面的是常用的关键字的执行顺序:

    麒思妙想
  • electron桌面应用开发(七)-electron-vue基本概念

    今天想继续写electron,在准备素材做实验的时候,发现基本概念太薄弱了,对工程的目录作用和基本运行逻辑都不是很熟,导致做起实验举步维艰,所以还是需要加强下基...

    efonfighting
  • 利用Electron把Web项目打包成桌面应用

    1.Electron是基于Node.js开发的,第一步当然要安装node盒npm了,就不多说了。

    xferris
  • 如何设置合适的 batch 大小收获 4 倍加速 & 更好的泛化效果

    有一次,我在 twitter 上看到 Jeremy Howard 引用 Yann LeCun 关于 batch 大小的话:

    AI研习社
  • 打破监控壁垒,棉花厂3D可视化建设让生产更加智能化

    现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加下厂的设备突然出现故障时,控制程序需要更换。这种情况下,首先需要客户向设备生产厂家请求派出技术人员进...

    HT_hightopo
  • 智慧工厂|全方位监控管理,可视化让生产变得透明

    现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加工厂的设备突然出现故障时,控制程序需要更换。这种情况下,首先需要客户向设备生产厂家请求派出技术人员进...

    万物皆可联i
  • 打破监控壁垒,棉花厂3D可视化建设让生产加工更加智能化

    现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加下厂的设备突然出现故障时,控制程序需要更换。这种情况下,首先需要客户向设备生产厂家请求派出技术人员进...

    HT for Web
  • 【原创】互联网常见架构模式 之 nginx负载均衡

    一:什么是Nginx Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like协议下发行。...

    码神联盟

扫码关注云+社区

领取腾讯云代金券