JNI String Corruption
- by Chris Dennett
Hi everyone, I'm getting weird string corruption across JNI calls which is causing problems on the the Java side. Every so often, I'll get a corrupted string in the passed array, which sometimes has existing parts of the original non-corrupted string. The C++ code is supposed to set the first index of the array to the address, it's a nasty hack to get around method call limitations. Additionally, the application is multi-threaded.
remoteaddress[0]: 10.1.1.2:49153
remoteaddress[0]: 10.1.4.2:49153
remoteaddress[0]: 10.1.6.2:49153
remoteaddress[0]: 10.1.2.2:49153
remoteaddress[0]: 10.1.9.2:49153
remoteaddress[0]: {garbage here}
java.lang.NullPointerException
at kokuks.KKSAddress.<init>(KKSAddress.java:139)
at kokuks.KKSAddress.createAddress(KKSAddress.java:48)
at kokuks.KKSSocket._recvFrom(KKSSocket.java:963)
at kokuks.scheduler.RecvOperation$1.execute(RecvOperation.java:144)
at kokuks.scheduler.RecvOperation$1.execute(RecvOperation.java:1)
at kokuks.KKSEvent.run(KKSEvent.java:58)
at kokuks.KokuKS.handleJNIEventExpiry(KokuKS.java:872)
at kokuks.KokuKS.handleJNIEventExpiry_fjni(KokuKS.java:880)
at kokuks.KokuKS.runSimulator_jni(Native Method)
at kokuks.KokuKS$1.run(KokuKS.java:773)
at java.lang.Thread.run(Thread.java:717)
remoteaddress[0]: 10.1.7.2:49153
The null pointer exception comes from trying to use the corrupt string. In C++, the address prints to standard out normally, but doing this reduces the rate of errors, from what I can see.
The C++ code (if it helps):
/*
* Class: kokuks_KKSSocket
* Method: recvFrom_jni
* Signature: (Ljava/lang/String;[Ljava/lang/String;Ljava/nio/ByteBuffer;IIJ)I
*/
JNIEXPORT jint JNICALL Java_kokuks_KKSSocket_recvFrom_1jni
(JNIEnv *env, jobject obj, jstring sockpath, jobjectArray addrarr, jobject buf, jint position, jint limit, jlong flags) {
if (addrarr && env->GetArrayLength(addrarr) > 0) {
env->SetObjectArrayElement(addrarr, 0, NULL);
}
jboolean iscopy;
const char* cstr = env->GetStringUTFChars(sockpath, &iscopy);
std::string spath = std::string(cstr);
env->ReleaseStringUTFChars(sockpath, cstr); // release me!
if (KKS_DEBUG) {
std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << std::endl;
}
ns3::Ptr<ns3::Socket> socket = ns3::Names::Find<ns3::Socket>(spath);
if (!socket) {
std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " socket not found for path!!" << std::endl;
return -1; // not found
}
if (!addrarr) {
std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " array to set sender is null" << std::endl;
return -1;
}
jsize arrsize = env->GetArrayLength(addrarr);
if (arrsize < 1) {
std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " array too small to set sender!" << std::endl;
return -1;
}
uint8_t* bufaddr = (uint8_t*)env->GetDirectBufferAddress(buf);
long bufcap = env->GetDirectBufferCapacity(buf);
uint8_t* realbufaddr = bufaddr + position;
uint32_t remaining = limit - position;
if (KKS_DEBUG) {
std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " bufaddr: " << bufaddr << ", cap: " << bufcap << std::endl;
}
ns3::Address aaddr;
uint32_t mflags = flags;
int ret = socket->RecvFrom(realbufaddr, remaining, mflags, aaddr);
if (ret > 0) {
if (KKS_DEBUG) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " addr: " << aaddr << std::endl;
ns3::InetSocketAddress insa = ns3::InetSocketAddress::ConvertFrom(aaddr);
std::stringstream ss;
insa.GetIpv4().Print(ss);
ss << ":" << insa.GetPort() << std::ends;
if (KKS_DEBUG) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " addr: " << ss.str() << std::endl;
jsize index = 0;
const char *cstr = ss.str().c_str();
jstring jaddr = env->NewStringUTF(cstr);
if (jaddr == NULL) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " jaddr is null!!" << std::endl;
//jaddr = (jstring)env->NewGlobalRef(jaddr);
env->SetObjectArrayElement(addrarr, index, jaddr);
//if (env->ExceptionOccurred()) {
// env->ExceptionDescribe();
//}
}
jint jret = ret;
return jret;
}
The Java code (if it helps):
/**
* Pass an array of size 1 into remote address, and this will be set with
* the sender of the packet (hax). This emulates C++ references.
*
* @param remoteaddress
* @param buf
* @param flags
* @return
*/
public int _recvFrom(final KKSAddress remoteaddress[], ByteBuffer buf, long flags) {
if (!kks.isCurrentlyThreadSafe()) throw new RuntimeException(
"Not currently thread safe for ns-3 functions!"
);
//lock.lock();
try {
if (!buf.isDirect()) return -6; // not direct!!
final String[] remoteAddrStr = new String[1];
int ret = 0;
ret = recvFrom_jni(
path.toPortableString(),
remoteAddrStr,
buf,
buf.position(),
buf.limit(),
flags
);
if (ret > 0) {
System.out.println("remoteaddress[0]: " + remoteAddrStr[0]);
remoteaddress[0] = KKSAddress.createAddress(remoteAddrStr[0]);
buf.position(buf.position() + ret);
}
return ret;
} finally {
errNo = _getErrNo();
//lock.unlock();
}
}
public int recvFrom(KKSAddress[] fromaddress, final ByteBuffer bytes, long flags, long timeoutMS) {
if (KokuKS.DEBUG_MODE) printMessage("public synchronized int recvFrom(KKSAddress[] fromaddress, final ByteBuffer bytes, long flags, long timeoutMS)");
if (kks.isCurrentlyThreadSafe()) {
return _recvFrom(fromaddress, bytes, flags); // avoid event
}
fromaddress[0] = null;
RecvOperation ro = new RecvOperation(
kks,
this,
flags,
true,
bytes,
timeoutMS
);
ro.start();
fromaddress[0] = ro.getFrom();
return ro.getRetCode();
}