JVM字节码数据结构总览和读取
前言
最近写J2ME深感各种java反编译不得我心,决定自己尝试写一个自己的java反编译,第一步实现javap。现在把字节码的数据结构贴出来一部分,以及我的实现思路。
CLASSFILE
官网文档如下
cpp
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
常量池
| Constant Kind | Tag | Section |
|---|---|---|
CONSTANT_Class |
7 | §4.4.1 |
CONSTANT_Fieldref |
9 | §4.4.2 |
CONSTANT_Methodref |
10 | §4.4.2 |
CONSTANT_InterfaceMethodref |
11 | §4.4.2 |
CONSTANT_String |
8 | §4.4.3 |
CONSTANT_Integer |
3 | §4.4.4 |
CONSTANT_Float |
4 | §4.4.4 |
CONSTANT_Long |
5 | §4.4.5 |
CONSTANT_Double |
6 | §4.4.5 |
CONSTANT_NameAndType |
12 | §4.4.6 |
CONSTANT_Utf8 |
1 | §4.4.7 |
CONSTANT_MethodHandle |
15 | §4.4.8 |
CONSTANT_MethodType |
16 | §4.4.9 |
CONSTANT_Dynamic |
17 | §4.4.10 |
CONSTANT_InvokeDynamic |
18 | §4.4.10 |
CONSTANT_Module |
19 | §4.4.11 |
CONSTANT_Package |
20 | §4.4.12 |
代码结构
宏定义
c
#define u2 unsigned short
#define u4 unsigned int
#define u1 unsigned char
#define s2 short
#define s4 int
#define s1 char
// 既然是java,多一个null也是很合理的
#define null nullptr
ClassFile.h
cpp
#ifndef CLASSFILE_H
#define CLASSFILE_H
/**
* @since 2025-11-27
* @author authorZhao
*/
#include <iosfwd>
#include <string>
#include "ConstPool.h"
#include "AttributeInfo.h"
// 前向声明
class field_info;
class method_info;
typedef field_info* FieldInfo;
typedef method_info* MethodInfo;
/**
* 字段信息类 - 表示类或接口中声明的字段
* 字段的属性如下所示
* {@link ConstantValue }
* {@link Synthetic }
* {@link Deprecated }
* {@link Signature }
* {@link RuntimeVisibleAnnotations }
* {@link RuntimeInvisibleAnnotations }
* {@link RuntimeVisibleTypeAnnotations }
* {@link RuntimeInvisibleTypeAnnotations }
*/
class field_info {
public:
u2 access_flags; // 访问标志
u2 name_index; // 字段名在常量池中的索引
u2 descriptor_index; // 字段描述符在常量池中的索引
u2 attributes_count; // 属性数量
AttributeInfo* attributes; // 属性表attributes[attributes_count];
// 辅助信息
std::string flagName;
std::string fieldName;
std::string fieldDescriptor;
std::string javaField;
field_info() : access_flags(0), name_index(0), descriptor_index(0), attributes_count(0), attributes(nullptr) {}
~field_info();
friend std::ostream& operator<<(std::ostream& os, const field_info& obj);
std::string getFlagName();
std::string getFieldName();
std::string getFieldDescriptor();
std::string getJavaField();
void parseFiledInfo(const ConstPool* constPool);
// 属性解析暂时忽略
};
/**
* 方法信息类 - 表示类或接口中声明的方法
* 方法的属性有下面集中类型
* {@link Code }
* {@link Exceptions }
* {@link RuntimeVisibleParameterAnnotations }
* {@link RuntimeInvisibleParameterAnnotations }
* {@link AnnotationDefault }
* {@link MethodParameters }
* {@link Synthetic }
* {@link Deprecated }
* {@link Signature }
* {@link RuntimeVisibleAnnotations }
* {@link RuntimeInvisibleAnnotations }
* {@link RuntimeVisibleTypeAnnotations }
* {@link RuntimeInvisibleTypeAnnotations }
*/
class method_info {
public:
u2 access_flags; // 访问标志
u2 name_index; // 方法名在常量池中的索引
u2 descriptor_index; // 方法描述符在常量池中的索引
u2 attributes_count; // 属性数量
AttributeInfo* attributes; // 属性表 attributes[attributes_count];
// 辅助信息,暂时和field分开
std::string flagName;
std::string methodName;
std::string methodDescriptor;
std::string javaMethod;
std::string getFlagName();
std::string getMethodName();
std::string getMethodDescriptor();
std::string getJavaMethod();
void parseMethodInfo(const ConstPool* constPool);
method_info() : access_flags(0), name_index(0), descriptor_index(0), attributes_count(0), attributes(nullptr) {}
~method_info();
friend std::ostream& operator<<(std::ostream& os, const method_info& obj);
};
/**
* 类文件格式,其中属性如下
* {@link SourceFile }
* {@link InnerClasses }
* {@link EnclosingMethod }
* {@link SourceDebugExtension }
* {@link BootstrapMethods }
* {@link Module }
* {@link ModulePackages }
* {@link ModuleMainClass }
* {@link NestHost }
* {@link NestMembers }
* {@link Record }
* {@link PermittedSubclasses }
* {@link Synthetic }
* {@link Deprecated }
* {@link Signature }
* {@link RuntimeVisibleAnnotations }
* {@link RuntimeInvisibleAnnotations }
* {@link RuntimeVisibleTypeAnnotations }
* {@link RuntimeInvisibleTypeAnnotations }
*
*/
class ClassFile {
public:
// 构造函数
ClassFile();
// 析构函数
~ClassFile();
u4 magic; // 魔数 0xCAFEBABE
u2 minor_version; // 次版本号 1.1 = 45
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池计数
ConstPool* constant_pool; // 常量池对象指针 constant_pool[constant_pool_count-1];
u2 access_flags; // 访问标志
u2 this_class; // 当前类在常量池中的索引
u2 super_class; // 父类在常量池中的索引
u2 interfaces_count; // 接口数量
u2* interfaces; // 接口索引数组 interfaces[interfaces_count];
u2 fields_count; // 字段数量
FieldInfo fields; // 字段信息数组 fields[fields_count];
u2 methods_count; // 方法数量
MethodInfo methods; // 方法信息数组 methods[methods_count];
u2 attributes_count; // 属性数量
AttributeInfo* attributes; // 属性信息数组 attributes[attributes_count];
friend std::ostream& operator<<(std::ostream& os, const ClassFile& obj);
};
#endif //CLASSFILE_H
常量池代码设计
从上到下第一个核心结构就是常量池,我这里直接使用指针表示
cpp
ConstPool* constant_pool;
常量池管理常量,其核心结构图下
cpp
class ConstPool {
private:
std::vector<std::shared_ptr<constant_pool_info>> constants; // 常量池项数组
u2 constant_pool_count; // 常量池项数量(包括dummy项)
// 省略部分方法
}
constant_pool_info
cpp
/**
* 常量池项基类 - 所有常量池项的抽象基类
* 定义了常量池项的基本接口和公共属性
*/
class constant_pool_info {
public:
u1 tag; // 常量池项类型标识
virtual ~constant_pool_info() = default;
// 获取常量池项的字符串表示
virtual std::string toString() const = 0;
// 获取常量池项的大小(字节)
virtual size_t getSize() const = 0;
// 验证常量池项的有效性
virtual bool validate() const = 0;
// 从文件流中读取数据
virtual void readFrom(std::istream& file) = 0;
};
具体常量下一期在水
字段和方法
cpp
u2 access_flags;
u2 name_index;的索引
u2 descriptor_index;
u2 attributes_count;
AttributeInfo* attributes;
其中字段和方法结构基本一样,但是不做管理,等待javap实现完毕,需要增删改查字段和方法的时候在进行管理
目前仅需简单的使用数组管理,在代码里面退化为指针。
AttributeInfo
这个属性恐怕才是字节码里面最为复杂的,整个attribute_info起码还能水几十章,暂放,设计思路和常量池一样
cpp
class AttributeInfo {
private:
std::vector<std::shared_ptr<attribute_info>> attributes; // 常量池项数组
u2 attributes_count; // 属性数量(包括dummy项)
// 省略...
}
我在classfile里面注释了每个属性出现的位置,特别是字段和方法,虽然属性很多,但不是都是通用的。
javap读取代码思路
我自己实现的
cpp
ClassFile* ClassFileReader::readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Error: Cannot open file " << filename << std::endl;
return nullptr;
}
auto classFile = new ClassFile();
try {
// 读取魔数
classFile->magic = IoUtil::readU4(file);
// 检查魔数
if (classFile->magic != 0xCAFEBABE) {
std::cerr << "Error: Invalid magic number: 0x" << std::hex << classFile->magic << std::dec << std::endl;
delete classFile;
file.close();
return nullptr;
}
// 读取版本号
classFile->minor_version = IoUtil::readU2(file);
classFile->major_version = IoUtil::readU2(file);
// 读取常量池
readConstantPool(file, classFile);
// 读取访问标志
classFile->access_flags = IoUtil::readU2(file);
// 访问标识符暂时也不贴出来,下期再水
auto classFlag = AccessFlags(classFile->access_flags).getClassFlags();
for (auto class_flag : classFlag) {
std::cout << "classFlag=" << class_flag << std::endl;
}
// 读取类信息
classFile->this_class = IoUtil::readU2(file);
classFile->super_class = IoUtil::readU2(file);
// 读取接口
classFile->interfaces_count = IoUtil::readU2(file);
readInterfaces(file, classFile);
// 读取字段
readFields(file, classFile);
// 读取方法
readMethods(file, classFile);
// 读取属性
readAttributes(file, classFile);
file.close();
return classFile;
}
catch (const std::exception& e) {
std::cerr << "Error reading class file: " << e.what() << std::endl;
delete classFile;
file.close();
return nullptr;
}
}
工具类
IoUtil
cpp
u1 IoUtil::readU1(std::istream &file) {
u1 value;
file.read(reinterpret_cast<char *>(&value), 1);
if (file.gcount() != 1) {
throw std::runtime_error("Failed to read u1");
}
return value;
}
u2 IoUtil::readU2(std::istream &file) {
u1 high = readU1(file);
u1 low = readU1(file);
return (high << 8) | low;
}
u4 IoUtil::readU4(std::istream &file) {
u2 high = readU2(file);
u2 low = readU2(file);
return (high << 16) | low;
}