I am writing a Java application that executes USB I/O on Windows 10 with Java 10 JDK. My i/o library is the libusb api. The app loads a JNI library .dll compiled/built in Visual Studio 2017 with both Microsoft Visual 2013 C++ and Microsoft Visual C++ 2015-2019 installed. This application generates the following crash warning when ran inside a Netbeans 11.1 project.
EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000002e1bbcd122e, pid=444, tid=3244
JRE version: Java(TM) SE Runtime Environment (10.0+46) (build 10+46)
Java VM: Java HotSpot(TM) 64-Bit Server VM (10+46, mixed mode, tiered, compressed oops, g1 gc, windows-amd64)
Problematic frame:
C [VCRUNTIME140D.dll+0x122e]
The problem function is my USB read function within the C code in the VisualStudio-built .dll.
JNI.c
JNIEXPORT jint JNICALL Java_SWMAPI_IoUSB_read
(JNIEnv * env, jobject thisObj, jbyteArray jArr, jint size, jlong javaHandle){
//Convert jLong to device handle pointer
libusb_device_handle * handle = (libusb_device_handle * )javaHandle;
//jbyteArray jArr: byte [] buffer. Must be overwritten with USB read data from device with
// corresponding handle javaHandle.
//jlong javaHandle: long passed from java representing the device to I/O.
//jint size: jArr length (e.g. buffer.length), used for debugging.
int status; //return code value
int bytes_written;
int bytes_read;
MESSAGE Message; //C struct containing 9 bytes of message header and (n) bytes of payload.
int length = (*env)->GetArrayLength(env, jArr);
jbyte * bufferIn = (*env)->GetPrimitiveArrayCritical(env, jArr, NULL); //get memory from jArr
//copy data in bufferIn to a Message struct.
memcpy((unsigned char *)&Message, (unsigned char *)bufferIn, length);
//write the Message out to device
bytes_written = libusb_control_transfer(handle, CTRL_OUT, MEM_RQ, 0, 0, (unsigned char *)&Message, length, 0);
if(bytes_written == length){
//read data in from Device to &Message
bytes_read = libusb_control_transfer(handle, CTRL_IN, MEM_RQ, 0, 0, (unsigned char
*)&Message, sizeof(MESSAGE), 30000);
status = (bytes_written < 0) ? bytes_read : // I/O Error
(bytes_written < length) ? USB_STATUS_SHORT_WRITE : // Check for short write
LIBUSB_SUCCESS;
if(Message.payload.length >= 0){ //If read data > 0
printf("Attempting memcpy\n");
fflush(stdout);
// The error appears on the line below
memcpy((unsigned char *)bufferIn,(unsigned char *)&Message, MAX_HEADER_LENGTH +
Message.payload.length);
printf("Success memcpy\n");
fflush(stdout);
}
}
(*env)->ReleasePrimitiveArrayCritical(env, jArr, bufferIn, 0);
return status;
}
This function has been working for my project up until today. Its frequency is approx 1 out of 3 runs. Can anyone please advise me on how I can move forward to solving this issue?
Related
Context
I am working on my Master Thesis using Intel SGX in order to use Enclave Code alongside a Java project.
The project uses Gradle for installation, and I have added a .jar containing a Class containing Native calls and the .so file containing the code for these calls.
I am currently only being able to open up an enclave and calculate an EC Curve for DH Key exchange, however, as the java code moves on, segfaults occur. These Segfaults however, dont occur inside the C code, as I am able to retrieve the correct and expected results from the native code in my java project. These segmentation faults usually happen there is heavy I/O workload (At least that is what I deduced so far), usually Sockets, which is something I use, since my thesis is in Distributed Systems.
C Code:
JNIEXPORT jint JNICALL Java_sgxUtils_SgxFunctions_jni_1initialize_1enclave
(JNIEnv *env , jobject,jint enclaveId, jbyteArray file_path){
if(file_path == NULL){
printf("NULL FILEPATH. error.");
return -1;
}
char token_path[MAX_PATH] = {'\0'};
sgx_launch_token_t token = {0};
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
int updated = 0;
//Get the id of a given Enclave:
global_eid = (int)enclaveId;
int length = snprintf(NULL , 0, "%d", enclaveId) + 1; //Gives the length of the Integer as a String.
char* id_ptr = (char*)malloc(length);
snprintf(id_ptr,length,"%d",enclaveId);
/* Step 1: try to retrieve the launch token saved by last transaction
* if there is no token, then create a new one.
*/
/* try to get the token saved in $HOME */
const char *home_dir = getpwuid(getuid())->pw_dir;
if (home_dir != NULL &&
(strlen(home_dir)+strlen("/")+sizeof(TOKEN_FILENAME)+1) <= MAX_PATH) {
/* compose the token path */
strncpy(token_path, home_dir, strlen(home_dir));
strncat(token_path, "/", strlen("/"));
char* correctName = (char*) malloc(length + sizeof(TOKEN_FILENAME) + 1);
memcpy(correctName,id_ptr,length);
strncat(correctName,TOKEN_FILENAME,sizeof(TOKEN_FILENAME)+1);
strncat(token_path,correctName,length + sizeof(TOKEN_FILENAME)+1);
// strncat(token_path, TOKEN_FILENAME, sizeof(TOKEN_FILENAME)+1);
// free(correctName);
} else {
/* if token path is too long or $HOME is NULL */
strncpy(token_path, TOKEN_FILENAME, sizeof(TOKEN_FILENAME));
}
FILE *fp = fopen(token_path, "rb");
if (fp == NULL && (fp = fopen(token_path, "wb")) == NULL) {
printf("Warning: Failed to create/open the launch token file \"%s\".\n", token_path);
}
if (fp != NULL) {
/* read the token from saved file */
size_t read_num = fread(token, 1, sizeof(sgx_launch_token_t), fp);
if (read_num != 0 && read_num != sizeof(sgx_launch_token_t)) {
/* if token is invalid, clear the buffer */
memset(&token, 0x0, sizeof(sgx_launch_token_t));
printf("Warning: Invalid launch token read from \"%s\".\n", token_path);
}
}
printf("\n");
/* Step 2: call sgx_create_enclave to initialize an enclave instance */
/* Debug Support: set 2nd parameter to 1 */
//Here we obtain the char* of the given Enclave file path:
size_t filename_size = env->GetArrayLength(file_path);
jbyte* jb_filePath = env->GetByteArrayElements(file_path,NULL);
char* enclave_filePath = (char*) malloc(filename_size);
memcpy(enclave_filePath,jb_filePath,filename_size);
ret = sgx_create_enclave(enclave_filePath, SGX_DEBUG_FLAG, &token, &updated, &global_eid, NULL);
if (ret != SGX_SUCCESS) {
if (fp != NULL) fclose(fp);
return -1;
}
/* Step 3: save the launch token if it is updated */
if (updated == FALSE || fp == NULL) {
/* if the token is not updated, or file handler is invalid, do not perform saving */
if (fp != NULL) fclose(fp);
return 0;
}
/* reopen the file with write capablity */
fp = freopen(token_path, "wb", fp);
if (fp == NULL) return 0;
size_t write_num = fwrite(token, 1, sizeof(sgx_launch_token_t), fp);
if (write_num != sizeof(sgx_launch_token_t))
printf("Warning: Failed to save launch token to \"%s\".\n", token_path);
fclose(fp);
free(id_ptr);
// env->ReleaseStringUTFChars(file_path,enclave_filePath);
return 0;
}
JNIEXPORT jbyteArray JNICALL Java_sgxUtils_SgxFunctions_jni_1sgx_1begin_1ec_1dh(JNIEnv *env, jobject){
printf("Begin DH \n");
if(init_happened == 1){ //Verify if the Random generators were seeded.
sgx_status_t init_status;
init_mbed(global_eid,&init_status);
init_happened = 0;
//printf("Init happened inside enclave.");
}
unsigned char* public_ec_key = (unsigned char*)malloc(EC_KEY_SIZE); //32 bytes allocated. (256)
printf("Calculating EC_DH.\n");
//Start the ECDH Ecall.
sgx_status_t ecdh_status;
create_ec_key_pair(global_eid,&ecdh_status,public_ec_key,EC_KEY_SIZE); //Send the buffer and its size.
if(ecdh_status != SGX_SUCCESS){
printf("Failed. ec_key_pair returned %d\n",ecdh_status);
}
//Copy the information to a Jbyte in order to return in a JbyteArray:
jbyte* toCopy = (jbyte*)malloc(EC_KEY_SIZE * sizeof(jbyte));
memcpy(toCopy,public_ec_key,EC_KEY_SIZE);
//Return the Public Key:
jbyteArray result = env->NewByteArray(EC_KEY_SIZE);
env->SetByteArrayRegion(result,0,EC_KEY_SIZE,toCopy);
//Free the pointers and return the arrray.
free(public_ec_key);
free(toCopy);
printf("EC_DH Calculated.\n");
return result;
}
I will omit the large output from Valgrind, it is just a huge file with '?' every line, since SGX hides the Stack trace, giving me no info on the invalid reads/Writes.
Valgrind Output
==38490== Warning: client switching stacks? SP change: 0x6a937a0 --> 0x28789d48
==38490== to suppress, use: --max-stackframe=567240104 or greater
==38490== Warning: client switching stacks? SP change: 0x28789d48 --> 0x6a937a0
==38490== to suppress, use: --max-stackframe=567240104 or greater
==38490== Warning: client switching stacks? SP change: 0x6a97220 --> 0x28789d48
==38490== to suppress, use: --max-stackframe=567225128 or greater
==38490== further instances of this message will not be shown.
==38490==
==38490== Process terminating with default action of signal 11 (SIGSEGV)
==38490== Access not within mapped region at address 0x2
==38490== at 0x27CD0B05: sig_handler_sim(int, siginfo_t*, void*) (in /opt/intel/sgxsdk/lib64/libsgx_urts_sim.so)
==38490== If you believe this happened as a result of a stack
==38490== overflow in your program's main thread (unlikely but
==38490== possible), you can try to increase the size of the
==38490== main thread stack using the --main-stacksize= flag.
==38490== The main thread stack size used in this run was 8388608.
==38490==
==38490== HEAP SUMMARY:
==38490== in use at exit: 16,636,139 bytes in 39,334 blocks
==38490== total heap usage: 55,264 allocs, 15,930 frees, 71,819,001 bytes allocated
Java Code
private void initEnclave() {
this.logger.info("Starting InitEnclave");
enclave = new SgxFunctions(this.enclaveId);
try {
File pemFile = new File(HOME_DIR + "/" + this.enclaveId + ".pem");
if(!pemFile.exists()) {
pemFile = SgxFunctions.createPem(this.enclaveId);
}
File enclaveFile = new File(HOME_DIR + "/" + this.enclaveId + "enclave_signed.so");
if(!enclaveFile.exists()) {
enclave.createSignedEnclave(HOME_DIR, pemFile.getAbsolutePath(), enclaveId);
this.logger.info("Enclave signed");
}
else
this.logger.info("Enclave file exists. Starting enclave.");
logger.info("ABSOLUTE PATH: " + enclaveFile.getAbsolutePath());
byte[] enclaveFilePath = enclaveFile.getAbsolutePath().substring(0).getBytes(StandardCharsets.UTF_8);
byte[] correctPath = Arrays.copyOf(enclaveFilePath, enclaveFilePath.length+1);
correctPath[correctPath.length-1] = '\0';
int created = enclave.jni_initialize_enclave(enclaveId,correctPath);
if(created == 0)
this.logger.info("Enclave created.");
else
this.logger.info("Enclave failed to create.");
//
// //Create DH parameters:
this.dh_params = this.enclave.jni_sgx_begin_ec_dh(); //Begin the Enclave EC parameters for DH key exchange.
this.logger.info("EC-DH Parameters created for Key exchange.");
} catch (Exception e) {
e.printStackTrace();
}
}
If I comment out the 'begin_ec_dh()' Call, the program will not segfault. However, even when running this native call, the Enclave will return an acceptable result and I am able to use the result in my Java code no problem, but only up until a certain point where it segfaults for an unkown reason.
I suspect its obviously my code, but I have no idea what is going on.
Any help is appreciated, Thank you for your help and sorry for the long post.
Edit:
I have added a printf() in order to retrieve a Crash file from the JVM over the filename Pointer.
What I dont understand is that If you look at my C code, I have allocated exactly the Array Size that comes from java (And I have added the null terminator in my Java Code, which was previously the source of another segfault), any idea what is causing this? Seems like I am dereferencing an Invalid pointer, but why is it invalid?
JVM Crash File
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007f9f51fd9627, pid=9910, tid=9911
#
# JRE version: OpenJDK Runtime Environment (17.0.3+7) (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
# Java VM: OpenJDK 64-Bit Server VM (17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
# Problematic frame:
# C [libc.so.6+0x188627]
#
# Core dump will be written. Default location: Core dumps may be processed with "/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E" (or dumping to /home/andrecruz/Tese/Con-BFT/build/install/library/core.9910)
#
# If you would like to submit a bug report, please visit:
# Unknown
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
--------------- S U M M A R Y ------------
Command Line: -Djava.security.properties=./config/java.security -Djava.library.path=./lib/ -Dfile.encoding=UTF-8 -XX:ErrorFile=/home/andrecruz/Tese/Con-BFT/ -XX:+ShowCodeDetailsInExceptionMessages -Dlogback.configurationFile=./config/logback.xml bftsmart.demo.map.MapServer 0 10
Host: Intel(R) Core(TM) i5-6440HQ CPU # 2.60GHz, 4 cores, 7G, Ubuntu 20.04.4 LTS
Time: Sun May 29 23:13:08 2022 WEST elapsed time: 2.134101 seconds (0d 0h 0m 2s)
--------------- T H R E A D ---------------
Current thread (0x00007f9f4c012b40): JavaThread "main" [_thread_in_native, id=9911, stack(0x00007f9f5061b000,0x00007f9f5071c000)]
Stack: [0x00007f9f5061b000,0x00007f9f5071c000], sp=0x00007f9f50718c28, free space=1015k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C [libc.so.6+0x188627]
C [libc.so.6+0x61d6f] _IO_printf+0xaf
j sgxUtils.SgxFunctions.jni_initialize_enclave(I[B)I+0
j bftsmart.tom.ServiceReplica.initEnclave()V+248
j bftsmart.tom.ServiceReplica.<init>(ILjava/lang/String;Lbftsmart/tom/server/Executable;Lbftsmart/tom/server/Recoverable;Lbftsmart/tom/server/RequestVerifier;Lbftsmart/tom/server/Replier;Lbftsmart/tom/util/KeyLoader;I)V+147
j bftsmart.tom.ServiceReplica.<init>(ILbftsmart/tom/server/Executable;Lbftsmart/tom/server/Recoverable;I)V+17
j bftsmart.demo.map.MapServer.<init>(II)V+35
j bftsmart.demo.map.MapServer.main([Ljava/lang/String;)V+34
v ~StubRoutines::call_stub
V [libjvm.so+0x83ae75]
V [libjvm.so+0x8cf4e9]
V [libjvm.so+0x8d2392]
C [libjli.so+0x4abe]
C [libjli.so+0x84ed]
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j sgxUtils.SgxFunctions.jni_initialize_enclave(I[B)I+0
j bftsmart.tom.ServiceReplica.initEnclave()V+248
j bftsmart.tom.ServiceReplica.<init>(ILjava/lang/String;Lbftsmart/tom/server/Executable;Lbftsmart/tom/server/Recoverable;Lbftsmart/tom/server/RequestVerifier;Lbftsmart/tom/server/Replier;Lbftsmart/tom/util/KeyLoader;I)V+147
j bftsmart.tom.ServiceReplica.<init>(ILbftsmart/tom/server/Executable;Lbftsmart/tom/server/Recoverable;I)V+17
j bftsmart.demo.map.MapServer.<init>(II)V+35
j bftsmart.demo.map.MapServer.main([Ljava/lang/String;)V+34
v ~StubRoutines::call_stub
siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000020
Register to memory mapping:
RAX=0x0000000000000010 is an unknown value
RBX=0x00007f9f51ec7b43: <offset 0x0000000000076b43> in /lib/x86_64-linux-gnu/libc.so.6 at 0x00007f9f51e51000
RCX=0x000000000000000f is an unknown value
RDX=0x000000000000002f is an unknown value
RSP=0x00007f9f50718c28 is pointing into the stack for thread: 0x00007f9f4c012b40
RBP=0x00007f9f507191a0 is pointing into the stack for thread: 0x00007f9f4c012b40
RSI=0x00007f9f50719168 is pointing into the stack for thread: 0x00007f9f4c012b40
RDI=0x0000000000000020 is an unknown value
R8 =0x00000000ffffffff points into unknown readable memory: 00
R9 =0x0000000000000009 is an unknown value
R10=0x00007f9f50610099: <offset 0x0000000000007099> in /home/andrecruz/Tese/Con-BFT/build/install/library/lib/libSgx.so at 0x00007f9f50609000
R11=0x000000000000002f is an unknown value
R12=0x00007f9f5203e6a0: _IO_2_1_stdout_+0x0000000000000000 in /lib/x86_64-linux-gnu/libc.so.6 at 0x00007f9f51e51000
R13=0x00007f9f5061008f: <offset 0x000000000000708f> in /home/andrecruz/Tese/Con-BFT/build/install/library/lib/libSgx.so at 0x00007f9f50609000
R14=0x00007f9f507191b0 is pointing into the stack for thread: 0x00007f9f4c012b40
R15=0x0000000000000073 is an unknown value
I am trying to call the following java method from C++
final String[] games = GameLoader.listGames();
where GameLoader was imported with import player.GameLoader;. The GameLoader class exists within a jar file. I am trying to use JNI to load the jar file from C++ and then call the above method. The following is my C++ code which I have tried to extend from this SO post.
#include <iostream>
#include <string.h>
#include <jni.h>
#include <stdlib.h>
using namespace std;
#define PATH_SEPARATOR ';'
#define USER_CLASSPATH "Ludii-0.3.0.jar"
#define GAME_LOADER "player/GameLoader"
JNIEnv *env;
JavaVM *jvm;
jint res;
void initJVM() {
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString =
"-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
}
void closeJVM() {
jvm->DestroyJavaVM();
}
int main() {
initJVM();
jclass gameLoader = env->FindClass(GAME_LOADER);
//final String[] games = GameLoader.listGames();
jmethodID mid = env->GetMethodID(gameLoader,"listGames","()[Ljava/lang/String;");
jobjectArray stringArray = (jobjectArray) env->CallObjectMethod(gameLoader,mid);
closeJVM();
}
I am able to successfully create the JVM and the code compiles however at runtime I get the error
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007f0e835619d7, pid=3504, tid=0x00007f0e84028740
#
# JRE version: OpenJDK Runtime Environment (8.0_222-b10) (build 1.8.0_222-8u222-b10-1ubuntu1~16.04.1-b10)
# Java VM: OpenJDK 64-Bit Server VM (25.222-b10 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V [libjvm.so+0x6849d7]
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/alex/jni_examples/hs_err_pid3504.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
#
Aborted (core dumped)
I think that the error is related to the CallObjectMethod call but I'm not sure.
Also I am using Java 8
EDIT:
It looks like mid evaluated to 0 which means that the method was not found. I guess this is the problem however I'm not sure why it wasn't found. This was checked with
int main() {
initJVM();
jclass gameLoader = env->FindClass("player/GameLoader");
//final String[] games = GameLoader.listGames();
if(gameLoader == NULL)
{
cout << "Could not load class!" << endl;
return 1;
}
jmethodID mid = env->GetMethodID(gameLoader,"listGames","()[Ljava/lang/String;");
if(mid == NULL)
{
cout << "Could not load method!" << endl;
return 1;
}
jobjectArray stringArray = (jobjectArray) env->CallObjectMethod(gameLoader,mid);
if(stringArray == NULL)
{
cout << "Could not load object!" << endl;
return 1;
}
closeJVM();
}
ANOTHER EDIT:
So it looks like the problem was that listGames() should be found with GetStaticMethodID rather than GetMethodID and it should be called with CallStaticObjectMethod rather than CallObjectMethod. Thank you.
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x5f0c25fe, pid=14780, tid=11168
#
# JRE version: Java(TM) SE Runtime Environment (7.0_80-b15) (build 1.7.0_80-b15)
# Java VM: Java HotSpot(TM) Client VM (24.80-b11 mixed mode, sharing windows-x86 )
# Problematic frame:
# C [ZBRGraphics.dll+0x25fe]
I keep getting this error when using the Zebra printer DLL in Java program.
public class Tester {
public static void main(String[] args) {
ZBRGraphics zGraphics = ZBRGraphics.INSTANCE;
String text = "Print this";
byte[] textB = text.getBytes(StandardCharsets.UTF_8);
String font= "Arial";
byte[] fontB = text.getBytes(StandardCharsets.UTF_8);
System.out.println(zGraphics.ZBRGDIDrawText(0, 0, textB, fontB, 12, 1, 0x0FF0000, 0));
}
}
public interface ZBRGraphics extends Library {
ZBRGraphics INSTANCE = (ZBRGraphics) Native.loadLibrary("ZBRGraphics", ZBRGraphics.class);
int ZBRGDIDrawText(int x, int y, byte[] text, byte[] font, int fontSize, int fontStyle, int color, int err);
}
I have the DLL in C:\Windows\System32 and in my 32 bit Java .
I'm using a 64 bit machine as my laptop for development.
If my google-fu skills are any good, you appear to be interfacing with the Zebra printer's API. According to the "ZXP1 & ZXP3 Software Developers Reference Manual" (found here), the Java mapping of the function is incorrect.
This is the actual C function prototype:
int ZBRGDIDrawText(
int x,
int y,
char *text,
char *font,
int fontSize,
int fontStyle,
int color,
int *err
)
As you can see, err is not an int, but a pointer to one. Also, since text and font are strings, you can just use a String as the Java type. Additionally, the API docs say that the return value is an int with either 1 for success or 0 for failure, meaning that you can use a boolean for ease of use.
The following Java mapping should be correct:
boolean ZBRGDIDrawText(
int x,
int y,
String text,
String font,
int fontSize,
int fontStyle,
int color,
IntByReference err
);
and you might use it like so:
IntByReference returnCode = new IntByReference(0);
boolean success = zGraphics.ZBRGDIDrawText(
0,
0,
"Print this",
"Arial",
12,
1,
0x0FF0000,
returnCode
);
if (success) {
System.out.println("success");
} else {
System.out.println("ZBRGDIDrawText failed with code " + returnCode.getValue());
}
I have in my Java class a string variable:
public class myclass {
protected final String file;
myclass(String f) {
file = f;
}
public native void processFiles();
public static void main(String[] args) {
myclass mc = new myclass(args[0]);
mc.processFiles();
}
}
In C++, I have:
JNIEXPORT void JNICALL Java_myclass_processFiles(JNIEnv *env, jobject obj) {
jclass baseClass = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(baseClass, "file", "Ljava/lang/String;");
jchar filename = env->GetCharField(baseClass, fid);
jstring fileFieldString = env->NewString(&filename, sizeof(filename));
const char *nativeFileString = env->GetStringUTFChars(fileFieldString, NULL);
printf("JNI: File path: %s\n", nativeFileString);
env->ReleaseStringUTFChars(fileFieldString, nativeFileString);
}
My output is:
JNI: File path: ??2
What am I doing wrong that isn't converting the Java string to the char* string properly? I am providing the path ~/Desktop/myfile as the sole argument, so there is a value in args[0]. My thought was that sizeof(filename) wasn't right, but there's no other option from what I can tell.
I did try this: JNI. How to get jstring from jobject and convert it to char* but when I typecast the result from GetObjectField() to jstring, I get an error:
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00000001043111e8, pid=6191, tid=3591
#
# JRE version: Java(TM) SE Runtime Environment (8.0_45-b14) (build 1.8.0_45-b14)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.45-b02 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# V [libjvm.dylib+0x3111e8] jni_GetStringUTFChars+0x66
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
Also, this is just JNI and Java 8 on OSX, nothing related to Android.
Thank you.
Update:
I was able to get a friend to look at it, and got it working with:
jfieldID fid = env->GetFieldID(baseClass, "file", "Ljava/lang/String;");
jstring jstr = (jstring) env->GetObjectField(thiz, fid);
const char *nativeFileString = env->GetStringUTFChars(jstr, NULL);
printf("JNI: File path: %s\n", nativeFileString);
You are doing jchar filename = env->GetCharField(baseClass, fid);
But fid is a field of type Ljava/lang/String;, not a char. So you should get that String using env->GetObjectField() and then follow what that link says.
You can also debug this better by adding env->ExceptionDescribe() after every line to see if an Exception is being thrown after your calls to env (that would be just to debug, in real production code you should be checking for exceptions after every env call and do something if something goes wrong).
By the way, maybe your code is just an example, but if that's your real code, it would be a lot easier to declare the native method as static and just pass the string as a parameter.
I want to load my own native libraries in my java application. Those native libraries depend upon third-party libraries (which may or may not be present when my application is installed on the client computer).
Inside my java application, I ask the user to specify the location of dependent libs. Once I have this information, I am using it to update the "LD_LIBRARY_PATH" environment variable using JNI code. The following is the code snippet that I am using to change the "LD_LIBRARY_PATH" environment variable.
Java code
public static final int setEnv(String key, String value) {
if (key == null) {
throw new NullPointerException("key cannot be null");
}
if (value == null) {
throw new NullPointerException("value cannot be null");
}
return nativeSetEnv(key, value);
}
public static final native int nativeSetEnv(String key, String value);
Jni code (C)
JNIEXPORT jint JNICALL Java_Test_nativeSetEnv(JNIEnv *env, jclass cls, jstring key, jstring value) {
const char *nativeKey = NULL;
const char *nativeValue = NULL;
nativeKey = (*env)->GetStringUTFChars(env, key, NULL);
nativeValue = (*env)->GetStringUTFChars(env, value, NULL);
int result = setenv(nativeKey, nativeValue, 1);
return (jint) result;
}
I also have corresponding native methods to fetch the environment variable.
I can successfully update the LD_LIBRARY_PATH (this assertion is based on the output of C routine getenv().
I am still not able to load my native library. The dependent third-party libraries are still not detected.
Any help/pointers are appreciated. I am using Linux 64 bit.
Edit:
I wrote a SSCE (in C) to test if dynamic loader is working. Here is the SSCE
#include
#include
#include
#include
int main(int argc, const char* const argv[]) {
const char* const dependentLibPath = "...:";
const char* const sharedLibrary = "...";
char *newLibPath = NULL;
char *originalLibPath = NULL;
int l1, l2, result;
void* handle = NULL;
originalLibPath = getenv("LD_LIBRARY_PATH");
fprintf(stdout,"\nOriginal library path =%s\n",originalLibPath);
l1 = strlen(originalLibPath);
l2 = strlen(dependentLibPath);
newLibPath = (char *)malloc((l1+l2)*sizeof(char));
strcpy(newLibPath,dependentLibPath);
strcat(newLibPath,originalLibPath);
fprintf(stdout,"\nNew library path =%s\n",newLibPath);
result = setenv("LD_LIBRARY_PATH", newLibPath, 1);
if(result!=0) {
fprintf(stderr,"\nEnvironment could not be updated\n");
exit(1);
}
newLibPath = getenv("LD_LIBRARY_PATH");
fprintf(stdout,"\nNew library path from the env =%s\n",newLibPath);
handle = dlopen(sharedLibrary, RTLD_NOW);
if(handle==NULL) {
fprintf(stderr,"\nCould not load the shared library: %s\n",dlerror());
exit(1);
}
fprintf(stdout,"\n The shared library was successfully loaded.\n");
result = dlclose(handle);
if(result!=0) {
fprintf(stderr,"\nCould not unload the shared library: %s\n",dlerror());
exit(1);
}
return 0;
}
The C code also does not work. Apparently, the dynamic loader is not rereading the LD_LIBRARY_PATH environment variable. I need to figure out how to force the dynamic loader to re-read the LD_LIBRARY_PATH environment variable.
See the accepted answer here:
Changing LD_LIBRARY_PATH at runtime for ctypes
In other words, what you're trying to do isn't possible. You'll need to launch a new process with an updated LD_LIBRARY_PATH (e.g., use ProcessBuilder and update environment() to concatenate the necessary directory)
This is a hack used to manipulate JVM's library path programmatically. NOTE: it relies on internals of ClassLoader implementation so it might not work on all JVMs/versions.
String currentPath = System.getProperty("java.library.path");
System.setProperty( "java.library.path", currentPath + ":/path/to/my/libs" );
// this forces JVM to reload "java.library.path" property
Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
fieldSysPath.setAccessible( true );
fieldSysPath.set( null, null );
This code uses UNIX-style file path separators ('/') and library path separator (':'). For cross-platform way of doing this use System Properties to get system-specific separators: http://download.oracle.com/javase/tutorial/essential/environment/sysprop.html
I have successfully implemented something similar for CollabNet Subversion Edge, which depends on the SIGAR libraries across ALL Operating Systems (we support Windows/Linux/Sparc both 32 bits and 64 bits)...
Subversion Edge is a web application that helps one managing Subversion repositories through a web console and uses SIGAR to the SIGAR libraries helps us provide users data values directly from the OS... You need to update the value of the property "java.library.path" at runtime. (https://ctf.open.collab.net/integration/viewvc/viewvc.cgi/trunk/console/grails-app/services/com/collabnet/svnedge/console/OperatingSystemService.groovy?revision=1890&root=svnedge&system=exsy1005&view=markup Note that the URL is a Groovy code, but I have modified it to a Java here)...
The following example is the implementation in URL above... (On Windows, your user will be required to restart the machine if he/she has downloaded the libraries after or downloaded them using your application)... The "java.library.path" will update the user's path "usr_paths" instead of System path "sys_paths" (permissions exception might be raised depending on the OS when using the latter).
133/**
134 * Updates the java.library.path at run-time.
135 * #param libraryDirPath
136 */
137 public void addDirToJavaLibraryPathAtRuntime(String libraryDirPath)
138 throws Exception {
139 try {
140 Field field = ClassLoader.class.getDeclaredField("usr_paths");
141 field.setAccessible(true);
142 String[] paths = (String[])field.get(null);
143 for (int i = 0; i < paths.length; i++) {
144 if (libraryDirPath.equals(paths[i])) {
145 return;
146 }
147 }
148 String[] tmp = new String[paths.length+1];
149 System.arraycopy(paths,0,tmp,0,paths.length);
150 tmp[paths.length] = libraryDirPath;
151 field.set(null,tmp);
152 String javaLib = "java.library.path";
153 System.setProperty(javaLib, System.getProperty(javaLib) +
154 File.pathSeparator + libraryDirPath);
155
156 } catch (IllegalAccessException e) {
157 throw new IOException("Failed to get permissions to set " +
158 "library path to " + libraryDirPath);
159 } catch (NoSuchFieldException e) {
160 throw new IOException("Failed to get field handle to set " +
161 "library path to " + libraryDirPath);
162 }
163 }
The Bootstrap services (Groovy on Grails application) class of the console runs a service and executes it with the full path to the library directory... UNiX-based servers do not need to restart the server to get the libraries, but Windows boxes do need a server restart after the installation. In your case, you would be calling this as follows:
String appHomePath = "/YOUR/PATH/HERE/TO/YOUR/LIBRARY/DIRECTORY";
String yourLib = new File(appHomePath, "SUBDIRECTORY/").getCanonicalPath();
124 try {
125 addDirToJavaLibraryPathAtRuntime(yourLib);
126 } catch (Exception e) {
127 log.error("Error adding the MY Libraries at " + yourLib + " " +
128 "java.library.path: " + e.message);
129 }
For each OS you ship your application, just make sure to provide a matching version of the libraries for the specific platform (32bit-Linux, 64bit-Windows, etc...).