【发布时间】:2019-06-24 16:46:37
【问题描述】:
我正在编写一个要加载到 JVM 中的共享库,但下面的行为让我陷入了困境。这是我的 Java 类:
package com.test;
public class UnixUtil {
static {
System.loadLibrary("myfancylibrary");
}
static native int openReadOnlyFd(String path);
static native int closeFd(int fd);
}
public class Main {
public static void main(String[] args){
int fd = UnixUtil.openReadOnlyFd("/tmp/testc");
UnixUtil.closeFd(fd);
}
}
要加载的库如下所示:
test_jni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_test_UnixUtil */
#ifndef _Included_com_test_UnixUtil
#define _Included_com_test_UnixUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_test_UnixUtil
* Method: openReadOnlyFd
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_test_UnixUtil_openReadOnlyFd
(JNIEnv *, jclass, jstring);
/*
* Class: com_test_UnixUtil
* Method: closeFd
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_test_UnixUtil_closeFd
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
test_jni.c
#include "test_jni.h"
#include "fs.h"
JNIEXPORT jint JNICALL Java_com_test_UnixUtil_openReadOnlyFd
(JNIEnv *e, jclass jc, jstring path){
const char *const native_path = ((*e) -> GetStringUTFChars)(e, path, NULL);
int fd = read_only_open(native_path);
((*e) -> ReleaseStringUTFChars)(e, path, native_path);
return fd;
}
JNIEXPORT jint JNICALL Java_com_test_UnixUtil_closeFd
(JNIEnv *e, jclass jc, jint fd){
printf("Closing files descriptord %d... \n", fd);
return close(fd);
}
fs.h
#ifndef FS_H
#define FS_H
int read_only_open(const char *path);
int close(int fd);
#endif //FS_H
fs.c
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include "fs.h"
int read_only_open(const char *path){
printf("Entering %s.%s:%d\n", __FILE__, __func__, __LINE__);
int fd = open(path, O_RDONLY);
return fd;
}
int close(int fd){ //Java_com_test_UnixUtil_closeFd does not invoke this function
printf("Entering %s.%s:%d\n", __FILE__, __func__, __LINE__);
int close_result = close(fd);
return close_result;
}
当编译和运行这个Main 类时,JVM 不会崩溃。它根本没有进入函数fs.h::close(int)。相反,stdlib 的 close 被调用,如在 GDB 中所见:
Thread 2 "java" hit Breakpoint 1, Java_com_test_UnixUtil_closeFd (e=<optimized out>,
jc=<optimized out>, fd=4) at /home/rjlomov/test_jni/src/main/java/com/test/lib/test_jni.c:17
17 return close(fd);
(gdb) step
18 }
(gdb)
17 return close(fd);
(gdb)
__close (fd=4) at ../sysdeps/unix/sysv/linux/close.c:27
27 ../sysdeps/unix/sysv/linux/close.c: No such file or directory.
(gdb)
26 in ../sysdeps/unix/sysv/linux/close.c
运行objdump -dS libmyfancylibrary.so 表明
JNIEXPORT jint JNICALL Java_com_test_UnixUtil_closeFd
(JNIEnv *e, jclass jc, jint fd){
7d0: 53 push %rbx
}
//...
return close(fd);
7e9: e9 62 fe ff ff jmpq 650 <close@plt> // <--- PLT section,
// resolved by linker to stdlib::close?
7ee: 66 90 xchg %ax,%ax
问题: 为什么在Java_com_test_UnixUtil_closeFd 中调用stdlib::close 而不是fs.c::close(int)?我唯一能想象的是 JVM 有自己的动态链接器来完成这项工作......
【问题讨论】:
-
尝试更改这个非常具体的名称。
-
@P__J__ 是的,它会起作用的。但我只是好奇它是如何工作的,因为类似的代码工作了很长时间,直到我的同事突然发现它。动态链接器可以简单地丢弃它已经知道的函数吗?
-
有些函数可能是内置的,具有特殊含义,用户代码无法更改。
-
@P__J__ 实际上我还尝试构建一个简单的手写二进制文件来检查行为是否相同并将其与类似的共享库链接(替换 JVM 所需的
test_jni.c的函数与int do_open_fd(const char *path);和void do_close_fd(int fd);) 并按预期与SEGV 崩溃... -
@Holger 你没有包含test_jni.h的内容我故意省略了它,因为它是由
javac -h . com/test/UnixUtil.java生成的,然后重命名为test_jni.h。#define close(X) implementation_specific_name(X)但这是否意味着fs.c::close(int)也会以这种方式进行预处理,引入implementation_specific_name的 2 个定义。
标签: java c jvm java-native-interface shared-libraries