如何为Java转储打印托管和非托管调用堆栈?

时间:2016-09-29 01:59:09

标签: java debugging windbg dump

在.Net中,我们可以使用!DumpStack从转储中打印调用堆栈中的托管和非托管代码方法,我是否可以知道Java中是否还有一个可以同时打印托管和未管理的调用堆栈?

3 个答案:

答案 0 :(得分:1)

首先,我想评论这个问题对我来说似乎没问题,我发现关闭你之前的问题和一些令人震惊的评论,再次证明了SO的大部分问题。

如果问这些关于.NET的问题是否正常,那么询问他们关于Java,即使答案结果是&# 34;你不能"。

(我将加入并挑选片刻:" Managed"是在CLR下运行的代码的Microsoft术语。你想问一些类似于"我怎么能在WinDbg中获取一个可用的堆栈跟踪,其中包含JVM本机代码和在其中运行的JITted Java代码?")

现在问你的问题:

简介

不幸的是,我不知道WinDbg的工具或插件可以做到这一点,但我认为有一种方法可以让你自己制作一个。 (我以为我可以接受它,但我找不到时间,所以我想我至少会给你提供信息。既然你问了两次这个问题,我想这可能很重要所以你很难采取必要的步骤。)

解决您的问题可以分为两部分:

  1. 获取与JITted代码的地址相对应的名称("生成符号")。
  2. 在WinDbg中使用生成的符号。
  3. 我会按顺序讨论它们并完成一些警告。

    生成符号

    Brendan Gregg basically the same problem with profiling Java code。他需要JITted代码的函数名称。解决方案,用他自己的话说:

      

    解决上述两个问题的一种方法是:

         
        
    1. JVMTI代理perf-map-agent,可以为perf提供Java符号表(/tmp/perf-PID.map)。
    2.   
    3. 修补JDK热点,重新引入帧指针寄存器,允许完全堆栈行走。
    4.   

    JVMTI代表Java虚拟机工具接口。 JVMTI代理基本上是JVM的插件,它为他感兴趣的JVM事件实现回调。它可以动态加载或链接到JVM二进制文件中(如果您正在进行JVM的自定义构建)。

    在perf-map-agent的情况下,插件要求了解所有JITted方法和一些其他动态生成的代码。它得到了他们的名字,地址和大小。然后它将此信息吐出到稍后由Linux perf。

    使用的文件中

    你应该分叉它,更改任何特定于Linux的东西(我没有注意到任何东西,但可能有隐藏的东西)并在Windows中将其构建为DLL Windows版的HotSpot。

    您还应该更改生成输出文件的函数(在src/c/perf-map-file.c中),以您可以使WinDbg使用的格式输出信息,这将我们带到第二部分。

    在WinDbg中使用符号

    有几种方法可以让WinDbg消耗由JVMTI代理生成的符号,你可以考虑更多。我会想到一些想到的东西:

    1. 生成MAP文件并将其转换为WinDbg将加载的内容。
      • 也许可以使用map2dbg将其转换为DBG文件。
      • 也许甚至尝试使用cv2pdb来制作PDB。
    2. 自己生成PDB,可能使用microsoft-pdb项目中GitHub上Microsoft发布的信息。
    3. 使用调用AddSyntheticSymbol的WinDbg扩展名将符号加载到WinDbg中 你有一个执行here的代码(由blabb编写,他在windbg标签上的SO中处于活动状态)。在www.woodmann.com上已经习惯了该代码的二进制文件,但该网站现在对我不起作用(并且已经有很长一段时间了)。似乎有二进制here
    4. 选项3对我来说似乎最简单(并且从我的经验中效果最好)但你可以做任何你想做的事情。

      注意事项

      RBP链

      你可能还记得在Brendan Gregg的引言中,有一些关于修补HotSpot以保持RBP链的东西。也许HotSpot的Windows实现动态创建了WinDbg在x64上用于遍历堆栈的展开信息。也许它没有。我不知道。

      在任何情况下,如果您使用的是相对较新的版本,则无需修补JVM。从JDK 8u60 build 19开始,此功能随-XX:+PreserveFramePointer标志一起提供。

      冻结JVM

      SOS知道如何解析内存中的CLR数据结构。我认为它并不需要CLR本身的帮助。这就是为什么你总是需要用于创建转储的CLR的确切SOS版本的原因。例如。

      与此相反,JVMTI插件已加载 JVM中。一旦你进入调试器,代理就无法做任何事情,假设它甚至被加载了。

      perf-map-agent由一个小型Java程序(在他的repo中src/java/AttachOnce.java)加载,获取有关当前JITted函数的信息及其相关信息。您可以执行相同操作,并在中断之前运行代理,或让代理始终工作并不断输出有关JITted方法的信息(如果您正在进行事后调试,例如Java和Java)过程是mostly dead)。在这种情况下,您必须对perf-map-agent代码进行一些更改。

      将参数传递给嵌入式JVM

      如果您正在调试具有嵌入式JVM的应用程序(如Eclipse等;您运行java.exe args ) ,您可能希望使用JAVA_TOOL_OPTIONS环境变量,该变量允许您传递上述-XX:+PreserveFramePointer-agentlib / -agentpath等参数,以便从头开始加载JVMTI代理附加到像pref-map-agent那样的正在运行的JVM上。

      结束语

      很抱歉,我无法为您提供一个工作工具,但是如果您确实需要,那么它似乎非常可行。除非在没有放松信息的情况下WinDbg无法完全堆叠,否则我不认为应该有任何特殊问题。

答案 1 :(得分:1)

使用jstack.exe。这很直接。

C:\Users\conio\Desktop\code\JNA2>"C:\Program Files\Java\jdk1.8.0_102\bin\jstack.exe" -?
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

您希望使用-m来获取Java堆栈和本机堆栈 如果你想从一个崩溃的进程中获取堆栈但是附加了一个事后调试器,你应该使用-F标志。

例如,我有以下使用JNA的代码:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

import com.sun.jna.Pointer;

public class Foo
{
public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary)Native.loadLibrary("msvcrt", CLibrary.class);

        void printf(String format, Object... args);
        Pointer malloc(int size);
        Pointer memset(Pointer dest, int ch, int count);
        void free(Pointer ptr);
    }


    public static void main(String[] args)
    {    
        CLibrary.INSTANCE.printf("Hello, World\n");

        Pointer buf = CLibrary.INSTANCE.malloc(2);
        CLibrary.INSTANCE.memset(buf, 0, 0x20);
        CLibrary.INSTANCE.free(buf);

        CLibrary.INSTANCE.printf("Goodbye, Cruel World\n");
    }
}

当我构建并执行此代码时,它崩溃并且WinDbg附加到它(因为它被设置为我的事后调试器)。

Microsoft (R) Windows Debugger Version 10.0.14321.1024 X86
Copyright (c) Microsoft Corporation. All rights reserved.

*** wait with pending attach
<<<snip>>>
(1f0.14d0): Unknown exception - code c0000374 (!!! second chance !!!)
eax=011eea00 ebx=77b5c908 ecx=00000001 edx=77b5c8d0 esi=00000002 edi=01251240
eip=77b29841 esp=011ee9dc ebp=011eea6c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!RtlReportCriticalFailure+0x89:
77b29841 eb33            jmp     ntdll!RtlReportCriticalFailure+0xbe (77b29876)
0:003> !error c0000374
Error code: (NTSTATUS) 0xc0000374 (3221226356) - A heap has been corrupted.
0:003> k
 # ChildEBP RetAddr  
00 011eea6c 77b2cfe2 ntdll!RtlReportCriticalFailure+0x89
01 011eea78 77b2b763 ntdll!RtlpReportHeapFailure+0x32
02 011eea88 77ad16cf ntdll!RtlpHeapHandleError+0x1c
03 011eeab8 77ae278b ntdll!RtlpLogHeapFailure+0x9f
04 011eebc8 77a978aa ntdll!RtlpFreeHeap+0x4aa1b
05 011eebf4 749d77c5 ntdll!RtlFreeHeap+0xba
*** WARNING: Unable to verify checksum for C:\Users\conio\AppData\Local\Temp\jna-71761103\jna991038711348528033.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Users\conio\AppData\Local\Temp\jna-71761103\jna991038711348528033.dll - 
06 011eec40 6e562670 msvcrt!free+0x65
WARNING: Stack unwind information not available. Following frames may be wrong.
07 011eec4c 6e55d8e2 jna991038711348528033!Java_com_sun_jna_Native_setDetachState+0x7020
08 011eec88 6e553c74 jna991038711348528033!Java_com_sun_jna_Native_setDetachState+0x2292
09 011ef580 6e5548c4 jna991038711348528033!Java_com_sun_jna_Native_invokePointer+0xca4
0a 011ef5a8 02c4d3b3 jna991038711348528033!Java_com_sun_jna_Native_invokeVoid+0x24
0b 011ef5ec 02c44854 0x2c4d3b3
0c 011ef62c 02c447b4 0x2c44854
0d 011ef678 02c447b4 0x2c447b4
0e 011ef784 02c40697 0x2c447b4
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files (x86)\Java\jre1.8.0_102\bin\client\jvm.dll - 
0f 011ef794 6e74a1b2 0x2c40697
10 011ef838 6e8104fe jvm!JVM_GetThreadStateNames+0x4cc82
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files (x86)\Java\jre1.8.0_102\bin\java.exe
11 011ef9bc 0083229e jvm!JVM_FindSignal+0x63b3e
12 011ef9d4 02b31310 java+0x229e
13 011ef9d8 02b31310 0x2b31310
14 011efa08 0083aeaf 0x2b31310
15 011efa40 0083af39 java+0xaeaf
16 011efa4c 751f62c4 java+0xaf39
17 011efa60 77ab0609 KERNEL32!BaseThreadInitThunk+0x24
18 011efaa8 77ab05d4 ntdll!__RtlUserThreadStart+0x2f
19 011efab8 00000000 ntdll!_RtlUserThreadStart+0x1b
0:003> |
.  0    id: 1f0 attach  name: C:\Program Files (x86)\Java\jre1.8.0_102\bin\java.exe
0:003> ? 0x1f0
Evaluate expression: 496 = 000001f0
0:003> .dump /ma C:\Users\conio\Desktop\code\JNA2\Foo.dmp
Creating C:\Users\conio\Desktop\code\JNA2\Foo.dmp - mini user dump
Dump successfully written

执行jstack

C:\Users\conio\Desktop\code\JNA2>"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\jstack.exe" -F -m 496
Attaching to process ID 496, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 25.102-b14
Deadlock Detection:

No deadlocks found.

----------------- 0 -----------------
<<<snip>>>
----------------- 3 -----------------
0x77b29841      ntdll!RtlpNtSetValueKey + 0x4e1
0x77b2cfe2      ntdll!RtlpNtSetValueKey + 0x3c82
0x77ad16cf      ntdll!wcstok_s + 0x5baf
0x77ae278b      ntdll!LdrSetAppCompatDllRedirectionCallback + 0xff2b
0x77a978aa      ntdll!RtlFreeHeap + 0xba
0x749d77c5      msvcrt!free + 0x65
0x6e562670      jna991038711348528033!_Java_com_sun_jna_Native_setDetachState@20 + 0x7020
0x6e55d8e2      jna991038711348528033!_Java_com_sun_jna_Native_setDetachState@20 + 0x2292
0x6e553c74      jna991038711348528033!_Java_com_sun_jna_Native_invokePointer@24 + 0xca4
0x6e5548c4      jna991038711348528033!_Java_com_sun_jna_Native_invokeVoid@24 + 0x24
0x02c4d3b3      * com.sun.jna.Native.invokeVoid(long, int, java.lang.Object[]) bci:0 (Interpreted frame)
0x02c44854      * com.sun.jna.Function.invoke(java.lang.Object[], java.lang.Class, boolean) bci:29 line:374 (Interpreted frame)
0x02c447b4      * com.sun.jna.Function.invoke(java.lang.reflect.Method, java.lang.Class[], java.lang.Class, java.lang.Object[], java.util.Map) bci:249 line:323 (Interpreted frame)
0x02c447b4      * com.sun.jna.Library$Handler.invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) bci:348 line:236 (Interpreted frame)
0x02c447e9      * com.sun.proxy.$Proxy0.free(com.sun.jna.Pointer) bci:16 (Interpreted frame)
0x02c44889      * Foo.main(java.lang.String[]) bci:41 line:25 (Interpreted frame)
0x02c40697      <StubRoutines>
0x6e74a6e5      jvm!JVM_GetThreadStateNames + 0x4d1b5
0x6e8104fe      jvm!_JVM_FindSignal@4 + 0x63b3e
0x6e74a77e      jvm!JVM_GetThreadStateNames + 0x4d24e
0x6e6cc337      jvm!JNI_GetCreatedJavaVMs + 0x6f27
0x6e6d48cf      jvm!JNI_GetCreatedJavaVMs + 0xf4bf
0x0083229e      java + 0x229e
0x0083aeaf      java + 0xaeaf
0x0083af39      java + 0xaf39
0x751f62c4      KERNEL32!BaseThreadInitThunk + 0x24
0x77ab0609      ntdll!RtlSubscribeWnfStateChangeNotification + 0x439
0x77ab05d4      ntdll!RtlSubscribeWnfStateChangeNotification + 0x404
----------------- 4 -----------------
<<<snip>>>

ntdll!RtlFreeHeap上面的符号有些混乱,但它有效。

要将它与转储文件一起使用,您必须将正确的java.exe二进制文件传递给它,以防您有多个版本,或者如果您在与生成转储的计算机不同的计算机上对其进行分析(类似于你需要SOS.dll的版本来准确地使用用于转储的.NEt框架版本。)

C:\Users\conio\Desktop\code\JNA2>"C:\Program Files (x86)\Java\jdk1.8.0_102\bin\jstack.exe" -m "C:\Program Files (x86)\Java\jre1.8.0_102\bin\java.exe" Foo.dmp
Attaching to core Foo.dmp from executable C:\Program Files (x86)\Java\jre1.8.0_102\bin\java.exe, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 25.102-b14
Deadlock Detection:

No deadlocks found.

<<<snip - same output as above>>

答案 2 :(得分:0)

您正在比较两件无法比较的事情。

一方面,您可以参考!DumpStack这是调试器的插件。为特定操作系统编写调试器,在本例中是WinDbg for Windows。当然,Windows的调试器知道如何读取本机调用堆栈。

另一方面,您希望从JVM获取该信息。 JVM是Java虚拟机的缩写。如果您使用过像VMWare这样的虚拟机,那么您就知道虚拟机运行的是另一个操作系统&#34;。在这种情况下,操作系统的名称是Java。

如果您考虑在Windows主机上运行的Linux VM,并且在VM中使用Linux调试器(GDB),它是否会显示Windows调用堆栈?不,它不会 - 至少在VM设计正确的时候。

将其映射到JVM,您应该无法从Java程序中看到本机调用堆栈,因为它在VM中运行。

那么,为什么!DumpStack可以呢?因为反过来说:当您在Windows主机上运行调试器时,您可以看到Linux VM的调用堆栈。与!DumpStack相同:您从.NET VM外部运行它,它旨在解释.NET VM的内容。

所以,基本上你想要的是可以读取本机端的WinDbg以及可以解释Java VM(JVM)内容的WinDbg插件。这又是the question you have asked before,这被认为是偏离主题的,让我引用我的评论:

  

不幸的是,不,AFAIK没有这样的工具。

我在WinDbg业务中工作了7年,我找到了很多用于各种目的的插件,但是没有用于显示Java调用堆栈的插件。我很乐意看到一个,因为我有不止一个用例。

相关问题