This method modifies the failure handling of native method resolution by allowing retry with a prefix applied to the name. When used with the
ClassFileTransformer
, it enables native methods to be instrumented.
Since native methods cannot be directly instrumented (they have no bytecodes), they must be wrapped with a non-native method which can be instrumented. For example, if we had:
native boolean foo(int x);
We could transform the class file (with the ClassFileTransformer during the initial definition of the class) so that this becomes:
boolean foo(int x) {
... record entry to foo ...
return wrapped_foo(x);
}
native boolean wrapped_foo(int x);
Where foo
becomes a wrapper for the actual native method with the appended prefix "wrapped_". Note that "wrapped_" would be a poor choice of prefix since it might conceivably form the name of an existing method thus something like "$$$MyAgentWrapped$$$_" would be better but would make these examples less readable.
The wrapper will allow data to be collected on the native method call, but now the problem becomes linking up the wrapped method with the native implementation. That is, the method wrapped_foo
needs to be resolved to the native implementation of foo
, which might be:
Java_somePackage_someClass_foo(JNIEnv* env, jint x)
This function allows the prefix to be specified and the proper resolution to occur. Specifically, when the standard resolution fails, the resolution is retried taking the prefix into consideration. There are two ways that resolution occurs, explicit resolution with the JNI function RegisterNatives
and the normal automatic resolution. For RegisterNatives
, the JVM will attempt this association:
method(foo) -> nativeImplementation(foo)
When this fails, the resolution will be retried with the specified prefix prepended to the method name, yielding the correct resolution:
method(wrapped_foo) -> nativeImplementation(foo)
For automatic resolution, the JVM will attempt:
method(wrapped_foo) -> nativeImplementation(wrapped_foo)
When this fails, the resolution will be retried with the specified prefix deleted from the implementation name, yielding the correct resolution:
method(wrapped_foo) -> nativeImplementation(foo)
Note that since the prefix is only used when standard resolution fails, native methods can be wrapped selectively.
Since each ClassFileTransformer
can do its own transformation of the bytecodes, more than one layer of wrappers may be applied. Thus each transformer needs its own prefix. Since transformations are applied in order, the prefixes, if applied, will be applied in the same order (see addTransformer
). Thus if three transformers applied wrappers, foo
might become $trans3_$trans2_$trans1_foo
. But if, say, the second transformer did not apply a wrapper to foo
it would be just $trans3_$trans1_foo
. To be able to efficiently determine the sequence of prefixes, an intermediate prefix is only applied if its non-native wrapper exists. Thus, in the last example, even though $trans1_foo
is not a native method, the $trans1_
prefix is applied since $trans1_foo
exists.