原始 MediaWiki 页面

我知道编辑这个网站吗?

调试

自迁移出 MediaWiki 以来,本页内容尚未经过审查。如果您愿意帮忙,请查看帮助指南

This page has approaches for software developers to use for debugging ImageJ and Fiji. If you are a user looking to troubleshoot issues, see the Troubleshooting page.

以调试模式启动

为了调试问题,在调试模式下启动通常很有帮助。有关说明,请参阅第 Troubleshooting 页。

附加到正在运行的实例

有时,我们需要直接在 ImageJ 中进行调试,例如因为插件发现可能存在问题(ImageJ 希望在 ImageJ/plugins/ 中找到插件,并且通常我们希望将它们捆绑为 .jar 文件,这两个文件与 Eclipse 调试不兼容)。 JDWP(Java 调试线协议)来救援!

以一种特殊模式启动Java虚拟机后,调试器(例如Eclipse的内置调试器)可以附加到它。要以上述模式启动,您需要传递 --debugger=<port> 选项:

./fiji --debugger=8000

在 Eclipse(或任何支持 JDWP 的调试器)中,选择正确的项目以便可以找到源代码,标记要停止执行的断点(例如检查变量值),然后单击RunDebug Configurations…后,右键单击左侧列表中的 Remote Java Application 项,然后选择 New。现在您只需确保端口与您指定的值匹配(在上面的示例中,8000,Eclipse 的默认端口号)。

如果您需要对调试器配置进行更多控制,还可以直接使用 -agentlib:jdwp=... Java 选项(--debugger=<port> 只是为了方便起见的快捷方式):

./fiji -agentlib:jdwp=server=y,suspend=y,transport=dt_socket,address=localhost:8000 --

--标记将Java选项(如果有的话)与程序选项分开)。一旦以这种方式启动,ImageJ 将在打印如下消息后等待附加调试器:

> Listening for transport dt\_socket at address: 46317

Calling imagej -agentlib:jdwp=help -- will print nice usage information with documentation of other JDWP options.

将 ImageJ 附加到等待的 Eclipse

在调试启动事件和无头操作时,将 ImageJ 设为客户端并将 Eclipse(或等效项)设为服务器会更容易,而不是将 ImageJ 设为the debugging server

在这种情况下,您首先启动调试会话,例如在 Eclipse 调试配置中,您指定“标准(套接字侦听)”作为连接类型。然后,只需启动不带 server=y 标志的 ImageJ 即可进行连接和调试:

./fiji -agentlib:jdwp=suspend=y,transport=dt_socket,address=localhost:8000 --

监控系统调用

Linux

在 Linux 上,您应该使用 strace command 调用 ImageJ:

strace -Ffo syscall.log ./fiji <args>

macOS

使用 dtruss 包装 dtrace 来监控系统调用:

dtruss ./fiji <args>

窗口

要监视 Windows 上进程的各个方面,请使用Sysinternal’s Process Monitor

调试共享(动态)库问题

Linux

在启动 ImageJ 之前设置 LD_DEBUG 环境变量:

LD_DEBUG=1 ./fiji <args>

macOS

在启动 ImageJ 之前设置 DYLD_PRINT_APIS 环境变量:

DYLD_PRINT_APIS=1 ./fiji <args>

窗口

通常,动态库问题与缺少的依赖 .dll 文件有关。下载 depends.exe 并加载您怀疑缺少依赖项的 .dll 文件。

调试 JVM 挂起

当 Java VM 挂起时,原因可能是死锁。尝试采取stack trace。如果遇到问题,您可以尝试以下高级技术之一:

  1. 您可以使用jstack命令(在这种情况下,您不需要从命令行运行ImageJ)。这需要你首先找到ImageJ的PID(进程ID)。您可以通过运行以下命令来执行此操作:
     jps
    

    from the command line to print a list of running Java processes. If you’re not sure which PID is ImageJ’s, you can close ImageJ, run jps, open ImageJ and run jps again. Whichever PID is present in the second run but not the first is ImageJ’s. Then, to acquire a stack trace, just run:

     jstack <ImageJ's PID>
    
  2. 对于基于 GUI 的调试,还可以使用 jvisualvm 程序(您可以在java/<platform>/<jdk>/bin/中找到)连接到 ImageJ PID。在这里,您只需按一个大的“线程转储”按钮即可查看堆栈跟踪。

无论您使用哪种方法来获取堆栈跟踪,为了调试,您都需要随着时间的推移获取多个堆栈跟踪并进行比较。如果所有堆栈跟踪都在同一个方法执行中,那么这就是死锁(或速度减慢)的根源。

调试内存泄漏

有时,内存未正确释放,导致OutOfMemoryException

了解发生情况的一种方法是使用jvisualvm(参见#Debugging JVM hangs)连接到 ImageJ 进程,单击 Monitor 选项卡中的 Heap Dump,在所述选项卡中选择子选项卡 Classes 并按大小排序。双击顶部用户应该会显示实例的详细列表,您可以在其中展开引用树以找出仍然保留引用的内容。

调试 JVM 硬崩溃

当您发现导致 JVM 崩溃的问题,并且可以可靠地重复该崩溃时,可以使用多种选项来查明发生了什么情况。

使用 gdb

通常,当您调试崩溃的程序时,您会在调试器中启动它,以检查崩溃时的堆栈跟踪和变量。然而,启动Java VM时gdb存在实质性问题;要么 gdb 被分段错误(JVM 使用它以有效的方式处理 NullPointerExceptions)迷惑,要么被线程系统迷惑——除非您自己编译 gdb。

但是有一个非常简单的方法可以使用 gdb 来检查严重错误,例如分段错误或陷阱信号:

./fiji -XX:OnError="gdb - %p" --

使用 lldb

在较新的 macOS 版本上,gdb 已替换为 lldb。对于那些已经熟悉 gdb 的人来说,有一个 LLDB to GDB Command Map 备忘单可能有用。

使用hs_err_pid<pid>.log文件

Java 虚拟机 (JVM) 在崩溃后经常会在当前工作目录中留下 hs_err_pid<number>.log 格式的文件。此类文件以类似于以下内容的序言开头:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f3dc887dd8b, pid=12116, tid=139899447723792
#
# JRE version: 6.0_20-b02
# Java VM: Java HotSpot(TM) 64-Bit Server VM (16.3-b01 mixed mode linux-amd64 )
# Problematic frame:
# C  [libc.so.6+0x86d8b]  memcpy+0x15b
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

接下来是线程转储和其他有用信息,包括传递给 JVM 的命令行参数。

最重要的部分是 # Problematic frame: 行之后的行,因为它通常会让您知道崩溃是在哪个组件中触发的。

内存不足错误

如果您收到(或怀疑)的特定异常是 OutOfMemoryError,则可以在运行 ImageJ 时启用 JVM 标志来帮助查明问题:

./fiji -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/desired/path/

第一个选项:

-XX:+HeapDumpOnOutOfMemoryError

如果抛出 OutOfMemoryError,则告诉 JVM 创建堆转储(.hprof 文件)。这基本上是内存耗尽时 JVM 状态的快照。

第二个选项:

-XX:HeapDumpPath=/desired/path/

不是必需的,但便于控制生成的 .hprof 文件的写入位置。请注意,这些堆转储由 PID 命名,因此人类不易区分。

获取堆转储后,您可以自己分析它,例如带有 memory analyzer,或在 Image.sc Forum 上发帖并简要说明您的问题。

使用 jdb 调试 Java 代码

如何将 Java 调试器 jdb 附加到正在运行的进程

这需要两个独立的进程,ImageJ 本身和调试器。您可以在一个 shell 中执行此操作,将第一个进程置于后台,也可以在两个 shell 中执行此操作,建议这样做。在两个 shell 中执行以下操作:

外壳1 在第一个 shell 中,使用特殊参数启动 ImageJ 以打开一个端口(在本例中为 8000),之后 jdb 可以连接到该端口:

./fiji --debugger=8000,suspend=y --

外壳2 在第二个 shell 中,告诉 jdb 连接到该端口:

jdb -attach 8000

这是默认 Java 调试器 jdb 的超快速入门

希望您对 gdb 有一点熟悉,因为 jdb 与它有些相似。

显着差异:

  • 断点设置为“stop in .”或“:”。请记住,必须完整指定该类,即 ..
  • 没有制表符补全
  • 没有阅读行(光标向上/向下)
  • 没有捷径;你必须写“run”,而不是“r”来运行程序
  • 加载类之前没有列出文件
  • 指定源位置的更简单方法:“使用 "
  • “直到”是“步骤”,“步骤”是“stepi”

好的,现在就给大家做个小演示:

(如果将 jdb 附加到正在运行的 ImageJ 进程,则必须使用上一节中的行。)

$ jdb -classpath ij.jar ij.ImageJ
> stop in ij.ImageJ.main
Deferring breakpoint ij.ImageJ.main.
It will be set after the class is loaded.
> run
run ij.ImageJ
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint ij.ImageJ.main

Breakpoint hit: "thread=main", ij.ImageJ.main(), line=466 bci=0

main[1] use .
main[1] list
462             //prefs.put(IJ_HEIGHT, Integer.toString(size.height));
463     }
464
465     public static void main(String args[]) {
466 =>          if (System.getProperty("java.version").substring(0,3).compareTo("1.4")<0) {
467                     javax.swing.JOptionPane.showMessageDialog(null,"ImageJ "+VERSION+" requires Java 1.4.1 or later.");
468                     System.exit(0);
469             }
470             boolean noGUI = false;
471             arguments = args;
main[1] print args[0]
java.lang.IndexOutOfBoundsException: Invalid array range: 0 to 0
 args[0] = null
main[1] print args.length
 args.length = 0
main[1] step
>
Step completed: "thread=main", ij.ImageJ.main(), line=470 bci=28
470             boolean noGUI = false;

main[1] step
>
Step completed: "thread=main", ij.ImageJ.main(), line=471 bci=30
471             arguments = args;

main[1] set noGUI = true
 noGUI = true = true
main[1] cont
>
The application exited

检查序列化对象

如果您有一个包含序列化对象的文件,则可以在 Script Editor 中使用此 BeanShell 打开该对象的树视图(双击可打开/关闭该视图的分支):

import fiji.debugging.Object_Inspector;

import ij.io.OpenDialog;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

dialog = new OpenDialog("Classifier", null);
if (dialog.getDirectory() != null) {
    path = dialog.getDirectory() + "/" + dialog.getFileName();
    in = new FileInputStream(path);
    in = new ObjectInputStream(in);
    object = in.readObject();
    in.close();
    Object_Inspector.openFrame("classifier", object);
}

调试 Swing(事件调度线程)问题

Swing 不允许我们从任何我们想要的地方调用所有 UI 对象上的所有方法。有些事情,例如 setVisible(true)pack() 需要在事件调度线程(AKA EDT)上调用。请参阅 Sun 的 detailed explanation 了解为什么会出现这种情况。

有几种方法可以测试此类 EDT 违规,请参阅this blog post by Alexander Potochkin(可以在here中找到当前版本的 debug.jar)。

调试 Java 3D 问题

参见Troubleshooting Java 3D

使用共享终端会话进行交互式调试

对于运行 LinuxmacOS 计算机(或在带有 OpenSSH 服务器的 WindowsCygwin 上)的用户,可以使用 SSH 隧道在用户和开发人员之间共享调试会话。所需要的只是公共 SSH 服务器上的共享帐户。

用户应执行此命令:

ssh -R 2222:127.0.0.1:22 -t $ACCOUNT@$SSHSERVER screen

连接后,命令

ssh -p 2222 $LOCALACCOUNT@127.0.0.1

将打开回本地计算机的连接。

然后开发人员应该执行以下命令:

ssh -t $ACCOUNT@$SSHSERVER 'screen -x'

由于这提供了共享的GNU screen会话,因此用户和开发人员都可以执行命令并查看输出。通过键入您要说的内容,以 ⌃ Ctrl + C 结束该行(以免它作为命令执行),将终端窗口用作私人聊天室,这种情况甚至很常见。

调试结束后,用户可以通过按⌃ Ctrl + D从本地计算机注销来安全地注销(由于用户自己在GNU屏幕会话中输入密码,因此未经用户明确同意,开发人员无法重新登录)。另一个⌃ Ctrl + D将终止GNU屏幕会话,而另一个⌃ Ctrl + D将从SSH服务器上的共享帐户注销。