通过动态编译和加载的代码了解 Eclipse 调试源查找

2022-09-01 21:24:14

背景信息:

我有这个java框架,旨在运行外部脚本。为此,我使用类装入器和系统 java 编译器的组合来编译项目构建路径上不存在的“脚本”文件。所有这些都有效,编译器黑魔法和所有。.java

外部加载代码的固有复杂性是调试的难度。我已经通过使用java运行时的远程调试功能解决了这个问题。

因此,我有一个附加到可执行jar的调试配置,该jar具有源查找路径上带有外部java脚本的目录。这实际上工作了一段时间。实际上,它从未正常工作,我只是在我的构建路径上意外地有脚本。令人困惑的是,我可以在脚本中放置断点,调试器实际上在那里停止(一致的行号,日志记录和所有)。不过,了解 eclipse 如何找到源文件会有所帮助。毕竟,Eclipse的大部分文档都是由用户手册组成的。-verbose:class

我怀疑的是,我不小心复制了某些脚本文件,从而将源查找与不同步的源文件混淆了。事实并非如此,我已经删除了重复的文件,eclipse仍然无法找到源。

我尝试了什么

  • 双重、三重、四重检查源查找路径,确保它包括每个相关目录
  • 启用/禁用的搜索子文件夹
  • 启用/禁用的搜索重复项
  • 使用目录的绝对路径而不是相对工作区路径

解决方法

这里唯一的解决方法是将脚本文件添加到项目的生成路径上,这对我来说是不可接受的。

我现在在做什么

我正在慢慢地爬行在eclipse开源项目基础存储库中寻找答案。事实证明,Eclipse是一个非常大的项目。

问题

任何人都可以提供 Eclipse 源代码查找工作原理的准确算法表示吗?

知道了这一点,我可能会想出一种方法来强制Eclipse调试器使用反射的正确路径。据我所知,没有任何技术限制阻止动态编译代码进行调试。我知道这一点,因为我的断点正在像我预期的那样挂起我的线程,源代码似乎不想加载:(

相关研究:这似乎可能与如何使用空代码源位置定义类有关,但显然,在将代码动态编译到内存中时,正确的做法是给出空参数...问题仍然存在于如何/为什么这对eclipse的调试器很重要。

4/22 3:30更新:因此,我追求了上面链接的解决方案。现在,我看到我的类正在使用开关从正确的文件路径位置加载,但源查找仍然失败。断点仍然被正确捕获,但迎接我的是熟悉的红色字母。CodeSource-verbose:classSource not found

5/6更新 3:15:我追求安德鲁的答案中讨论的解决方案。事实证明,我的.class字节码中的源文件属性与我的源查找路径上存在的文件完全匹配。这让我感到困惑,因为这暗示了文件夹层次结构对源查找有影响。但是,我已经创建了表示“true”包(如.java文件顶部所定义)的“幻像”包层次结构,并将源文件移动到这些文件夹,但是当我将这些路径添加到源查找路径时,源查找仍然失败。关于哪些其他因素在源查找中的作用的任何额外见解都将是巨大的。javap


答案 1

我在这方面有一些经验,在调试通过JDT动态编译的时髦脚本方面做了一些工作。我从来没有让它完美地工作,我认为这主要是JDT的局限性,它从未被设计为处理动态编译的代码。

TLDR:我的猜测是,您的动态编译脚本在字节码中具有不正确的源文件属性。此属性由编译器在类文件中设置。查看 https://en.wikipedia.org/wiki/Java_class_file

我认为,您的困惑之处在于,调试器在脚本中设置的断点处正确停止,但 IDE 无法加载源代码。这当然令人困惑,但对此有一个很好的解释。

断点实际上由 VM 处理,VM 通过完全限定的名称和行号跟踪断点。这允许命中断点,而不管哪个类装入器装入类文件,但如果通过具有相同限定名称但源代码不同的类装入器装入多个类文件,则可能会导致一些混淆。此用于确定何时停止 VM 的算法与在 VM 停止时实际查找源代码无关。

查找源代码由 IDE 处理。因为即使在静态编译的世界中,源文件名也可能与类名(内部类,匿名类等)不匹配。类名不能用于查找源文件。

下面是对 IDE 在断点处停止时所执行操作的简化:

  1. 在该断点处查找类文件
  2. 获取源属性
  3. 在源查找路径中查找与源属性名称匹配的源文件
  4. 如果找到多个同名的源文件,请使用一些启发式方法(我认为这将是源查找选项卡中的排名)
  5. 返回最合适的源文件。

(注意,我认为source属性只是源文件的简单名称(即没有目录),所以我认为IDE将包名称转换为目录结构作为查找的一部分,但我可能错了)。

因此,如果动态编译的脚本没有正确的源属性,则查找将失败。您可以通过查看字节码来检查此理论。您必须以某种方式编译脚本并将位保存到磁盘。然后你可以在上面运行。我敢打赌,这就是问题所在。我以前在其他动态编译的语言中看到过这种情况。javap -v myScript


答案 2

我遇到过类似但不太复杂的问题。这是由使用两个不同的编译器引起的。你写过,在你的情况下使用系统java编译器,确保你的Eclipse使用相同的JDK,并且系统编译器通过使用-g:vars参数在编译的类中包含调试信息。