2014-01-29 37 views
19

我發現我的應用程序在市場上發佈了一些在某些手機上產生奇怪的結果。經過調查發現,一個計算兩個GeoPoint之間距離的函數存在問題 - 有時它返回完全錯誤的值。此問題僅在具有MediaTek MT6589 SoC(又名MTK6589)的設備上重現。 AFAIK所有這些設備都安裝了Android 4.2。聯發科處理器上的雙精度值計算錯誤

更新我也能夠重現聯想S6000平板電腦的錯誤與MediaTek MT8125/8389芯片和Fly IQ444 Quattro與MT6589,並採用Android 4.1 安裝

我創建了一個測試項目,幫助重現bug。它重複運行計算1'000或100'000次迭代。爲了排除線程問題的可能性,計算在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 CodeAPK也是可用)。測試類的源介紹如下:

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; 

} 

要修復它足以只是改變所有doublefloatcalcX()方法的問題。

進一步調查
關閉JIT(通過添加android:vmSafeMode="true"到應用清單)修復錯誤,以及。

有沒有人見過這個bug?也許這是一個已知的問題?

P.S。:如果任何人將能夠重現此錯誤在設備上與其他芯片,或者可以與任何芯片發科和Android> = 4.3,我將高度讚賞它進行測試。

+1

https://code.google.com/p/android/issues/detail?id=63790詳細信息在運行Android 4.1.1的HTC One S設備上以及許多其他設備上看到的確切問題。在這個bug中,它最初看起來像與ProGuard的一些不良交互,但是進一步的分析已經將它降低到一些較小的Dalvik字節碼集。根本原因尚未確定,唯一的解決方法是您發現關閉JIT的解決方案。 –

+0

是的,它與我發佈的內容非常相似。谷歌代碼鏈接包含一個測試apk你可以嘗試,並[這裏](http:// stackoverflow。com/questions/20573598/proguard-can-cause-incorrect-computation)是與ProGuard的創建者更詳細的描述和討論。 – Dmitry

+1

我能夠在Android 4.2.1的MT6589手機(Canvas HD)中重現此錯誤。 – HackToHell

回答

3

這是從2012年底到2013年初在JellyBean源代碼中處於活動狀態的JIT錯誤。簡言之,如果兩個或兩個以上的雙精度常量在高32位中不同,但在低32位中相同在同一個基本塊中使用JIT認爲它們是相同的,並且不恰當地優化其中一個。

我介紹的缺陷:https://android-review.googlesource.com/#/c/47280/

,並在固定的:https://android-review.googlesource.com/#/c/57602/

缺陷不應該出現在任何Android的最新版本。

1

你所面臨的問題可能被與處理器相關的硬件。 有在計算曆史上臭名昭著的一些例子:
1994年,有些英特爾奔騰處理器確實有錯誤,產生浮點運算錯誤(錯誤FDIV)。這只是小數點後的第四位。英特爾最終制定了一個更換計劃,將有缺陷的CPU換成好的CPU。 DEC VAX 11/785(1984年推出)在其(可選)浮點協處理器中存在設計缺陷。由於硬件中存在競爭條件,有時浮點協處理器在某些機器上返回任意值而不是所需的結果。數字設備公司制定了一項計劃,以取代所有客戶的(5大印刷電路板)協處理器,並簽訂硬件維護合同。

我建議你可以嘗試做一個更廣泛的硬件基礎一些測試,以更好地理解這個問題。如果問題真的與硬件有關,我猜你最好的辦法是找到一種方法來解決問題&爲其他開發人員提供的文檔。

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

1

我的最後一個星期裏,調查這個問題,這裏就是我發現:

  • 此bug已被MT6589設備的用戶(例如herehere
  • 常見的解決方法是之前發現JIT的禁用(針對特定應用或整個系統)
  • 我能夠在MT6589和MT8125/8389, 這兩款設備上重現此問題,目前它尚未在其他芯片的設備上再現比提到 更新se下面
  • 抄錄錯誤的表達ctions如果簡單得多,比我張貼的問題,只是:
    X = A + b/D
  • 計算之間的延遲是錯誤的一個重要組成部分:沒有它的bug偶爾出現,用小睡眠後計算它總是再現(一旦代碼已被JITed)
  • 我已經創建了一個腳本,它組裝一個簡單的jar文件,並直接運行dalvikvm,傳遞參數給它。這允許設置jit閾值並接收由JIT生成的輸出ARM代碼
  • 向Dalvik傳遞-Xjitdisableopt:1修復了問題(此參數禁用kLoadStoreElimination優化)。你也可以添加dalvik.vm.extra-opts=-Xjitdisableopt:1build.prop文件作爲一個快速的解決方法,它保留了JIT(需要root和reboot)
  • 儘管這個問題看起來類似於Scott Barta提到的bug #63790,但我認爲它是不同的錯誤梅德已經證實,這種「聯發科」錯誤不會再現受#63790手機)上
  • 更新:我已經在模擬器上的推杆libdvm.so(從飛IQ4410與MT6589芯片)和錯誤複製有。但是,如果我使用從Android 4.2源編譯的libdvm.so,則錯誤消失。它看起來像有與隨受影響的設備
  • libdvm庫的某些特定版本的生產JIT編譯的代碼的問題
  • UPDATE:複製成功的bug三星王牌使用相同的2手機(NovaThor U8500,安卓4.1.2)技術 - 從飛IQ444(MT6589,安卓4.1.2)

我已提交bug報告#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; 
    }  
} 

JIT輸出的Dalvik的通常運行:

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 

所有三個常量都按預期載入並執行正確評估。