在JNI集成的C++应用中,如何符号化Java代码的ASan栈追踪?
JNI嵌入JVM场景下ASan栈追踪的Java代码符号化问题
问题背景
我维护一个混合C和Java代码的应用,通过JNI在C中嵌入JVM。启用ASan(具体使用LSan)检测内存泄漏时,发现Java代码对应的栈追踪完全没有符号信息,还出现了<unknown module>标记。我有两个疑问:
- 是否有办法对这些无符号的地址进行符号化?
- 这些
<unknown module>是什么,如何对其进行符号化?
示例栈追踪1
Direct leak of 8 byte(s) in 1 object(s) allocated from: #0 0x4be0ed in malloc (/path/to/my/binary+0x4be0ed) #1 0x7f047a6ebd2c (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x958d2c) #2 0x7f047a3a3cee (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x610cee) #3 0x7f047a695f8d (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x902f8d) #4 0x7f047a3e44ff (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x6514ff) #5 0x7f047a690c82 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x8fdc82) #6 0x7f047a693137 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x900137) #7 0x7f047a6933ec (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x9003ec) #8 0x7f0466ad17a6 (<unknown module>) #9 0x7f0466ac0e3f (<unknown module>) #10 0x7f0466ac0e3f (<unknown module>) #11 0x7f0466ac0e3f (<unknown module>) #12 0x7f0466ac0e3f (<unknown module>) #13 0x7f0466ac0e3f (<unknown module>) #14 0x7f0466ac0e3f (<unknown module>) #15 0x7f0466ac0e3f (<unknown module>) #16 0x7f0466ac0e3f (<unknown module>) #17 0x7f0466ac0e3f (<unknown module>) #18 0x7f0466ac0e3f (<unknown module>) #19 0x7f0466ab94e6 (<unknown module>) #20 0x7f047a42a48f (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x69748f) #21 0x7f047a428b8e (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x695b8e) #22 0x7f047a82bc3e (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0xa98c3e) #23 0x7f047a5ae365 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x81b365) #24 0x7f047a5b5706 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x822706) #25 0x7f047a5b6e24 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x823e24) #26 0x7f047a41dbd0 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x68abd0) #27 0x7f0466ae2a87 (<unknown module>) #28 0x7f0466ac10f5 (<unknown module>) #29 0x7f0466ac0e3f (<unknown module>)
示例栈追踪2
Direct leak of 24 byte(s) in 1 object(s) allocated from: #0 0x4be0ed in malloc (/path/to/my/binary+0x4be0ed) #1 0x7f047a6ebd2c (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x958d2c) #2 0x7f047a1d4538 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x441538) #3 0x7f047a1d45f4 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x4415f4) #4 0x7f047a3e4e58 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x651e58) #5 0x7f047a3e4eb6 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x651eb6) #6 0x7f047a49ceb9 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x709eb9) #7 0x7f0475cd6e8c in Java_java_lang_System_setIn0 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/libjava.so+0x14e8c) #8 0x7f0466ad17a6 (<unknown module>) #9 0x7f0466ac10f5 (<unknown module>) #10 0x7f0466ab94e6 (<unknown module>) #11 0x7f047a42a48f (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x69748f) #12 0x7f047a428b8e (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x695b8e) #13 0x7f047a429187 (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x696187) #14 0x7f047a864f9a (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0xad1f9a) #15 0x7f047a4ab81e in JNI_CreateJavaVM (/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so+0x71881e) #16 0x71c1ab in jni::JVMManager::Start() /path/.../jni/jvm_manager.cpp:138:17 #17 0x4f3fbe in (anonymous namespace)::maybe_setup_jvm(...) /path/to/my/main/file.cpp:48:30 #18 0x4f3fbe in main /path/to/my/main/file.cpp:158:22 #19 0x7f04798c7d09 in __libc_start_main csu/../csu/libc-start.c:308:16
解答
1. 如何对Java代码对应的栈地址进行符号化
ASan原生仅支持解析C++这类原生代码的符号,Java代码运行在JVM的字节码解释器或JIT编译层,需要结合JVM专属工具链来解析:
- 安装带调试符号的JDK:
对于OpenJDK,安装对应的调试包(如openjdk-8-dbg),确保JVM本身的符号完整,同时为Java代码保留调试信息。 - 启用JVM栈帧指针保留:
启动应用时添加参数-XX:+PreserveFramePointer,让JVM保留栈帧指针,帮助工具更精准捕获Java栈信息。 - 结合JVM工具解析地址:
当ASan输出泄漏地址后,用jstack导出目标进程的当前栈,或者查看应用崩溃时生成的hs_err_pid文件,里面会包含Java栈与地址的映射,可关联到具体的Java方法和行号。 - 使用Java专用分析工具:
用async-profiler这类工具,它能同时捕获原生和Java栈的完整信息,配合ASan的泄漏地址,直接定位到Java代码中的泄漏点。
2. <unknown module>的本质与符号化方法
这些标记对应的是JVM运行时JIT编译生成的Java代码段,或是动态生成的字节码(比如动态代理、Lambda表达式)。由于JIT代码是运行时动态生成的,没有被系统动态链接器注册为标准模块,所以ASan无法识别。
符号化方法:
- 启用JIT编译的符号输出:
添加JVM参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+DebugNonSafepoints,JVM会在JIT编译时输出代码地址与Java方法的映射关系到控制台或日志,你可以根据ASan给出的地址在日志中查找对应方法。 - 用JVM工具查看JIT代码缓存:
执行jcmd <进程ID> Compiler.codecache,查看JIT代码缓存中所有编译方法的地址范围,匹配ASan输出的地址即可找到对应的Java方法。 - 加载
libjsig.so辅助识别:
设置环境变量LD_PRELOAD=/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/libjsig.so,这个库能让ASan等原生工具识别JIT生成的代码模块,直接解析出对应的Java方法。
内容的提问来源于stack exchange,提问作者vpshastry
相关产品推荐
相关产品推荐

