32位匯編第七講,混合編程,內聯匯編
32位匯編第七講,混合編程
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
轉載請註明出處,謝謝
混合編程的概念,有時候我們會想,C語言可不可以調用匯編的函數,或者反過來調用
或者說C語言裏面內部直接內聯匯編去編寫.
可以實現,靜看怎麽實現
一丶C語言調用匯編語言的函數
1.創建工程和代碼
①創建VC++控制臺程序
FILE(文件) - NEW (新建)
然後我們打開源文件,一級一級展開,找到我們的main函數
那麽我們現在要調用匯編寫的,那麽我們用匯編寫一個代碼
②,創建匯編程序
創建匯編程序,這個比較簡單,我們新建個文件夾,裏面新建一個文本文檔,後綴名改為ASM,然後用RadAsm打開,開始編寫代碼
使用RadAsm編寫,這樣比較快
編寫我們的匯編代碼
,註意下方的end結束符號,我們並沒有指明開始位置是MyAdd,也就是說這個匯編程序,只能編譯
編譯出的OBJ 和我們上邊寫的程序的OBJ 一起連接(上面的程序也是編譯,不連接)
看下匯編代碼
.386 .model flat,stdcall option casemap:none .const .data .code MyAdd proc c ,n1:DWORD,n2:DWORD mov eax,n1 add eax,n2 ret MyAdd endp End
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
③丶C/cpp文件調用匯編中的MyAdd函數
1.先編譯匯編程序,產生obj文件
將此obj文件復制到我們的C/c++的目錄下
2.修改C/c++程序,調用我們的增加函數
C/C++代碼如下,註意這樣寫你只能編譯,不能連接,只能先生成OBJ
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
2.連接程序使用的幾個步驟
我們要想使用上面幾個程序,有多中方式去掉用,分別是
①丶手工編譯C/C++程序,產生.obj文件,然後和匯編的.obj連接起來
編譯我們的.cpp文件,產生.boj文件
命令是 cl /c 文件名
,然後把上面的MyAdd.obj(匯編程序編譯的)
匯編程序的編譯可以通過RadAsm,如果配置好了就直接F5編譯即可,如果沒配置好,可以手工用命令行編譯,這裏不講解了,以前課程都有講怎麽編譯,還有配置RadAsm
現在我們拿到了兩個.obj文件
開始連接成一個.exe文件
手工連接
Link C/C++文件名.obj 匯編文件名.obj
理論上來說連個obj文件誰在前誰在後都沒有區別,但是現在是我們的C/C++程序要調用 MyAdd,所以C/C++在前
打開CMD定位到我們的目錄下(怎麽打開百度搜索)
你可以直接文件夾上面輸入CMD 回車,則會在當前的目錄下,註意這裏為了演示命令的截圖
把兩個obj文件拷貝了出來
開始連接
成功生成
打開程序校驗一下
可以調用了
②丶將匯編程序的.obj文件,放到C/C++工程的目錄下,利用工程特性,直接連接
我們可以把obj放到VC++中,這樣我們可以直接編譯連接使用,不用手工編譯連接了
因為VC++6.0的Bug,我使用了一個插件修復,本來可以直接在 File(文件) - > Open(打開)的
解決VC的Bug,這裏我直接提供一個Dll,把Dll放在VC++的目錄的上一層,Addins中
操作步驟
1.右鍵屬性打開文件位置
2.返回上一層目錄
3.進入Addins文件夾下,把FileTool.dll拷貝進去
拷貝FileTool.dll
4.重新打開VC++6.0(註意管理員權限打開)
在菜單中點擊 Tools(工具) -> Customize(定制) -> Add-ins And Macro Files
選中即可,如果再有問題,可以百度搜索,DLL會打包
上面解決了一個BUG,那麽現在看下我們的工程中是否有了MyAdd.obj
現在編譯連接則可以執行
③將obj定義為lib去使用
上面我們直接使用的obj,但是這樣不太好,因為obj一多,工程文件就多了,不好維護(當然目的不再這裏)
那麽我們把obj定義為lib
怎麽定義?,可以使用vc++自帶的lib工具,如果配置了環境變量,則直接輸入cmd,跳轉到目錄下,把MyAdd.obj生成為lib
輸入lib 則會出現這個幫助,如果沒有配置環境變量,那麽輸出lib則會出錯,不過一般默認配置了,如果不會配置,請看前邊的配置環境,RadAsm IDE的配置,裏面內容一樣
先介紹一下Lib工具的使用把
這個工具很簡單, lib 選項(可選) 文件名(可選)
例如我們要把MyAdd.obj變為lib,則語法是
Lib MyAdd.obj...... (...代表了有多個obj 依次後面填寫即可,註意中間不要加逗號,隔開即可)
生成了
遍歷lib 看下有多少obj,著用list語法
語法
Lib /list lib名稱
,,為了測試C2ASM我也打包了
現在我們可以使用Lib去編程了
現在只需要我們的工程中包含這個lib則可以使用,不用再把MyAdd.obj添加到工程中了
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
二丶匯編調用C寫的函數
匯編調用C寫的函數,那麽是一樣的,因為.obj都是一樣的
首先看下匯編代碼,匯編代碼應該挺熟悉了,(不熟悉,看下以前的)
.386 .model flat,stdcall option casemap:none include msvcrt.inc includelib msvcrt.lib ;C庫的動態的靜態使用 includelib MyAdd.lib ;lib調用,加載自己的lib MyAdd proto c n1:dword, n2:dword .const g_szFmt db "1+2=%d", 0dh, 0ah, 0 g_szPause db "pause", 0 .data .code main proc c argc:dword, argv:dword invoke MyAdd, 1, 2 invoke crt_printf, offset g_szFmt, eax invoke crt_system, offset g_szPause mov eax, 0 ret main endp end main
註意,因為我們這裏使用printf了,我們是動態的靜態使用,什麽是靜態的動態使用,前邊已經說過了,不會的請看下前邊的文章
那麽現在我們新建個.c文件,裏面單獨寫一個函數(不用工程了)
編譯這個文件,生成.obj,然後和匯編程序的.obj連接,但是註意現在是匯編程序的.obj在前
因為匯編調用這個的obj
當然這兩個obj我們也可以打包成lib使用,上面的匯編程序就是用的打包好的lib
所以這幾個步驟就不寫了,生成lib給匯編程序使用,至於手工的編譯匯編程序,連接匯編程序其實不建議去用了,隨著編譯器的提升,以後加的選項越來越多
手工生成lib
Lib MyAdd.obj (MyAdd.obj 是手工編譯的MyAdd.c的文件)
使用RadAsm編譯連接1.asm程序
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
三丶匯編DLL的使用
像我們上面的生成的lib只能給C/C++使用,但是別的程序不見得能使用
所以我們寫一個匯編的DLL,給C/C++程序使用
至於C/C++調用dll,那麽有兩種方式
一種是使用靜態方式,就是加載dll的靜態lib庫(這個lib庫中保存的是dll名稱)
然後添加個頭文件去使用
另一種是動態的loadlibrary,和GetprocessAddress去使用,動態的加載dll去使用
這裏簡單說下第一種,至於動態使用有開發知識的應該會調用,如果沒開發知識也沒有關系
因為咱們這個是匯編,不是再講開發,(雖然開發很重要),但是咱們今天的主要內容不是上面所有的,壓軸的在最下面
1.首先利用RadAsm新建一個dll的空工程,填寫以下代碼
匯編代碼:
.386 .model flat,stdcall option casemap:none include windows.inc .const .data .code MyAdd Proc n1:DWORD, n2:DWORD mov eax, n1 add eax, n2 ;寫一個我們的MyAdd函數 ret MyAdd endp DllMain proc hModule:HINSTANCE , dwReason:DWORD, lpvReserved:LPVOID mov eax, TRUE ret DllMain endp end DllMain ;註意DLL的入口點要指定
然後在DEF文件導出我們的定義的代碼
編譯連接之後則會生成DLL,和保存DLL信息的lib
那麽我們的工程可以使用了
靜態使用
結果
至於代碼,會上傳課堂資料中
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
四丶壓軸的內聯匯編
1.內聯匯編簡單了解
首先我們會想,上面雖然完成的 匯編和C的互相調用,也解決的跨語言的DLL調用
但是覺著還是不好,為什麽,因為可能我想寫的匯編代碼就那麽一點,我還得生成DLL
或者生成lib
那麽我們突發奇想,可不可以在C/C++中寫匯編代碼
比如我們寫個int 3的中斷指令
C/C++代碼
#include "stdafx.h" typedef int (*PFN)(int n1,int n2); //定義函數指針 int main(int argc, char* argv[]) { //寫二進制代碼 unsigned char Code[] = {0xCC, 0x55, 0x8B, 0xEC, 0x8B, 0x45, 0x08, 0x03, 0x45, 0x0c, 0xc9, 0xC3}; int result = ((PFN)(void*)Code)(1, 2); printf("%d",result); return 0; }
看到這個代碼是不是暈了,沒關系,誰叫我們是學匯編的,用OD調試看下
因為是Dbg程序,所以int 3指令對齊了,我們發現確實是斷點到這裏停止了,我們需要價格ret
直接打開int 3.exe看看是否會崩潰,如果崩潰則用OD調試,看下到底出現了什麽情況
調試看看
發現是int3斷點斷下來了,我們發現,剛在我們寫入的代碼其實是二進制代碼我們把它當做函數執行,也就是Call一下,我們寫入的是一個加法的函數
難道匯編代碼都要這樣寫嗎
所以VC++6.0為我們提供了一個語法,叫做
_asm 註意前邊是一個下劃線
也可以加塊語句去寫
但是一般我們不這樣寫,因為這樣會破壞寄存器環境所以開始和結束我們要保存一下寄存器的環境
Pushad 和push s是保存所有寄存器環境,和所有標誌寄存器標誌
我們看下VC++6.0的匯編到底做了什麽
(在VC++6.0中內聯匯編,可以下短點,然後ALT +8跳轉到VC的匯編中查看)
是一樣的
2.內聯匯編調用函數
一丶普通調用的無參數調用
上面我們知道的怎麽寫內聯匯編了,那麽下邊我們則可以把這個內聯匯編定位為函數
寫個ADD函數把
首先我們工程封裝成一個函數
我們可以直接這樣寫,因為編譯器內部已經幫我們壓棧,平棧...各種東西都幫我們做了
我們一會ALT + 8看下
現在我們要調用了,因為返回值問題,是怎麽返回我們不知道,雖然我們知道是放在eax中
但是如果你改成int,那麽我們要寫為 return eax?顯然是不可以的,而如果在_asm中
寫ret,那麽這個函數不知道你返回了所以先定義為void,我們一會解決返回值問題
調用:
我們要自己push,自己Call,又因為MyAdd是C調用約定,所以我們要自己平棧,
我們看下匯編代碼
這個是我們調用的代碼
我們看下MyAdd的時候裏面做了什麽
我們發現其實我們的核心代碼就是兩句,但是編譯器幫我們做了很多事
從第一個循環申請局部變量上面就不說了,前邊講過了
(保存棧底,開辟局部空間,保存環境.....)
主要看下面,恢復完寄存器信息之後就開始釋放局部變量空間,然後在Debug版本下會檢測棧
是否平衡,如果不平衡,就彈個錯誤框,最後ret的時候,因為壓入了兩個參數還沒有平棧
所以上面我們需要自己平棧,一個參數4個字節,所以+8
看下結果
2.解決普通的調用有返回值的問題
上面我們如果調用,那麽就要自己內聯,自己調用,但是很不方便,所以我們加個返回值
直接調用也可以,編譯器智慧給警告,因為編譯器支持這個語法
調用
直接調用即可
看下結果
那麽就完美解決了
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
3.(inline)裸函數
我們這樣想,上面編譯器幫我們做了很多事情,我們不知道,我們有的時候這個函數就行自己用我們的寫的代碼
那麽這個時候就可以用裸函數了,有關鍵字
_declspec(naked) ....
看下反匯編是什麽
可以看到,明顯的編譯器沒有幫我們做申請局部變量空間....等等一系列的操作
但是我們就要自己去寫了
看下結果
4.內聯調用API
如果內聯了,那麽就不支持invoke這種偽指令去操作了,都是真實的去寫匯編代碼
調用其實挺簡單,加上數據段,和函數名就可以的,但是註意函數的頭文件要包含(Windows.h)
5.內聯尋找函數的參數
我們上面調用一個Add函數,自己還要計算
mov eax,[ebp +8]
Sub eax,[ebp + 0ch]
但是其實這些我們的函數有參數了,我們可以使用參數來弄
比如
Mov eax,n1
Sub eax,n2 這樣去寫就行
反正怎麽像偽指令怎麽寫,不支持也要想辦法優化.
不然參數多了就容易混亂
博客園IBinary原創 QQ:2510908331 博客連接:http://www.cnblogs.com/iBinary/
課堂資料:
鏈接:http://pan.baidu.com/s/1jIJzsyE 密碼:dtu4
32位匯編第七講,混合編程,內聯匯編