【问题标题】:Why doesn't the JVM crash when entering infinite recursion?为什么JVM在进入无限递归时不会崩溃?
【发布时间】: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)。相反,stdlibclose 被调用,如在 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


【解决方案1】:

由于您正在编译共享库,并且函数close() 不是static,因此编译器通过过程链接表 (PLT) 进行间接调用。反汇编库时,您可能会看到一条指令

    call <close@plt>

myfancylibrary被加载时,进程已经有了来自libc的close的实现,所以动态liker更新PLT指向libc的close()版本。

【讨论】:

    猜你喜欢
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 1970-01-01
    相关资源
    最近更新 更多