Как мне вызвать код Java из C в плагине и избежать случайных сбоевLinux

Ответить
Anonymous
 Как мне вызвать код Java из C в плагине и избежать случайных сбоев

Сообщение Anonymous »

Затем мой плагин, написанный на C, загружается и выполняет первоначальный вызов JNI_CreateJavaVM(). Кажется, он выходит из строя совершенно случайно. Запуск его один раз может оказаться неудачным, но в следующий раз он может оказаться успешным. Очень похоже, что что-то внутри JNI_CreateJavaVM() опирается на неинициализированные данные. Поскольку каждое небольшое изменение, которое я вношу в код, может изменить шансы. А если произойдет сбой, то, скорее всего, произойдет сбой позже, как если бы память была повреждена. Если это удастся, после этого все будет работать.
Запуск через valgrind, похоже, увеличивает (но не гарантирует) шансы на успех. Он также генерирует массу предупреждений (среди прочего, неверные записи под указателем стека).
Я пробовал это только в Linux, но с разными версиями Java, в основном с openjdk-25. Связывание осуществляется с помощью мезона (

Код: Выделить всё

dependency('jni')
и путь к libjvm.so задается с помощью rpath).
Плагин загружается с помощью dlopen(plugin, RTLD_NOW|RTLD_GLOBAL). На самом деле не имеет значения, для чего это плагин. Я попытался создать крошечную программу, которая просто загружала бы и мой плагин. (Считайте это концепцией написания плагинов на Java для приложений, написанных на C.)
Конечно, я позаботился о том, чтобы JavaVMInitArgs не содержал случайных байтов. И что jvm и env имеют значение NULL перед вызовом JNI_CreateJavaVM().
Я также пытался выполнить вызов в потоке с большим стеком. Я попытался увеличить стек с помощью setrlimit(). И я попробовал несколько разных вариантов JavaVMINitArgs. Любое такое изменение может привести к временной работе кода. Но тогда любое несвязанное изменение может снова перестать работать.
Есть предложения? Или JNI не должен работать в таких условиях. В таком случае, почему?
Изменить:
Это крошечная программа, которая загружает плагин:

Код: Выделить всё

#include 
#include 

int main(int argc, char **argv){
void *plugin;
int(*entry_point)();
plugin = dlopen("./plugin.so", RTLD_NOW|RTLD_GLOBAL);
if(!plugin){
fprintf(stderr, "dlopen() failed: %s\n", dlerror());
return 1;
}
entry_point = dlsym(plugin, "entry_point");
if(!entry_point){
fprintf(stderr, "dlsym() failed\n");
return 1;
}
entry_point();
entry_point();

printf("Back without crashing!\n");

return 0;
}
Точка входа в мой плагин в самой минимальной форме выглядит следующим образом:

Код: Выделить всё

#include 
#include 
#include 

static JavaVM *jvm;

int entry_point(){
JNIEnv *env;
if(!jvm){
JavaVMInitArgs args;
env = NULL;
memset(&args, 0, sizeof(JavaVMInitArgs));
args.version = JNI_VERSION_1_6;
args.nOptions = 0;
int result;
result = JNI_CreateJavaVM(&jvm, (void**)&env, &args);
if(result < 0){
fprintf(stderr, "JNI_CreateJavaVM() failed\n");
return result;
}
}else{
fprintf(stderr, "JVM already started by this plugin\n");
}

/* Other code that works if it gets this far. */

return 0;
}
Чтобы уточнить: все предупреждения от valgrind исходят из JNI_CreateVM(). Запуск с GBD всегда приводит к сбою сегмента.
Мне сказали, что JVM требует много места в стеке для работы (кто-то предложил не менее 16 МБ), поэтому я попробовал различные способы увеличения стека.
Мне также сказали, что JVM «очень чувствительна» к пользовательским обработчикам сигналов. Отсутствие возможности устанавливать обработчики сигналов неоптимально. Но отказ от обработки сигналов также не решает проблему.
И, наконец, мне сказали, что мне не следует даже пытаться это сделать, поскольку мне явно не хватает более глубокого понимания внутренней работы JVM. Это говорит о том, что здесь замешана какая-то черная магия. Но что вам действительно нужно знать, чтобы выполнить, казалось бы, простой вызов функции?
Редактировать 2:
Это файл сборки мезона для создания кода:

Код: Выделить всё

project('test', ['c', 'java'])

jni_dep = dependency('jni', modules: 'jvm', required: true, method: 'cmake')

x = run_command('java', '-XshowSettings:properties', '-version', check: false)
foreach line : x.stderr().split('\n')
tok = line.split('=')
if tok[0].strip() == 'java.home'
java_home = tok[1].strip()
x = run_command('find', java_home, '-name', 'libjvm.so', check: false).stdout().strip()
java_runpath = import('fs').parent(x)
break
endif
endforeach

executable('loader',
'loader.c')

shared_library('plugin',
'plugin.c',
name_prefix: '',
build_rpath: java_runpath,
dependencies: jni_dep)
Вот что вызывает мезон:

Код: Выделить всё

cc -Iloader.p -I. -I.. -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -O0 -g -MD -MQ loader.p/loader.c.o -MF loader.p/loader.c.o.d -o loader.p/loader.c.o -c ../loader.c

cc  -o loader loader.p/loader.c.o -Wl,--as-needed -Wl,--no-undefined

cc -Iplugin.so.p -I. -I.. -I/etc/java-config-2/current-system-vm/include -I/etc/java-config-2/current-system-vm/include/linux -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -O0 -g -fPIC -MD -MQ plugin.so.p/plugin.c.o -MF plugin.so.p/plugin.c.o.d -o plugin.so.p/plugin.c.o -c ../plugin.c

cc  -o plugin.so plugin.so.p/plugin.c.o -Wl,--as-needed -Wl,--no-undefined -shared -fPIC -Wl,-soname,plugin.so -Wl,-rpath,/usr/lib64/openjdk-25/lib/server /etc/java-config-2/current-system-vm/lib/server/libjvm.so
(Путь /etc/java-config-2/current-system-vm является символической ссылкой на /usr/lib64/openjdk-25, так что это не несоответствие версий. Но я также ожидаю, что libjvm.so будет иметь стабильный ABI и быть взаимозаменяемым даже между реализациями без перекомпиляции.)
Компилятор C Я использую GCC 15.2.1 (я также пробовал несколько старых версий GCC с тем же результатом). Точная версия JDK, которую я использую, — OpenJDK 25.0.2 (я также пробовал OpenJDK 8.462 и Icedtea 3.21 и, по крайней мере, еще одну, точно не помню, с тем же результатом).
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Linux»