运行返回签名不正确_如果调用约定不匹配,会发生什么?
蝎子
信不信由你,調用約定不匹配是程序經常出問題的原因之一。當你的程序代碼中出現不相匹配的調動約定的時候,編譯器會”大吼大叫”,但是懶惰的程序員只會在其中進行強制轉換,以使編譯器”盡快閉嘴”。
結果是:Windows不得不永遠支持你編寫的錯誤代碼。
Windows窗口過程
有很多人會錯誤地聲明了Windows窗口過程(通常是將它聲明為__cdecl,而不是__stdcall),因為這個原因,我們分發消息給窗口過程的函數添加了額外的保護,這樣它就可以檢測到錯誤的函數聲明,并執行適當的修復。
這是你為什么在堆棧上經常會看到神秘的0xdcbaabcd的原因。向窗口過程分發消息的函數會檢查這個值是否在堆棧中的正確位置。如果不是,那么它會檢查窗口過程是否從堆棧中彈出了一個多余的雙字(如果是這樣的話,它將嘗試修復堆棧),或者窗口過程是否錯誤地聲明為__cdecl而不是__stdcall(如果是這樣的話,它將參數從窗口過程的堆棧中彈出)。
DirectX回調函數
在DirectX庫中大量地使用了回調函數,并且人們再次將其回調函數聲明為__cdecl,而不是__stdcall。因此DirectX枚舉器必須對這些錯誤聲明的函數進行特殊的堆棧清理。(真難啊…)
IShellFolder::CreateViewObject
我記得,曾經這么一個程序,它錯誤地聲明了一個名為CreateViewWindow的函數,并且開發者設法以某種方式欺騙了編譯器以接受它。如下圖所示:
他們不僅錯誤地聲明了函數簽名,而且在函數的內部,即使函數沒有成功完成,它也返回了S_OK。調用此函數后,我不得不添加額外的代碼來清理堆棧,并確認它的返回值是不是正確的。
Rundll32.exe入口點函數
在Microsoft的知識庫文章中,介紹了rundll32.exe調用的函數所需要的函數簽名。但這并沒有阻止人們使用rundll32調用那些并非由rundll32設計的隨機函數,例如user32模塊里的LockWorkStation或ExitWindowsEx。
下面,就讓我們來看看,如果使用rundll32.exe調用ExitWindowsEx之類的函數時,會發生什么情況。
rundll32.exe會解析其命令行,并假定函數的編寫方式如下:
void CALLBACK ExitWindowsEx(HWND hwnd, HINSTANCE hinst,LPSTR pszCmdLine, int nCmdShow);
但是,實際的ExitWindowsEx的函數原型是這樣的:
BOOL WINAPI ExitWindowsEx(UINT uFlags, DWORD dwReserved);
接下來會發生什么呢?
當進入ExitWindowsEx函數時,堆棧看起來像這樣:
但是,函數希望看到堆棧是這樣的:
會發生什么?
rundll32.exe傳遞的hwnd窗口句柄,會被誤認為是uFlags,而hinst會誤認為為dwReserved。由于窗口句柄是隨機的,因此最終會將這個隨機標志傳遞給ExitWindowsEx。也許今天是EWX_LOGOFF,明天是EWX_FORCE,下次是EWX_POWEROFF。
現在假設該函數設法返回。(例如,函數執行失敗)。ExitWindowsEx函數清除堆棧中的兩個參數,而不知道它已傳遞給四個參數。生成的堆棧是這樣的:
現在堆棧的數據已經被破壞了,真正有趣的事情即將發生。
例如,假設上圖中的”.. rest of the stack ..”里保存的是一個返回地址。好了,原始代碼將執行”返回”指令以通過該返回地址返回,但是在堆棧被破壞的情況下,”返回”指令將返回命令行并嘗試像執行代碼一樣嘗試執行它。
Random custom functions
開發者可能會將一個函數導出為__cdecl,但將其視為__stdcall。這似乎可行,但是返回時,堆棧將被破壞(因為調用者期望使用__stdcall函數來清理堆棧,但是得到的卻是無法使用的__cdecl函數)。
好的,我們已經舉了這么多的例子了。我想你應該可以明白我想表達的要點了,下面是一些你肯定會問的問題。
為什么編譯器不能捕獲這些錯誤呢?
編譯器確實這樣做了。(不是上面的那個rundll32例子啊)
但是人們似乎已經習慣了只使用強制轉換來關閉編譯器的警告。
我們看看下面的函數原型:
LRESULT CALLBACK DlgProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
上面的函數原型是錯誤的,正確的版本請看下面:
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg,WPARAM wParam, LPARAM lParam);
如果你這樣調用:
DialogBox(hInst, MAKEINTRESOURCE(IDD_CONTROLS_DLG), hWnd, DlgProc);
編譯器會馬上發出如下的編譯器錯誤信息:
error C2664: ‘DialogBoxParamA’ : cannot convert parameter 4 from ‘LRESULT (HWND,UINT,WPARAM,LPARAM)’ to ‘DLGPROC’.
然后,你想懶惰一下,使用下面的強制轉換來關閉錯誤信息:
DialogBox(hInst, MAKEINTRESOURCE(IDD_CONTROLS_DLG), hWnd, reinterpret_cast(DlgProc));
“噢,拜托,誰會這么愚蠢,會在沒有實際修復代碼錯誤的情況下,僅僅是加入強制類型轉換來以使編譯錯誤消失?”
看來,大部分人都會這樣做。
在網絡上,我還看到了很多開發者犯了這樣的錯誤,這里就不一一列舉了。
帶有這些錯誤的程序代碼是如何工作的?
當然,這些程序在某種程度上可以正常工作,要不然使用者肯定會注意到并要求開發者修復這個錯誤。
但是,程序如何在被破壞的堆棧中生存呢?(這個問題我后面會回答)
總結
強制類型轉換,不應該出現在任何一個完美代碼主義者的字典里。
但,沒它,還真不行。
最后
Raymond Chen的《The Old New Thing》是我非常喜歡的博客之一,里面有很多關于Windows的小知識,對于廣大Windows平臺開發者來說,確實十分有幫助。
本文來自:《What can go wrong when you mismatch the calling convention?》
總結
以上是生活随笔為你收集整理的运行返回签名不正确_如果调用约定不匹配,会发生什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python实现守护进程_守护进程原理及
- 下一篇: spring boot 自动跳转登录页面