![]() |
|||
![]() ![]() |
![]() |
|
![]() |
The key method of this program is disassemble(). This is the method that actually reads the bytes. It does this by calling the 11 methods readMagic(), readMinorVersion(), readMajorVersion(), readConstantPool(), readAccessFlags(), readClass(), readSuperclass(), readInterfaces(), readFields(), readMethods(), and readAttributes() in that order. You have to call them in that order because, except for the first few parts, the parts dont start on any particular byte. For example, to find where the seventh part begins, you have to pick up where the sixth ended, and so on. Once all the pieces have been read, you write them back out with writeImports(), writeAccess(), writeClassName(), writeSuperclass(), writeInterfaces(), writeFields(), and writeMethods(). You must write these in the order in which they normally appear in a .java source code file, not in the order in which they were read in the byte code file. Indeed, some of these parts, like the import statements, are not specifically included in the compiled file but can be deduced from it. In the event that theres a problem with a .class file, a java.lang.ClassFormatError appears. Once this program encounters an invalid file, it prints out an error message and stops executing. It is rather unusual to catch an error rather than an Exception. You cant normally recover from a ClassFormatError because it means that one of the classes that the program needs will not be available. However, because youre not loading the class, but just parsing it, you have a little more leeway. This might get you into trouble if a ClassFormatError that you did not create yourself bubbles up during the file parsing. This is rather unlikely, but a somewhat more robust solution would provide a means to distinguish between the ClassFormatErrors that indicate a problem with the file being parsed and the ClassFormatErrors thrown by the Java VM when it fails to load a requested class. The following sections explain each part in detail and fill in the code needed to make these methods work. The next chapter, Chapter 5, expands the readMethods() method to provide a better analysis of the code inside method bodies. Magic numberAll Java .class files are supposed to begin with the four-byte magic number 0xCAFEBABE, that is -1,258,207,934 in decimal. If you dont see this at the beginning of the file, then you know that its not a valid Java byte code file, even if the file name ends in .class. In this event, the disassembler should throw a ClassFormatError and bail out. The easiest way to read the number is with the java.io.DataInputStream.readInt() method. Listing 4-7 shows the filled-out readMagic() method. Listing 4-7 A method that reads the magic number and verifies that it is 0xCAFEBABE int magic; void readMagic() throws IOException { magic = theInput.readInt(); if (magic != 0xCAFEBABE) { throw new ClassFormatError(Incorrect Magic Number: + magic); } } Magic numbers are pure byte code phenomena. They do not appear anywhere in the .java source code file. Therefore, theres no corresponding writeMagic() method. Minor versionBytes four and five of a .class file (the two bytes immediately following CAFEBABE) are the minor version of the compiler that produced this file. This is an unsigned 2-byte int (like a char) and can have a value between 0 and 65,535. This book documents minor version 3. The easiest way to read an unsigned 2-byte int is with java.io.DataInputStream.read UnsignedShort() method.
Listing 4-8 is the fleshed readminorVersion() method. Listing 4-8 The readMajorVersion() method void readMinorVersion() throws IOException { minor_version = theInput.readUnsignedShort(); if (minor_version != 3) { throw new ClassFormatError(Minor Version not 3); } } Like magic numbers, minor and major versions are part of only the byte code, not the source code. Therefore, this method checks only that the minor version is what its expected to be. It doesnt need to be saved because it doesnt have any effect on what comes after it. Major versionThe next two bytes are the major version of the compiler. Like the minor version, the major version is a 2-byte, unsigned integer. The major version you expect is 45. The parser is unlikely to be able to read anything different, so the program throws a ClassFormatException and bails out. Listing 4-9 fleshes out this method: Listing 4-9 The readMajorVersion() method void readMajorVersion() throws IOException { major_version = theInput.readUnsignedShort(); if (major_version != 45) { throw new ClassFormatError(Major Version not 45); } }
|
![]() |
|