+作者HuangJC (吹笛牧童)
看板java
标题[问题] 有没有人碰过所谓的灵异现象
时间Tue Jun 21 12:58:02 2016
抱歉,"灵异现象"一词是敝公司内部在用的
主要是讲各种 执行不如预期
通常,有很大机率,会被人说"那是你自己写程式不小心"
比如,程式在不该当的地方当了
但怎麽查也查不到原因
其实原因是更早之前使用了不存在或已删除的变数
(这状况在 java 还没碰过;我是举 C 的例子)
因为无效指标会让程式流程跑到乱码
也可能破坏堆叠;而且不一定"马上"当
不过这次的例子比较奇怪
主管已经追一个礼拜了
(幸好不是发生在我身上,不然一定骂死我又不相信我
发生在他身上,他则说"解决了有赏"XD)
public void run() {
long __lCurrentTime = 1466154837;
long __lTimeout = __lCurrentTime + 6;
boolean __result = false;
// 中断点1:请在下一行放置一个中断点
int __count = 0;
for ( ; __lCurrentTime<=__lTimeout ; __lCurrentTime++) {
__result = __lCurrentTime<=__lTimeout;
__count++;
}
// 中断点2:请在下一行放置一个中断点
}
这次的例子是上面的小程式
中断点 2 永远执行不到
而检查 __result, 也竟然永远为 true
自己直接把程式放入自己的小专案当然没问题
我们这个是放在敝公司的专案中
而奇怪的是,它还挑 build machine
这段 code 要在特定机器上 build 出 APK
才会执行不完
其他机器去 build, 则不会执行出问题
(而我们信任自己的 build machine 啊,那是装好後只用来 build 程式的一台专用机器)
重灌 build machine 吗?也不对,因为不只一台会出问题
(会出问题的就一直 build 出问题)
程式的逻辑很简单(虽然不实用;因为它是专为了重制问题,简化出来的 code)
同事因此做了个猜测:堆叠炸了
如果堆叠炸了,那当然程式就不必谈逻辑了
但这段程式是位在另一个 thread, 同事加大 thread stack
new Thread(null, null, TAG, 2*1024*1024)
没有用
主管现在用另一个方法回避问题
把原来的 long, 故意 cast 成 double
这样是很无聊啦,但 compare 结果会正确了!!
(如果是堆叠炸了,应该要减少使用自动变数才对
奇奇怪怪的回避方法未必能解决问题吧!)
同事说,这种奇怪的问题,未必能用以前 C/C++ 时的逻辑去猜想
可能要更深入 byte code 去推断
请问有没有人解过类似的问题
(这不像 java 语法问题,而是 java 环境使用上的问题)
谢谢
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 60.251.197.55
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/java/M.1466485086.A.EF4.html
1F:推 KekeMonster: 是我木眼吗,result 本来就该是 true,false 才是灵 06/21 18:18
2F:→ KekeMonster: 异现象吧? 06/21 18:18
__lCurrentTime 会渐渐变大,而它比起另一个变数,只大6
因此这个 forloop 执行六次左右就该结束
结束时 __lCurrentTime<=__lTimeout 这个判断条件应为 false
而 __result 应该不会被更新到
目前的问题就是不结束,__result 永远为 true
我应该没写错
3F:→ qrtt1: 不要把找不出 root cause 当灵异现象啊 @o@ 06/21 19:08
我常在板上挨骂就算了,他们可资深得不得了
工研院出来的,Trace 过整个 Unix,
我会怀疑他们在 java 上的功力,毕竟碰没多久
但不会怀疑他们在 c/c++, multi-thread 的功力 (因为也曾是 multithread 讲师)
因此比如变数被另一个 thread 干扰,这个也排除了
我自己前阵子是解掉一个,原因就是 multi-thread
变数 a 的值,我一直 trace 都是我要的,突然一转眼就变了
但是同事避这个问题的方法很简单:他没必要共享变数,他的是 local 变数
既然变数没被另一个 thread 参考到,那值就没有变的理由
那为什麽会出现像
boolean a = (5 <= 3);
结果 a 为 true, 这样很根本上的错误呢?
-------------------
问题可能解掉了
方法是把一个四千多行的函式缩小,拆成数个
因此目前我们只能认为,这也是 java 的极限
(我不是说不能写四千多行喔,别自己去写一个四千多行的来打我脸,不是这样喔)
麻烦的就是,既然炸了也好歹打声招呼,丢个 fatal 出来
都没有啊,默默的...
那我们就有一堆不确定的猜测,大量的测试
操死一堆同事...
※ 编辑: HuangJC (60.251.197.55), 06/21/2016 19:26:31
4F:推 KekeMonster: result 已经在 loop 里被更新 6 次 true 了怎麽会是 06/22 00:21
5F:→ KekeMonster: false 06/22 00:21
6F:→ dennisxkimo: 我怎麽看都不觉得结果是flase 06/22 11:44
7F:→ HuangJC: 不会是false,但也不该被执行到 06/22 11:58
8F:→ HuangJC: 我们是因为奇怪它为什麽被执行 第七次以上 06/22 11:59
9F:→ HuangJC: 大家还是在正常逻辑里打转,那不可能看懂我这篇啊... 06/22 12:00
10F:→ dennisxkimo: 我看起来是0> 1> 2> 3> 4> 5> 6 七次 06/22 12:06
11F:→ KekeMonster: 哈 对是 7 次 06/22 12:09
12F:→ KekeMonster: 我觉得你为了呈现现象,把范例程式码过度简化到不会 06/22 12:11
13F:→ KekeMonster: 有错了吧 06/22 12:11
14F:推 KekeMonster: 推 qrtt1 大所说的,我觉得比较像是逻辑性的错误造 06/22 12:16
15F:推 KekeMonster: 成无穷回圈,找出 root cause 吧 06/22 12:16
16F:→ bitlife: java method的max code size是有64KB的上限,但compiler多 06/22 12:30
17F:→ bitlife: 半会警告,4000行过了没有难以确定,但写这麽长确定不太好 06/22 12:30
18F:→ bitlife: 除错及维护 06/22 12:30
19F:→ HuangJC: 那我再说一次,不是跑了七次,是 endless,大家还是觉得 06/22 13:05
20F:→ HuangJC: 逻辑性错误? 06/22 13:05
21F:→ HuangJC: 当跑了二十次,我们就怀疑其布林值,这时还为true就怪了 06/22 13:08
22F:→ HuangJC: 程式有简化,但简化的版本足以重制问题… 06/22 13:09
23F:→ HuangJC: 我不是在表达逻辑而已,而是就这样的code就会有endless l 06/22 13:11
24F:→ HuangJC: oop 06/22 13:11
25F:→ dennisxkimo: "不结束,__result永远为true",for loop 无关resut吧 06/22 14:08
26F:→ ssccg: 有错和没错的版本bytecode长一样? 以你的说法看起来跟build 06/22 16:00
27F:→ ssccg: 环境有关,跟执行环境没关? 06/22 16:01
28F:→ ssccg: 那去研究stack memory好像方向错误 06/22 16:03
29F:→ HuangJC: For loop 是否结束,决定於那个布林算式,那是和result一 06/22 16:17
30F:→ HuangJC: 模一样的算式 06/22 16:17
我文章从头到尾都说和build machine 有关啊,换一台就不会
从前我做 embedded system 时
用的 cross compiler 很让人怀念
那是一个只要一模一样的 source code 进去
build 出来的 bin 就会一模一样的 compiler
因此,每当我拿到一台新的电脑,重架起 compiler 时
第一件事,就是 build 一下我们最新的 release
拿到结果後做 file compare, 一定要一模一样
若没一模一样,就一定有错
又比如,加入新 model, 新功能
而同一个 code 要 build 旧 model
code 已经有变了,要证明没影响旧 model
也是 build 一版出来做 file compare
像现在这个问题,特定 build machine 有问题
其实我是很想说"就那台 build machine 自己有问题啦"
但不只一台会
因此如果没找到问题,有个合理假设
换台机器 build 仍然只会被认为是头痛医头脚痛医脚而已
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 16:34:28
31F:→ bitlife: 作业系统,CPU,JDK版本等都一模一样吗? 这当然有可能是遇 06/22 16:37
32F:→ bitlife: compiler本身的错误 06/22 16:38
__lCurrentTime = 1466154837;
__lTimeout = __lCurrentTime + 6
跑数次後, __lTimeout 没变
__lCurrentTime 变成 1446154850
然後 __result = __lCurrentTime<=__lTimeout;
这个 __result 依然是 true
这样大家感觉到问题没有?
当然我自己不会这样写程式
我认为如果是记忆体错乱,那麽 for loop 里的布林值和算式里的布林值,就未必结果相同
真的要龟毛,必需这样写
for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) {
Log.v(TAG, __result);
__count++;
}
也就是一定要和 forloop 的判断式,用同一个
看到底是 boolean 不如预期
还是明明为 false 了但却不往外跳
不管哪一个,其实都是执行不如预期
但我觉得这样会更精准点
这已经不是语言逻辑层次的问题,我其实见过很多次
文章前面也说了:当记忆体配置出问题,那就见怪不怪了
java 用的是 GC,而不像 C 要由工程师自己去管理记忆体,new & delete
因此在 GC 极强的记忆体管理下
我其实还不太清楚怎麽造出以前的状况
在 C,这种问题可以说是层出不穷,都是要去检讨有没有做了违犯规定的存取..
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 16:45:12
33F:→ ssccg: 所以bytecode到底一不一样... 06/22 16:45
34F:→ HuangJC: bytecode 是指 build 出来的结果吗?不一样 06/22 16:45
35F:→ ssccg: compiler有可能就真的有bug啊... 06/22 16:45
36F:→ HuangJC: android compiler 可以做到一模一样吗?我试试 06/22 16:46
37F:→ ssccg: android build过程有几个step,至少先看一下是从.class就不 06/22 16:50
38F:→ ssccg: 一样,还是dex不一样,还是哪边...才知道问题在哪步吧 06/22 16:50
39F:→ HuangJC: 我自己的电脑 build 两次,就不会一模一样了 06/22 16:53
40F:→ HuangJC: 所以这种 compiler 是无法这样验证的 06/22 16:53
41F:推 kiwatami: 我觉得问题不在机器 而是程式里面有不够严谨的判断 06/22 16:58
42F:→ kiwatami: 而机器的差异导致这个 bug 产生 06/22 16:58
43F:→ kiwatami: 有没有试着在回圈内 print 相关的值? 06/22 16:58
44F:→ kiwatami: 看看值的变化在两台机器上有什麽不同? 06/22 16:58
45F:→ kiwatami: 并且确定每次值的异动是不是都有执行到 06/22 16:58
46F:→ kiwatami: 感觉你的判断式跟时间有关 如果加上 thread.sleep 06/22 16:58
47F:→ kiwatami: 有没有可能导致结果不如预期? 06/22 16:58
48F:→ kiwatami: 例如时间差太大跑进了其他判断式 导致 r 或 c 没被更新 06/22 16:58
49F:→ kiwatami: 然後这个回圈本来就会执行七次 r 也会是 true 06/22 16:58
50F:→ kiwatami: 如果 r 不是 true 就根本进不去回圈了 06/22 16:58
51F:→ kiwatami: 你仔细看 回圈的条件跟你的 r 值条件是一样的 06/22 16:58
52F:→ HuangJC: 为什麽和时间有关?又没有 time api.. 06/22 17:07
53F:→ ssccg: 最後的apk当然会不一样,但是.class和.dex我试过同电脑相同 06/22 17:09
54F:→ dennisxkimo: for结束跳出後 {}内result还是true很正常 06/22 17:11
没结束,没跳出;我再强调一次,这变成 endless loop
55F:→ HuangJC: ... 谢谢大家,因为问题已解,所以我也重制不了了... 06/22 17:11
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:12:08
56F:→ dennisxkimo: __result不能拿来判断回圈结束条件的状态 06/22 17:12
57F:→ HuangJC: 但如果回圈死不结束,它就有意义了 06/22 17:14
58F:→ dennisxkimo: 死不结束 要检查你结束条件设的变数 不是result 06/22 17:19
可是 result 和结束条件一模一样啊
> → kiwatami: 你仔细看 回圈的条件跟你的 r 值条件是一样的
是的,我知道;故意的
就是一模一样,所以我们现在在这里各说各话了...
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:22:41
59F:→ dennisxkimo: for条件内的A<=B 跟{}内的C=A<=B的C不同 06/22 17:21
是的,你和我一样龟毛
所以如果让我主导,我主张这样写
for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) {
分成两行,就要小心有别的机会被修改变数
我再申明一点:
这不是示意 code, 我们就只把这麽简单的 code 放在程式里
就变成 endless loop
所以谁还能偷改变数?
逻辑内已经没什麽好检讨,要检讨的是逻辑外...
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:25:30
60F:→ HuangJC: 还有一点,因为"一定要特定机器 build 才会出错" 06/22 17:28
61F:→ HuangJC: 所以,无法在自己机器上步进执行.. 06/22 17:28
62F:→ HuangJC: 只能不断的设计 log;所有变数,我们都监看了 06/22 17:28
63F:→ HuangJC: 当我们 trace 到,current 值'远'大於 timeout值时 06/22 17:29
64F:→ HuangJC: 真的,无法解释.. 06/22 17:29
65F:→ HuangJC: unsigned 的溢位我也假设过;所以我们才把布林值记下来 06/22 17:30
66F:→ HuangJC: 不然不必加上那一行的.. 06/22 17:30
67F:推 dennisxkimo: 不要被改结束条件就for(int i=0;i<=6;i++){},不行吗? 06/22 17:33
行啊,但那就不是我们的程式了
同事只说"这片段可以重制 BUG"
我们是要找问题
最後,我们同意这是堆叠爆了的现象
他说完後,谁赞成,谁反对?
68F:→ dennisxkimo: 不然当curtime某原因不小心大於long.max就... 06/22 17:34
所以我们的实验方法是有注意到这点的
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:36:38
69F:→ KekeMonster: " 分成两行,就要小心有别的机会被修改变数" 写成一 06/22 17:36
70F:→ KekeMonster: 行就没机会? 你程式最後被编译成 bytecode 後变成几 06/22 17:36
71F:→ KekeMonster: 个指令? 06/22 17:36
72F:→ HuangJC: 尽人事而已,我没更好的工具了.. 06/22 17:37
73F:→ dennisxkimo: 假设Timeout保证不变,应该是观察__lCurrentTime值 06/22 17:37
有 log 它没错
74F:→ HuangJC: 我们对 bytecode 会有的现象真的不熟.. 06/22 17:37
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:38:02
题外话,在以前写 C 时,我碰过另一个灵异现象
void F2() { a = 5; }
void F1() {
F2();
a = 6;
a = 7;
.
.
.
}
如上,F2 是帮忙设一些初始值
接下去 F1 的片段是开始修改那些值
可是我发现那些值一直跳回来
比如上例的 a, 明明後续设为 6, 设为 7
但监控它会发现它一直是 5
我全域搜寻 a 何时被改为 5,只有 F2 里有做这件事
後来忍不住了,在 F2 开头下一个中断点
结果发现,F1 "每执行一行",就会跳去执行 F2 一次
程式里完全看不出来,但执行结果就是这样
那次我加班到凌晨都解不掉
幸好第二天,主管说那个案子不做了
我真的感觉莫名其妙
不管重开机,clean build
种种能把环境弄乾净的方法我都试了
但我就是不知道,谁夺取了控制权,硬去执行 F2 的
那种感觉就像陷入了单步中断
debugger 就会做这种事
但那是 VC 的特权,谁抢去做了?
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 17:49:37
75F:→ dennisxkimo: 你是用debug 一一观察每次回圈 各变数变化吗? 06/22 17:59
76F:→ dennisxkimo: 还是在回圈内外插入print之类来看数值? 06/22 18:00
这要讲两支程式,一支是线上实际出货的
这个有看所有变数
另外一个是为了缩小测试范围,所以简化(但仍然可以重制 bug)
公开在板上的是这支程式
这支程式里已经没有 log 了
我也不可能用步进执行
因为,一但步进执行,就是在我的机器上重 build
(不重 build 但又可以步进执行,以前 code view 玩过,现在在 android 我还没试过)
而我们强调是'那一台特定 build machine 才会有问题'
公开的这一支,同事只说,永远执行不到第二个中断点
endless loop
这句话,和实际出货的程式倒是一样
QA 测到的就是回圈跑不完;但比较值已经递增到极大了,为什麽还不结束 loop?
(比较值及被比较值,两者都有 log)
我是很想参战啦,但我没重制过问题
我参战的必要条件是:让我可以操作 build machine,任意 build 我改的 code
这点,没法子
大家烦得要死,不给我玩
--------------
我们出货的程式,不是任意 RD 电脑 build 出来都可以出
而是特定的 build machine
它一连串的批次动作,从取 code, build, 到包装成出货 package 放至特定子目录
(子目录名称和时间日期相关)都是自动化的
我无法把我的 code 塞给它,除非我 check in code
他们不给我玩啦..
所以,谢谢大家,警察可以出来洗地了...
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 18:15:49
77F:推 kiwatami: 喔 因为你特别用 time millis 当范例 我才以为你很直觉 06/22 18:15
78F:→ kiwatami: 的使用你原本的原始码使用的判断式当例子 才会猜你原本 06/22 18:15
79F:→ kiwatami: 的判断式是不是跟时间有关 06/22 18:15
80F:→ kiwatami: 不过既然你知道是一样的 06/22 18:15
81F:→ kiwatami: 怎麽会认为他跑不了第七次? 06/22 18:15
82F:→ kiwatami: 是跑不了第八次才对 06/22 18:15
83F:→ kiwatami: 如果是你说的那个什麽堆叠爆了... 06/22 18:15
84F:→ kiwatami: 这范例才 10 行不到 不太可能吧 06/22 18:15
但我有说,这段程式,要放在我们程式内
我不想怪你没爬文耶,我是来请教大家的,要有礼貌 :P
85F:→ kiwatami: print currenttime 会超过 timeout? 还是永远等於? 06/22 18:15
会超过 :P
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 18:18:22
86F:→ ssccg: 这次没得试了,下次遇到再说吧,我是觉得比较可能是编出来 06/22 18:35
87F:→ ssccg: 执行档就是错的,不然stack爆了再把long cast成double反而 06/22 18:36
88F:→ ssccg: 会对不太合理 06/22 18:37
89F:→ HuangJC: 我多少表达个参战的决心,唉 06/22 18:38
90F:→ HuangJC: 讲话要先大声才有机会 06/22 18:38
91F:→ HuangJC: 我很有想法,我觉得log太少了 06/22 18:39
for ( ; ; __lCurrentTime++) {
__result = (__lCurrentTime<=__lTimeout);
Log.v();//__result, __lCurrentTime, __lTimeout 三个都要看
//一次就能让大家标定问题
if ( !__result )
break;
}
这样更龟毛
问题应该可以限缩至"为什麽 long compare 出错了"
※ 编辑: HuangJC (60.251.197.55), 06/22/2016 19:09:55
92F:→ HuangJC: 我一向不喜欢同样的事做两次,然後预设它们结果一样 06/22 19:10
93F:→ HuangJC: 我喜欢的 code style,就是只做一次,把它存起来用两次 06/22 19:10
94F:→ HuangJC: 这样争议小很多.. 06/22 19:11
95F:→ HuangJC: 比如 multi-thread 变数瞬变,我踢的铁板也够多了.. 06/22 19:12
96F:推 kiwatami: 我没爬完整 我错了 我自己去角落玩沙... 06/22 19:51
97F:→ HuangJC: :P 我自己安全过关就好.. 06/23 00:14
98F:→ realmeat: c compile出问题并不是啥新鲜事 06/23 11:45
99F:→ realmeat: 通常都要追bytecode才看的出来 06/23 11:45
100F:→ HuangJC: byte code 要怎样追?以前写 VC 时,可以轻易调出组合语 06/23 12:41
101F:→ HuangJC: 言,以及监看所有暂存器;现在在 Android Studio 下,也 06/23 12:41
102F:→ HuangJC: 可以轻易看到 bytecode 吗? 06/23 12:41
103F:→ Chikei: 拿artifact出来直接看编出来的bytecode不同机器是不是一样 06/24 14:56
104F:→ eieio: 1446154850 本来就比 1466154837+6 要小啊 07/01 12:41