System and method for injecting hooks into Java classes to handle exception and finalization processing6662359Abstract The present invention is directed to a system, method and instructions for handling path flow exception and finalization processing in an object oriented programming language. Initially, each instrumentation method is checked for a code to indicated an exception being thrown. A hook is inserted before the exception code and identifies the method throwing the exception. Methods must also be checked for exception tables. When an exception table is found, then a hook is inserted at the entry point of each exception handler for every entry in the exception table. This hook identifies the method which catches the exception. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
major/minor class/method
22/01 TEST.method_X(I)
22/81 TEST.method_X_exit
22/02 TEST.method_Y( )
22/82 TEST.method_Y_exit
In the above excerpt from a possible HDF file, major code 22 and minor code 01 are associated with the trace hook placed at the entry of method_X in class TEST. The method signature (e.g., "(I)") is recorded to allow for clear discrimination between overloaded methods (i.e. class constructors are frequently overloaded, and the only way to discriminate between one constructor and the other is to examine the method signature). Note that an HDF file is not a requirement but serves as an optimization in implementing the trace hooks. The resulting trace stream consists of sequences of major/minor codes (with time stamps). A postprocessing phase takes the HDF file and merges it with a trace stream, resulting in a trace stream that identifies methods/classes by name. The following is an example of a trace stream that may be generated by merging trace data with the above HDF file:
major minor timestamp description
12 1 15:985860940 Dispatch thread: e18507a0
22 1 15:985633507 ENTRY: TEST.method_X (I)
12 1 15:985845671 Dispatch thread: e17d5bb0
12 1 15:985860940 Dispatch thread: e1807a0
22 81 15:985833507 EXIT: TEST.method_X_exit
22 2 15:985833507 ENTRY: TEST.method_Y ( )
22 82 15:985833507 EXIT: TEST.method Y exit
As shown in FIG. 5, a reference to the instrumentation class is added into the constant pool section of the class file (step 502). While there are more methods to hook (step 504), the following steps are performed. For each method, the entry point and one or more exit points are located (step 506). An entry hook and exit hooks are inserted for the major and minor codes (step 508). Selective instrumentation is possible if only some of the methods are to be instrumented. In the described embodiment, an inclusion/exclusion list is used to specify which methods are to be instrumented. However, any number of procedures may be used to determine whether a particular method is to be instrumented or to specify which methods are to be instrumented. On entry into a method, the following invocation is inserted: instrumentation Library.log(major_code,minor_code); Just prior to returning from the method, the following invocation is inserted: instrumentation Library.log(major_code,minor_code); There are often several exit points for a single method, and each exit point must be so instrumented. At post-processing time, the major codes and minor codes are resolved with specific method and class names from the HDF file. This allows collection to proceed without the overhead of recording large amounts of double-byte string data. Bytecode modification requires the insertion of Java bytecode instructions that affect the method invocations noted above. Insertion requires the identification of method bodies, interpretation of existing bytecodes, and location of entry and exit points. It is critical that the modifications be consistent with the criteria employed by the class file verifier. The class file verifier ensures that the bytecode, and the rest of the class file, is valid and can be executed by the Jvm. Thus, the next step is to modify the other components of the class file to ensure that they satisfy the constraints of the class file verifier (step 510). Further details regarding step 514 are described below with reference to FIG. 8. Next, an entry is added to the HDF (step 512). The entry includes the major and minor codes, and method signature as the description. The minor code is then incremented to ensure that it is unique for each method (step 514). When there are no more methods to hook, the HDF is closed and written to the file system (step 516). The modified class file is then created (step 518) and closed (step 522). The modified, or transformed, class file is then ready to be executed. In the above described process for adding performance instrumentation code at the entry and exit of every method contained in a class file, a postprocessor attributes the consumption of resources to the method in control at the time the resources were consumed. However, when the flow of control includes exception and finalization processing the postprocessing results are subject to incorrect results. Exception code within the bytecode of a method transfers control away from the method in which an error has occurred, similar to a method exit, conversely, catch code within the bytecode of a method receives control from the method having the exception. The catching method may also provide finalization processing which closes all open files and exits out of the class. However, without instrumenting the exception path between methods, a method entry would be written to a record for the method generating the exception without a corresponding record for the exit from the method because control was passed away from the method without writing a method exit record. Similarly, a method exit record would be written for the method catching the exception without a corresponding method entry because control was received due to the catch without writing an method entry record. All resources consumed by both methods would be attributed to the method throwing the exception because no record exists indicating a transfer of control from the method throwing the exception to the method receiving the exception. In the case where the exception results in finalization processing, all resources devoted to exiting out of the class will be attributed to the method throwing the exception. Therefore, in an effort to reduce postprocessing errors, class files must also be instrumented for exceptions and finalization processing. Exceptions and finalization are handled similarly in accordance with a preferred embodiment of the present invention and therefore they will be discussed together. FIG. 6 is a flowchart illustrating a high level method for instrumenting an exception in accordance with a preferred embodiment of the present invention. The process begins by instrumenting a method at the "athrow," or equivalent code, to indicate where an exception is thrown (step 602). Next, the point in the catching method must also be instrumented, where the exception is caught, thus the method is instrumented at the exception handler code (step 604). Importantly, the catching method may continue executing, in an attempt to "execute around" the exception, or may instead provide finalization processing for exiting out of the preloaded class. The high level process for instrumenting path flow for an exception is complete, however, it should be understood that instrumenting for finalization process is identical to the above-described process. With respect to FIG. 7, a flowchart depicts a process for injecting hooks in a class file for handling exception and finalization processing. The process described below is similar to the process for instrumenting a class at an entry and exit of a method, described above, therefore, only the differences in the processes will be discussed in detail. Initially, a reference to the instrumentation class is added into the constant pool section of the class file (step 702). A check is then made to determine if there are more selected methods that may have exceptions to be hooked (step 704). Methods which will throw an exception have an "athrow" instruction at the point in the bytecode where the exception is thrown (and control is passed away from that method). Therefore, each method in a selected class file is searched for an athrow instruction (step 706). An exception hook (or finalization hook) is inserted in the method's bytecode before the occurrence of the athrow instruction (step 708). The hook has an encoded ID for the method from which it is thrown. The encoded ID can be decoded as at postprocessing time to a method name, using the HDF file. Next, each method is searched for exception tables (step 710). Once found, each entry in the exception table is read. Entries in the exception may define two different types of processing code, exception processing and finalization processing. A hook is added at the "handler_pc" offset in the method for each entry in the exception table which defines exception processing code, while conversely, a hook is added at the finalization routine offset in the method for each entry in the table which defines finalization processing (step 712). A handler_pc offset is the point in the method where an exception handler resides for an entry in the exception table. The exception handler in a method is the catch clause for the exception identified by the entry in the exception table. The method which includes the catch clause will receive control from the method throwing the exception. Similarly, a finalization routine offset is the point in the method where finalization processing code resides for an entry in the exception table. The finalization routine is the catch clause for the exception which is identified by the entry in the exception table. Each instrumented method must have its catch clauses hooked in case the method is executed via an exception catch. Each hook has an encoded ID for the method where the exception is caught, whether the method performs exception processing or finalization processing. Again, the encoded ID can be decoded as at postprocessing time to a method name, using the HDF file. Selective instrumentation of exceptions is not possible without considering if the method itself has been instrumented because the exception control path will not be recorded. Each exception in an instrumented method and all catch clauses in an instrumented method must be instrumented in order to avoid un-matching method entries and exits. Modifications to the athrow and the exception handled bytecodes, for instrumenting an exception or finalization path, are identical to modifications to a method's bytecode for entries and exits. To indicated where an exception is thrown, the following invocation is inserted: instrumentation Library.log(major_code,minor_code); To indicated where the exception is being caught, the following invocation is inserted: instrumentation Library.log(major_code,minor_code); There may be several entries in an exception table and all catch points represented by entries in the method's exception table must be instrumented in order to assure that all exception paths to the method are recorded. At post-processing time, the major codes and minor codes are resolved with specific method and class names from the HDF file. Modifications of the bytecode must be consistent with the criteria employed by the class file verifier. The class file verifier ensures that the bytecode, and the rest of the class file, is valid and can be executed by the Jvm. Thus, other components of the class file must be modified to ensure that they satisfy the constraints of the class file verifier (step 714). Further details regarding step 714 are described below with reference to FIG. 8. Next, an entry is added to the HDF file (step 716). The entry includes the major and minor codes, and method signature as the description. The minor code is then incremented to ensure that it is unique for each method (step 718). When there are no more methods to hook, the HDF file is closed and written to the file system (step 720). The modified class file is then created (step 722) and closed (step 724). The modified, or transformed, class file is then ready to be executed. Step 510, in FIG. 5 (step 714 in FIG. 7) is depicted in greater detail in FIG. 8. As code is inserted in the existing stream for each method, verification is ensured by: 1. Creating constant pool entries for the instrumentation class and methods (steps 502 and 512 in FIG. 5). 2. Ensuring code that is moved is correctly relocated, and that all related references are adjusted for the relocation (step 510). As shown in FIG. 8, several steps must be taken to ensure that code which is moved due to either insertions or deletions is correctly relocated and related references are adjusted. First, the exception table is modified (step 802). In Java programs or applications, an exception handler may be set up to handle specific exceptions that occur at specified line numbers. Adding or deleting code may change the line numbers associated with a particular exception handler. Therefore, the exception table may need to be adjusted so that the correct exception handler is called for exceptions which occur at certain line numbers. If a line number table is present (step 804), it is modified (step 806). Similarly, if a local variable table is present (step 808), it is modified (step 810). For each relative jump address (step 812), it is first determined if the jump is a jump forward (step 814). If so, the next step is to determine if it is a jump around inserted code (step 816). If it is, the relative address is incremented by the insert length (step 818). If the jump is a jump backward (answer to step 814 is "no"), and the jump is around inserted code (step 820), then the relative address is decremented by the insert length (step 822). In the described embodiment, the instrumentation class is a separate and distinct Java class. Of course, different types of classes are used for different purposes. If the purpose of the code modifications is to change the functionality of the code, rather than to instrument the code, a different type of class may be used. There is also no requirement that the instrumentation class be a Java class. The instrumentation class could be a native class, and one skilled in the art would appreciate that appropriate linkages would have to be established in order to make calls to the native class. In the described embodiment, the instrumentation class exports several methods, including: 1. log: Used to log entry/exit, exceptions and finalizations (or any other control point). Note that serialization is provided to ensure that more than one instrumented class can log simultaneously. Also note that logging will alternately be to a hardware or software trace capability, and logging may exploit native machine methods (i.e. methods that are written in assembly code, rather than Java) that make use of efficient machine timestamp acquisition. 2. Init: Initialized logging capability (e.g., allocates a buffer). 3. Dump: Produces an output file of logged data. The following example depicts the source code of a Java class file being instrumented, a disassembly of the class file before it is hooked, and a disassembly of the same class file after it is hooked. The example depicts how code (i.e. methods) in the class file can be modified. In this particular case, instrumentation hooks are inserted at method entry and exit(s) for performance measurement. Notice how the major and minor code for the hook is pushed on the stack with the short integer push ("sipush") instructions before a call (invokestatic instruction) is made to the "log" method of the instrumentation class. Three Jvm instructions are inserted for each entry or exit hook. Also note that in some cases, dummy instructions (e.g., iconst_0 and pop2) may be added to keep the inserted code size in multiples of four. This is done because some specific bytecode instructions require 4-byte alignment. In the example below, none of the instructions require 4-byte alignment; however, the dummy instructions are added as part of the described embodiment.
/* A Java class file named hello.java */
class hello
{
public static void main (String args[ ])
{
System.out.println ("Hello, how are you today?");
}
}
/* A disassembly of hello.class before it is hooked */
compiled from hello.java
synchronized class hello extends java.lang.Object
/* ACC_SUPER bit set */
{
public static void main (java.lang.String[ ]);
hello ( );
}
Method void main (java.lang.String[ ]);
0 getstatic #7 <Field java.io.PrintStram out>
3 1dc #1 <String "Hello, how are you today? ">
5 invokevirtual #8
<Method void println (java.lang.String)>
8 return
Method hello ( )
0 aload_0
1 invokespecial #6 <Method java.lang.Object( )>
4 return
/* A disassembly of hello.class after it is hooked */
Compiled from hello.java
synchronized class hello extends java.lang.Object
/* ACC_SUPER bit set */
{
public static void main (java.lang.String[ ]);
hello ( );
}
Method void main (java.lang.STring[ ])
0 sipush 2560
3 sipush 1
6 invokestatic #37 <Method void log (int,int)>
9 iconst_0
10 iconst_0
11 pop2
12 getStatic #7 <Field java.io.PrintStream out>
15 1dc #1 <String "Hello, how are you today?">
17 invokevirtual #8
<Method void println (java.lang.String)>
20 sipush 2688
23 sipush 1
26 invokestatic #37 <Method void log (int,int)>
29 iconst_0
30 iconst_0
31 pop2
32 return
Method hello ( )
0 sipush 2560
3 sipush 2
6 invokestatic #37 <Method void log (int,int)>
9 iconst_0
10 iconst_0
11 pop2
12 aload_0
13 invokespecial #6 <Method java.lang.Object ( )>
16 sipush 2688
19 sipush 2
22 invokestatic #37 <Method void log (int,int)>
25 iconst_0
26 iconst_0
27 pop2
28 return
Consider the following example for instrumenting exceptions in a method: The program testjava has 2 functions, "main" which calls "testfunc." Note that testfunc throws an exception that is caught in an exception table in "main." Instrumentation is inserted before the athrow bytecode in testfunc and also at the beginning of the catch code in main. The java code for the example is:
lass class test {
1 public static void main (String[ ] args) {
try {
System.out.println ("In main");
testfunc (1);
System.out.println ("Back in main");
} catch (Exception e) {
System.out.println ("exception caught");
}
}
public static void testfunc (int i) throws Exception {
if (i==1) {
throw (new Exception( ));
} else {
System.out.println ("Exception not thrown");
}
}
}
The unmodified bytecodes for "main" and "testfunc" are:
---------------------------------------------------------------------------
--------
Method void main (java.lang.String[ ])
0 getstatic #11 <Field java.io.PrintStream out>
3 Idc #3 <String "In main">
5 invokevirtual #12 <Method void println (java.lang.String)>
8 iconst_1
9 invokestatic #14 <Method void testfunc (int)>
12 getstatic #11 <Field java.io.PrintStream out>
15 Idc #1 <String "Back in main">
17 invokevirtual #12 <Method void println (java.lang.String)>
20 goto 32
23 pop
24 getstatic #11 <Fieldjava.io.PrintStream out>
27 Idc #4 <String "exception caught">
29 invokevirtual #12 <Method void println(java.lang.String)>
32 return
Exception table:
from to target type
0 20 23 <Class java.lang.Exception>
Method void testfunc (int)
0 iload_0
1 iconst_1
2 if_icmpne 13
5 new #7 <Class java.lang.Exception>
8 dup
9 invokespecial #10 <Method java.lang.Exception( )>
12 athrow
13 getstatic #11 <Field java.io.PrintStream out>
16 Idc #2 <String "Exception not thrown">
18 invokevirtual #12 #Method void println(java.lang.String)>
21 return
The modified bytecodes of "main" and "testfunc" after the
---------------------------------------------------------------------------
--------
insertion of the exception instrumentation is:
---------------------------------------------------------------------------
--------
Method void main (java.lang.String[ ])
0 getstatic #11 <Field java.io.PrintStream out>
3 ldc #3 <String "In main">
5 invokevirtual #12 <Method void println (java.lang.String)>
8 iconst_1
9 invokestatic #14 <Method void testfunc (int)>
12 getstatic #11 <Field java.io.PrintStream out>
15 ldc #1 <String "Back in main">
17 invokevirtual #12 <Method void println (java.lang.String)>
20 goto 44
/* catch clause instrumentation begin */
23 sipush 2560
26 sipush 2
29 invokestatic #99 <Method void log (int,int)>
32 iconst_0
33 iconst_0
34 pop2
/* catch clause instrumentation end */
35 pop
37 getstatic #11 <Field java.io.PrintStream out>
39 Idc #4 <String "exception caught">
41 invokevirtual #12 <Method void println (java.lang.String)>
44 return
Exception table:
from to target type
0 20 23 <Class java lang.Exception>
Method void testfunc (int)
0 iload_0
1 iconst_1
2 if_icmpne 13
5 new #7 <Class java.lang.Exception>
8 dup
9 invokespecial #10 <Method java.lang.Exception ( )>
/* athrow instrumentation begin */
12 sipush 2560
15 sipush 1
18 invokestatic #99 <Method void log (int,int)>
21 iconst_0
22 iconst_0
23 pop2
/* athrow instrumentation end */
24 athrow
25 getstatic #11 <Field java.io.PrintStream out>
28 ldc #2 <String "Exception not thrown">
30 invokevirtual #12 <Method void println (java.lang.String)>
33 return
Referring now to FIG. 9, is a flowchart depicting a process for handling an exception thrown from a modified method in a class to a method in another instrumented class in accordance with a preferred embodiment of the present invention. Initially, the code is executing inside a modified method in class A (step 902). The reader will recall that code was injected in class A, such as the instrumentation hooks for handling exception and finalization processing, as well as in class B, as was described in the foregoing example. Those skilled in the art will appreciate that other types of code performing other functions could be called by the injected code according to the present invention. Class instrumentation is performed prior to any methods being called or exception being thrown in order to allow for performance measuring. Initially, injected code in the executing method in class A calls a method in class B (step 904). Thus, control is passed from class A to class B via the call and a record is written describing the flow path (step 906). This record may also include the name of the entering method and class. The current call stack now includes at least the method in class A and the method in class B. The called method in class B then executes, or attempt to execute (step 908). At some point an error occurs in the execution of the method in class B and the method currently executing in class B throws an exception (step 910). At this point the injected code in the method writes an exception hook to a record which identifies the method throwing the exception and also describes the event as an exception (step 912). The exception is caught by a method, for example a method in the calling class, class A, which is preferably a Java class (step 914). The exception could, instead, be caught by any method in the call stack including the currently executing method. In the present example, control is then passed back to class A from class B via an exception being thrown. The catching method either executes exception or finalization processing code. Here again, the instrumentation writes record describing the catch and identifying the catching method and class (step 916). The catching method is executed in class A, e.g., an instrumented method (step 918). The process for handling an exception thrown from a modified method in a class to a method in another instrumented class is then complete. Consider the following trace where an exception is thrown in testfun ( ) and caught in the function main ( ), from the example test.java given earlier. Note that in the trace here, major code 23 is being used for normal Method Entry/Exit instrumentation and 24 is being used for the exception throw and catch instrumentation.
major minor timestamp description
12 1 15:985860940 Dispatch thread: e18507a0
23 1 15:985861001 ENTRY: test.main
Ljava.lang.String [ ]) I
12 1 15:985861120 Dispatch thread: e17d5bb0
12 1 15:985900110 Dispatch thread: e1807a0
23 2 15:985901050 ENTRY: test.testfunc (I) V
24 82 15:985903804 Exception thrown Exiting
test.testfunc (I) V
.
.
.
24 1 15:985905500 Exception Caught
test.main
(Ljava.lang.String [ ]) I
23 82 15:985907050 EXIT:
test .main
(Ljava.lang.String[ ]) I
Please note that when exception thrown hook 24/82 is encountered, it is taken as an exit from the method indicated by it ("test.testfunc(I)V") for accounting purposes. The accounting is continued for the method catching the exception, ie the 24/1 hook. Also note that this is a trivial example, but any number of functions could have been on the call stack between "testfunc( )" and "main( )", and for accounting purposes an exit would be implied on all functions on the call stack, between the function throwing the exception and the one catching it. Although the invention has been described with a certain degree of particularity, it should be recognized that elements thereof may be altered by persons skilled in the art without departing from the spirit and scope of the invention. One of the preferred implementations of the invention is as sets of instructions resident in the main memory 154 of one or more computer systems configured generally as described in FIG. 1A. Until required by the computer system, the set of instructions may be stored in another computer readable memory, for example in a hard disk drive, or in a removable memory such as an optical disk for eventual use in a CD-ROM drive or a floppy disk for eventual use in a floppy disk drive. Further, the set of instructions can be stored in the memory of another computer and transmitted over a local area network or a wide area network, such as the Internet, when desired by the user. One skilled in the art would appreciate that the physical storage of the sets of instructions physically changes the medium upon which it is stored electrically, magnetically, or chemically so that the medium carries computer readable information. The invention is limited only by the following claims and their equivalents.
|
Same subclass Same class Consider this |
||||||||||
