【问题标题】:Understanding Eclipse debug source lookup with dynamically compiled and loaded code使用动态编译和加载的代码了解 Eclipse 调试源查找
【发布时间】:2023-04-06 06:48:01
【问题描述】:

背景信息:

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

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

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

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

我的尝试

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

解决方法

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

我现在在做什么

我正在通过 eclipse 开源项目基础存储库慢慢爬来寻找答案。事实证明,Eclipse 是一个相当大的项目。

问题

谁能提供 Eclipse 源代码查找工作原理的准确算法表示?

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

相关研究: 似乎是this might be linked with how the class is defined with a null CodeSource location,但显然,在将代码动态编译到内存中时,正确的做法是给出 null arg...问题仍然是这对 eclipse 的调试器如何/为什么很重要。

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

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

【问题讨论】:

  • 您是否收到任何错误消息?也许在错误日志视图或控制台视图中?您是否在调试窗口中通过右键单击编辑源查找 -> 在调试期间编辑源查找?
  • @calon 这是麻烦的一部分,Eclipse 除了“找不到源”之外什么也没说...不过,所有路径都在查找路径上。
  • 用一些额外的研究更新了这个问题,但仍然没有走得太远
  • 1) 您确定编译后的代码与源文件中的类名和包名匹配吗?编译器的技巧可能会重命名类。 2) 你确定 Eclipse 运行的是正确的类加载器吗?它可能认为源是与远程调试会话中的类同名的外来类。
  • @LorenzoGatti 我非常有信心类名匹配,我追求链接的CodeSource 可能的解决方案,现在我看到我的类文件(限定名称和所有)被声明为正在加载从我希望它被加载的位置。这是通过在我的重载getClassLoader() 方法和getJavaFileForOutput() 中编辑我的逻辑来实现的,在我的扩展ForwardingJavaFileManager<JavaFileManager> 中支持为返回的classLoader 提供正确的CodeSource URL 位置。

标签: java eclipse debugging external


【解决方案1】:

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

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

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

断点实际上由 VM 处理,VM 通过完全限定名称和行号跟踪它们。这允许无论哪个类加载器加载类文件都可以命中断点,但如果多个类文件通过具有相同限定名但不同源代码的不同类加载器加载,则会导致一些混乱。这种确定何时停止 VM 的算法与 VM 停止时实际查找源代码无关。

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

下面是 IDE 在断点处停止时所做的简化:

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

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

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

【讨论】:

  • 天哪,这行:“我认为 IDE 将包名称转换为目录结构作为查找的一部分”,我认为你已经找到了黄金!现在 AFK,但脚本的有趣之处在于它们通常与您的包名称不位于相同的文件夹结构中。显然这对我来说实际上不是一个选择,但确认这将是很棒的信息。我将介绍将编译后的字节保存到文件的概念...我只有一个问题:how?
  • 我们或许可以在这里得出关于如何执行此操作的结论。最后。
  • 开个玩笑,我不需要知道怎么做。其实我真的很傻。让我们看看这让我...源属性在哪里?
  • javap -v 应该显示所有属性。还有更多更漂亮的反编译器可供您使用。
  • 当我尝试这个时,我会赏金并接受这个答案
【解决方案2】:

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

【讨论】:

  • 这并不能真正回答问题。如果您有其他问题,可以点击 进行提问。一旦你有足够的reputation,你也可以add a bounty 来引起对这个问题的更多关注。 - From Review
  • 我没有其他问题。当问题与编译器及其参数有关时,我也花了很多时间检查源代码查找。我在上面的文字中没有看到 rpg711 引起了编译器的问题,它是调试参数。我不知道存在,我相信它们对每个人都不是显而易见的。
  • 感谢您的意见,但这个问题是两年前提出的,已经解决了,请保持相关内容:-)
  • 您没有针对此问题发布解决方案。如果我能在几天前​​看到我的回答,我会节省很多休息时间。问候。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-02
  • 2012-04-25
  • 2016-09-18
  • 2011-07-31
  • 2016-11-25
  • 2020-09-20
  • 2014-01-15
相关资源
最近更新 更多