Okay, I'm all out of ideas on this one. Does anyone have any idea how I can hook into Java's exception pipeline in order to catch (and log to a text file) all exceptions that are occurring?
The situation is this: I have a library in a JAR file (A) which in turn depends on a second JAR file (B). A has no main class, as it's simply a class library, which I'm accessing and invoking through the JNI. The problem I'm having is this. When I attempt to initialise the JNI with A loaded, the JNI returns an unspecified error.
I strongly suspect that this error originates from an instantiation of Log4J's logger unit, which is occurring in static code (outside of a method) in B, which I believe is throwing an IOException as a result of permissions problems on the log file. I'm having issues finding out what's going on, however, as the exception (which I suspect is the cause of the problem) is being thrown during the linking stage (when A imports B) and so cannot be caught by a try-catch block. Also, since there is no main method there is no obvious place to put a try-catch block in order to catch this exception.
I would like some way of catching all exceptions that arise in either JAR and dumping them into a text file. I cannot (easily) modify B (I do not have the decompiled JAR). Any ideas?
Here is the C code which invokes the JNI with the specified libraries and options:
_DLL_EXPORT PyObject *initVM(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwnames[] = {
"classpath", "initialheap", "maxheap", "maxstack",
"vmargs", NULL
};
char *classpath = NULL;
char *initialheap = NULL, *maxheap = NULL, *maxstack = NULL;
char *vmargs = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzzzz", kwnames,
&classpath,
&initialheap, &maxheap, &maxstack,
&vmargs))
return NULL;
if (env->vm)
{
PyObject *module_cp = NULL;
if (initialheap || maxheap || maxstack || vmargs)
{
PyErr_SetString(PyExc_ValueError,
"JVM is already running, options are ineffective");
return NULL;
}
if (classpath == NULL && self != NULL)
{
module_cp = PyObject_GetAttrString(self, "CLASSPATH");
if (module_cp != NULL)
classpath = PyString_AsString(module_cp);
}
if (classpath && classpath[0])
env->setClassPath(classpath);
Py_XDECREF(module_cp);
return getVMEnv(self);
}
else
{
JavaVMInitArgs vm_args;
JavaVMOption vm_options[32];
JNIEnv *vm_env;
JavaVM *vm;
unsigned int nOptions = 0;
PyObject *module_cp = NULL;
vm_args.version = JNI_VERSION_1_4;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
if (classpath == NULL && self != NULL)
{
module_cp = PyObject_GetAttrString(self, "CLASSPATH");
if (module_cp != NULL)
classpath = PyString_AsString(module_cp);
}
#ifdef _jcc_lib
PyObject *jcc = PyImport_ImportModule("jcc");
PyObject *cp = PyObject_GetAttrString(jcc, "CLASSPATH");
if (classpath)
add_paths("-Djava.class.path=", PyString_AsString(cp), classpath,
&vm_options[nOptions++]);
else
add_option("-Djava.class.path=", PyString_AsString(cp),
&vm_options[nOptions++]);
Py_DECREF(cp);
Py_DECREF(jcc);
#else
if (classpath)
add_option("-Djava.class.path=", classpath,
&vm_options[nOptions++]);
#endif
Py_XDECREF(module_cp);
if (initialheap)
add_option("-Xms", initialheap, &vm_options[nOptions++]);
if (maxheap)
add_option("-Xmx", maxheap, &vm_options[nOptions++]);
if (maxstack)
add_option("-Xss", maxstack, &vm_options[nOptions++]);
if (vmargs)
{
#ifdef _MSC_VER
char *buf = _strdup(vmargs);
#else
char *buf = strdup(vmargs);
#endif
char *sep = ",";
char *option;
for (option = strtok(buf, sep); option; option = strtok(NULL, sep))
{
if (nOptions < sizeof(vm_options) / sizeof(JavaVMOption))
add_option("", option, &vm_options[nOptions++]);
else
{
free(buf);
for (unsigned int i = 0; i < nOptions; i++)
delete vm_options[i].optionString;
PyErr_Format(PyExc_ValueError, "Too many options (> %d)",
nOptions);
return NULL;
}
}
free(buf);
}
//vm_options[nOptions++].optionString = "-verbose:gc";
//vm_options[nOptions++].optionString = "-Xcheck:jni";
vm_args.nOptions = nOptions;
vm_args.ignoreUnrecognized = JNI_FALSE;
vm_args.options = vm_options;
if (JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args) < 0)
{
for (unsigned int i = 0; i < nOptions; i++)
delete vm_options[i].optionString;
PyErr_Format(PyExc_ValueError,
"An error occurred while creating Java VM");
return NULL;
}
env->set_vm(vm, vm_env);
for (unsigned int i = 0; i < nOptions; i++)
delete vm_options[i].optionString;
t_jccenv *jccenv = (t_jccenv *) PY_TYPE(JCCEnv).tp_alloc(&PY_TYPE(JCCEnv), 0);
jccenv->env = env;
#ifdef _jcc_lib
registerNatives(vm_env);
#endif
return (PyObject *) jccenv;
}
}
Okay, so I've got the solution I was after. The solution is an update to the following segment of the code listed in the question:
if (JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args) < 0)
{
for (unsigned int i = 0; i < nOptions; i++)
delete vm_options[i].optionString;
PyErr_Format(PyExc_ValueError,
"An error occurred while creating Java VM");
return NULL;
}
The adaptation supports the construction of a more detailed error message which adds two specific pieces of information:
The error code (if any) which is returned by the JNI_CreateJavaVM method;
The detailed Java exception which occurs in the event that such an error code arises.
The above snippet from the original code was replaced with the following:
vmInitSuccess = JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args);
if (vmInitSuccess < 0)
{
for (unsigned int i = 0; i < nOptions; i++)
delete vm_options[i].optionString;
//Set up basic error message
sprintf(strVMInitSuccess, "%d", vmInitSuccess);
strcpy(strVMError, "An error occurred while creating Java VM (No Exception): ");
strcat(strVMError, strVMInitSuccess);
//Get exception if there is one
if((exc = vm_env->ExceptionOccurred()))
{
//Clear the exception since we have it now
vm_env->ExceptionClear();
//Get the getMessage() method
if ((java_class = vm_env->FindClass ("java/lang/Throwable")))
{
if ((method = vm_env->GetMethodID(java_class, "getMessage", "()Ljava/lang/String;")))
{
int size;
strExc = static_cast<jstring>(vm_env->CallObjectMethod(exc, method));
charExc = vm_env->GetStringUTFChars(strExc, NULL);
size = sizeof(strVMError) + sizeof(charExc);
char strVMException[size];
strcpy(strVMException, "An error occurred while creating Java VM (Exception): ");
strcat(strVMException, charExc);
PyErr_Format(PyExc_ValueError, strVMException);
return NULL;
}
}
}
PyErr_Format(PyExc_ValueError, strVMError);
return NULL;
}
Thanks to #Parsifal for help with this solution.
Related
i have following Problem. I write a little Application , that creates a Java Virtual Machine. If I start this programm inside of Visual Studio it works fine. But if i start it outside of visual studio the programm does not work and i have a ntdll.dll crash.
Here is my Code :
int result = 0;
LoadRuntimeLibrary(libPath);
// Load the JVM library
g_jniLibrary = LoadLibrary(libPath);
if (g_jniLibrary == NULL) {
info->Error("Could not load libary: ");
return -1;
}
// Grab the create VM function address
JNI_createJavaVM createJavaVM = (JNI_createJavaVM)GetProcAddress(g_jniLibrary, "JNI_CreateJavaVM");
if (createJavaVM == NULL) {
info->Error("ERROR: Could not find JNI_CreateJavaVM function");
return -1;
}
// Count the vm args
int numVMArgs = -1;
while (vmArgs[++numVMArgs] != NULL) {}
// Add the options for exit and abort hooks
int numHooks = 0;
JavaVMOption* options = (JavaVMOption*)malloc((numVMArgs + numHooks) * sizeof(JavaVMOption));
for (int i = 0; i < numVMArgs; i++){
options[i].optionString = vmArgs[i];
options[i].extraInfo = 0;
}
// Setup hook pointers
options[numVMArgs].optionString = "abort";
options[numVMArgs].extraInfo = (void*)&AbortHook;
options[numVMArgs + 1].optionString = "exit";
options[numVMArgs + 1].extraInfo = (void*)&ExitHook;
JavaVMInitArgs init_args;
memset(&init_args, 0, sizeof(init_args));
init_args.version = JNI_VERSION_1_8;
init_args.options = options;
init_args.nOptions = numVMArgs + numHooks;
init_args.ignoreUnrecognized = JNI_FALSE;
result = createJavaVM(&jvm, &env, &init_args); // here is the crash
env = GetJNIEnv(false);
Init(env);
result = RunMainClass(env, mainCls, argc, javaargs);
jvm->DestroyJavaVM();
FreeLibrary(g_jniLibrary);
return result;
I hope you hava any idea , what could be wrong
You are accessing the options array out of bounds. It only contains numVMArgs elements, as numHooks is zero.
This of course leads to undefined behavior when you do
options[numVMArgs].optionString = "abort";
options[numVMArgs].extraInfo = (void*)&AbortHook;
options[numVMArgs + 1].optionString = "exit";
options[numVMArgs + 1].extraInfo = (void*)&ExitHook;
as the indexes numVMArgs and numVMArgs + 1 are out of bounds.
I'm using QtCreator to deploy C++/Java applications on Android. But I think my problem may not be specific to the way QtCreator deploys the app.
I want to create a C++ library providing a specific functionnality. To do so, the library needs to instantiate a Java class, this last one will be used to do some SDK functions class (for stuff that are not available in the NDK/C++ API).
Creating and using java objects from a C++ program works fine. I package the .java file to the application environment during compilation/deployment and then I can use the Java class via two approachs:
Declare JNI_OnLoad, load class id, method id, and later call them using jni
Use Qt QAndroidJniObject objects (this is specific to QtCreator)
Now the problem comes when I want to create and use java objects from a C++ library. It only works if the .java file is packaged with the top-level application. I could not find a way to package the java with and only with the library itself. Meaning that anyone why needs to use my library will not only have to simply link with the library, but will also need to package the .java file(s) needed by my library. This breaks encapsulation and gives a hard time to the end developer writing programs and simply wanting to load a library and expecting it to embed all it needs to work on its own...
My question is: How can the library embed the java file, so that this java file does not need to be part of the top level program package to let the library use it?
Here is a quick sample: MainWindow constrctor calls 4 functions themselves trying to create and use Java objects. Only the first two calls work...
main.cpp:
#include <QApplication>
#include <QMainWindow>
#include "MyLib.h"
#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"
#include <assert.h>
// load java classes from main program
JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;
bool loadJava( JNIEnv *env )
{
jclass clazz = env->FindClass("my/FooPrg");
if (!clazz)
{
qCritical("Can't find FooPrg class");
return false;
}
// keep a global reference to it
s_classID = (jclass)env->NewGlobalRef(clazz);
// search for its contructor
s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
if (!s_ctorMethodID )
{
qCritical("Can't find class contructor");
return false;
}
// search for a method
s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
if (!s_callmethodID )
{
qCritical("Can't find Mult method");
return false;
}
return true;
}
jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
{
s_javaVM = vm;
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
return -1;
loadJava( env );
return JNI_VERSION_1_4;
}
void callJavaFunctionFromPrgWithQt()
{
if ( QAndroidJniObject::isClassAvailable("my/FooPrg") )
{
QAndroidJniObject obj("my/FooPrg","()V");
if ( obj.isValid() )
{
jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
assert( res == 4 );
}
else
{
assert( false );
}
}
else
{
assert( false );
}
}
void callJavaFunctionFromPrgWithJniLoad()
{
if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
{
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
assert(false);
jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
assert( res == 4 );
}
else
{
assert( false );
}
}
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
callJavaFunctionFromPrgWithQt(); // works
callJavaFunctionFromPrgWithJniLoad(); // works
callJavaFunctionFromLibWithQt(); // fails, assert
callJavaFunctionFromLibWithJniLoad(); // fails, because libraries JNI_OnLoad can't find FooLib.java!
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
MyLib.h:
#pragma once
void callJavaFunctionFromLibWithQt();
void callJavaFunctionFromLibWithJniLoad();
MyLib.cpp:
#include "MyLib.h"
#include <QtAndroidExtras/QAndroidJniObject>
#include "jni.h"
#include <assert.h>
// load java classes from main program
JavaVM* s_javaVM = NULL;
jclass s_classID = 0;
jmethodID s_ctorMethodID = 0;
jmethodID s_callmethodID = 0;
bool loadJava( JNIEnv *env )
{
jclass clazz = env->FindClass("my/FooLib");
if (!clazz)
{
qDebug("Can't find FooLib class");
return false;
}
// keep a global reference to it
s_classID = (jclass)env->NewGlobalRef(clazz);
// search for its contructor
s_ctorMethodID = env->GetMethodID(s_classID, "<init>", "()V");
if (!s_ctorMethodID )
{
qDebug("Can't find class contructor");
return false;
}
// search for a method
s_callmethodID = env->GetMethodID(s_classID, "Mult", "(I)I");
if (!s_callmethodID )
{
qDebug("Can't find Mult method");
return false;
}
return true;
}
jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
{
s_javaVM = vm;
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
return -1;
// uncommenting this makes the application crash upon load....
//loadJava( env );
return JNI_VERSION_1_4;
}
void callJavaFunctionFromLibWithQt()
{
if ( QAndroidJniObject::isClassAvailable("my/FooLib") )
{
QAndroidJniObject obj("my/FooLib","()V");
if ( obj.isValid() )
{
jint res = obj.callMethod<jint>("Mult", "(I)I", 0x0002);
assert( res == 4 );
}
else
{
assert( false );
}
}
else
{
assert( false ); // this assertion is reached!
}
}
void callJavaFunctionFromLibWithJniLoad()
{
if ( s_classID != 0 && s_ctorMethodID != 0 && s_callmethodID != 0 )
{
JNIEnv* env = NULL;
if (s_javaVM->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
assert(false);
jobject j_object = env->NewGlobalRef( env->NewObject(s_classID, s_ctorMethodID ) );
jint res = env->CallIntMethod(j_object, s_callmethodID, 0x0002 );
assert( res == 4 );
}
else
{
assert( false ); // this assertion is reached!
}
}
FooPrg.java:
package my;
import java.lang.Integer;
public class FooPrg
{
public FooPrg()
{
}
public int Mult(int val)
{
return val * 2;
}
}
FooLib.java:
package my;
import java.lang.Integer;
public class FooLib
{
public FooLib()
{
}
public int Mult(int val)
{
return val * 2;
}
}
jniload.pro:
TARGET = jniload
CONFIG += qt resources
QT += core gui widgets
android: QT += androidextras
SOURCES += src/main.cpp
TEMPLATE = app
INCLUDEPATH += ifc
LIBS += \
-l$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so
ANDROID_EXTRA_LIBS += \
$$OUT_PWD/../../lib/jniload_lib/libjniload_lib.so
ANDROID_PACKAGE_SOURCE_DIR = data/android/root
OTHER_FILES += data/android/root/src/my/FooPrg.java
jniload_lib.pro:
TARGET = jniload_lib
CONFIG += qt resources
QT += core gui widgets
android: QT += androidextras
SOURCES += src/MyLib.cpp
HEADERS += ifc/MyLib.h
TEMPLATE = lib
INCLUDEPATH += ifc
# This does has apparently no effect on library
ANDROID_PACKAGE_SOURCE_DIR = data/android/root
OTHER_FILES += data/android/root/src/my/FooLib.java
Finaly got a way to work this out.
I removed ANDROID_PACKAGE_SOURCE_DIR line from jniload.pro file and hanlde manual copy of the .java files through custom build steps:
custom_jniload_lib_step.target = jniload_lib_mockup.h
custom_jniload_lib_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_lib_step
PRE_TARGETDEPS += jniload_lib_mockup.h
custom_jniload_step.target = jniload_mockup.h
custom_jniload_step.commands = $(COPY_DIR) data\android\root ..\..\android-build
QMAKE_EXTRA_TARGETS += custom_jniload_step
PRE_TARGETDEPS += jniload_mockup.h
Then, upon deployment, android-build/src contains both FooLib.java and FooPrg.java and then both library and program can access them!
Environment:
PC sys:windows xp
jdk:1.6
ndk:android-ndk-r9b
compile env:MinGW
Descrpiton:
I'm developing a sip endpoint app, which is based on the opensource sip stack "doubango", in Android device. The socket functions are called using NDK but not directly JAVA. When normally use, it's OK. Then clean the device's memory using some software, or stop Service manully, and make it exit abnormally. Seems that it stops without call function close(socket). Next time when I use, it will throw error calling socket function :sendto(),the errno=1 which means "operation not permitted".
I've added "uses-permission android:name="android.permission.INTERNET""
Does anyone can give me a hand?
PS:I test in devices without ROOT permission, and this issue will happen. But I test a device with ROOT permission, it doesn't happen. Does it matter?
Job Is Done. There are sth mistakes i made in the Java layer. Some parameters i set in Sip stack are wrong.
Here follows the code segment:
int tnet_sockfd_sendto(tnet_fd_t fd, const struct sockaddr *to, const void* buf, tsk_size_t size)
{
tsk_size_t sent = 0;
int ret = -1;
if(fd == TNET_INVALID_FD){
TSK_DEBUG_ERROR("Using invalid FD to send data.");
goto bail;
}
if(!buf || !size){
TSK_DEBUG_ERROR("Using invalid BUFFER.");
ret = -2;
goto bail;
}
while(sent < size){
int try_guard = 10;
#if TNET_UNDER_WINDOWS
WSABUF wsaBuffer;
DWORD numberOfBytesSent = 0;
wsaBuffer.buf = ((CHAR*)buf) + sent;
wsaBuffer.len = (size - sent);
try_again:
ret = WSASendTo(fd, &wsaBuffer, 1, &numberOfBytesSent, 0, to, tnet_get_sockaddr_size(to), 0, 0); // returns zero if succeed
if(ret == 0){
ret = numberOfBytesSent;
}
#else
try_again:
ret = sendto(fd, (((const uint8_t*)buf)+sent), (size-sent), 0, to, tnet_get_sockaddr_size(to)); // returns number of sent bytes if succeed
#endif
if(ret <= 0){
if(tnet_geterrno() == TNET_ERROR_WOULDBLOCK){
TSK_DEBUG_INFO("SendUdp() - WouldBlock. Retrying...");
if(try_guard--){
tsk_thread_sleep(10);
goto try_again;
}
}
else{
***/* Just throw error here*/***
TSK_DEBUG_INFO("errno: %d",tnet_geterrno());
TNET_PRINT_LAST_ERROR("sendto() failed");
}
goto bail;
}
else{
sent += ret;
}
}
bail:
return (size == sent) ? sent : ret;
}
#define tnet_soccket(family, type, protocol) socket((family), (type), (protocol))
tnet_socket_t* tnet_socket_create_2(const char* host, tnet_port_t port_,tnet_socket_type_t type, tsk_bool_t nonblocking, tsk_bool_t bindsocket)
{
tnet_socket_t *sock;
if((sock = tsk_object_new(tnet_socket_def_t))){
int status;
tsk_istr_t port;
struct addrinfo *result = tsk_null;
struct addrinfo *ptr = tsk_null;
struct addrinfo hints;
tnet_host_t local_hostname;
sock->port = port_;
tsk_itoa(sock->port, &port);
sock->type = type;
memset(local_hostname, 0, sizeof(local_hostname));
/* Get the local host name */
if(host != TNET_SOCKET_HOST_ANY && !tsk_strempty(host)){
memcpy(local_hostname, host, tsk_strlen(host)>sizeof(local_hostname)-1 ? sizeof(local_hostname)-1 : tsk_strlen(host));
}
else{
if(TNET_SOCKET_TYPE_IS_IPV6(sock->type)){
memcpy(local_hostname, "::", 2);
}
else{
memcpy(local_hostname, "0.0.0.0", 7);
}
}
TSK_DEBUG_INFO("local_hostname: %s",local_hostname);
/* hints address info structure */
memset(&hints, 0, sizeof(hints));
hints.ai_family = TNET_SOCKET_TYPE_IS_IPV46(sock->type) ? AF_UNSPEC : (TNET_SOCKET_TYPE_IS_IPV6(sock->type) ? AF_INET6 : AF_INET);
hints.ai_socktype = TNET_SOCKET_TYPE_IS_STREAM(sock->type) ? SOCK_STREAM : SOCK_DGRAM;
hints.ai_protocol = TNET_SOCKET_TYPE_IS_STREAM(sock->type) ? IPPROTO_TCP : IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE
#if !TNET_UNDER_WINDOWS || _WIN32_WINNT>=0x600
| AI_ADDRCONFIG
#endif
;
/* Performs getaddrinfo */
if((status = tnet_getaddrinfo(local_hostname, port, &hints, &result))){
TNET_PRINT_LAST_ERROR("tnet_getaddrinfo(family=%d, hostname=%s and port=%s) failed: [%s]",
hints.ai_family, local_hostname, port, tnet_gai_strerror(status));
goto bail;
}
/* Find our address. */
for(ptr = result; ptr; ptr = ptr->ai_next){
sock->fd = tnet_soccket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if(ptr->ai_family != AF_INET6 && ptr->ai_family != AF_INET){
continue;
}
/* To avoid "Address already in use" error
* Check issue 368 (https://code.google.com/p/doubango/issues/detail?id=368) to understand why it's not used for UDP/DTLS.
*/
//
if (TNET_SOCKET_TYPE_IS_STREAM(sock->type)) {
TSK_DEBUG_INFO("Socket Type: Stream");
if ((status = tnet_sockfd_reuseaddr(sock->fd, 1))) {
// do not break...continue
}
}
if(bindsocket){
/* Bind the socket */
if((status = bind(sock->fd, ptr->ai_addr, ptr->ai_addrlen))){
TNET_PRINT_LAST_ERROR("bind to [%s:%s]have failed", local_hostname, port);
tnet_socket_close(sock);
continue;
}
/* Get local IP string. */
if((status = tnet_get_ip_n_port(sock->fd , tsk_true/*local*/, &sock->ip, &sock->port))) /* % */
//if((status = tnet_getnameinfo(ptr->ai_addr, ptr->ai_addrlen, sock->ip, sizeof(sock->ip), 0, 0, NI_NUMERICHOST)))
{
TNET_PRINT_LAST_ERROR("Failed to get local IP and port.");
tnet_socket_close(sock);
continue;
}
}
/* sets the real socket type (if ipv46) */
if(ptr->ai_family == AF_INET6) {
TNET_SOCKET_TYPE_SET_IPV6Only(sock->type);
}
else{
TNET_SOCKET_TYPE_SET_IPV4Only(sock->type);
}
break;
}
/* Check socket validity. */
if(!TNET_SOCKET_IS_VALID(sock)) {
TNET_PRINT_LAST_ERROR("Invalid socket.");
goto bail;
}
#if TNET_UNDER_IPHONE || TNET_UNDER_IPHONE_SIMULATOR
/* disable SIGPIPE signal */
{
int yes = 1;
if(setsockopt(sock->fd, SOL_SOCKET, SO_NOSIGPIPE, (char*)&yes, sizeof(int))){
TNET_PRINT_LAST_ERROR("setsockopt(SO_NOSIGPIPE) have failed.");
}
}
#endif /* TNET_UNDER_IPHONE */
/* Sets the socket to nonblocking mode */
if(nonblocking){
if((status = tnet_sockfd_set_nonblocking(sock->fd))){
goto bail;
}
}
bail:
/* Free addrinfo */
tnet_freeaddrinfo(result);
/* Close socket if failed. */
if(status){
if(TNET_SOCKET_IS_VALID(sock)){
tnet_socket_close(sock);
}
return tsk_null;
}
}
return sock;
}
int tnet_sockfd_reuseaddr(tnet_fd_t fd, int reuseAddr)
{
if (fd != TNET_INVALID_FD) {
int ret;
#if defined(SOLARIS)
static const char yes = '1';
static const char no = '0';
#else
static const int yes = 1;
static const int no = 0;
#endif
if ((ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)(reuseAddr ? &yes : &no), sizeof(int)))) {
TNET_PRINT_LAST_ERROR("setsockopt(SO_REUSEADDR, fd=%d) have failed", fd);
return ret;
}
// #if defined(SO_REUSEPORT)
if ((ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)(reuseAddr ? &yes : &no), sizeof(int)))) {
TNET_PRINT_LAST_ERROR("setsockopt(SO_REUSEPORT, fd=%d) have failed", fd);
return ret;
}
// #endif
return 0;
}
return -1;
}
#define tnet_sockfd_set_nonblocking(fd) tnet_sockfd_set_mode(fd, 1)
int tnet_sockfd_set_mode(tnet_fd_t fd, int nonBlocking)
{
if(fd != TNET_INVALID_FD)
{
#if TNET_UNDER_WINDOWS
ULONG mode = nonBlocking;
if(ioctlsocket(fd, FIONBIO, &mode))
//if(WSAIoctl(fd, FIONBIO, &nonblocking, sizeof(nonblocking), NULL, 0, NULL, NULL, NULL) == SOCKET_ERROR)
{
TNET_PRINT_LAST_ERROR("ioctlsocket(FIONBIO) have failed.");
return -1;
}
#else
int flags;
if((flags = fcntl(fd, F_GETFL, 0)) < 0) {
TNET_PRINT_LAST_ERROR("fcntl(F_GETFL) have failed.");
return -1;
}
if(fcntl(fd, F_SETFL, flags | (nonBlocking ? O_NONBLOCK : ~O_NONBLOCK)) < 0){
TNET_PRINT_LAST_ERROR("fcntl(O_NONBLOCK/O_NONBLOCK) have failed.");
return -1;
}
#endif
// int on = 1;
// ioctl(fd, FIONBIO, (char *)&on);
}
return 0;
}
I have the following java code
public class Test {
public void sayHello(String msg) {
System.out.println(msg);
}
}
new Test().sayHello("Bonjour");
I have a jvmti agent attached to java where I catch function calls. I want to get parameter value which was passed to my method (e.g. "Bonjour")
static void JNICALL cbMethodEntry(jvmtiEnv *jvmti,
JNIEnv* jni_env, jthread thread, jmethodID method) {
// here I want to get a parameter value "Bonjour"
// which was passed to my method sayHello("Bonjour")
}
jvmtiEventCallbacks callbacks;
callbacks.MethodEntry = &cbMethodEntry;
In the callback itself I have a thread and method ID.
Looking into a jvmti.h header I found only this structure dealing with parameters but there are no values.
typedef struct {
char* name;
jvmtiParamKind kind;
jvmtiParamTypes base_type;
jboolean null_ok;
} jvmtiParamInfo;
How can I get parameter values from my callback?
I'm working on similar tasks. Here are two code examples written in C++. Example 1 shows how to get local variables in the MethodEntry callback using GetLocalVariableTable and GetLocalObject. Example 2 shows how to preform this using BCI (Bytecode Instrumentation).
Example 1:
HandleMethodEntry is the call back method for MethodEntry event. It logs some information about the method's parameters. GetLocalVariableTable retrieves local variable information, which is used by GetLocalObject. Frame at depth zero is the current frame, the first parameter is at slot 0. For non-static frames, slot 0 contains the "this" object. To retrieve "this" object from native method frames, you should use GetLocalInstance instead of GetLocalObject.
The first char of the signature is the value type. This example simply checks the tag of a jobject. For String values, you can use GetStringUTFChars. An example can be found here.
void JNICALL MethodTraceAgent::HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method)
{
try {
jvmtiError error;
jclass clazz;
char* name;
char* signature;
// get declaring class of the method
error = m_jvmti->GetMethodDeclaringClass(method, &clazz);
Errors::Check(error);
// get the signature of the class
error = m_jvmti->GetClassSignature(clazz, &signature, 0);
Errors::Check(error);
// get method name
error = m_jvmti->GetMethodName(method, &name, NULL, NULL);
Errors::Check(error);
char tmp[1024];
sprintf(tmp, "%s%s", signature, name);
if(pFilter->Match("method", tmp)) { // intrested method?
char out[1024];
jint param_size = 0;
error = m_jvmti->GetArgumentsSize(method, ¶m_size);
int line_len = sprintf(out, "method_entry: %s%s%, param_size:%d", signature, name, param_size);
// visit local variable
jint entry_count = 0;
jvmtiLocalVariableEntry *table_ptr = NULL;
jlocation cur_loc;
// this call may return JVMTI_ERROR_ABSENT_INFORMATION, this error is avoided by initialize entry_count to 0 to escape the following for loop
error = m_jvmti->GetLocalVariableTable(method, &entry_count, &table_ptr);
error = m_jvmti->GetFrameLocation(thread, 0, NULL, &cur_loc);
for(int j=0; j<min(param_size, entry_count); j++) {
if(table_ptr[j].start_location > cur_loc) break;
if(table_ptr[j].signature[0] == 'L') { // fully-qualified-class
jobject param_obj;
jlong param_obj_tag = 0;
error = m_jvmti->GetLocalObject(thread, 0, table_ptr[j].slot, ¶m_obj); // frame at depth zero is the current frame
m_jvmti->GetTag(param_obj, ¶m_obj_tag);
if(param_obj_tag == 0) {
m_jvmti->SetTag(param_obj, theTag);
param_obj_tag = theTag;
++theTag;
}
line_len += sprintf(out + line_len, ", param_obj_tag: %ld", param_obj_tag);
//line_len += sprintf(out+line_len, ", slot:%d, start:%ld, cur:%ld, param:%s%s", table_ptr[j].slot, table_ptr[j].start_location, cur_loc, table_ptr[j].signature, table_ptr[j].name);
jni->DeleteLocalRef(param_obj);
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].signature));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].name));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].generic_signature));
}
}
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr));
Errors::Check(error);
// put to log list
logList.push_back(out);
printf("\r%-10d", logList.size());
}
// release resources
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(name));
Errors::Check(error);
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
Errors::Check(error);
} catch (AgentException& e) {
cout << "Error when enter HandleMethodEntry: " << e.what() << " [" << e.ErrCode() << "]" << endl;
}
}
Example 2:
As mentioned in the answer of a similar question, dealing with it in MethodEntry even callback may have preformance problem. You can consider the BCI approach. MTRACE_native_entry is a native method injected to very beginning of every method call. It's called from MTrace's method_entry method.
In MTRACE_native_entry, you need to track back to the intrested method at frame 2 (current frame of the executing native method is at frame 0). Similar example of param trace can be found in another project stackparam in GitHub. However, performence differences of these two methods is not tested.
Unshown code of this example can be found in the jdk document dir demo/jvmti/mtrace. The core step is to inject method_entry in ClassFileLoadHook event callback using java_crw_demo.
This example also shows how to get an param object's field value.
void JNICALL MethodTraceAgent::MTRACE_native_entry(JNIEnv *jni, jclass klass, jthread thread, jint cnum, jint mnum)
{
/* It's possible we get here right after VmDeath event, be careful */
if ( !pTheAgent->vmInitialized || pTheAgent->vmDead || thread == NULL)
return;
jvmtiError error;
char out[1024];
int line_len = 0;
jvmtiFrameInfo frames[3];
jint cframe;
error = m_jvmti->GetStackTrace(thread, 0, 3, frames, &cframe);
Errors::Check(error);
if(cframe < 3)
return;
jmethodID method = frames[2].method;
jclass dec_cls;
char *mtd_name, *dec_cls_sig;
m_jvmti->GetMethodDeclaringClass(method, &dec_cls);
m_jvmti->GetClassSignature(dec_cls, &dec_cls_sig, NULL);
m_jvmti->GetMethodName(method, &mtd_name, NULL, NULL);
jboolean isNative = false;
m_jvmti->IsMethodNative(method, &isNative);
if(isNative)
return;
line_len += sprintf(out + line_len, "m_en: %s%s", dec_cls_sig, mtd_name);
// operate tags
jint param_size = 0;
jint entry_count = 0;
jvmtiLocalVariableEntry *table_ptr = NULL;
error = m_jvmti->GetArgumentsSize(method, ¶m_size);
error = m_jvmti->GetLocalVariableTable(method, &entry_count, &table_ptr);
Errors::Check(error);
line_len += sprintf(out + line_len, ", %d, %d", param_size, entry_count);
for(int j=0; j<min(param_size, entry_count); j++) {
jobject param_obj = 0;
if(j==0 && strcmp(table_ptr[0].name, "this") == 0) { // this instance
error = m_jvmti->GetLocalInstance(thread, 2, ¶m_obj);
if(thiso == 0) thiso = param_obj;
else {
line_len += sprintf(out + line_len, ", same_this: %d", jni->IsSameObject(thiso, param_obj));
}
jfieldID field = jni->GetFieldID(dec_cls, "a", "I");
jint a = jni->GetIntField(param_obj, field);
line_len += sprintf(out + line_len, ", a: %d", a);
Errors::Check(error);
}
else if(table_ptr[j].signature[0] == 'L') { // object
error = m_jvmti->GetLocalObject(thread, 2, table_ptr[j].slot, ¶m_obj); // frame at depth zero is the current frame
Errors::Check(error);
}
if(param_obj != 0) {
//line_len += sprintf(out + line_len, ", modi: %d, this: %d, same: %d", modied, param_obj, jni->IsSameObject(param_obj, modied));
jlong param_obj_tag = 0;
m_jvmti->GetTag(param_obj, ¶m_obj_tag);
if(param_obj_tag == 0) {
error = m_jvmti->SetTag(param_obj, pTheAgent->ctag);
Errors::Check(error);
param_obj_tag = pTheAgent->ctag;
++pTheAgent->ctag;
}
line_len += sprintf(out + line_len, ", param_obj_tag: %ld", param_obj_tag);
//line_len += sprintf(out+line_len, ", slot:%d, start:%ld, cur:%ld, param:%s%s", table_ptr[j].slot, table_ptr[j].start_location, cur_loc, table_ptr[j].signature, table_ptr[j].name);
jni->DeleteLocalRef(param_obj);
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].signature));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].name));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].generic_signature));
}
}
error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr));
Errors::Check(error);
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(dec_cls_sig));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(mtd_name));
logList.push_back(out);
}
The class method used to inject:
public class MTrace {
private static int engaged = 0;
/* At the very beginning of every method, a call to method_entry()
* is injected.
*/
private static native void _method_entry(Object thread, int cnum, int mnum);
public static void method_entry(int cnum, int mnum)
{
if ( engaged != 0 ) {
_method_entry(Thread.currentThread(), cnum, mnum);
}
}
/* Before any of the return bytecodes, a call to method_exit()
* is injected.
*/
private static native void _method_exit(Object thread, int cnum, int mnum);
public static void method_exit(int cnum, int mnum)
{
if ( engaged != 0 ) {
_method_exit(Thread.currentThread(), cnum, mnum);
}
}
}
Note that these two examples is written for test purpose, not all the return value of jvmti functions are checked. Some other problems may also exist.
You are going to want to start by using GetLocalObject. In this respect I was able to find the following example that should help get you going in the right direction.
I need to wrap a BSD-like C socket API to Java with JNA. It has basically the same functions as standard BSD socket API.
Wrapping select() is problematic because of the fd_set-structure required in its arguments and the FD_* masking functions (macros) that are needed to handle fd_sets. I tried to crawl through the header files (e.g. sys/select.h in Ubuntu 8.04) but the definitions are not so straightforward. Especially I found it difficult to find the implementation of FD_*-macros, which is needed when wrapping them with JNA's InvocationMapper.
Note: I'm not trying to wrap the standard TCP or unix-socket API, but a custom one. Thus built-in sockets in Java do not fit the bill.
Especially I found it difficult to find the implementation of FD_*-macros, which is needed when wrapping them with JNA's InvocationMapper.
The C pre-processor cpp is useful to find out how macros are expanded. Write a dummy program that uses the relevant macros (it should be lexically correct, but needn't compile), run it through cpp and watch what happens.
I use a byte array for the fd_set structure and some arithmetic to find the right byte position within the array:
private static final int FD_SETSIZE = 1024;
private static final boolean isBigEndian = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
private static interface Libc extends Library {
int select (int nfds, byte[] readfds, byte[] writefds, byte[] errfds, TimeVal timeout);
//...
}
private static class FdSet {
byte[] a;
FdSet() {
a = new byte[FD_SETSIZE / 8]; }
void set (int fd) {
a[getBytePos(fd)] |= getBitMask(fd); }
boolean isSet (int fd) {
return (a[getBytePos(fd)] & getBitMask(fd)) != 0; }
private static int getBytePos (int fd) {
if (fd < 0 || fd >= LibcDefs.FD_SETSIZE) {
throw new RuntimeException("File handle out of range for fd_set."); }
if (isBigEndian) {
return (fd / 8 / Native.LONG_SIZE + 1) * Native.LONG_SIZE - 1 -
fd / 8 % Native.LONG_SIZE; }
else {
return fd / 8; }}
private static int getBitMask (int fd) {
return 1 << (fd % 8); }}
private static class TimeVal extends Structure {
public NativeLong tv_sec;
public NativeLong tv_usec;
TimeVal (int ms) {
set(ms); }
void set (int ms) {
tv_sec.setValue(ms / 1000);
tv_usec.setValue(ms % 1000 * 1000); }
#Override protected List<?> getFieldOrder() {
return Arrays.asList("tv_sec", "tv_usec"); }}
public boolean waitInputReady (int timeoutMs) throws IOException {
TimeVal timeVal = (timeoutMs < 0) ? null : new TimeVal(timeoutMs);
FdSet rxSet = new FdSet();
FdSet errorSet = new FdSet();
rxSet.set(fileHandle);
errorSet.set(fileHandle);
int rc = libc.select(fileHandle + 1, rxSet.a, null, errorSet.a, timeVal);
checkSelectErrors(rc, errorSet);
if (rc == 0) {
return false; }
if (!rxSet.isSet(fileHandle)) {
throw new RuntimeException("rxSet bit is not set after select()."); }
return true; }
public boolean waitOutputReady (int timeoutMs) throws IOException {
TimeVal timeVal = (timeoutMs < 0) ? null : new TimeVal(timeoutMs);
FdSet txSet = new FdSet();
FdSet errorSet = new FdSet();
txSet.set(fileHandle);
errorSet.set(fileHandle);
int rc = libc.select(fileHandle + 1, null, txSet.a, errorSet.a, timeVal);
checkSelectErrors(rc, errorSet);
if (rc == 0) {
return false; }
if (!txSet.isSet(fileHandle)) {
throw new RuntimeException("txSet bit is not set after select()."); }
return true; }
private void checkSelectErrors (int rc, FdSet errorSet) throws IOException {
if (rc == -1) {
throw new IOException("Error in select(), errno=" + Native.getLastError() + "."); }
boolean error = errorSet.isSet(fileHandle);
if (!(rc == 0 && !error || rc == 1 || rc == 2 && error)) {
throw new RuntimeException("Invalid return code received from select(), rc=" + rc + ", error=" + error + "."); }
if (error) {
throw new IOException("Channel error state detected"); }}