/* gnu.classpath.tools.GjDump Copyright (C) 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Classpath; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package gnu.classpath.tools; import gnu.bytecode.*; import java.io.*; import java.util.*; /** * Disassembles the contents of a Java class file into a format suitable for an * as-yet unwritten assembler. * * @author Elliott Hughes */ public class GjDump { private static String classPath = System.getProperty("java.class.path"); private static String bootClassPath = System.getProperty("sun.boot.class.path"); private static final String HEX_CHARS = "0123456789abcdef"; private static final int ACC_PUBLIC = 0x1; private static final int ACC_PRIVATE = 0x2; private static final int ACC_PROTECTED = 0x4; private static final int ACC_STATIC = 0x8; private static final int ACC_FINAL = 0x10; private static final int ACC_SUPER = 0x20; // top-level classes private static final int ACC_SYNCHRONIZED = 0x20; // methods private static final int ACC_VOLATILE = 0x40; // fields private static final int ACC_BRIDGE = 0x40; // methods private static final int ACC_TRANSIENT = 0x80; // fields private static final int ACC_VARARGS = 0x80; // methods private static final int ACC_NATIVE = 0x100; private static final int ACC_INTERFACE = 0x200; private static final int ACC_ABSTRACT = 0x400; private static final int ACC_STRICT = 0x800; private static final int ACC_SYNTHETIC = 0x1000; private static final int ACC_ANNOTATION = 0x2000; private static final int ACC_ENUM = 0x4000; private String classname; private ClassType classType; private PrintStream out; public GjDump(String classname) { this.classname = classname; } public void dump(PrintStream out) { // FIXME: use an output stream that escapes non-ASCII to \\u sequences. this.out = out; readClass(); dumpClass(); } private void readClass() { // FIXME: remove this dependency; move into gnu.classpath.tools.Util? InputStream is = Javaph.getInputStream(classname); if (is == null) { die("class not found"); } try { classType = ClassFileInput.readClassType(is); } catch (Exception ex) { ex.printStackTrace(); die("couldn't read class (" + ex.getMessage() + ")"); } } private void dumpClass() { dumpClassHeader(); dumpClassAttributes(); out.println(); dumpFields(); dumpMethods(); // FIXME: dump inner classes. } private void dumpClassAttributes() { dumpAttributes(classType.getAttributes()); } private static String join(ArrayList list) { StringBuffer result = new StringBuffer(); for (int i = 0; i < list.size(); ++i) { if (i != 0) { result.append(" "); } result.append(list.get(i)); } return result.toString(); } private String decodeClassModifiers(int flags) { ArrayList modifiers = new ArrayList(); if ((flags & ACC_PUBLIC) != 0) modifiers.add("public"); if ((flags & ACC_FINAL) != 0) modifiers.add("final"); if ((flags & ACC_SUPER) != 0) modifiers.add("super"); if ((flags & ACC_INTERFACE) != 0) modifiers.add("interface"); if ((flags & ACC_ABSTRACT) != 0) modifiers.add("abstract"); if ((flags & ACC_SYNTHETIC) != 0) modifiers.add("synthetic"); if ((flags & ACC_ANNOTATION) != 0) modifiers.add("annotation"); if ((flags & ACC_ENUM) != 0) modifiers.add("enum"); int unknown = flags & ~(ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM); if (unknown != 0) modifiers.add("0x" + Integer.toHexString(unknown)); return join(modifiers); } private String decodeFieldModifiers(int flags) { ArrayList modifiers = new ArrayList(); if ((flags & ACC_PUBLIC) != 0) modifiers.add("public"); if ((flags & ACC_PRIVATE) != 0) modifiers.add("private"); if ((flags & ACC_PROTECTED) != 0) modifiers.add("protected"); if ((flags & ACC_STATIC) != 0) modifiers.add("static"); if ((flags & ACC_FINAL) != 0) modifiers.add("final"); if ((flags & ACC_VOLATILE) != 0) modifiers.add("volatile"); if ((flags & ACC_TRANSIENT) != 0) modifiers.add("transient"); if ((flags & ACC_SYNTHETIC) != 0) modifiers.add("synthetic"); if ((flags & ACC_ENUM) != 0) modifiers.add("enum"); int unknown = flags & ~(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM); if (unknown != 0) modifiers.add("0x" + Integer.toHexString(unknown)); return join(modifiers); } private String decodeMethodModifiers(int flags) { ArrayList modifiers = new ArrayList(); if ((flags & ACC_PUBLIC) != 0) modifiers.add("public"); if ((flags & ACC_PRIVATE) != 0) modifiers.add("private"); if ((flags & ACC_PROTECTED) != 0) modifiers.add("protected"); if ((flags & ACC_STATIC) != 0) modifiers.add("static"); if ((flags & ACC_FINAL) != 0) modifiers.add("final"); if ((flags & ACC_SYNCHRONIZED) != 0) modifiers.add("synchronized"); if ((flags & ACC_BRIDGE) != 0) modifiers.add("bridge"); if ((flags & ACC_VARARGS) != 0) modifiers.add("varargs"); if ((flags & ACC_NATIVE) != 0) modifiers.add("native"); if ((flags & ACC_ABSTRACT) != 0) modifiers.add("abstract"); if ((flags & ACC_STRICT) != 0) modifiers.add("strict"); if ((flags & ACC_SYNTHETIC) != 0) modifiers.add("synthetic"); int unknown = flags & ~(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC); if (unknown != 0) modifiers.add("0x" + Integer.toHexString(unknown)); return join(modifiers); } private void dumpClassHeader() { SourceFileAttr sourceFile = (SourceFileAttr) Attribute.get(classType, "SourceFile"); if (sourceFile != null) { out.println(".source \"" + sourceFile.getSourceFile() + "\""); } out.println(".type " + classType.getName()); out.println(".modifiers " + decodeClassModifiers(classType.getModifiers())); ClassType superClass = classType.getSuperclass(); if (superClass != null) { out.println(".extends " + superClass.getName()); } else { out.println("; no superclass"); } dumpImplementedInterfaces(); } private void dumpImplementedInterfaces() { ClassType[] interfaces = classType.getInterfaces(); if (interfaces != null) { for (int i = 0; i < interfaces.length; ++i) { out.println(".implements " + interfaces[i].getName()); } } } private void dumpMethods() { for (Method m = classType.getMethods(); m != null; m = m.getNext()) { dumpMethod(m); out.println(); } } private void dumpFields() { for (Field f = classType.getFields(); f != null; f = f.getNext()) { dumpField(f); out.println(); } } private void dumpAttributes(Attribute as) { for (Attribute a = as; a != null; a = a.getNext()) { dumpAttribute(a); } } private static void dumpEscapedByte(PrintStream out, byte b) { out.print("\\x"); out.print(HEX_CHARS.charAt((b >> 4) & 0xf)); out.print(HEX_CHARS.charAt(b & 0xf)); } private void dumpAttribute(Attribute a) { String name = a.getName(); if (name == "Code") { return; } byte[] bytes = byteArrayForAttribute(a); out.print(".attribute " + name + " \""); for (int i = 0; i < bytes.length; ++i) { dumpEscapedByte(out, bytes[i]); } out.println("\" ; length=" + a.getLength()); } private byte[] byteArrayForAttribute(Attribute a) { try { ByteArrayOutputStream stream = new ByteArrayOutputStream(); a.write(new DataOutputStream(stream)); byte[] bytes = stream.toByteArray(); return bytes; } catch (IOException ex) { out.println("; (couldn't get bytes for attribute)"); return new byte[0]; } } private void dumpMethod(Method method) { out.println(".method " + method.getName()); out.println(".modifiers " + decodeMethodModifiers(method.getModifiers())); out.println(".signature " + method.getSignature()); ClassType[] exceptions = method.getExceptions(); if (exceptions != null) { for (int i = 0; i < exceptions.length; ++i) { out.println(".throws " + exceptions[i].getName()); } } dumpCode(method); } private void dumpCode(Method method) { CodeAttr code = method.getCode(); if (code == null) { out.println("; no code"); return; } out.println(".stack " + code.getMaxStack()); out.println(".locals " + code.getMaxLocals()); dumpDisassembly(code); dumpExceptionHandlers(code); dumpAttributes(method.getAttributes()); } private void dumpDisassembly(CodeAttr code) { StringWriter writer = new StringWriter(); code.disAssemble(new ClassTypeWriter(classType, new PrintWriter(writer), 0), 0, code.getCodeLength()); String disassembly = writer.toString(); // Insert slot comments. LocalVarsAttr localVariables = (LocalVarsAttr) Attribute.get(code, "LocalVariableTable"); if (localVariables != null) { VarEnumerator e = localVariables.allVars(); for (Variable v = e.nextVar(); v != null; v = e.nextVar()) { out.println(".local " + v.getName() + " " + v.getOffset() + " " + v.getStartPC() + "-" + v.getEndPC()); // FIXME: we can't assume that the local's scope is the entire method. disassembly = disassembly.replaceAll("((?:load|store)[ _]" + v.getOffset() + ")(\n|$)", "$1 ; " + v.getName() + "$2"); } } // Insert .line directives. LineNumbersAttr lineNumberTable = (LineNumbersAttr) Attribute.get(code, "LineNumberTable"); if (lineNumberTable != null) { short[] lineNumbers = lineNumberTable.getLineNumberTable(); for (int i = 0; i < lineNumberTable.getLineCount(); ++i) { int bci = (lineNumbers[2 * i] & 0xffff); int line = (lineNumbers[2 * i + 1] & 0xffff); String labelPattern = "(^|\n)(\\s*" + bci + ":)"; String labelDirective = "$1.line " + line + "\n$2"; disassembly = disassembly.replaceFirst(labelPattern, labelDirective); } } out.print(disassembly); dumpAttributes(code.getAttributes()); } private void dumpExceptionHandlers(CodeAttr code) { // FIXME: gnu.bytecode should expose this. byte[] bytes = byteArrayForAttribute(code); int index = 8 + code.getCodeLength(); int exceptionTableLength = readU2(bytes, index); index += 2; for (int i = 0; i < exceptionTableLength; ++i) { int from_bci = readU2(bytes, index); index += 2; int to_bci = readU2(bytes, index); index +=2; int handler_bci = readU2(bytes, index); index +=2; int catch_type = readU2(bytes, index); index += 2; String typeName = ((CpoolClass) classType.getConstant(catch_type)).getStringName(); out.println(".catch " + typeName + " " + from_bci + "-" + to_bci + " " + handler_bci); } } private static int readU2(byte[] bytes, int offset) { return (bytes[offset] << 8) | (bytes[offset + 1]); } private void dumpField(Field field) { out.println(".field " + field.getType().getName() + " " + field.getSourceName()); out.println(".modifiers " + decodeFieldModifiers(field.getModifiers())); out.println("; signature " + field.getSignature()); dumpAttributes(field.getAttributes()); } private void die(String reason) { System.err.println(classname + ": error: " + reason); System.exit(1); } public static void main(String[] args) { for (int i = 0; i < args.length; ++i) { String classname = args[i]; GjDump dumper = new GjDump(classname); dumper.dump(System.out); } } }