为什么进入无限递归时JVM不会崩溃?

时间:2019-01-31 09:07:41

标签: java c jvm java-native-interface shared-libraries

我正在编写一个要加载到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)。相反,在GDB中可以看到stdlib的{​​{1}}:

close

运行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 中调用stdlib::close而不是Java_com_test_UnixUtil_closeFd?我唯一能想象的是JVM有自己的动态链接器,可以完成这项工作...

1 个答案:

答案 0 :(得分:5)

由于您正在编译共享库,而函数close()不是static,因此编译器会通过过程链接表(PLT)进行间接调用。拆卸图书馆时,您可能会看到一条说明

    call <close@plt>

在加载myfancylibrary时,该进程已经从libc实现了close的实现,因此动态Liker将PLT更新为指向libc的close()的版本。