1. 程式人生 > >dll檔案32位64位檢測工具以及Windows資料夾SysWow64的坑

dll檔案32位64位檢測工具以及Windows資料夾SysWow64的坑

自從作業系統升級到64位以後,就要不斷的需要面對32位、64位的問題。相信有很多人並不是很清楚32位程式與64位程式的區別,以及Program Files (x86),Program Files的區別。同時,對於程式的dll檔案應該放到System32資料夾,還是SysWow64,大部分人做的決定是,32位程式放到System32,64位程式放到SysWow64。是不是這樣呢,那麼今天就由我身邊發生的一個案例來詳細的說明一下。

回到頂部

dll檔案不匹配導致資料庫無法啟動

前段時間,資料庫做了一些功能上的改進,於是用VS2010編譯檢出了一個版本,供測試部測試。測試部拿到資料庫後,通過批處理將資料庫程式,註冊為服務。雖然執行的是批處理,實際上註冊服務的過程,是通過執行資料庫程式,並給其傳入命令列引數來完成的,詳情請看這篇文章

玩轉Windows服務系列——Debug、Release版本的註冊和解除安裝,及其原理

通過批處理執行程式後,出現如下問題:

應用程式無法啟動

出現這種問題,測試部不淡定了,叫我去看。我又試著運行了一下程式,依然出現這個問題。“可是在我的機器上執行的挺好的啊”,這是我說的第一句話,相信很多人看了這句話就會心的笑了。

有問題就是有問題,既然我的機器上可以正常執行,那麼測試機為什麼不行呢,首先要查詢原因。

資料庫是用VS2010編譯的,那麼在其他機器上執行,就需要執行的作業系統中以及安裝了VS2010的執行時,否則就會因為缺少程式執行所必須的dll檔案而無法正常執行。我想應該是這個原因,但又一想,如果沒有裝執行時的話,會提示缺少msvcr100.dll、msvcp100.dll等檔案,上圖中的問題顯然不是缺少dll問題。問題有點複雜,為了簡單,先試著安裝執行時,看能不能解決吧。

將VS2010的x86和x64 Runtime安裝包全裝了一遍。再執行程式,依然是這個醒目的錯誤。

雖然安裝執行時沒有解決這個問題,但根據經驗判斷,要麼是缺少dll檔案,要麼就是dll檔案版本出了問題。那麼,接下來就是想辦法證明這個猜想。

通過Dependency Walker檢測資料庫程式,所有依賴的dll檔案都存在,沒有發現什麼問題。然後通過Windows Sysinternals中的ListDLLs工具檢測當前執行的程序已經載入的dll檔案,從列表中看到msvcr100.dll沒有載入,估計就是這個dll檔案出了問題。從我的機器上找到這個檔案,替換了測試機上的msvcr100.dll檔案後,資料庫就正常運行了。

原來,剛剛啟動資料庫的時候,提示找不到msvcr100.dll檔案,測試的同事就從其他的XP系統的機器上找了這個檔案,並分別放入到System32和SysWow64中,於是就導致了上圖中的這個問題。

由於XP系統是32位的,所以找到的msvcr100.dll檔案也是32位,當把這個32位程式放到System32資料夾後,啟動64位資料庫,就會載入這個32位dll,由於64位程式只能載入64位dll,所以當程式嘗試載入32位dll時,就會報錯了。

回到頂部

究竟是System32還是SysWow64

Win7、Server2008等64位系統出來以後,為了相容32位程式,所以採用了Wow64方案,在系統資料夾中,可以看到一個System32資料夾,和一個SysWow64資料夾。雖然這個方案對於程式來說,可以很方便的相容32位程式,但是對於一般使用者來說,想分辨System32和SysWow64那就有點困難了,因為名字太有迷惑性了。

至於微軟為什麼採用Wow64方案,我就不細說了,感興趣的朋友可以看這篇文章:什麼是SysWow64。這篇文章詳細的介紹了Wow64技術,以及64位系統相容32位程式的情況。

最後,我們可以知道:

  • SysWow64資料夾,是64位Windows,用來存放32位Windows系統檔案的地方,而System32資料夾,是用來存放64位程式檔案的地方。
  • 當32位程式載入System32資料夾中的dll時,作業系統會自動對映到SysWow64資料夾中的對應的檔案。

看到這些,你一定會認為你真正的明白了System32和SysWow64的區別,我也一樣,我以為我真的懂了,但是真的懂了嗎,是真懂了嗎?

無論怎樣還是請你堅持看完。

回到頂部

區分dll檔案32位64位的程式讓我倍感迷惑

上面說到的資料庫無法啟動的這種情況,已經遇到了不止一次了。每次遇到這種問題,我都想能不能有個工具可以檢查System32和SysWow64資料夾中的dll程式是不是對應的64位和32位程式。據我所知只有dumpbin可以檢視一個dll檔案是32位還是64位,但它明顯不是我想要的工具,因為每次只能檢視一個檔案。

好吧,自己動手,豐衣足食,既然沒有這種工具,那就來寫一個吧,好在判斷dll檔案是32位還是64位也不是很難。

Windows系統下,exe、dll檔案都可以稱為PE檔案,他們有相同的檔案格式,稱為PE檔案格式。

PE檔案的第一個部分是IMAGE_DOS_HEADER,大小為64B,對於檢查32位64位來說,有一個重要的成員e_lfanew,這個成員的值為IMAGE_NT_HEADERS的偏移。

IMAGE_DOS_HEADER的定義如下:

複製程式碼  Collapse 複製程式碼
typedef struct _IMAGE_DOS_HEADER
{//(注:最左邊是檔案頭的偏移量。)
+0h  WORD e_magic         //Magic DOS signature MZ(4Dh 5Ah)         DOS可執行檔案標記
+2h  WORD e_cblp          //Bytes on last page of file  
+4h  WORD e_cp            //Pages in file
+6h  WORD e_crlc          //Relocations
+8h  WORD e_cparhdr       //Size of header in paragraphs
+0ah WORD e_minalloc      //Minimun extra paragraphs needs
+0ch WORD e_maxalloc      //Maximun extra paragraphs needs
+0eh WORD e_ss            //intial(relative)SS value                DOS程式碼的初始化堆疊SS
+10h WORD e_sp            //intial SP value                         DOS程式碼的初始化堆疊指標SP
+12h WORD e_csum          //Checksum
+14h WORD e_ip            //intial IP value                         DOS程式碼的初始化指令入口[指標IP]
+16h WORD e_cs            //intial(relative)CS value                DOS程式碼的初始堆疊入口
+18h WORD e_lfarlc        //File Address of relocation table
+1ah WORD e_ovno          //Overlay number
+1ch WORD e_res[4]        //Reserved words
+24h WORD e_oemid         //OEM identifier(for e_oeminfo)
+26h WORD e_oeminfo       //OEM information;e_oemid specific 
+29h WORD e_res2[10]      //Reserved words
+3ch DWORD e_lfanew       //Offset to start of PE header            指向PE檔案頭
} IMAGE_DOS_HEADER;
複製程式碼 複製程式碼

IMAGE_NT_HEADERS的定義如下:

複製程式碼  Collapse 複製程式碼
typedef struct _IMAGE_NT_HEADERS 
{ 
+0h  DWORD                     Signature;
+4h  IMAGE_FILE_HEADER         FileHeader;
+18h IMAGE_OPTIONAL_HEADER32   OptionalHeader;
} IMAGE_NT_HEADERS;
複製程式碼 複製程式碼

Signature 欄位:在一個有效的 PE 檔案裡,Signature 欄位被設定為00004550h,ASCII 碼字元是“PE00”。標誌這 PE 檔案頭的開始。“PE00” 字串是 PE 檔案頭的開始,DOS 頭部的 e_lfanew 欄位正是指向這裡。

IMAGE_FILE_HEADER 結構定義:

複製程式碼  Collapse 複製程式碼
typedef struct _IMAGE_FILE_HEADER 
{
+04h  WORD  Machine;                        // 執行平臺
+06h  WORD  NumberOfSections;               // 檔案的區塊數目
+08h  DWORD TimeDateStamp;                  // 檔案建立日期和時間
+0Ch  DWORD PointerToSymbolTable;           // 指向符號表(主要用於除錯)
+10h  DWORD NumberOfSymbols;                // 符號表中符號個數(同上)
+14h  WORD  SizeOfOptionalHeader;           // IMAGE_OPTIONAL_HEADER32 結構大小
+16h  WORD  Characteristics;                // 檔案屬性
} IMAGE_FILE_HEADER;
複製程式碼 複製程式碼

其中Machine欄位表示可執行檔案的目標CPU型別:

  • IMAGE_FILE_MACHINE_I386         0x014c   x86
  • IMAGE_FILE_MACHINE_IA64         0x0200   Intel Itanium
  • IMAGE_FILE_MACHINE_AMD64        0x8664  x64

這樣不是很直觀,上張圖來看一下:

32位64位PE檔案

有了這些,我們就可以通過程式來判斷32位、64位了,程式碼如下:

複製程式碼  Collapse 複製程式碼
public static bool IsPE32(string path)
{
    FileStream file = File.OpenRead(path);
    //移動到e_lfanew的位置處
    stream.Seek(0x40 - 4, SeekOrigin.Begin);
    byte[] buf = new byte[4];
    stream.Read(buf, 0, buf.Length);
    //根據e_lfanew的值計算出Machine的位置
    int pos = BitConverter.ToInt32(buf,0) + 4;
    stream.Seek(pos, SeekOrigin.Begin);
    buf = new byte[2];
    stream.Read(buf, 0, buf.Length);
    //得到Machine的值,0x14C為32位,0x8664為64位
    Int16 machine = BitConverter.ToInt16(buf, 0);
    if (machine == 0x14C)
    {
        return true;
    }
    else
    {
        return false;
    }
}
複製程式碼 複製程式碼

最核心的功能完成了,剩下的就是介面和遍歷檔案夾了,效果圖:

檢測效果圖

根據檢測結果和實際情況判斷,檢測結果沒問題。那麼就開始真正的檢測吧,System32和SysWow64。檢測結果如下圖:

32位程式System32SysWow64檢測結果對比

從圖中看出,System32、SysWow64中檢測出的所有的檔案均為32位程式,根據常識也可以判斷出,實際肯定不是這樣的。一定是程式出了什麼問題,那麼直接用十六進位制編輯器看一下兩個檔案是否一致吧。

回到頂部

再次判斷究竟是System32還是SysWow64——意想不到的坑

通過UE檢視兩個資料夾中的msvcr110d.dll確實都是32位程式,而且用Beyond Compare進行比較,兩個檔案也沒有差異。用工具檢視兩個檔案的MD5也是完全一致:

System32_SysWow64_msvcr110d_md5

難道兩個檔案真的都是32位嗎,我還是覺得不太可能。

接下來將System32和SysWow64中的msvcr110d.dll分別移動到其他資料夾,這樣System32和SysWow64就沒有這個dll檔案了,然後執行一個32位的需要這個dll檔案的程式PeTest,提示找不到這個dll檔案。分別將原來System32和SysWow64中的msvcr110.dll拷貝到這個PeTest所在的目錄,執行程式。當使用SysWow64中的msvcr110d.dll時,程式可以正常執行,說明這個檔案確實是32位。當使用System32中的msvcr110d.dll時,程式無法正常執行,出現文章開始時提到的錯誤。為什麼通過Beyond Compare、UE、MD5檢測為同樣的dll檔案,一個可以正常執行,另外一個就不可以呢。

再次思考這兩句話:

  • SysWow64資料夾,是64位Windows,用來存放32位Windows系統檔案的地方,而System32資料夾,是用來存放64位程式檔案的地方。
  • 當32位程式載入System32資料夾中的dll時,作業系統會自動對映到SysWow64資料夾中的對應的檔案。

32位程式載入System32資料夾中的dll檔案,作業系統會自動對映到SysWow64資料夾,也就是說64位程式,系統不會再做對映。

通過工作管理員檢視UE、Beyond Compare和MD5三個程序全部為32位程序,即三個程式全部是32位。

至此我們可以重新理解“32位程式載入System32資料夾中的dll檔案,作業系統會自動對映到SysWow64資料夾”這句話,應該是“只要32位程式訪問System32資料夾,無論是載入dll,還是讀取文字資訊,都會被對映到SysWow64資料夾”。

這個理解對嗎,我們來做一個實驗驗證一下。

找一個32位的文字編輯器Notepad++(Win7系統自帶的是64位),在SysWow64資料夾中新建一個1.txt的檔案,開啟編輯此檔案,內容為SysWow64。然後在開啟檔案對話方塊中的輸入框中輸入“C:\Windows\System32\1.txt”,開啟檔案,看到內容為SysWow64,如圖所示:

System32_SysWow64_文字檔案

System32中沒有建立1.txt檔案,32位程式訪問System32中的1.txt檔案,被自動對映到SysWow64資料夾中的1.txt檔案,而如果用64位的Notepad編輯器開啟System32中的1.txt檔案,就會提示找不到檔案:

Notepad_1.txt

由此就可以驗證猜想“只要32位程式訪問System32資料夾,無論是載入dll,還是讀取文字資訊,都會被對映到SysWow64資料夾”是正確的。

回到頂部

Program Files (x86)與Program Files

由System32與SysWow64的情況,考慮到Program Files (x86)與Program Files是不是也是這種情況。當32位程式訪問Program Files目錄時,會被自動對映到Program Files (x86)目錄?

還是通過1.txt的方式來驗證,發現當32位程式訪問Program Files目錄時,並沒有被對映到Program Files (x86)目錄。

回到頂部

32位程式真的需要訪問System32嗎

經過了這麼多驗證,總算是知道32位程式無法訪問System32,只有64位程式才能訪問,由此認為,這是Windows的一個非常大的坑。但是仔細想想,32位程式真的需要訪問System32嗎。就用這個dll檢測工具來說吧。

如果在64位系統上,32位程式無法訪問System32,為了訪問它,就需要編譯為64位。而如果程式編譯為64位,就無法在32位系統上執行,同時由於在32位系統上不需要檢測32位、64位,所以只需要32位程式即可。這可真是一個矛盾的事情,難道必須編譯兩個程式,一個32位,一個64位,來適應不同的作業系統嗎。如果是C++的話,那麼答案是這樣的,必須編譯一個32位一個64位。而DotNet就不一樣了,編譯的時候選擇“AnyCPU”,並且不選擇“首選32位”(VS2012中預設選中),編譯後的程式,可以同時在32位和64位系統上執行,32位系統上是32位程序,64位系統上是64位程序,是不是很方便呢,這正是DotNet和AnyCPU的魅力所在。

至此,dll檢測程式,不需要做任何程式碼修改,只需在編譯的時候選擇AnyCPU,並去掉“首選32位”選項,即可正常檢測System32、SysWow64資料夾中的dll檔案。

回到頂部

32位程式與64位程式的區別總結

至此,我想應該是真的明白了System32與SysWow64的區別了吧,這個不大不小的坑,算是邁過去了,那麼就來總結一下32位程式與64位程式的區別:

  • SysWow64資料夾,是64位Windows,用來存放32位Windows系統檔案的地方,而System32資料夾,是用來存放64位程式檔案的地方。
  • .Net程式以AnyCPU配置,並選擇“首選32位”編譯,會以32位的程序執行,此時就無法訪問System32資料夾中的檔案;如果沒有選擇“首選32位”,則會以64位的程序執行,這樣就可以訪問System32檔案夾了。(VS2012中,“首選32位”預設是選中的)。
  • 32位程式的定址空間有限,最多達到4G,而64位程式的定址空間可以達到TB級,想要使用大記憶體的話,就升級到64位吧,好在DotNet程式從32位升級到64位比較簡單,不像C++那麼麻煩。
  • 32位程式訪問System32目錄,會自動被對映到SysWOW64目錄,而64位程式可以訪問System32目錄和SysWOW64目錄。
  • 32位程式與64位程式有各自的登錄檔。
  • 32位與64位程式都可以訪問Program Files (x86)與Program Files目錄。

工作與學習過程中會遇到很多坑,一不小心就會跌倒,但是從哪裡跌倒的就從哪裡爬起來,總結經驗教訓,以飽滿的熱情再次起航,勝利就在不遠的前方。

由於本人水平有限,文中如有不對之處,還請批評指正,本人不勝感激!