Skip to content

Instantly share code, notes, and snippets.

@devdilson
Last active March 29, 2019 17:15
Show Gist options
  • Save devdilson/0dc68b170b18089043d5e2cce2fa6286 to your computer and use it in GitHub Desktop.
Save devdilson/0dc68b170b18089043d5e2cce2fa6286 to your computer and use it in GitHub Desktop.
Experiment with parsing .class file format and decompiling a java class structure
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