文件格式分析 --- 最小class
为什么要研究文件格式,就像我们在学习到一定程度会学习汇编,不是用汇编写代码,而是理解底层原理有助于我们理解程序的执行流程
class是java编译后的可执行的字节码文件。
解析class的工具有javap,010editor,idapro
下面是java
package com.example; public class { public static void main(String[] args) { System.out.println("Hello World by gk"); } }
编译后的二进制
我们可以看出整个文件最大的部分是 常量池和方法
常量池
常量池[0] - CONSTANT_Methodref 标记字节:0A(表示方法引用) 类索引:00 02(指向常量池#2,即Object类) 名称类型索引:00 03(指向常量池#3,即构造方法的名称和类型)
常量池[1] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 04(指向常量池#4,即"java/lang/Object"字符串)
常量池[2] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 05(指向常量池#5,即"") 描述符索引:00 06(指向常量池#6,即"()V")
常量池[3] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 10(表示长度16) 字符串内容:"java/lang/Object"的UTF8编码
常量池[4] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 06(表示长度6) 字符串内容:"<init>"的UTF8编码
常量池[5] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 03(表示长度3) 字符串内容:"()V"的UTF8编码
常量池[6] - CONSTANT_Fieldref 标记字节:09(表示字段引用) 类索引:00 07(指向常量池#7,即System类) 名称类型索引:00 08(指向常量池#8,即out字段的名称和类型)
常量池[7] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 09(指向常量池#9,即"java/lang/System"字符串)
常量池[8] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 0A(指向常量池#10,即"out") 描述符索引:00 0B(指向常量池#11,即"Ljava/io/PrintStream;")
常量池[9] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 10(表示长度16) 字符串内容:"java/lang/System"的UTF8编码
常量池[10] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 03(表示长度3) 字符串内容:"out"的UTF8编码
常量池[11] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 15(表示长度21) 字符串内容:"Ljava/io/PrintStream;"的UTF8编码
常量池[12] - CONSTANT_String 标记字节:08(表示字符串常量) 字符串索引:00 0D(指向常量池#13,即"Hello World"字符串)
常量池[13] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0B(表示长度11) 字符串内容:"Hello World"的UTF8编码
常量池[14] - CONSTANT_Methodref 标记字节:0A(表示方法引用) 类索引:00 0F(指向常量池#15,即PrintStream类) 名称类型索引:00 10(指向常量池#16,即println方法的名称和类型)
常量池[15] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 11(指向常量池#17,即"java/io/PrintStream"字符串)
常量池[16] - CONSTANT_NameAndType 标记字节:0C(表示名称和类型) 名称索引:00 12(指向常量池#18,即"println") 描述符索引:00 13(指向常量池#19,即"(Ljava/lang/String;)V")
常量池[17] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 13(表示长度19) 字符串内容:"java/io/PrintStream"的UTF8编码
常量池[18] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 07(表示长度7) 字符串内容:"println"的UTF8编码
常量池[19] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 15(表示长度21) 字符串内容:"(Ljava/lang/String;)V"的UTF8编码
常量池[20] - CONSTANT_Class_info 标记字节:07(表示类引用) 名称索引:00 15(指向常量池#21,即"com/example/HelloWorld"字符串)
常量池[21] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 16(表示长度22) 字符串内容:"com/example/HelloWorld"的UTF8编码
常量池[22] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"Code"的UTF8编码
常量池[23] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0F(表示长度15) 字符串内容:"LineNumberTable"的UTF8编码
常量池[24] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 12(表示长度18) 字符串内容:"LocalVariableTable"的UTF8编码
常量池[25] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"this"的UTF8编码
常量池[26] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 18(表示长度24) 字符串内容:"Lcom/example/HelloWorld;"的UTF8编码
常量池[27] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"main"的UTF8编码
常量池[28] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 16(表示长度22) 字符串内容:"([Ljava/lang/String;)V"的UTF8编码
常量池[29] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 04(表示长度4) 字符串内容:"args"的UTF8编码
常量池[30] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 13(表示长度19) 字符串内容:"[Ljava/lang/String;"的UTF8编码
常量池[31] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0A(表示长度10) 字符串内容:"SourceFile"的UTF8编码
常量池[32] - CONSTANT_Utf8 标记字节:01(表示UTF8字符串) 长度值:00 0F(表示长度15) 字符串内容:"HelloWorld.java"的UTF8编码
什么是属性,这里的class的属性类似与exe的节区的概念,所有内容都是由属性构成的。code属性表示代码区域对应exe的text区域
不同位置可能的属性:
- 类属性:SourceFile, InnerClasses, Deprecated等
- 字段属性:ConstantValue, Synthetic, Deprecated等
- 方法属性:Code, Exceptions, Deprecated等
方法1
访问标志 值:0x0001 表示这是一个public的构造方法 方法名索引 值:5 指向常量池#5,内容:"<init>",这是构造方法的特殊名称 方法描述符索引 值:6 指向常量池#6 内容:"()V" 描述符解析: ():表示无参数 V:表示返回值是void 属性计数 值:1 表示这个构造方法有1个属性(Code属性) Code属性详细信息: a. 属性名索引 值:23 指向常量池#23 内容:"Code" b. 属性长度 值:47 表示Code属性的内容占47字节 c. 最大栈深度 值:1 表示操作数栈最多需要1个空间 d. 局部变量表大小 值:1 表示需要1个局部变量空间(用于this引用) e. 代码长度 值:5h 表示方法的字节码指令占5个字节 第一条指令: 字节码:2A (aload_0) 含义:将第0个局部变量(this引用)加载到操作数栈顶 作用:准备调用父类构造方法时需要的this引用 第二条指令: 字节码:B7 (invokespecial) 操作数:00 01(指向常量池#1) 含义:调用父类的构造方法 具体调用:java/lang/Object.()V 第三条指令: 字节码:B1 (return) 含义:从构造方法返回 作用:完成构造方法的执行 方法的字节码 子属性计数 : 类型:struct attribute_info 值:2 含义:表示有两个属性表项 作用:告诉JVM需要解析的属性数量 LineNumberTable属性 : 属性名索引 值:24 含义:指向常量池中的"LineNumberTable"字符串 作用:标识这是行号表属性 属性长度 值:6 含义:属性信息的长度(字节) 作用:确定属性数据的范围 表长度 类型:u2 值:1 含义:行号表中的条目数 作用:说明有多少行号映射关系 行号表 start_pc: 值:0 含义:字节码的偏移位置 作用:指示代码开始位置 line_number: 值:3 含义:源代码的行号 作用:对应源文件中的行号 LocalVariableTable属性: 属性名索引 值:25 含义:指向常量池中的"LocalVariableTable"字符串 作用:标识这是局部变量表属性 属性长度 值:12 含义:属性信息的长度(字节) 作用:确定属性数据的范围 表长度 值:1 含义:局部变量表中的条目数 作用:说明有多少局部变量 LocalVariableTable项: start_pc: 值:0 含义:变量的作用域起始位置 作用:标记变量开始有效的位置 length: 值:5 含义:变量的作用域长度 作用:确定变量的有效范围 name_index: 值:26 含义:指向变量名的常量池索引 作用:获取变量的名称this descriptor_index: 值:27 含义:指向变量类型描述符的常量池索引 作用:确定变量的类型Lcom/example/HelloWorld; index: 值:0 含义:变量在局部变量表中的槽位 作用:定位变量的存储位置
方法2同上忽略
javap反汇编
C:\Users\21558\CascadeProjects\class_demo>javap -v "C:\Users\21558\Documents\WeChat Files\wxid_nwercn4twf1c22\FileStorage\File\2025-02\HelloWorld.class" Classfile /C:/Users/21558/Documents/WeChat Files/wxid_nwercn4twf1c22/FileStorage/File/2025-02/HelloWorld.class Last modified 2025年1月19日; size 557 bytes SHA-256 checksum a4c83c79ac32ed56d145ff91391e36f338b37eb95ebab2a4d4192795afee16d0 Compiled from "HelloWorld.java" public class com.example.HelloWorld minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #21 // com/example/HelloWorld super_class: #2 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #2.#3 // java/lang/Object."<init>":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "<init>":()V #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream; #8 = Class #10 // java/lang/System #9 = NameAndType #11:#12 // out:Ljava/io/PrintStream; #10 = Utf8 java/lang/System #11 = Utf8 out #12 = Utf8 Ljava/io/PrintStream; #13 = String #14 // Hello World #14 = Utf8 Hello World #15 = Methodref #16.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V #16 = Class #18 // java/io/PrintStream #17 = NameAndType #19:#20 // println:(Ljava/lang/String;)V #18 = Utf8 java/io/PrintStream #19 = Utf8 println #20 = Utf8 (Ljava/lang/String;)V #21 = Class #22 // com/example/HelloWorld #22 = Utf8 com/example/HelloWorld #23 = Utf8 Code #24 = Utf8 LineNumberTable #25 = Utf8 LocalVariableTable #26 = Utf8 this #27 = Utf8 Lcom/example/HelloWorld; #28 = Utf8 main #29 = Utf8 ([Ljava/lang/String;)V #30 = Utf8 args #31 = Utf8 [Ljava/lang/String; #32 = Utf8 SourceFile #33 = Utf8 HelloWorld.java { public com.example.HelloWorld(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String Hello World 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java" C:\Users\21558\CascadeProjects\class_demo>javap -p "C:\Users\21558\Documents\WeChat Files\wxid_nwercn4twf1c22\FileStorage\File\2025-02\HelloWorld.class" Compiled from "HelloWorld.java" public class com.example.HelloWorld { public com.example.HelloWorld(); public static void main(java.lang.String[]); }
smali反汇编
.class public Lcom/example/HelloWorld; .super Ljava/lang/Object; .source "HelloWorld.java" # direct methods .method public constructor <init>()V .registers 1 .line 3 invoke-direct {p0}, Ljava/lang/Object;-><init>()V return-void .end method .method public static main([Ljava/lang/String;)V .registers 3 .line 5 sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; const-string v1, "Hello World" invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V .line 6 return-void .end method
用ida反汇编
; +-------------------------------------------------------------------------+ ; | 这个文件由 IDA (交互式反汇编器) 生成 | ; | 版权所有 (c) 2023 Hex-Rays, <support@hex-rays.com> | ; +-------------------------------------------------------------------------+ ; 文件校验信息 ; Input SHA256 : A4C83C79AC32ED56D145FF91391E36F338B37EB95EBAB2A4D4192795AFEE16D0 ; Input MD5 : FBC27DC5D80422D99FACC4CA045323F6 ; Input CRC32 : 0E1D6D53 ; 文件信息 ; 文件名称 : C:\Users\21558\Pictures\jar2\HelloWorld.class ; 文件格式 : JavaVM Class File (JDK 1.8版本) ; 字节码版本:Java 8 (52.0) .bytecode 52.0 .source "HelloWorld.java" ; 导入的类和方法原型声明 ;.import java.lang.Object ; 导入Object类,HelloWorld类的父类 ; java.lang.Object.<init>()void ; 导入Object类的构造方法 ;.import java.lang.System ; 导入System类 ; java.lang.System.out java.io.PrintStream ; 导入System.out静态字段 ;.import java.io.PrintStream ; 导入PrintStream类 ; java.io.PrintStream.println(java.lang.String)void ; 导入println方法 ; 类声明部分 .class public com.example.HelloWorld ; 声明公共类 HelloWorld .super java.lang.Object ; 声明父类为 Object ; 构造方法 .method public <init> ; 默认构造函数 .limit stack 1 ; 操作数栈最大深度为1 .limit locals 1 ; 局部变量表大小为1 met001_begin: .line 3 aload_0 ; 加载this引用到操作数栈 invokespecial java.lang.Object.<init>()void ; 调用父类Object的构造方法 return ; 返回 met001_end:
;met001_slot000 ; DATA XREF: <init>↑r
.var 0 is this com.example.HelloWorld from met001_begin to met001_end
.end method
- 是局部变量表中的第一个槽位(slot)的标签名称
- 表示这是局部变量表中索引为0的变量
- 表明这个变量是当前类的实例引用(this引用)
- 指定了变量的类型
- 定义了这个变量的作用域范围,从方法开始到方法结束
- 每个方法都有自己的局部变量表
- 局部变量表中的每个位置称为一个槽位(slot)
- 对于实例方法,slot0 永远保存this引用
- 方法的参数从slot1开始存放
- 方法内声明的局部变量从参数之后的slot开始存放
; main方法 .method public static main ; 主方法声明 .limit stack 2 ; 操作数栈最大深度为2 .limit locals 1 ; 局部变量表大小为1 met002_begin: .line 5 getstatic java.lang.System.out java.io.PrintStream ; 获取System.out静态字段 ldc "Hello World" ; 将字符串常量"Hello World"压入栈 invokevirtual java.io.PrintStream.println(java.lang.String)void ; 调用println方法 .line 6 return ; 返回 met002_end: .end method