![]() |
|||
![]() ![]() |
![]() |
![]()
|
![]() |
The nameIndex() and CodeAttribute() methods are the same ones that you saw in the preceding chapter. The readCode() method is a big lookup table that returns the next opcode mnemonic and its arguments from the code array. Some instructions take one or more of their arguments from the bytes that follow them in the code array. Therefore, you cant just blindly convert each byte to a mnemonic. Some bytes must remain bytes. Opcodes 170, 171, and 196 (tableswitch, lookupswitch, and wide) can take a varying number of arguments, so extra logic is required to handle them. I describe this logic later in this chapter, when I discuss those instructions. Armed with this information, you should now have a better idea of whats going on inside the methods when you disassemble a program. For example, disassembling HelloWorld, you get the output shown in Listing 5-2. Listing 5-2 HelloWorld disassembled into byte code mnemonics import java.io.PrintStream; class HelloWorld extends java.lang.Object { public static void main() { 0 getstatic 7 3 ldc 1 5 invokevirtual 8 8 return } void <init>() { 0 aload_0 1 invokespecial 6 4 return } } /* 1: String 18 2: ClassInfo 19 3: ClassInfo 25 4: ClassInfo 26 5: ClassInfo 27 6: MethodRef ClassIndex: 4; NameAndTypeIndex: 9 7: FieldRef ClassIndex: 5; NameAndTypeIndex: 10 8: MethodRef ClassIndex: 3; NameAndTypeIndex: 11 9: NameAndType NameIndex: 14; DescriptorIndex: 12 10: NameAndType NameIndex: 29; DescriptorIndex: 22 11: NameAndType NameIndex: 30; DescriptorIndex: 13 12: UTF8 ()V 13: UTF8 (Ljava/lang/String;)V 14: UTF8 <init> 15: UTF8 Code 16: UTF8 ConstantValue 17: UTF8 Exceptions 18: UTF8 Hello World 19: UTF8 HelloWorld 20: UTF8 HelloWorld.java 21: UTF8 LineNumberTable 22: UTF8 Ljava/io/PrintStream; 23: UTF8 LocalVariables 24: UTF8 SourceFile 25: UTF8 java/io/PrintStream 26: UTF8 java/lang/Object 27: UTF8 java/lang/System 28: UTF8 main 29: UTF8 out 30: UTF8 print */ Listing 5-2 is the most intelligible disassembly yet. The rest of this chapter describes the various opcodes, so you can really understand whats going on inside the file. Stacks, Frames, and PoolsInstructions require data on which to operate. You can take this data from five places: the method stack, the heap, the constant pool, the local variable array, and the byte code itself. The method stack, the local variable array, and the byte code are specific to the method. The constant pool and the heap are shared by all other threads and methods executing in the same virtual machine. All of the operations in a method such as addition or subtraction take place on the stack. For example, to add two integers, the integers are first pushed onto the stack, then theyre popped off of the stack, and then their sum is pushed back onto the stack. The local variable array is a temporary holding area for local variables declared in the method and arguments passed to the method. You have to copy those local variables onto the stack before you can do anything with them. Instructions that access the heap get references to items in the heap from the stack. You learned about the constant pool in the last chapter. Operations that access the constant pool also use indices into the pool placed on the heap. When the Java virtual machine is running, each thread has a stack of frames. (This is related neither to HTML frames nor to the java.util.Stack class.) A frame holds the local variables and arguments for a method and a working area for the method called the operand stack. When a method is called, the virtual machine creates a new frame for the method with space for its local variables and its local stack. This frame is placed on top of the threads stack. When the method completes, this frame is popped from the threads stack, and the methods return value (if any) is pushed onto the top of the calling methods stack. Each method operates on the values contained in its local variable array and on its method stack. You can think of the local variable array as an array of 32-bit words, and the method stack as a stack of 32-bit words. Lets look at a simple example. Consider the following method, which adds two to four and returns the sum: public int six() { int a = 4; int b = 2; int c = a + b; return c; } Heres the same method as disassembled byte code: public int six() { 0 iconst_4 1 istore_1 2 iconst_2 3 istore_2 4 iload_1 5 iload_2 6 iadd 7 istore_3 8 iload_3 9 ireturn } Lets investigate this byte code instruction by instruction, to see what effect each one has on the array of local variables and the stack. You first need to look at the number of different locations referenced by load and store instructions to determine how many local variables are used. Instruction 0 pushes the constant 4 onto the stack. That doesnt affect the local variable array at all. Instruction 1 stores a value into position 1 in the local variable array. The local variable array therefore must have at least one entry. Instruction 2 pushes the constant value 2 onto the method stack. Again, this has no effect on the local variable array. Instruction 3 stores a variable into position 2 in the local variable array. Therefore, there are at least positions 1 and 2 in the local variable array, so that array has to be at least 2 entries long. Instructions 4 and 5 move values from the local variable array at positions 1 and 2 onto the stack. However, youve already seen variables 1 and 2, so thats nothing new. Instruction 6 adds the two variables onto the top of the stack and puts the result back on the stack. Again, the local variable array is not changed. Instructions 7 and 8 store something into the third position in the local variable array and then load it onto the stack. So there now have to be at least 3 entries in the local variable array. Finally, instruction 9 returns and completes the method.
|
![]() |
|