[心得] 如何造成程式 Crash

最近在改程式的時候,發生以下異常存取的狀況


原因是以下的指標指到不合法的記憶體,但查很久就是查不到在那裡被改的

 if (m_pStaticCoreVoltage->IsWindowVisible())
 {
  m_pStaticCoreVoltage->SetWindowText(m_strMonCoreVoltage);
 }



後來採用 Data Breakpoint 的方式,先在指標建立的地方設定中斷點,然後在建立後取得其指標變數的位址設定 Data Breakpoint,因此當該位址的資料變化時就會產生中斷。指令為

DEBUG | New Breakpoint | New Data Breakpoint


通常中斷後 Keyboard 幾乎無法操作 (控制權被 Debug 搶走了),所以我在後面加了一行 TRACE 的指令,執行後用滑鼠選取變數名稱的位址,開啟 Data Breakpoint 視窗後會自動帶入 Address 欄位,只要再把 Byte Count 改成 4 即可 (Win32 point 佔 4 Bytes)。繼續往下執行即會產生下列中斷


中斷後有問題的程式碼

 for (INT i = 0; i < _countof(TempId); i++)
 {
  PIObservable pObj = m_pMonitorCtrl->RegistryMonitor(this, TempId[i], nGpuIndex + 1, bRegister);
  if (bRegister && pObj)
  {
   m_nTemperature[i] = (INT)pObj->getCur();
  }
 }

再看一下變數的宣告

 INT m_nTemperature[9]; // GPU x 1, Power x 5, Memory x 3

 CWnd* m_pStaticCoreVoltage;

以及 TempId 的宣告

const MonitorId TempId[] =
{
 ID_THERMAL, // GPU
 ID_GPU_TEMP2,

 ID_MEM_TEMP1, // Memory
 ID_MEM_TEMP2,
 ID_MEM_TEMP3,

 ID_POWER_TEMP1, // Power
 ID_POWER_TEMP2,
 ID_POWER_TEMP3,
 ID_POWER_TEMP4,
 ID_POWER_TEMP5,
};

原來 m_Temperature 只有宣告 9 個 INT 的空間,但 TempId 因為其他原因改成 10 個,且未同步更新 m_Temperature 的宣告,所以第 10 個就不小心蓋到 m_pStaticCoreVoltage 指標造成程式異常結束了

但奇怪的是,這一段程式只有在 Win32 下有問題,x64 並未發生問題,原來是 m_pStaticCoreVoltage 前面有 4 Bytes padding (可能是 x64 Pointer 為 8 Bytes,為了 Performance 的關係有作 Align 的調整),所以錯誤的資料沒有蓋到該指標變數

Win32 Memory


x64 Memory


結論:
  1. 程式中不要使用靜態的陣列宣告,改用 STL vector 取代
     vector<int> m_nTemperatureVector; // GPU x 2, Power x 5, Memory x 3
    
     for (INT i = 0; i < _countof(TempId); i++)
     {
      PIObservable pObj = m_pMonitorCtrl->RegistryMonitor(this, TempId[i], nGpuIndex + 1, bRegister);
      if (bRegister && pObj)
      {
       m_nTemperatureVector.push_back(pObj->getCur());
      }
     }
    
  2. 存取陣列元素一定要做邊界檢查 (即使是 vector 也是會產生邊界異常)

留言

這個網誌中的熱門文章

Linux 批次檔的寫法

【分享】如何顯示 Debug Message

SketchUp 如何列印 1:1 圖檔