Invoking Assembly Language Programs from Java
Added 31 Jul 2008
The JNI Approach
When a Java method calls assembly language code, some information will almost always have to move from one environment to the other. The calling method will usually pass parameters to the called function and the called function may return some information to the caller. In addition to this, each environment requires information about the other to be able to work together. The problem is that data representation within the Java Virtual Machine (JVM) is different from that in the assembly language environment. Also some information, especially within the JVM, is of a specialized nature and there is no provision in native languages (C/C++/assembly) to directly access such information. JNI provides a rich set of interface functions that facilitate exchange of such data by providing access to the internal database of the JVM and by providing the required mapping from the data type of one environment to the corresponding data type of the other.
JNI also has certain other support structures that make it easy for C and C++ programs to call these interface functions. Unfortunately, these support mechanisms are not directly usable by assembly language programs. The assembly language programmer, therefore, needs to understand how the interface functions can be directly accessed, and an appreciation of the structure of JNI is necessary to achieve this understanding.
JNI Structure
Whenever a Java program calls a native method, the called method
compulsorily receives two parameters in addition to those specified by
the calling method. The first is the JNIEnv pointer and
the second is a reference to the calling object or class. It is the
first parameter that is the key to the world of JNI.
JNIEnv is a pointer that, in turn, points to another
pointer. This second pointer points to a function table that is an
array of pointers. Each pointer in the function table points to a JNI
interface function. In order to call an interface function, we have to
determine the value of the corresponding entry in the function table.
Let us see how we can do this in two steps.
First we find out what the value of the second pointer in our chain
is. In other words, we get the contents of the location pointed to by JNIEnv. We do that as follows:
mov ebx, JNIEnv
mov eax, [ebx]
The first instruction loads the contents of JNIEnv into ebx and the second loads the contents of the address pointed to by ebx into eax. Since the contents of ebx is the same as that of JNIEnv, eax now has the contents of the location pointed to by JNIEnv--the second pointer referred to above. Which means eax now contains the starting address of the function table.
Next we need to retrieve the contents of the entry in the function
table that corresponds to the function we want to call. To do this, we
have to multiply the zero based index of the function (see Sheng Liang's book)
by four--since each pointer is four bytes long--and add the result to
the starting address of the function table which we have formed in eax earlier. We do it thus: