VS編譯器優化誘發一個的Bug

嗷嗷發表於2013-09-29

VS編譯器優化誘發一個的Bug

Bug的背景

我正在把某個C++下的驅動程式移植到C下,前幾天發生了一個比較詭異的問題。

驅動程式有一個bug,但是這個bug只能 Win32 Release 版本下的驅動才能重現。在 Win32 Debug 版本下,和 Win64 Release/Debug 版本下均無法重新。

隨著一步步的分析,最終發現問題是由於VS編譯器的一個優化誘發的。當然這並不是VS編譯器的bug,只是由於優化誘發程式裡面的某個bug.

除錯的思路

1. Debug Vs Release

首先想到的自然是看看Debug版本和Release版本執行是有啥區別了。Release版本本質上和Debug版本沒啥區別,一樣都可以使用偵錯程式除錯,只不過有些大大小小需要注意的地方罷了。基本上熟知編譯器的優化原理和偵錯程式,除錯Release版本的程式也不是啥困難的事情。

一般導致Debug和Release不同的常見問題無非下面這些,未初始化的區域性變數,程式指標訪問越界,多執行緒同步問題。而這些問題都是很容易發現的,但隨著除錯的深入,並沒有發現這些問題的蹤跡,反而所有的程式碼都工作的很好,沒啥異常發生。對比Debug和Release版本下的流程,Release版本的問題在於在某個時候硬體沒有如期的觸發中斷。但在這之前兩個版本所執行的程式碼邏輯完全一樣,沒有啥區別。

問題越發詭異了。

2. 32bit Vs 64 bit

既然64bit Release的版本沒有問題,但32bit Release有問題,這也是一個突破的思路。

把相關的程式碼拿出來仔細梳理,注意看一些32/64bit下的常見問題,諸如指標大小,整數溢位等問題。不過依然沒有發現問題。

3. 編譯器的優化

Debug和Release的最大的區別自然是編譯器的優化不同,但是哪個編譯器不是久經考驗,要是說編譯器上出問題,那真是撞了大運了,所以沒有第一時間考慮是由於優化造成的。不過既然直接除錯程式碼沒發現問題的所在,那只有使用另外一種分析方法了,那就是不斷縮小導致問題的範圍。

既然我手上有兩個版本,一個好的,一個壞的。那隻要不斷縮小兩個版本之間的不同,最終就能定位到導致問題的部分。

所以第一個拿來開刀的自然是編譯器的優化選項。幾經嘗試,終於發現誘發問題的編譯器優化是這一項,Favor Size Or Speed. 詭異,真是相當的詭異呀。不過無所謂,既然找到了這一個線索,下面就好找了。這個優化只是一個函式內的區域性優化,只要針對不同的函式分別開啟和關閉這個優化,自然就能發現問題所在了。

問題的根源

針對一些可疑的程式碼,分別開啟和關閉這個Favor Size Or Speed的優化,不斷的縮小範圍之後,最終定位到某個函式是罪魁禍首。然後比較了一下生成的彙編程式碼,最後定位到了下面這行語句

pDevice->pReg->S = 0xFFFFFFFF;

而這行程式碼生成的彙編程式碼如下:

or      dword ptr [eax+10h],0FFFFFFFFh   //Release版本,Favor Size優化
mov     dword ptr [eax+10h],0FFFFFFFFh   //Debug版本,無優化

乍一看一定會覺得這個詭異,因為or和mov指令在這裡的邏輯其實是一模一樣的,編譯器沒有任何錯誤。那為何會有不同的執行效果呢?

其實原因在於pDevice->pReg->S並不是一個普通的記憶體地址,他是一個MMIO(memory mapped IO) address地址。也就是說這個變數並不存在於記憶體裡面,而是硬體的某個暫存器,只不過和記憶體共用了一個地址空間。而對於這種MMIO address,雖然可以直接象訪問普通記憶體一樣讀寫他們,但最安全的方式還是使用Windows提供的函式,所以對於這個bug的修復是

WRITE_REGISTER_ULONG(&pDevice->pReg->S,0xFFFFFFFF);

至於為啥會出這種烏龍,其實就是一開始移植的時候粗心大意了。pDevice->pReg->S = 0xFFFFFFFF; 這句C++語句中的=其實一個過載過的C++運算子,內部的實現就是WRITE_REGISTER_ULONG,移植過來的時候也沒有細看具體哪個S是個什麼東西,就抄過來了

相關文章