我发现我在市场上发布的一个应用程序会在某些手机上产生奇怪的结果。经过调查发现,一个函数存在一个问题,即计算两个GeoPoints之间的距离 - 有时它会返回完全错误的值。此问题仅在具有MediaTek MT6589 SoC(又名MTK6589)的设备上重现。而AFAIK所有这些设备都安装了Android 4.2。
更新我还能够使用MediaTek MT8125/8389芯片重播联想S6000平板电脑上的错误,使用MT6589和安卓 4.1 重播Fly IQ444 Quattro安装。
我创建了一个有助于重现错误的测试项目。它重复运行计算1000次或100次迭代。为了排除线程问题的可能性,在UI线程上执行计算(具有小的暂停以保持UI响应)。在测试项目中,我只使用了原始距离公式中的一部分:
private double calcX() {
double t = 1.0;
double X = 0.5 + t / 16384;
return X;
}
您可以在web2.0calc.com上自行查看,X
的值应大约为:0.50006103515625
。
但是,在使用MT6589芯片的设备上,通常会计算出错误的值:2.0
。
项目为available at Google Code(APK也可用)。测试类的来源如下:
public class MtkTestActivity extends Activity {
static final double A = 0.5;
static final double B = 1;
static final double D = 16384;
static final double COMPUTED_CONST = A + B / D;
/*
* Main calculation where bug occurs
*/
public double calcX() {
double t = B;
double X = A + t / D;
return X;
}
class TestRunnable implements Runnable {
static final double EP = 0.00000000001;
static final double EXPECTED_LOW = COMPUTED_CONST - EP;
static final double EXPECTED_HIGH = COMPUTED_CONST + EP;
public void run() {
for (int i = 0; i < SMALL_ITERATION; i++) {
double A = calcX();
if (A < EXPECTED_LOW || A > EXPECTED_HIGH) {
mFailedInCycle = true;
mFails++;
mEdit.getText().append("FAILED on " + mIteration + " iteration with: " + A + '\n');
}
mIteration++;
}
if (mIteration % 5000 == 0) {
if (mFailedInCycle) {
mFailedInCycle = false;
} else {
mEdit.getText().append("passed " + mIteration + " iterations\n");
}
}
if (mIteration < mIterationsCount) {
mHandler.postDelayed(new TestRunnable(), DELAY);
} else {
mEdit.getText().append("\nFinished test with " + mFails + " fails");
}
}
}
public void onTestClick(View v) {
startTest(IT_10K);
}
public void onTestClick100(View v) {
startTest(IT_100K);
}
private void startTest(int iterationsCount) {
Editable text = mEdit.getText();
text.clear();
text.append("\nStarting " + iterationsCount + " iterations test...");
text.append("\n\nExpected result " + COMPUTED_CONST + "\n\n");
mIteration = 0;
mFails = 0;
mFailedInCycle = false;
mIterationsCount = iterationsCount;
mHandler.postDelayed(new TestRunnable(), 100);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler(getMainLooper());
mEdit = (EditText) findViewById(R.id.edtText1);
}
private static final int IT_10K = 1000;
private static final int IT_100K = 100000;
private static final int SMALL_ITERATION = 50;
private static final int DELAY = 10;
private int mIteration;
private int mFails;
private boolean mFailedInCycle;
private Handler mHandler;
private int mIterationsCount;
private EditText mEdit;
}
要解决此问题,只需在double
方法中将所有float
更改为calcX()
即可。
进一步调查
关闭JIT(通过向应用清单添加android:vmSafeMode="true"
)也可以修复bug。
之前有没有人见过这个bug?也许这是一个已知的问题?
p.s。:如果有人能够使用其他芯片在设备上重现此错误,或者可以使用任何MediaTek芯片和Android&gt; = 4.3进行测试,我将非常感激。
答案 0 :(得分:3)
这是一个JIT错误,它在2012年末到2013年初在JellyBean源中处于活动状态。简而言之,如果两个或更多个双精度常量在高32位中不同,但在低32位中是相同的在同一个基本块中使用JIT会认为它们是相同的,并且不恰当地优化其中一个。
我在https://android-review.googlesource.com/#/c/47280/
中介绍了这个缺陷并将其修复为:https://android-review.googlesource.com/#/c/57602/
此缺陷不应出现在任何最近的Android版本中。
答案 1 :(得分:1)
您遇到的问题可能与处理器硬件有关。
计算历史中有一些臭名昭着的例子:
1994年,一些英特尔奔腾处理器确实出现错误,产生浮点计算错误(FDIV错误)。这只是小数点后的第4位数字。英特尔最终实施了一项替换计划,将有缺陷的CPU换成好的CPU。
DEC VAX 11/785(1984年推出)在其(可选)浮点协处理器中存在设计缺陷。由于硬件中的竞争条件,有时浮点协处理器在某些机器上返回任意值而不是所需的结果。数字设备公司制定了一项计划,在所有客户处更换(5个大型印刷电路板)协处理器并签订硬件维护合同。
我建议您可以尝试在更广泛的硬件基础上进行更多测试,以便更好地了解问题。如果问题确实与硬件有关,我猜你最好的方法就是找到解决问题的方法。将文档记录给其他开发者。
答案 2 :(得分:1)
之前有没有人见过这个bug?也许这是一个已知的问题?
有时会出现在几个Android邮件列表中。
我相信你所看到的是(1)不同的CPU及其对浮点值的处理,以及(2)导致不同舍入和截断的存储大小差异的影响。
对于(1)在本机代码中使用类似以下的内容:
_controlfp(_PC_24, _MCW_PC);
_controlfp(_RC_NEAR, _MCW_RC);
对于(2)使用公共存储大小,即float
。
本机世界中有时会出现另一个相关问题:将float传递给函数,但函数的值始终为0.0f
(而不是用于调用函数的非0值)。您可以使用-mfloat-abi=softfp
清除该内容。请参阅Hard-float and JNI。
不幸的是,在使用Android Java端口时,您将受到制造商的支配。享受他们的调整,疏忽和实施错误。至少它不是corrupting your VM。
答案 3 :(得分:1)
我花了最后一周调查这个问题,这是我发现的:
X = A + b / D
dalvikvm
,并将参数传递给它。这允许设置jit阈值并接收由JIT生成的输出ARM代码-Xjitdisableopt:1
传递给Dalvik会解决问题(此参数会禁用kLoadStoreElimination优化)。也可以将dalvik.vm.extra-opts=-Xjitdisableopt:1
添加到build.prop
文件作为快速解决方法,保留JIT(需要root和reboot)libdvm.so
(来自带有MT6589芯片的Fly IQ4410),并在那里再现了错误。但是,如果我使用从Android 4.2源代码编译的libdvm.so
,则该bug将消失。看起来由受影响的设备随附的某些特定版本的libdvm
库生成的JIT编译代码存在问题libdvm.so
( MT6589,Android 4.1.2)我已提交错误报告#65750。
以下是用于重现错误的测试的源和JIT程序集输出:
public class Calc {
static final double A = 0.5;
static final double B = 1;
static final double D = 16384;
public double calcX() {
double t = B;
double X = A + t / D;
return X;
}
}
通常的Dalvik运行的JIT输出:
D/dalvikvm: Dumping LIR insns
D/dalvikvm: installed code is at 0x45deb000
D/dalvikvm: total size is 124 bytes
D/dalvikvm: 0x45deb000 (0000): data 0xc278(49784)
D/dalvikvm: 0x45deb002 (0002): data 0x457a(17786)
D/dalvikvm: 0x45deb004 (0004): data 0x0044(68)
D/dalvikvm: 0x45deb006 (0006): ldr r0, [r15pc, -#8]
D/dalvikvm: 0x45deb00a (000a): ldr r1, [r0, #0]
D/dalvikvm: 0x45deb00c (000c): adds r1, r1, #1
D/dalvikvm: 0x45deb00e (000e): str r1, [r0, #0]
D/dalvikvm: -------- entry offset: 0x0000
D/dalvikvm: L0x4579e28c:
D/dalvikvm: -------- dalvik offset: 0x0000 @ const-wide/high16 v0, (#16368), (#0)
D/dalvikvm: 0x45deb010 (0010): vldr d8, [r15, #96]
D/dalvikvm: -------- dalvik offset: 0x0002 @ const-wide/high16 v2, (#16352), (#0)
D/dalvikvm: 0x45deb014 (0014): vmov.f64 d9, d8
D/dalvikvm: -------- dalvik offset: 0x0004 @ const-wide/high16 v4, (#16592), (#0)
D/dalvikvm: 0x45deb018 (0018): vmov.f64 d10, d9
D/dalvikvm: -------- dalvik offset: 0x0006 @ div-double/2addr v0, v4, (#0)
D/dalvikvm: 0x45deb01c (001c): vdivd d8, d8, d10
D/dalvikvm: -------- dalvik offset: 0x0007 @ add-double/2addr v0, v2, (#0)
D/dalvikvm: 0x45deb020 (0020): vadd d8, d8, d9
D/dalvikvm: -------- dalvik offset: 0x0008 @ return-wide v0, (#0), (#0)
D/dalvikvm: 0x45deb024 (0024): vmov.f64 d11, d8
D/dalvikvm: 0x45deb028 (0028): vstr d11, [r6, #16]
D/dalvikvm: 0x45deb02c (002c): vstr d8, [r5, #0]
D/dalvikvm: 0x45deb030 (0030): vstr d10, [r5, #16]
D/dalvikvm: 0x45deb034 (0034): vstr d9, [r5, #8]
D/dalvikvm: 0x45deb038 (0038): blx_1 0x45dea028
D/dalvikvm: 0x45deb03a (003a): blx_2 see above
D/dalvikvm: 0x45deb03c (003c): b 0x45deb040 (L0x4579f068)
D/dalvikvm: 0x45deb03e (003e): undefined
D/dalvikvm: L0x4579f068:
D/dalvikvm: -------- reconstruct dalvik PC : 0x457b83f4 @ +0x0008
D/dalvikvm: 0x45deb040 (0040): ldr r0, [r15pc, #28]
D/dalvikvm: Exception_Handling:
D/dalvikvm: 0x45deb044 (0044): ldr r1, [r6, #108]
D/dalvikvm: 0x45deb046 (0046): blx r1
D/dalvikvm: -------- end of chaining cells (0x0048)
D/dalvikvm: 0x45deb060 (0060): .word (0x457b83f4)
D/dalvikvm: 0x45deb064 (0064): .word (0)
D/dalvikvm: 0x45deb068 (0068): .word (0x40d00000)
D/dalvikvm: 0x45deb06c (006c): .word (0)
D/dalvikvm: 0x45deb070 (0070): .word (0x3fe00000)
D/dalvikvm: 0x45deb074 (0074): .word (0)
D/dalvikvm: 0x45deb078 (0078): .word (0x3ff00000)
D/dalvikvm: End LCalc;calcX, 6 Dalvik instructions.
最有趣的部分是:
vldr d8, [r15, #96] ; d8 := 1.0
vmov.f64 d9, d8 ; d9 := d8
vmov.f64 d10, d9 ; d10 := d9 // now d8, d9 and d10 contains 1.0 !!!
vdivd d8, d8, d10 ; d8 := d8 / d10 = 1.0
vadd d8, d8, d9 ; d8 := d8 + d9 = 2.0
vmov.f64 d11, d8
JIT生成的代码看起来完全错误。而不是三个只有一个常量读取1.0,因此我们得到X = 1.0 + 1.0 / 1.0
的计算,这不奇怪地评估为2.0
这是Dalvik运行的JIT输出,禁用kLoadStoreElimination
优化(修复了错误):
D/dalvikvm: Dumping LIR insns
D/dalvikvm: installed code is at 0x45d64000
D/dalvikvm: total size is 124 bytes
D/dalvikvm: 0x45d64000 (0000): data 0x5260(21088)
D/dalvikvm: 0x45d64002 (0002): data 0x4572(17778)
D/dalvikvm: 0x45d64004 (0004): data 0x0044(68)
D/dalvikvm: 0x45d64006 (0006): ldr r0, [r15pc, -#8]
D/dalvikvm: 0x45d6400a (000a): ldr r1, [r0, #0]
D/dalvikvm: 0x45d6400c (000c): adds r1, r1, #1
D/dalvikvm: 0x45d6400e (000e): str r1, [r0, #0]
D/dalvikvm: -------- entry offset: 0x0000
D/dalvikvm: L0x45717274:
D/dalvikvm: -------- dalvik offset: 0x0000 @ const-wide/high16 v0, (#16368), (#0)
D/dalvikvm: 0x45d64010 (0010): vldr d8, [r15, #96]
D/dalvikvm: -------- dalvik offset: 0x0002 @ const-wide/high16 v2, (#16352), (#0)
D/dalvikvm: 0x45d64014 (0014): vldr d10, [r15, #76]
D/dalvikvm: 0x45d64018 (0018): vldr d9, [r15, #80]
D/dalvikvm: 0x45d6401c (001c): vstr d9, [r5, #8]
D/dalvikvm: -------- dalvik offset: 0x0004 @ const-wide/high16 v4, (#16592), (#0)
D/dalvikvm: 0x45d64020 (0020): vstr d10, [r5, #16]
D/dalvikvm: -------- dalvik offset: 0x0006 @ div-double/2addr v0, v4, (#0)
D/dalvikvm: 0x45d64024 (0024): vdivd d8, d8, d10
D/dalvikvm: -------- dalvik offset: 0x0007 @ add-double/2addr v0, v2, (#0)
D/dalvikvm: 0x45d64028 (0028): vadd d8, d8, d9
D/dalvikvm: 0x45d6402c (002c): vstr d8, [r5, #0]
D/dalvikvm: -------- dalvik offset: 0x0008 @ return-wide v0, (#0), (#0)
D/dalvikvm: 0x45d64030 (0030): vmov.f64 d11, d8
D/dalvikvm: 0x45d64034 (0034): vstr d11, [r6, #16]
D/dalvikvm: 0x45d64038 (0038): blx_1 0x45d63028
D/dalvikvm: 0x45d6403a (003a): blx_2 see above
D/dalvikvm: 0x45d6403c (003c): b 0x45d64040 (L0x45718050)
D/dalvikvm: 0x45d6403e (003e): undefined
D/dalvikvm: L0x45718050:
D/dalvikvm: -------- reconstruct dalvik PC : 0x457313f4 @ +0x0008
D/dalvikvm: 0x45d64040 (0040): ldr r0, [r15pc, #28]
D/dalvikvm: Exception_Handling:
D/dalvikvm: 0x45d64044 (0044): ldr r1, [r6, #108]
D/dalvikvm: 0x45d64046 (0046): blx r1
D/dalvikvm: -------- end of chaining cells (0x0048)
D/dalvikvm: 0x45d64060 (0060): .word (0x457313f4)
D/dalvikvm: 0x45d64064 (0064): .word (0)
D/dalvikvm: 0x45d64068 (0068): .word (0x40d00000)
D/dalvikvm: 0x45d6406c (006c): .word (0)
D/dalvikvm: 0x45d64070 (0070): .word (0x3fe00000)
D/dalvikvm: 0x45d64074 (0074): .word (0)
D/dalvikvm: 0x45d64078 (0078): .word (0x3ff00000)
D/dalvikvm: End LCalc;calcX, 6 Dalvik instructions
按预期加载所有三个常量并执行正确的评估。