Last active
March 29, 2019 17:15
-
-
Save devdilson/0dc68b170b18089043d5e2cce2fa6286 to your computer and use it in GitHub Desktop.
Experiment with parsing .class file format and decompiling a java class structure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.classloader; | |
import java.io.IOException; | |
import java.net.URISyntaxException; | |
import java.net.URL; | |
import java.nio.file.Files; | |
import java.nio.file.Paths; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
public class Main { | |
enum MemberAccess { | |
ACC_PUBLIC(0x0001), | |
ACC_PRIVATE(0x0002), | |
ACC_PROTECTED(0x0004), | |
ACC_STATIC(0x0008), | |
ACC_FINAL(0x0010), | |
ACC_VOLATILE(0x0040), | |
ACC_TRANSIENT(0x0080), | |
ACC_SYNTHETIC(0x1000), | |
ACC_ENUM(0x4000); | |
private int value; | |
MemberAccess(int value) { | |
this.value = value; | |
} | |
public static boolean hasAccess(short mask, MemberAccess access) { | |
return (mask & access.value) > 0; | |
} | |
} | |
enum ConstantPoolType { | |
CONSTANT_Class(7), | |
CONSTANT_Field_ref(9), | |
CONSTANT_Method_ref(10), | |
CONSTANT_InterfaceMethod_ref(11), | |
CONSTANT_String(8), | |
CONSTANT_Integer(3), | |
CONSTANT_Float(4), | |
CONSTANT_Long(5), | |
CONSTANT_Double(6), | |
CONSTANT_NameAndType(12), | |
CONSTANT_Utf8(1), | |
CONSTANT_MethodHandle(15), | |
CONSTANT_MethodType(16), | |
CONSTANT_InvokeDynamic(18); | |
byte value; | |
ConstantPoolType(int value) { | |
this.value = (byte) value; | |
} | |
public static ConstantPoolType valueOf(byte tag) { | |
for (ConstantPoolType item : ConstantPoolType.values()) { | |
if (item.value == tag) { | |
return item; | |
} | |
} | |
return null; | |
} | |
} | |
static class ClassMemberRef { | |
private short access_flags; | |
private short name_index; | |
private short descriptor_index; | |
private int numberOfAttributes; | |
public ClassMemberRef(short accessFlags, short nameIndex, short descriptorIndex) { | |
this.access_flags = accessFlags; | |
this.name_index = nameIndex; | |
this.descriptor_index = descriptorIndex; | |
} | |
public short getAccess_flags() { | |
return access_flags; | |
} | |
public void setAccess_flags(short access_flags) { | |
this.access_flags = access_flags; | |
} | |
public short getNameIndex() { | |
return name_index; | |
} | |
public void setName_index(short name_index) { | |
this.name_index = name_index; | |
} | |
public short getDescriptor_index() { | |
return descriptor_index; | |
} | |
public void setDescriptor_index(short descriptor_index) { | |
this.descriptor_index = descriptor_index; | |
} | |
public boolean isPublic() { | |
return MemberAccess.hasAccess(access_flags, MemberAccess.ACC_PUBLIC); | |
} | |
public boolean isStatic() { | |
return MemberAccess.hasAccess(access_flags, MemberAccess.ACC_STATIC); | |
} | |
public short getName_index() { | |
return name_index; | |
} | |
public int getNumberOfAttributes() { | |
return numberOfAttributes; | |
} | |
public void setNumberOfAttributes(int numberOfAttributes) { | |
this.numberOfAttributes = numberOfAttributes; | |
} | |
} | |
public interface BigEndianReader { | |
default byte readByte() { | |
byte arr[] = getBuffer(); | |
return arr[incrementOffset()]; | |
} | |
default short readShort() { | |
byte arr[] = getBuffer(); | |
short magic = (short) ((arr[incrementOffset()] & 0xFF) << 8); | |
magic |= ((arr[incrementOffset()]) & 0xFF); | |
return magic; | |
} | |
default int readInt() { | |
byte arr[] = getBuffer(); | |
int magic = (arr[incrementOffset()] << 24); | |
magic |= ((arr[incrementOffset()]) & 0xFF) << 16; | |
magic |= ((arr[incrementOffset()]) & 0xFF) << 8; | |
return magic | ((arr[incrementOffset()]) & 0xFF); | |
} | |
default byte[] readByteArr(int length) { | |
byte buffer[] = new byte[length]; | |
int offset = getOffset(); | |
System.arraycopy(getBuffer(), offset, buffer, 0, length); | |
seek(offset + length); | |
return buffer; | |
} | |
byte[] getBuffer(); | |
int incrementOffset(); | |
void seek(int offset); | |
int getOffset(); | |
} | |
static class BigEndianByteArrayReader implements BigEndianReader { | |
private final byte[] arr; | |
private int currentOffset; | |
public BigEndianByteArrayReader(byte[] arr) { | |
this.arr = arr; | |
this.currentOffset = 0; | |
} | |
@Override | |
public byte[] getBuffer() { | |
return this.arr; | |
} | |
@Override | |
public int incrementOffset() { | |
return currentOffset++; | |
} | |
@Override | |
public void seek(int offset) { | |
this.currentOffset = offset; | |
} | |
@Override | |
public int getOffset() { | |
return this.currentOffset; | |
} | |
} | |
static class ClassStructure { | |
public static final String INVALID_UTF_8_ITEM_IN_POOL = "INVALID_UTF8_ITEM_IN_POOL"; | |
private int magic; | |
private short minor_version; | |
private short major_version; | |
private short constant_pool_count; | |
private HashMap<Integer, PoolItem> constant_pool = new HashMap<>(); | |
private short access_flags; | |
private short this_class; | |
private short super_class; | |
private short interface_count; | |
private List<String> interfaces = new ArrayList<>(); | |
private short fields_count; | |
private List<ClassMemberRef> fields = new ArrayList<>(); | |
private short methods_count; | |
private List<ClassMemberRef> methods = new ArrayList<>(); | |
private short attributes_count; | |
private final BigEndianReader reader; | |
public ClassStructure(BigEndianReader reader) { | |
this.reader = reader; | |
} | |
abstract class PoolItem { | |
private final ConstantPoolType type; | |
private final int selfIndex; | |
public PoolItem(int selfIndex, ConstantPoolType type) { | |
this.selfIndex = selfIndex; | |
this.type = type; | |
} | |
ConstantPoolType getType() { | |
return this.type; | |
} | |
public int getSelfIndex() { | |
return selfIndex; | |
} | |
} | |
class IndexPoolItem extends PoolItem { | |
private final short index; | |
public IndexPoolItem(int selfIndex, short index, ConstantPoolType type) { | |
super(selfIndex, type); | |
this.index = index; | |
} | |
public int getIndex() { | |
return index; | |
} | |
} | |
class NamedIndexPoolItem extends IndexPoolItem { | |
private short name_index; | |
public NamedIndexPoolItem(int selfIndex, short index, short name_index, ConstantPoolType type) { | |
super(selfIndex, index, type); | |
this.name_index = name_index; | |
} | |
public short getName_index() { | |
return name_index; | |
} | |
} | |
class NumberPoolItem extends PoolItem { | |
private final int value; | |
public NumberPoolItem(int selfIndex, int value, ConstantPoolType type) { | |
super(selfIndex, type); | |
this.value = value; | |
} | |
public int getValue() { | |
return value; | |
} | |
} | |
class PrecisionPoolItem extends PoolItem { | |
private final int left, right; | |
public PrecisionPoolItem(int selfIndex, int left, int right, ConstantPoolType type) { | |
super(selfIndex, type); | |
this.left = left; | |
this.right = right; | |
} | |
public int getRight() { | |
return right; | |
} | |
public int getLeft() { | |
return left; | |
} | |
} | |
class Utf8PoolItem extends PoolItem { | |
private final String text; | |
public Utf8PoolItem(int selfIndex, ConstantPoolType type, String text) { | |
super(selfIndex, type); | |
this.text = text; | |
} | |
public String getText() { | |
return text; | |
} | |
} | |
class MethodHandlePoolItem extends PoolItem { | |
private final byte reference_kind; | |
private final short reference_index; | |
public MethodHandlePoolItem(int selfIndex, byte reference_kind, short reference_index, ConstantPoolType type) { | |
super(selfIndex, type); | |
this.reference_kind = reference_kind; | |
this.reference_index = reference_index; | |
} | |
public byte getReference_kind() { | |
return reference_kind; | |
} | |
public short getReference_index() { | |
return reference_index; | |
} | |
} | |
public void parse() { | |
this.magic = reader.readInt(); | |
this.minor_version = reader.readShort(); | |
this.major_version = reader.readShort(); | |
this.constant_pool_count = reader.readShort(); | |
for (int i = 1; i < this.constant_pool_count; i++) { | |
ConstantPoolType type = ConstantPoolType.valueOf(reader.readByte()); | |
switch (type) { | |
case CONSTANT_Class: { | |
short name_index = reader.readShort(); | |
this.constant_pool.put(i, new IndexPoolItem(i, name_index, type)); | |
break; | |
} | |
case CONSTANT_Field_ref: | |
case CONSTANT_Method_ref: | |
case CONSTANT_InterfaceMethod_ref: { | |
short class_index = reader.readShort(); | |
short name_and_type_index = reader.readShort(); | |
this.constant_pool.put(i, new NamedIndexPoolItem(i, class_index, name_and_type_index, type)); | |
break; | |
} | |
case CONSTANT_String: { | |
short string_index = reader.readShort(); | |
this.constant_pool.put(i, new IndexPoolItem(i, string_index, type)); | |
break; | |
} | |
case CONSTANT_Integer: | |
case CONSTANT_Float: | |
int value = reader.readInt(); | |
this.constant_pool.put(i, new NumberPoolItem(i, value, type)); | |
break; | |
case CONSTANT_Long: | |
case CONSTANT_Double: | |
int low = reader.readInt(); | |
int high = reader.readInt(); | |
this.constant_pool.put(i, new PrecisionPoolItem(i, low, high, type)); | |
break; | |
case CONSTANT_NameAndType: { | |
short name_index = reader.readShort(); | |
short descriptor_index = reader.readShort(); | |
this.constant_pool.put(i, new NamedIndexPoolItem(i, name_index, descriptor_index, type)); | |
break; | |
} | |
case CONSTANT_Utf8: { | |
short length = reader.readShort(); | |
this.constant_pool.put(i, new Utf8PoolItem(i, type, new String(reader.readByteArr(length)))); | |
break; | |
} | |
case CONSTANT_MethodHandle: { | |
byte reference_kind = reader.readByte(); | |
short reference_index = reader.readByte(); | |
this.constant_pool.put(i, new MethodHandlePoolItem(i, reference_kind, reference_index, type)); | |
break; | |
} | |
case CONSTANT_MethodType: { | |
short descriptor_index = reader.readByte(); | |
this.constant_pool.put(i, new IndexPoolItem(i, descriptor_index, type)); | |
break; | |
} | |
case CONSTANT_InvokeDynamic: { | |
short bootstrap_method_attr_index = reader.readShort(); | |
short name_and_type_index = reader.readShort(); | |
this.constant_pool.put(i, new NamedIndexPoolItem(i, bootstrap_method_attr_index, name_and_type_index, type)); | |
break; | |
} | |
default: | |
throw new IllegalArgumentException("Invalid pool "); | |
} | |
} | |
this.access_flags = reader.readShort(); | |
this.this_class = reader.readShort(); | |
this.super_class = reader.readShort(); | |
this.interface_count = reader.readShort(); | |
for (int i = 0; i < this.interface_count; i++) { | |
short index = reader.readShort(); | |
IndexPoolItem e = (IndexPoolItem) this.constant_pool.get((int) index); | |
interfaces.add(getNamedItemFromPool(e.getIndex())); | |
} | |
decodeFields(); | |
decodeMethods(); | |
this.attributes_count = reader.readShort(); | |
for (int i = 0; i < this.attributes_count; i++) { | |
//TODO: Decode attributes | |
} | |
} | |
private void decodeMethods() { | |
this.methods_count = reader.readShort(); | |
decodeMemberRef(reader, methods_count, methods); | |
} | |
private void decodeFields() { | |
this.fields_count = reader.readShort(); | |
decodeMemberRef(reader, fields_count, fields); | |
} | |
private static void decodeMemberRef(BigEndianReader reader, short fieldsCount, List<ClassMemberRef> fields) { | |
for (int i = 0; i < fieldsCount; i++) { | |
short access_flags = reader.readShort(); | |
short name_index = reader.readShort(); | |
short descriptor_index = reader.readShort(); | |
ClassMemberRef ref = new ClassMemberRef(access_flags, name_index, descriptor_index); | |
short attributes_count = reader.readShort(); | |
ref.setNumberOfAttributes(attributes_count); | |
for (int j = 0; j < attributes_count; j++) { | |
short attribute_name_index = reader.readShort(); | |
int attribute_length = reader.readInt(); | |
for (int k = 0; k < attribute_length; k++) { | |
reader.readByte(); | |
} | |
} | |
fields.add(ref); | |
} | |
} | |
public void dump() { | |
log("Magic: %05x", this.magic); | |
log("Minor version: %05x %d", this.minor_version, this.minor_version); | |
log("Major version: %05x %d", this.major_version, this.major_version); | |
log("Constant pool size: %05x %d", this.constant_pool_count, this.constant_pool_count); | |
log("Access flags: %05x", this.access_flags); | |
log("class %s extends %s", getClassName().toLowerCase(), getSuperClassName().toLowerCase()); | |
log("Interface count: %d and are the following:", this.interface_count); | |
dumpInterfaces(); | |
log("This class has %d fields", this.fields_count); | |
dumpFields(); | |
log("This class has %d methods", this.methods_count); | |
dumpMethods(); | |
log("This class has %d attributes", this.attributes_count); | |
} | |
private void dumpMethods() { | |
for (ClassMemberRef f : this.methods) { | |
String descriptor = getNamedItemFromPool(f.getDescriptor_index()); | |
log("Found method %s %s isPublic: %s isStatic: %s , Attributes: %d", descriptor, | |
getNamedItemFromPool(f.getNameIndex()), | |
f.isPublic(), | |
f.isStatic(), | |
f.getNumberOfAttributes()); | |
} | |
} | |
private void dumpFields() { | |
for (ClassMemberRef f : this.fields) { | |
String descriptor = getNamedItemFromPool(f.getDescriptor_index()); | |
log("Found field %s %s isPublic: %s isStatic: %s, Attributes: %d", descriptor, | |
getNamedItemFromPool(f.getNameIndex()), | |
f.isPublic(), | |
f.isStatic(), | |
f.getNumberOfAttributes() | |
); | |
} | |
} | |
private void dumpInterfaces() { | |
for (String name : this.interfaces) { | |
log("Interface: %s", name); | |
} | |
} | |
private String getClassName() { | |
return getNamedItemFromPoolByIndex(this.this_class); | |
} | |
private String getSuperClassName() { | |
return getNamedItemFromPoolByIndex(this.super_class); | |
} | |
private String getNamedItemFromPoolByIndex(int index) { | |
IndexPoolItem e = (IndexPoolItem) this.constant_pool.get(index); | |
return getNamedItemFromPool(e.getIndex()); | |
} | |
public String getNamedItemFromPool(int index) { | |
for (PoolItem nestedItem : this.constant_pool.values()) { | |
if (nestedItem.getType().equals(ConstantPoolType.CONSTANT_Utf8)) { | |
if (nestedItem.getSelfIndex() == index) { | |
return ((Utf8PoolItem) nestedItem).getText(); | |
} | |
} | |
} | |
return INVALID_UTF_8_ITEM_IN_POOL; | |
} | |
public void log(String m, Object... args) { | |
System.out.println(String.format(m, args)); | |
} | |
} | |
public static void main(String args[]) throws URISyntaxException, IOException { | |
URL url = Main.class.getClassLoader().getResource("net/classloader/Foo.class"); | |
byte[] content = Files.readAllBytes(Paths.get(url.toURI())); | |
ClassStructure clazz = new ClassStructure(new BigEndianByteArrayReader(content)); | |
clazz.parse(); | |
clazz.dump(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment