
经过测试,mac 14.4.1 已经正式修复了这个 BUG,通过测试代码可知,不是给java专门打的补丁
#include <pthread.h> int main() { pthread_jit_write_protect_np(0); return *((int *)NULL); } <- 先前14.4是SIGKILL,现在正确SIGSEGV
1 codehz OP 补充一下,这个需要刚好在 jit 运行的时候(也就是线程正处于 write 的模式)才会触发的 sigkill 其他时候依然是正常流程,因此 grallvm 等直接生成 binary 的不受影响 |
2 ly841000 2024-03-17 09:32:53 +08:00 这种关键特性也能改?????? |
3 rainbowmolly 2024-03-17 09:43:28 +08:00 “access memory in protected memory regions” 这个行为为什么是“As a normal part of the just-in-time compile and execute cycle” 常规操作?这种即使在果子自己的 oc 中应该是 bad access 直接就 crash 掉吧 |
4 meloyang05 2024-03-17 09:55:46 +08:00 via iPhone 实在不理解苹果为什么要在一个小版本更新里进行这么重要的内核更改,完全没有充分的时间来验证更改后所可能引发的一系列后果。这已经是第三个 14.4 更新后所带来的问题了,前两个是显示器 usb 外设失灵,usb 打印机失灵。看得出 14.4 在内核上做了很多改动,然而这些改动没有经过充分测试和验证就能以正式版本推送给用户,实在让人对现在苹果的软件质量不敢恭维 |
5 codehz OP @rainbowmolly 以前的说法是,通过检测 segsigv 的方式,在 happy path (也就是没异常)的时候性能比较好,甚至于在特定情况(比如 jit 发现某个参数几乎没有 null 过)省略用户主动写的 null 判断更快 |
6 Focus9 2024-03-17 10:09:30 +08:00 怪不得 idea 偶尔闪退 |
7 meloyang05 2024-03-17 10:15:16 +08:00 感觉像这种程度的内核改动,至少应该放在 macOS 15 这种大版本更新中,来给开发者一些时间解决兼容性问题,而不是像现在这样不声不响地推送到一个小版本中,给用户带来本不必要的困扰 |
8 codehz OP @meloyang05 我的个人猜测应该是苹果发现了什么影响安全性的严重 bug ,可能是已经有在野利用了 |
9 BearCookie 2024-03-17 10:26:40 +08:00 via Android 请问有没有办法降级系统额 |
10 ysc3839 2024-03-17 10:30:10 +08:00 via Android @codehz 我感觉只是一个信号不至于牵扯到安全漏洞。如果真的无法解决,那也只能说明苹果技术差,隔壁一堆 Unix 都没改呢。 |
11 tanrenye 2024-03-17 10:32:06 +08:00 我已经升级了一段时间,还没有发现有什么问题,是不是 zulu 的 sdk 不受影响? |
12 ly841000 2024-03-17 10:32:10 +08:00 @meloyang05 #7 标准的做法应该是在可执行文件里加一个标记, 指明可以支持这个特征, 在新版本编译器中默认启用支持, 只有检测到标记, 系统才启用这个功能 |
13 ysc3839 2024-03-17 10:33:18 +08:00 via Android 另外我也不觉得这是“滥用这个特性”,这应该就是 SIGSEGV 的正当用法吧?不然为什么要加这个信号? |
15 EastLord 2024-03-17 10:39:40 +08:00 糟了,出了系统更新 我都是无脑升级 |
16 ychost 2024-03-17 10:41:38 +08:00 @meloyang05 #4 ε=(ο`*)))唉,我现在显示器外设 USB 还是偶发失灵,必须要重新插拔一次 |
17 bghtyu 2024-03-17 11:00:35 +08:00 via iPhone 怪不得最近 idea 闪退了几次 |
19 lslqtz 2024-03-17 11:32:35 +08:00 访问受保护的内存区域, 应用可以收到通知, 这意味着可以探测保护的内存区域块? |
20 felixlong 2024-03-17 11:39:07 +08:00 映像里面几乎所有支持 JIT 需要检测空指针/数组越界访问异常的都会利用这个特性啊! NodeJS 没影响嘛? Apple 在搞啥。 |
22 codehz OP @felixlong 目前据我所知只有 java 在广泛使用这个技巧,其他 jit 都没这么做 不过数组越界肯定不会用这个方法,总不能一个数组刚好吃一个 4k 页面(apple silicon 好像是 16k),然后上下两侧摆 guard page ,这也太奢侈了 至于空指针,支持 jit 的语言里有空指针暴露给用户的就挺少的了 js 肯定不是,go 和 net 据我所知也没人说有做类似优化 |
23 Evrins 2024-03-17 13:31:23 +08:00 14.4 一发布就升级了 Goland RustRover DataGrip 都用的好好的 |
24 eailfly 2024-03-17 13:36:00 +08:00 只影响 Apple silicon 版本,还在用 Intel 2019 的一下子就放心了 |
25 vincentchyu 2024-03-17 13:41:02 +08:00 我就说我的 goland 最近一直崩溃 |
26 neilroyer849 2024-03-17 14:40:08 +08:00 似乎应该是与 Apple Silicon 自 A11 开始引入的 Fast Permission Restrictions 有关系。原来是在 iOS 上的,后来 Mac 上了 AS 因而 macOS 也支持了这种 per thread 的权限限制? |
27 iseki 2024-03-17 14:43:01 +08:00 via Android 这有点坑啊,这种重大变更怎么能无预警直接推呢,至少也应该保留一个逃生门。 |
28 iseki 2024-03-17 14:44:54 +08:00 via Android @rainbowmolly 触发段错误信号的行为是良定义+可保证的,如果能用来提升性能为啥不能用。你说的直接退出只是因为没有好好处理段错误信号吧。 |
29 iseki 2024-03-17 14:59:31 +08:00 via Android @codehz Go 写的程序可能也受影响,它的空指针访问也是可以在程序内部捕获的,而且好像也是通过段错误信号兜住的(只观察过 Linux/Windows 平台) |
31 jay2049 2024-03-17 15:44:23 +08:00 感谢楼主提醒 |
32 coang 2024-03-17 15:52:21 +08:00 优秀的系统,垃圾的 JDK,居然能影响这么多个版本 Java 怎么做的兼容性测试!!(狗头 |
33 ipcjs 2024-03-17 16:50:12 +08:00 Windows 用的 beta 版都从来没问题,macOS 升一次一堆软件出问题,老早把个傻 diao 自动升级关掉了 |
34 ShadowPower 2024-03-17 17:22:32 +08:00 上星期已经被坑了,感觉 mac 做开发的体验真的不如 Windows 了…… Windows 的稳定版我都随意更新 |
35 codehz OP 找到了一个问题复现的代码 #include <stdio.h> #include <sys/mman.h> #include <pthread.h> int main() { pthread_jit_write_protect_np(0); char* mem = (char*)mmap(0, 16 * 1024, 0, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0); fprintf(stderr, "addr = %p\n", mem); char value = *mem; fprintf(stderr, "value = %c\n", value); return 0; } ,在之前版本里是 sigbus ,可以捕获,14.4 就直接 sigkill 了 原来这个 write 模式是指这玩意 |
36 stinkytofu 2024-03-17 17:36:13 +08:00 MAC 现在确实不敢随意更新版本, 说不定什么刚需软件就工作不正常了, 很难受。 |
37 tt0411 2024-03-17 17:41:26 +08:00 怪不得 clion 时不时 crash... |
38 iseki 2024-03-17 17:44:22 +08:00 @codehz 还行,看了下只要他们切换下保护就没事了 > We discovered that if we switch the jit protection mode to EXEC rather than WRITE, then these page faults again raise the expected SIGBUS not a SIGKILL. Note the faulting accesses are predominantly reads and have nothing at all to do with accessing a MAP_JIT memory region. > We have been working on a patch that switches the jit protection mode to EXEC around these potential faulting memory accesses. It has been a bit of an exercise in whack-a-mole finding them all, and testing is still in progress. |
39 iseki 2024-03-17 17:47:23 +08:00 不过这种破环兼容性的做法真是一言难尽 |
40 leonshaw 2024-03-17 19:21:50 +08:00 via Android 这么说哪天 CPU 也可以不给 page |
41 leonshaw 2024-03-17 19:22:59 +08:00 via Android 这么说哪天 CPU 也可以不给 page fault 了,直接 halt 麻烦你内核重新适配一下。 |
42 Solael 2024-03-17 19:39:09 +08:00 呵呵苹果一贯操作,你不像 windows 一样先前兼容也就罢了,测试恐怕也是阿三随便点几下就完事了,连编译程序都不测试是吧。 |
43 noahlias 2024-03-17 20:06:31 +08:00 还好没更新 14.4 |
44 DigitalG 2024-03-17 20:23:33 +08:00 看了一下,这个是不影响用户使用 java 程序是吧?只影响做 java 开发的用户,或者开发时用了基于 java 的 IDE 程序? |
46 OneMan 2024-03-17 21:13:53 +08:00 还好不升级 |
47 Rorysky 2024-03-17 21:23:24 +08:00 @meloyang05 #4 都是访问被保护区域了,java jit 还要依赖这个内核行为,这不是奇技淫巧的优化是什么? |
48 p2007 2024-03-17 21:24:31 +08:00 难怪这几天 IDEA 莫名其妙崩了。 |
49 Rorysky 2024-03-17 21:28:35 +08:00 @meloyang05 #4 苹果开发这个开发文档明确说了,访问受保护区域属于预期外的行为,这能怪谁? "A crash due to a memory access issue occurs when an app uses memory in **an unexpected way**" https://developer.apple.com/documentation/xcode/investigating-memory-access-crashes |
50 iseki 2024-03-17 21:47:21 +08:00 via Android @Rorysky 一大票设施利用这个段错误和总线错误信号来做兜底呢,这要是可以随便改大家程序都不要写了。好在这次水果只是改了一个特例而已。 预期外的行为,它也是良定义的,看看你引用的这句话的下一句。 |
51 iseki 2024-03-17 21:48:20 +08:00 via Android 注意下 unexpected 不等于 undefined 也不等于 unspecified |
52 felixlong 2024-03-17 21:50:10 +08:00 @Rorysky 这也洗,你没看 Oracle 的人说吗,Mac 14.4 最后一个 RC 版本行为都没改,最后正式版本突然改了。这让人家怎么来的及修? |
53 Rorysky 2024-03-17 22:36:53 +08:00 @iseki #50 不要随便迁移概念, 你说的良定义猜测就是 well-defined 吧,这是描述语言标准本身的可移植性(者说语意不依赖于运行环境,只要支持语言标准就能保证一致性输出),这和系统内核行为有什么关系,旧版本的信号量是来提示程序 bad access 的,它不是一个特性,java 不应该利用来实现功能。 普通的程序运行中 bad access ,系统认为进程有问题了,直接杀掉是激进了,但估计是为了安全的原因。 就像人犯罪了,监管机构是口头教育,还是 逮捕教育,还是就地正法,那是监管机构决定的,你不能说我利用这个机会去接受某些信息/更快的见到某些人 |
55 iseki 2024-03-17 22:41:34 +08:00 via Android @Rorysky 信号量怎么就不是一个特性了?这都是写在文档上的,就因为它预期中不常见,所以就不是个特性从而可以随便更改了?既然承诺了它所产生的效果和副作用,怎么就不能用于优化了?实际上因为这个动作的延迟相对较高,JVM 的 JIT 也是用来做非预期分支去优化判定的。 一个东西既然承诺了效果,那就不该去干涉使用者怎么用,除非你有合理的理由,提前注明。 |
56 iseki 2024-03-17 22:43:46 +08:00 via Android @Rorysky > Null checks are cheap. They usually fold straight into a related memory access instruction, and use the CPU bus logic to catch nulls. (Deoptimization follows, with regenerated code containing an explicit check.) |
57 iseki 2024-03-17 22:45:27 +08:00 via Android @Rorysky 如果利用捕获 sigsegv 去做某些事是错误的,那设计这个信号量又是为了什么?如果你说有些事可以做有些事不能做,那就得讲明白哪些事不能做,有什么合理的理由。 |
58 BeiChuanAlex 2024-03-17 23:17:13 +08:00 吓死我了,还我写 rust |
59 cubecube 2024-03-17 23:25:22 +08:00 only apple can do |
61 iseki 2024-03-17 23:57:40 +08:00 via Android @felixlong apple 这个只是一个很 corner case 的场景,到不好说会不会改回去,也许就让大家吃了。 |
62 CFM880 2024-03-18 00:05:21 +08:00 @ShadowPower windows Android Studio 也有 bug ,一个陈年老 bug https://issuetracker.google.com/issues/167701947 ,特别恶心,我基本新加一个包,再新建包下面新建一个类文件,必出这个 bug |
63 wpei 2024-03-18 03:22:19 +08:00 |
64 seanxx 2024-03-18 09:29:17 +08:00 mac 已经不是原本的 mac 了,充斥着烂臭咖喱味,能用就不会升级 |
65 ShadowPower 2024-03-18 09:33:00 +08:00 @CFM880 好吧,我已经 3 年没有做 Android/iOS 相关的工作了…… |
66 2kCS5c0b0ITXE5k2 2024-03-18 09:42:04 +08:00 @Rorysky java8 就开始用了. 苹果为什么突然修改? 而且问题是 rc 版都没问题. 正式版就修改 那测试版是来用干什么的? |
67 felixlong 2024-03-18 09:48:03 +08:00 @codehz 应该不是你说的那个检测 NullPointerException 的优化导致的。那个行为影响太大了,而且那是 Posix 的标准行为,Apple 应该没有改变。 好像与 Mac 在 M 系列处理器引入的新的 pthread_jit_write_protect_np 有关系。Apple 确实只说了用 MAP_JIT mmap 出来的 region 要么只能 write ,要么只能 executable ,从来没提过 read 会发生什么事情,以前是在 write protect on/off 都发送 SIG_BUS, 14.4 改成了 write protect on 的时候发 SIG_BUS ,write protect off 的时候发 SIG_KILL,真是迷惑行为。不知道为啥 JDK 生成的 jit code 需要 read 自己? |
68 codehz OP @felixlong 上面我发了复现代码了,可能确实不是 0 页面的访问,但 mmap 只是去申请了一个权限为 0 的页面,然后去访问,正常情况只会触发 sigbus ,但 14.4 结合 write protection 后会直接 sigkill 这里不涉及任何真正的 jit 只是单纯的开一个页面当做 guard page |
69 ShadowPower 2024-03-18 10:10:51 +08:00 @codehz 这是不是意味着 macOS 已经破坏了 POSIX 兼容 |
70 Jirajine 2024-03-18 10:25:16 +08:00 这是依赖 UB 的特性,也不是关键特性,当然可以 break 。为使用 UB 特性、未定义接口的程序做兼容才是万恶之源。 这里的问题是依赖这个 UB 的程序( jvm )被广泛部署,而这项改动在推送前没有充分测试。 |
71 fredweili 2024-03-18 10:26:33 +08:00 升级过了,还没遇到问题 |
72 codehz OP @Jirajine 首先这不是 ub ,是标准允许的做法,其次虽然可能不是 null 的那个问题,但另一个 guard page 更是这个 api 的原本目的,不然为何提供 mmap PROT NONE 这个选项 |
73 p2007 2024-03-18 11:08:15 +08:00 @seanxx #64 说得对,苹果总是犯一些低级错误。我甚至怀疑他们内部程序员为了刷 KPI ,喜欢把之前的功能反复拿出来重构。苹果通过破坏兼容性得以抛弃的历史包袱并没有能提升他们的代码质量。 |
74 codehz OP @felixlong 测试了一下,访问 null 也真的会导致 sigkill (去掉 pthread_jit_write_protect_np(0)这句就 SIGSEGV) #include <pthread.h> int main() { pthread_jit_write_protect_np(0); return *((int *)NULL); } |
75 fantaxi 2024-03-18 11:28:42 +08:00 Only apple can do ! |
76 felixlong 2024-03-18 11:42:37 +08:00 @codehz 哈哈,估计 Apple 认为在 write_protect disable 模式下这个线程就只应该运行生成 JIT 代码的代码,不能干其它的事。也许 Apple 认为生成 JIT 代码的代码本身不会有这些骚操作。 |
77 daveh 2024-03-18 12:31:39 +08:00 via iPhone @codehz OpenJDK 已经改了,JRE 自己屎山代码的问题,为之前 Solaris 做的一个优化,还需要 Solaris 专有 API 配合才行,然后 Solaris 死了十几年了,这个却作为垃圾代码一直残留下来。 直到在最新 macOS 下帮发现了问题,OP 自己去看下 OpenJDK 的修改记录,不要在这瞎猜。 JB 已经有测试 JBR 发布,可以自己临时规避一下。 Apple 可改可不改,如果是安全原因不一定会改,但老版 JRE 使用也挺多,问题报多了可能也会改回去。 |
78 codehz OP @daveh 这个问题就是苹果的错误,就算 JRE 触发是一个意外,也不能改变它就是苹果的错误,因为这个行为( pthread_jit_write_protect_np 0 的时候 SIGSEGV 和 SIGBUS 变为 SIGKILL )没有任何文档提及,也没在更新日志里包含,乃至专门介绍安全性的更新内容( https://support.apple.com/zh-cn/HT214084 )的文档也没有说有这个变更 OpenJRE 自己去 workaround 这个问题,和苹果意外搞出的错误没有任何关系 |
79 codehz OP @daveh 此外 pthread_jit_write_protect_np 是只有 macOS 给 apple sillicon 提供的功能,solaris 根本就没这个 api ,何来的十几年的垃圾代码 |
80 daveh 2024-03-18 12:52:04 +08:00 via iPhone @codehz #79 都说了你去看 OpenJDK 的修改,删了一拖屎山。不要在这瞎猜,如果你真对这个问题感兴趣的话。 |
81 lslqtz 2024-03-18 12:57:08 +08:00 @Rorysky 合理了. 不用担保的 API 或特性去产生的任何行为都不应该被视为可靠的, 利用这种特性去做的程序本身也是不可靠的, 只不过这个不可靠性取决于上游 (也就是 Apple). 大家都用 **不等于** 这么用正确. 不过 Apple 在 Dev 不改甚至 RC 不改, 却在正式版改, 太激进了, 这样的发布策略并不合理. |
82 lslqtz 2024-03-18 13:00:17 +08:00 即, 未担保的方法/接口 显然是可以改动的, 但在改动之前, 应该先进行小范围/大范围测试, 而不是直接推到正式版上, 利用这种未担保特性显然是程序本身的问题, 但如果这种未担保特性的影响特别广泛, 那么测试有助于提前解决问题. |
83 codehz OP @daveh 我不知道你是在哪里得来的这个结论 https://bugs.java.com/bugdatabase/view_bug?bug_id=8320317 是这个吗?确实删除了一个对 SafeFetch 的调用,可问题是是在这里吗,后文不也提及了 JDK-8320317 removes this specific call to SafeFetch. We could probably still still hit similar issues with other calls to SafeFetch |
84 lslqtz 2024-03-18 13:11:05 +08:00 我重新看了看这个文档: These crashes are **most often** identified by the EXC_BAD_ACCESS (SIGSEGV) or EXC_BAD_ACCESS (SIGBUS) exceptions in the **crash report** 所以 Apple 并不保证这个信号一定是两者中的其中之一, 但又说的很隐晦... |
85 codehz OP @lslqtz 你看的是 https://developer.apple.com/documentation/xcode/investigating-memory-access-crashes 这个吧,问题是这并不是同一个问题,它是“调查因内存问题而崩溃的方法” |
88 daveh 2024-03-18 15:01:54 +08:00 via iPhone |
89 daveh 2024-03-18 17:03:43 +08:00 via iPhone @codehz #83 问题就在这里。 首先,要明白不是说调用 SafeFetch 就有问题,是传无效或受保护地址才有问题。 删掉的屎山代码可能会传 0 地址,所以必然出问题。 至于其他地方是否乱用,让子弹飞一会吧。 导致问题的屎山代码删掉后,据说 JRE 性能都提高了一些。 所以说 macOS 这个新行为,反而督促了 JDK 去优化改进,去发现问题。 |
90 daveh 2024-03-18 17:07:13 +08:00 via iPhone @codehz #78 JDK 的改动不是 workaround ,建议你追加更新一下实际原因,不要误导。 |
91 codehz OP @daveh SafeFetch 整个函数的意义就是访问非法地址的时候能检测到结果,你是不明白这个 fetch 的含义吗。。。 https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safefetch.hpp#L31-L32 |
92 codehz OP @daveh https://github.com/openjdk/jdk/blob/master/src/hotspot/os/posix/safefetch_sigjmp.cpp 在 posix 下的实现就是 sigsetjmp 保存跳转地址,然后在异常处理代码里检测 SIGSEGV 和 SIGBUS 来跳转到返回 false 的分支 |
93 MrKrabs 2024-03-18 19:17:45 +08:00 java 配不上金子内存,自己滚吧 |
94 huijiewei 2024-03-18 19:56:37 +08:00 这种特性不是病毒最喜欢用的么。Java 也用啊。 |
95 ShadowPower 2024-03-18 20:38:40 +08:00 via Android @daveh 幸好苹果的客户主要是普通消费者。我上班做 toB 的项目,要是公司的项目像苹果这样做,免不了要赔偿,还得加班给客户处理好所有问题。 |
96 daveh 2024-03-18 21:18:43 +08:00 via iPhone |
97 codehz OP @daveh 没错是改成 static 了,但还是用信号的啊 https://github.com/openjdk/jdk/blob/master/src/hotspot/os/posix/safefetch_static_posix.cpp 只不过把 safefetch 本身的代码用汇编写成直接一个 mov 或者 ldr https://github.com/openjdk/jdk/blob/master/src/hotspot/os_cpu/bsd_aarch64/safefetch_bsd_aarch64.S 不是很懂为啥要杠这一点 |
98 codehz OP @daveh 这个修改也只是另一个性能优化 https://bugs.openjdk.org/browse/JDK-8283326 SafeFetch is important - mostly when writing hs-err reports, but it is also used in low level utility functions like `os::print_hex_dump()` and `os::is_readable_pointer()`. It is also used (JDK-8282306) as part of call stack printing. At the moment it is implemented via dynamically generated stub routines |
99 tanranran 2024-03-19 10:50:07 +08:00 |
100 fang2hou 2024-03-19 23:31:07 +08:00 via iPhone 我们公司每次有个专门 team 先把开发环境先全测了没问题才允许全公司升级,以前觉得小题大做,这次完美避过了 |