2024 Clojure状况调查!中分享您的想法。

欢迎!请查看关于页面以获得更多关于如何运作的信息。

+52
Clojure
关闭

Android ART在字节码运行时进行验证,并在使用锁定宏时失败。错误看起来像是CLJ-1829中看到的(在这种情况下,clojure.core.server/stop-server调用了锁定宏)

10-16 14:49:26.801 2008-2008/? E/AndroidRuntime: java.lang.VerifyError: 驳回类 clojure.core.server$stop_server,因为它验证失败('clojure.core.server$stop_server' 的声明出现在 /data/app/com.clojure_on_android-1/base.apk 中)

原因:从一个Android问题讨论中(https://code.google.com/p/android_issues/detail?id=80823),这似乎是因为在JVM规范中更加严格地执行“结构化锁定”规定(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10)。

锁定宏中的 monitor-enter、monitor-exit 和 try/finally 块的混合似乎创建了一些ART被标记为不平衡 monitorenter/monitorexit 字节码路径。特别是,monitorenter 和 monitorexit 本身可能会抛出异常(在null锁定对象上)。Java字节码对这些情况进行了一些复杂的异常表处理,这些处理(据我所知)没有修改Clojure编译器是无法进行的。

方法:一个可能的方法是使锁定在Java的synchronized块中调用传递的身体。这避免了通过将其交给Java处理的复杂字节码问题,但具有未知的影响。

补丁:clj-1472-3.patch

另请参阅:与Java synchronized块相比的字节码分析发现了一些差异。
https://gist.github.com/AdamClements/2ae6c4919964b71eb470

审核人:亚历克斯·米勒 - 我认为这是一个可行的方案,可以解决问题。由于其使用频率不高,我并不特别担心它会成为性能问题。我将标记这一点,我认为处理这种方式的一个方法是使锁定成为一个特殊的格式,并具有编译器支持,但我不确定这是否值得去做,所以我将留给瑞奇来决定。

已关闭,备注: 已修复

41 个答案

0
by
 
最佳答案

此问题的修复已包含在1.10.2-alpha1版本中发布。

0
by

评论者:adamclements

使用了一下这个方法后,我发现将其移出try块会导致nREPL无法工作。

从字节码分析,clojure.tools.nrepl.middleware.session/session-out中的锁定monitorenter和其他几个地方最终出现在完全不同的方法定义中,我们现在得到了JVM IllegalMonitorStateException以及ART对该函数的验证错误。

0
by

评论者:jafingerhut

亚当,我就你的补丁是否有兴趣不能发表评论,但是的确如此,如果没有签署贡献者协议,将不能向Clojure提交补丁,你现在已经可以在https://clojure.org/contributing

0
by

评论者:adamclements

上传了新的补丁(并已签署贡献者协议)。该补丁既通过了JVM和ART的字节码验证。围绕monitor退出的额外try/catch是可选的(无论是否有它验证都通过),但在java版本中 retries monitor-exit 无限期并显示了正确的时间点死锁,如果没有在monitor-exit中捕获错误,在未来的某个不确定的monitor-enter可能会失败,并不会显示出实际的问题。

它看起来并不美观,但如果没有更细粒度的对生成的字节码的控制,这就是我能做的最好的。

0
by

评论者:adamclements

刚刚用Lollipop测试过,这个补丁可能不再足够。

将与ART团队取得联系,看看他们是否能提供更多线索并验证它是否可以在AOSP当前主分支上工作

0
by

评论者:adamclements

在AOSP项目中提交了错误报告,希望他们能提供一些线索,看看是否是我们的问题,如果是,我们将如何修复它。

https://code.google.com/p/android/issues/detail?id=80823

0
by

评论者:adamclements

我已经上传了一个锁定宏的替代实现(0001-CLJ-1472-Locking-macro-without-explicit-monitor-ente.patch),略微有些投机取巧——实际上在Java中实现了synchronized块,从而保证了兼容性。这是以牺牲一点额外的间接性和更佳的命名/位置为代价的。

但它确实修复了错误,并在所有版本的Android、Android+ART和JVM上工作。这种做法会被接受吗?

0
by

评论者:hiredman

我还没有看到任何证据表明Clojure生成的字节码以某种方式违反了JVM规范,所以我怀疑Clojure需要JVM来运行,Android不提供JVM,只提供类似的东西,如果你不越界的话。

0
by

评论者:hiredman

鉴于在https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.11.10中的结构化锁定词汇,(locking nil)可能会生成违反结构化锁定的运行时行为的字节码。该问题的第一个补丁可能会导致编译器在不同的方法中发出monitorenter/exit指令,这肯定违反了结构化锁定。

0

评论者:adamclements

是的,第一个补丁显然是错误的,我留了一些背景对话,但为了清晰起见,最好还是去掉它。

对于想关注这次对话但又不想反编译和观察字节码的人来说,这里有一个差异数据:Java中的synchronized块与Clojure锁定https://gist.github.com/AdamClements/2ae6c4919964b71eb470

我也很难确定规范偏离在哪里,尽管我可以看出与Java版本的差异,如果有什么的话,Clojure版本看起来比Java版本更接近规范中的描述!

如果有人比我更多地了解这个主题,并且可以参与AOSP的bug https://code.google.com/p/android/issues/detail?id=80823,那么我们或许可以将这个问题作为android的bug解决,这比JVM规范更加关注Java实现,或者他们可能会发现Clojure实现中的某个错误。我已经上传了原始的Clojure行为,并请他们解释为什么它失败了的原因。

0

评论者:adamclements

ART团队对我们认为我们违反了什么的问题的回应是

“结构化锁定”部分包含以下内容:

“(链接: ...)实现可以允许但不必强制执行以下两个确保结构化锁定的规则。(链接: ...)
ART目前在整个验证过程中强制执行这两条规则,包括

“在方法调用过程中,T在M上执行监控退出的次数不得超过T自方法调用以来在M上执行的监控进入次数。”

...
...
...

0
...

评论者:adamclements

...

我认为这违反了你在链接到的JVM规范中提到的结构化锁定规则。

0

评论者:hiredman

关于结构化锁的一个有趣问题是,规范是指静态字节码还是字节码的运行时行为。给定字节码链接(https://gist.github.com/AdamClements/2ae6c4919964b71eb470#file-test_locks-class-L24),静态字节码有相同数量的入口和出口,但动态行为可能不同。我想知道在验证时间艺术大侠声称要执行的是什么(似乎应该是静态字节码而不是动态属性,但是那么他们不应该验证失败)。查看谷歌代码问题,评论https://code.google.com/p/android/issues/detail?id=80823#c6是由同一位开发者发表的,所以我有点怀疑存在一些沟通问题。由于之前的评论中提到了将monitor-enter和exit方法分散到不同方法中,所以不清楚开发者是在什么背景下回答的。如果所有的补丁、专业的Clojure Android构建等都被去除,事情会变得更清晰,然后用Vanilla Clojure jar,获取要验证失败的反汇编输出,然后将它带到Android问题跟踪器并询问“嘿,这个验证失败了,为什么?”

0

评论者:adamclements

是的,我不该把它和修补版本混淆。现在链接和已上传的当前版本使用的是Vanilla Clojure的锁定宏版本。

我认为问题出在异常表和覆盖指令上。如果第24行可以抛出异常,那么在运行时就会有Monitor-exit,但是从未遇到Monitor-enter。

0

评论者:gshayban

根据Marcus Lagergren的说法,JRockit和Hotspot都考虑了锁释放在不同方法中的场景...也许Android有不同(错误)的解释?

...