正常的情況下,我們寫一個 C/C++ 函數,即使是一個空函數,編譯器也為我們做了不少的工作,產生了一些“必要”的代碼。請看下面的函數 (為了說明問題隨便寫的):
int Test() { int iReturn; char szTemp[33]; szTemp[0] = 'A'; szTemp[1] = ''; iReturn = MessageBox(NULL, szTemp, szTemp, MB_OK); MessageBeep(iReturn); return iReturn; }
下面是用 VC6 在 Release 方式下編譯後的的反組譯碼代碼:
00401000 sub esp,24h // 增加堆棧空間存放局部變數 (24H = 36D,4 位元組對齊,注意這裡沒有為 iReturn 分配空間)
00401003 push esi // 儲存要使用的重要寄存器
00401004 lea eax,[esp+4] // 下面是傳遞 MessageBox() 要使用的參數
00401008 push 0
0040100A lea ecx,[esp+8] // 編譯器愚蠢,根本不用 ECX,兩個都是 szTemp,兩次 PUSH EAX 不得了
0040100E push eax
0040100F push ecx
00401010 push 0
00401012 mov byte ptr [esp+14h],41h
00401017 mov byte ptr [esp+15h],0
0040101C call dword ptr ds:[40509Ch] // 調用 MessageBox()
00401022 mov esi,eax // 儲存傳回值到變數 iReturn 。靠!變數 iReturn 自動使用 ESI,編譯器太聰明了:)
00401024 push esi
00401025 call dword ptr ds:[4050A0h] // 調用 MessageBeep()
0040102B mov eax,esi // 把變數 iReturn 交給 EAX 作為傳回值
0040102D pop esi // 恢複要使用的重要寄存器
0040102E add esp,24h // 減少堆棧空間
00401031 ret // 堆棧長度減 4 並返回
這段代碼雖然很精幹 (都能自動使用寄存器來儲存變數了),但是有的時候我們並不需要編譯器提供這些自作主張的代碼 (比如寫驅動程式的時候,不過我還沒遇到過這種情況,呵呵~~),我們希望整個全部函數都是自己親手寫進去的 (BT 呀^o^)。好,請出今天的主角 —— “naked”(怎麼是裸體呀?),歡迎!Visual C++ 的擴充關鍵字 naked 允許我們完全定製一個函數,廢話不說了,看例子 (熬夜寫的 Zzzzzz~~):
__declspec(naked) int Test() { __asm { SUB ESP,24H
PUSH ESI
LEA EAX,[ESP+4]
PUSH 0
PUSH EAX
PUSH EAX
PUSH 0
MOV BYTE PTR [ESP+14H],41H
MOV BYTE PTR [ESP+15H],0
CALL DWORD PTR [MessageBoxA]
MOV ESI,EAX
PUSH ESI
CALL DWORD PTR [MessageBeep]
MOV EAX,ESI
POP ESI
ADD ESP,24H
RET
} }
上面的代碼是使用的 VC 的內聯彙編,和 VC 編譯後產生的程式碼完全是一樣的 (很有完全控制的成就感吧^_^)。上面我們並沒有又節省什麼 (節省的 PUSH ECX 並不是 naked 的功勞),但是有的時候確實需要的 (舉不出例子來了,倒!)。最後隨便說說注意事項:
1.使用 naked 關鍵字需要自己構建 EBP 參數指標 (如果用到了 EBP 作為參數指標的話);
2.必須自己使用 RET 或 RET n 指令返回 (除非你不返回)。3.只有win32模式能用__asm ipf和和x64模式不能嵌入內聯彙編。沒什麼特殊情況還是用intrinsic吧