| /* Copyright 2006 aQute SARL |
| * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */ |
| package aQute.libg.classdump; |
| |
| import java.io.*; |
| import java.lang.reflect.*; |
| |
| public class ClassDumper { |
| /** |
| * <pre> |
| * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its |
| * package. |
| * ACC_FINAL 0x0010 Declared final; no subclasses allowed. |
| * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the |
| * invokespecial instruction. |
| * ACC_INTERFACE 0x0200 Is an interface, not a |
| * class. |
| * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated. |
| * </pre> |
| * |
| * @param mod |
| */ |
| final static int ACC_PUBLIC = 0x0001; // Declared public; may be accessed |
| // from outside its package. |
| final static int ACC_FINAL = 0x0010; // Declared final; no subclasses |
| // allowed. |
| final static int ACC_SUPER = 0x0020; // Treat superclass methods |
| // specially when invoked by the |
| // invokespecial instruction. |
| final static int ACC_INTERFACE = 0x0200; // Is an interface, not a classs |
| final static int ACC_ABSTRACT = 0x0400; // Declared abstract; may not be |
| // instantiated. |
| |
| final static class Assoc { |
| Assoc(byte tag, int a, int b) { |
| this.tag = tag; |
| this.a = a; |
| this.b = b; |
| } |
| |
| byte tag; |
| int a; |
| int b; |
| |
| } |
| |
| final String path; |
| final static String NUM_COLUMN = "%-30s %d\n"; |
| final static String HEX_COLUMN = "%-30s %x\n"; |
| final static String STR_COLUMN = "%-30s %s\n"; |
| |
| PrintStream ps = System.err; |
| Object[] pool; |
| InputStream in; |
| |
| public ClassDumper(String path) throws Exception { |
| this(path, new FileInputStream(new File(path))); |
| } |
| |
| public ClassDumper(String path, InputStream in) throws IOException { |
| this.path = path; |
| this.in = in; |
| } |
| |
| public void dump(PrintStream ps) throws Exception { |
| if (ps != null) |
| this.ps = ps; |
| DataInputStream din = new DataInputStream(in); |
| parseClassFile(din); |
| din.close(); |
| } |
| |
| void parseClassFile(DataInputStream in) throws IOException { |
| int magic = in.readInt(); |
| if (magic != 0xCAFEBABE) |
| throw new IOException("Not a valid class file (no CAFEBABE header)"); |
| |
| ps.printf(HEX_COLUMN, "magic", magic); |
| int minor = in.readUnsignedShort(); // minor version |
| int major = in.readUnsignedShort(); // major version |
| ps.printf(STR_COLUMN, "version", "" + major + "." + minor); |
| int pool_size = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, "pool size", pool_size); |
| pool = new Object[pool_size]; |
| |
| process: for (int poolIndex = 1; poolIndex < pool_size; poolIndex++) { |
| byte tag = in.readByte(); |
| |
| switch (tag) { |
| case 0: |
| ps.printf("%30d tag (0)\n", poolIndex); |
| break process; |
| |
| case 1: |
| String name = in.readUTF(); |
| pool[poolIndex] = name; |
| ps.printf("%30d tag(1) utf8 '%s'\n", poolIndex, name); |
| break; |
| |
| case 2: |
| throw new IOException("Invalid tag " + tag); |
| |
| case 3: |
| int i = in.readInt(); |
| pool[poolIndex] = Integer.valueOf(i); |
| ps.printf("%30d tag(3) int %s\n", poolIndex, i); |
| break; |
| |
| case 4: |
| float f = in.readFloat(); |
| pool[poolIndex] = new Float(f); |
| ps.printf("%30d tag(4) float %s\n", poolIndex, f); |
| break; |
| |
| // For some insane optimization reason are |
| // the long and the double two entries in the |
| // constant pool. See 4.4.5 |
| case 5: |
| long l = in.readLong(); |
| pool[poolIndex] = Long.valueOf(l); |
| ps.printf("%30d tag(5) long %s\n", poolIndex, l); |
| poolIndex++; |
| break; |
| |
| case 6: |
| double d = in.readDouble(); |
| pool[poolIndex] = new Double(d); |
| ps.printf("%30d tag(6) double %s\n", poolIndex, d); |
| poolIndex++; |
| break; |
| |
| case 7: |
| int class_index = in.readUnsignedShort(); |
| pool[poolIndex] = Integer.valueOf(class_index); |
| ps.printf("%30d tag(7) constant classs %d\n", poolIndex, |
| class_index); |
| break; |
| |
| case 8: |
| int string_index = in.readUnsignedShort(); |
| pool[poolIndex] = Integer.valueOf(string_index); |
| ps.printf("%30d tag(8) constant string %d\n", poolIndex, |
| string_index); |
| break; |
| |
| case 9: // Field ref |
| class_index = in.readUnsignedShort(); |
| int name_and_type_index = in.readUnsignedShort(); |
| pool[poolIndex] = new Assoc((byte) 9, class_index, |
| name_and_type_index); |
| ps.printf("%30d tag(9) field ref %d/%d\n", poolIndex, |
| class_index, name_and_type_index); |
| break; |
| |
| case 10: // Method ref |
| class_index = in.readUnsignedShort(); |
| name_and_type_index = in.readUnsignedShort(); |
| pool[poolIndex] = new Assoc((byte) 10, class_index, |
| name_and_type_index); |
| ps.printf("%30d tag(10) method ref %d/%d\n", poolIndex, |
| class_index, name_and_type_index); |
| break; |
| |
| case 11: // Interface and Method ref |
| class_index = in.readUnsignedShort(); |
| name_and_type_index = in.readUnsignedShort(); |
| pool[poolIndex] = new Assoc((byte) 11, class_index, |
| name_and_type_index); |
| ps.printf("%30d tag(11) interface and method ref %d/%d\n", |
| poolIndex, class_index, name_and_type_index); |
| break; |
| |
| // Name and Type |
| case 12: |
| int name_index = in.readUnsignedShort(); |
| int descriptor_index = in.readUnsignedShort(); |
| pool[poolIndex] = new Assoc(tag, name_index, descriptor_index); |
| ps.printf("%30d tag(12) name and type %d/%d\n", poolIndex, |
| name_index, descriptor_index); |
| break; |
| |
| default: |
| throw new IllegalArgumentException("Unknown tag: " + tag); |
| } |
| } |
| |
| int access = in.readUnsignedShort(); // access |
| printAccess(access); |
| int this_class = in.readUnsignedShort(); |
| int super_class = in.readUnsignedShort(); |
| ps.printf("%-30s %x %s(#%d)\n", "this_class", access, pool[this_class], |
| this_class); |
| ps.printf("%-30s %s(#%d)\n", "super_class", pool[super_class], |
| super_class); |
| |
| int interfaces_count = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, "interface count", interfaces_count); |
| for (int i = 0; i < interfaces_count; i++) { |
| int interface_index = in.readUnsignedShort(); |
| ps.printf("%-30s interface %s(#%d)", "interface count", |
| pool[interface_index], interfaces_count); |
| } |
| |
| int field_count = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, "field count", field_count); |
| for (int i = 0; i < field_count; i++) { |
| access = in.readUnsignedShort(); // access |
| printAccess(access); |
| int name_index = in.readUnsignedShort(); |
| int descriptor_index = in.readUnsignedShort(); |
| ps.printf("%-30s %x %s(#%d) %s(#%d)\n", "field def", access, |
| pool[name_index], name_index, pool[descriptor_index], |
| descriptor_index); |
| doAttributes(in, " "); |
| } |
| |
| int method_count = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, "method count", method_count); |
| for (int i = 0; i < method_count; i++) { |
| int access_flags = in.readUnsignedShort(); |
| printAccess(access_flags); |
| int name_index = in.readUnsignedShort(); |
| int descriptor_index = in.readUnsignedShort(); |
| ps.printf("%-30s %x %s(#%d) %s(#%d)\n", "method def", access_flags, |
| pool[name_index], name_index, pool[descriptor_index], |
| descriptor_index); |
| doAttributes(in, " "); |
| } |
| |
| doAttributes(in, ""); |
| if (in.read() >= 0) |
| ps.printf("Extra bytes follow ..."); |
| } |
| |
| /** |
| * Called for each attribute in the class, field, or method. |
| * |
| * @param in |
| * The stream |
| * @throws IOException |
| */ |
| private void doAttributes(DataInputStream in, String indent) |
| throws IOException { |
| int attribute_count = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "attribute count", attribute_count); |
| for (int j = 0; j < attribute_count; j++) { |
| doAttribute(in, indent + j + ": "); |
| } |
| } |
| |
| /** |
| * Process a single attribute, if not recognized, skip it. |
| * |
| * @param in |
| * the data stream |
| * @throws IOException |
| */ |
| private void doAttribute(DataInputStream in, String indent) |
| throws IOException { |
| int attribute_name_index = in.readUnsignedShort(); |
| long attribute_length = in.readInt(); |
| attribute_length &= 0xFFFF; |
| String attributeName = (String) pool[attribute_name_index]; |
| ps.printf("%-30s %s(#%d)\n", indent + "attribute", attributeName, |
| attribute_name_index); |
| if ("RuntimeVisibleAnnotations".equals(attributeName)) |
| doAnnotations(in, indent); |
| else if ("SourceFile".equals(attributeName)) |
| doSourceFile(in, indent); |
| else if ("Code".equals(attributeName)) |
| doCode(in, indent); |
| else if ("LineNumberTable".equals(attributeName)) |
| doLineNumberTable(in, indent); |
| else if ("LocalVariableTable".equals(attributeName)) |
| doLocalVariableTable(in, indent); |
| else if ("InnerClasses".equals(attributeName)) |
| doInnerClasses(in, indent); |
| else if ("Exceptions".equals(attributeName)) |
| doExceptions(in, indent); |
| else if ("EnclosingMethod".equals(attributeName)) |
| doEnclosingMethod(in, indent); |
| else if ("Signature".equals(attributeName)) |
| doSignature(in, indent); |
| else if ("Synthetic".equals(attributeName)) |
| ; // Is empty! |
| else if ("Deprecated".equals(attributeName)) |
| ; // Is Empty |
| else { |
| ps.printf("%-30s %d\n", indent + "Unknown attribute, skipping", |
| attribute_length); |
| if (attribute_length > 0x7FFFFFFF) { |
| throw new IllegalArgumentException("Attribute > 2Gb"); |
| } |
| byte buffer[] = new byte[(int) attribute_length]; |
| in.readFully(buffer); |
| printHex(buffer); |
| } |
| } |
| |
| /** |
| * <pre> |
| * Signature_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 signature_index; |
| * } |
| * </pre> |
| * |
| * @param in |
| * @param indent |
| */ |
| void doSignature(DataInputStream in, String indent) throws IOException { |
| int signature_index = in.readUnsignedShort(); |
| ps.printf("%-30s %s(#%d)\n", indent + "signature", |
| pool[signature_index], signature_index); |
| } |
| |
| /** |
| * <pre> |
| * EnclosingMethod_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 class_index |
| * u2 method_index; |
| * } |
| * |
| * </pre> |
| */ |
| void doEnclosingMethod(DataInputStream in, String indent) |
| throws IOException { |
| int class_index = in.readUnsignedShort(); |
| int method_index = in.readUnsignedShort(); |
| ps.printf("%-30s %s(#%d/c) %s\n", // |
| indent + "enclosing method", // |
| pool[((Integer) pool[class_index]).intValue()], // |
| class_index, // |
| (method_index == 0 ? "<>" : pool[method_index])); |
| } |
| |
| /** |
| * <pre> |
| * Exceptions_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 number_of_exceptions; |
| * u2 exception_index_table[number_of_exceptions]; |
| * } |
| * </pre> |
| * |
| * @param in |
| * @param indent |
| */ |
| private void doExceptions(DataInputStream in, String indent) |
| throws IOException { |
| int number_of_exceptions = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "number of exceptions", |
| number_of_exceptions); |
| StringBuilder sb = new StringBuilder(); |
| String del = ""; |
| for (int i = 0; i < number_of_exceptions; i++) { |
| int exception_index_table = in.readUnsignedShort(); |
| sb.append(del); |
| sb.append(pool[((Integer) pool[exception_index_table])]); |
| sb.append("(#"); |
| sb.append(exception_index_table); |
| sb.append("/c)"); |
| del = ", "; |
| } |
| ps.printf("%-30s %d: %s\n", indent + "exceptions", |
| number_of_exceptions, sb); |
| } |
| |
| /** |
| * <pre> |
| * Code_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 max_stack; |
| * u2 max_locals; |
| * u4 code_length; |
| * u1 code[code_length]; |
| * u2 exception_table_length; |
| * { u2 start_pc; |
| * u2 end_pc; |
| * u2 handler_pc; |
| * u2 catch_type; |
| * } exception_table[exception_table_length]; |
| * u2 attributes_count; |
| * attribute_info attributes[attributes_count]; |
| * } |
| * </pre> |
| * |
| * @param in |
| * @param pool |
| * @throws IOException |
| */ |
| private void doCode(DataInputStream in, String indent) throws IOException { |
| int max_stack = in.readUnsignedShort(); |
| int max_locals = in.readUnsignedShort(); |
| int code_length = in.readInt(); |
| ps.printf(NUM_COLUMN, indent + "max_stack", max_stack); |
| ps.printf(NUM_COLUMN, indent + "max_locals", max_locals); |
| ps.printf(NUM_COLUMN, indent + "code_length", code_length); |
| byte code[] = new byte[code_length]; |
| in.readFully(code); |
| printHex(code); |
| int exception_table_length = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "exception_table_length", |
| exception_table_length); |
| |
| for (int i = 0; i < exception_table_length; i++) { |
| int start_pc = in.readUnsignedShort(); |
| int end_pc = in.readUnsignedShort(); |
| int handler_pc = in.readUnsignedShort(); |
| int catch_type = in.readUnsignedShort(); |
| ps.printf("%-30s %d/%d/%d/%d\n", indent + "exception_table", |
| start_pc, end_pc, handler_pc, catch_type); |
| } |
| doAttributes(in, indent + " "); |
| } |
| |
| /** |
| * We must find Class.forName references ... |
| * |
| * @param code |
| */ |
| protected void printHex(byte[] code) { |
| int index = 0; |
| while (index < code.length) { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < 16 && index < code.length; i++) { |
| String s = Integer.toHexString((0xFF & code[index++])) |
| .toUpperCase(); |
| if (s.length() == 1) |
| sb.append("0"); |
| sb.append(s); |
| sb.append(" "); |
| } |
| ps.printf(STR_COLUMN, "", sb.toString()); |
| } |
| } |
| |
| private void doSourceFile(DataInputStream in, String indent) |
| throws IOException { |
| int sourcefile_index = in.readUnsignedShort(); |
| ps.printf("%-30s %s(#%d)\n", indent + "Source file", |
| pool[sourcefile_index], sourcefile_index); |
| } |
| |
| private void doAnnotations(DataInputStream in, String indent) |
| throws IOException { |
| int num_annotations = in.readUnsignedShort(); // # of annotations |
| ps |
| .printf(NUM_COLUMN, indent + "Number of annotations", |
| num_annotations); |
| for (int a = 0; a < num_annotations; a++) { |
| doAnnotation(in, indent); |
| } |
| } |
| |
| private void doAnnotation(DataInputStream in, String indent) |
| throws IOException { |
| int type_index = in.readUnsignedShort(); |
| ps.printf("%-30s %s(#%d)", indent + "type", pool[type_index], |
| type_index); |
| int num_element_value_pairs = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "num_element_value_pairs", |
| num_element_value_pairs); |
| for (int v = 0; v < num_element_value_pairs; v++) { |
| int element_name_index = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "element_name_index", |
| element_name_index); |
| doElementValue(in, indent); |
| } |
| } |
| |
| private void doElementValue(DataInputStream in, String indent) |
| throws IOException { |
| int tag = in.readUnsignedByte(); |
| switch (tag) { |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'F': |
| case 'I': |
| case 'J': |
| case 'S': |
| case 'Z': |
| case 's': |
| int const_value_index = in.readUnsignedShort(); |
| ps.printf("%-30s %c %s(#%d)\n", indent + "element value", tag, |
| pool[const_value_index], const_value_index); |
| break; |
| |
| case 'e': |
| int type_name_index = in.readUnsignedShort(); |
| int const_name_index = in.readUnsignedShort(); |
| ps.printf("%-30s %c %s(#%d) %s(#%d)\n", indent + "type+const", tag, |
| pool[type_name_index], type_name_index, |
| pool[const_name_index], const_name_index); |
| break; |
| |
| case 'c': |
| int class_info_index = in.readUnsignedShort(); |
| ps.printf("%-30s %c %s(#%d)\n", indent + "element value", tag, |
| pool[class_info_index], class_info_index); |
| break; |
| |
| case '@': |
| ps.printf("%-30s %c\n", indent + "sub annotation", tag); |
| doAnnotation(in, indent); |
| break; |
| |
| case '[': |
| int num_values = in.readUnsignedShort(); |
| ps.printf("%-30s %c num_values=%d\n", indent + "sub element value", |
| tag, num_values); |
| for (int i = 0; i < num_values; i++) { |
| doElementValue(in, indent); |
| } |
| break; |
| |
| default: |
| throw new IllegalArgumentException( |
| "Invalid value for Annotation ElementValue tag " + tag); |
| } |
| } |
| |
| /** |
| * <pre> |
| * LineNumberTable_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 line_number_table_length; |
| * { u2 start_pc; |
| * u2 line_number; |
| * } line_number_table[line_number_table_length]; |
| * } |
| * |
| * </pre> |
| */ |
| void doLineNumberTable(DataInputStream in, String indent) |
| throws IOException { |
| int line_number_table_length = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "line number table length", |
| line_number_table_length); |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < line_number_table_length; i++) { |
| int start_pc = in.readUnsignedShort(); |
| int line_number = in.readUnsignedShort(); |
| sb.append(start_pc); |
| sb.append("/"); |
| sb.append(line_number); |
| sb.append(" "); |
| } |
| ps.printf("%-30s %d: %s\n", indent + "line number table", |
| line_number_table_length, sb); |
| } |
| |
| /** |
| * |
| * <pre> |
| * LocalVariableTable_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 local_variable_table_length; |
| * { u2 start_pc; |
| * u2 length; |
| * u2 name_index; |
| * u2 descriptor_index; |
| * u2 index; |
| * } local_variable_table[local_variable_table_length]; |
| * } |
| * </pre> |
| */ |
| |
| void doLocalVariableTable(DataInputStream in, String indent) |
| throws IOException { |
| int local_variable_table_length = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "local variable table length", |
| local_variable_table_length); |
| for (int i = 0; i < local_variable_table_length; i++) { |
| int start_pc = in.readUnsignedShort(); |
| int length = in.readUnsignedShort(); |
| int name_index = in.readUnsignedShort(); |
| int descriptor_index = in.readUnsignedShort(); |
| int index = in.readUnsignedShort(); |
| ps.printf("%-30s %d: %d/%d %s(#%d) %s(#%d)\n", indent, index, |
| start_pc, length, pool[name_index], name_index, |
| pool[descriptor_index], descriptor_index); |
| } |
| } |
| |
| /** |
| * <pre> |
| * InnerClasses_attribute { |
| * u2 attribute_name_index; |
| * u4 attribute_length; |
| * u2 number_of_classes; |
| * { u2 inner_class_info_index; |
| * u2 outer_class_info_index; |
| * u2 inner_name_index; |
| * u2 inner_class_access_flags; |
| * } classes[number_of_classes]; |
| * } |
| * </pre> |
| * |
| */ |
| void doInnerClasses(DataInputStream in, String indent) throws IOException { |
| int number_of_classes = in.readUnsignedShort(); |
| ps.printf(NUM_COLUMN, indent + "number of classes", number_of_classes); |
| for (int i = 0; i < number_of_classes; i++) { |
| int inner_class_info_index = in.readUnsignedShort(); |
| int outer_class_info_index = in.readUnsignedShort(); |
| int inner_name_index = in.readUnsignedShort(); |
| int inner_class_access_flags = in.readUnsignedShort(); |
| printAccess(inner_class_access_flags); |
| |
| String iname = "<>"; |
| String oname = iname; |
| |
| if (inner_class_info_index != 0) |
| iname = (String) pool[((Integer) pool[inner_class_info_index]) |
| .intValue()]; |
| if (outer_class_info_index != 0) |
| oname = (String) pool[((Integer) pool[outer_class_info_index]) |
| .intValue()]; |
| |
| ps.printf("%-30s %d: %x %s(#%d/c) %s(#%d/c) %s(#%d) \n", indent, i, |
| inner_class_access_flags, iname, inner_class_info_index, |
| oname, outer_class_info_index, pool[inner_name_index], |
| inner_name_index); |
| } |
| } |
| |
| |
| void printClassAccess(int mod) { |
| ps.printf("%-30s", "Class Access"); |
| if ((ACC_PUBLIC&mod)!= 0) |
| ps.print(" public"); |
| if ((ACC_FINAL&mod)!= 0) |
| ps.print(" final"); |
| if ((ACC_SUPER&mod)!= 0) |
| ps.print(" super"); |
| if ((ACC_INTERFACE&mod)!= 0) |
| ps.print(" interface"); |
| if ((ACC_ABSTRACT&mod)!= 0) |
| ps.print(" abstract"); |
| |
| ps.println(); |
| } |
| |
| void printAccess(int mod) { |
| ps.printf("%-30s", "Access"); |
| if (Modifier.isStatic(mod)) |
| ps.print(" static"); |
| if (Modifier.isAbstract(mod)) |
| ps.print(" abstract"); |
| if (Modifier.isPublic(mod)) |
| ps.print(" public"); |
| if (Modifier.isFinal(mod)) |
| ps.print(" final"); |
| if (Modifier.isInterface(mod)) |
| ps.print(" interface"); |
| if (Modifier.isNative(mod)) |
| ps.print(" native"); |
| if (Modifier.isPrivate(mod)) |
| ps.print(" private"); |
| if (Modifier.isProtected(mod)) |
| ps.print(" protected"); |
| if (Modifier.isStrict(mod)) |
| ps.print(" strict"); |
| if (Modifier.isSynchronized(mod)) |
| ps.print(" synchronized"); |
| if (Modifier.isTransient(mod)) |
| ps.print(" transient"); |
| if (Modifier.isVolatile(mod)) |
| ps.print(" volatile"); |
| |
| ps.println(); |
| } |
| |
| public static void main(String args[]) throws Exception { |
| if (args.length == 0) { |
| System.err.println("clsd <class file>+"); |
| } |
| for (int i = 0; i < args.length; i++) { |
| File f = new File(args[i]); |
| if (!f.isFile()) |
| System.err.println("File does not exist or is directory " + f); |
| else { |
| ClassDumper cd = new ClassDumper(args[i]); |
| cd.dump(null); |
| } |
| } |
| } |
| |
| } |