匯編中的偽指令(基於匯編編譯器MASM講解)
一丶什么是偽指令,以及作用
首先我們用匯編開發效率低,如何才能開發效率高,甚至開發速度比C語言或這個高級語言快
答案: 偽指令
什么是偽指令
偽指令是匯編編譯器提供的,比如昨天我們寫的匯編代碼,假設調用一個Call我們每次都要手工處理
保存棧底,開辟就變量空間,保存寄存器環境....每次都要做,特別麻煩,所以編譯器幫我們提供了偽指令,只要我們
按照匯編編譯器的語法去寫,那么這些匯編編譯器則會自動幫我們補全
比如昨天的代碼:
;調用開始,把參數壓棧 mov ax,1 push ax mov bx,2 push bx CALL MY_ADD ...... MY_ADD: push bp ;保存棧底 mov bp,sp ;為了讓bp去尋址,所以一開始放到棧頂的位置 sub sp,4 ;開辟四個局部變量控件 push cx ;保存一起寄存器的環境,函數內部使用cx,但是函數完成之后需要把以前CX的值回復 ....你的核心代碼 pop cx ;恢復寄存器的環境 mov sp,bp ;釋放局部變量空間 pop bp ;恢復棧底 retn 4 ;平棧
我們發現這些代碼都要我們自己去寫,我們可不可以只寫我們的核心代碼,而這些教給編譯器去完成
下面開始匯編子程序(函數)的偽指令的編寫
二丶匯編中函數偽指令的詳細用法
1.偽指令函數關鍵字,以及用法
[<prologuearg>]
[USES reglist] [,parameter [:tag]]...
[LOCAL varlist]
statements
label ENDP
1.使用調用方式 distance
MY_Add proc near
函數名 關鍵字 調用方式
看下匯編代碼:
MyData segment db 100 ;數據段 MyData ends MyStack segment stack org 100 ;棧段 MyStack ends MyCode segment ;專門放函數的代碼段,代碼段同名將合並 MY_ADD proc near ret ;這里是利用了偽指令,主要看這里,我們直接寫個ret即可現在的調用方式寫的是near
;下面看下反匯編
MY_ADD endp MyCode ends MyCode segment START: ;代碼段 mov ax,MyData mov ds,ax mov es,ax mov ax,MyStack mov ss,ax ;分段 MyCode ends end START
反匯編
因為我們是段內調用,默認就是ret了,現在我們改成段間調用,讓大家看下是什么效果
所以調用方式應該明白是什么意思了吧,因為ret會自動根據我們給的調用方式去平棧,如果我們有參數,則會平正確的棧. 如果 retf 和 retn 不懂的,請看16位匯編第十講完結,里面具體分析了怎么平棧,以及他們兩個的區別
2.使用調用約定 langtype
使用調用約定,就不得不調用函數了,我們先簡單的調用一下函數
看匯編代碼:
mov ax,9 push ax ;傳遞參數,圧棧 mov bx,4 push bx CALL MY_ADD ;調用函數 MY_ADD proc far stdcall ret ;平衡棧 MY_ADD endp
如果我們指定stdcall,那么平棧的時候則會幫我們按照stdcall的形式去平棧
3.使用寄存器 (USES Reglist)
上面我們每次寫的時候,都要自己保存寄存器的信息,這樣很不方便,我們要做的就是和C語言一樣,聲明了函數,直接寫自己的代碼,所以看下列匯編代碼的變化
MY_ADD proc far stdcall USES bx cx ;這里我們USES bx cx 代表讓編譯器自動幫我們保存寄存器的信息 ret ;平衡棧 MY_ADD endp
如果是要保存多個,則在后面繼續寫寄存器即可
例如:
MY_ADD proc far stdcall USES bx cx dx si .....;如果是多個繼續往下下,我們看下反匯編 ret ;平衡棧 MY_ADD endp
反匯編:
他保存了bx,cx的信息,然后彈棧的時候自動彈出,恢復環境
4.使用參數 parameter:tag
我們寫代碼還有一個不方便的地方就是每次找參數的時候,我們都要去計算,比如bp -8 bp - 10這樣去寫
以前的代碼:
mov ax,[bp -8] ;找到參數二 mov bx,[bp -10] ;找到參數一 .....
現在不用這么麻煩了,我們只需要指定參數名字即可
MY_ADD proc far stdcall USES cx, nn1:WORD ,nn2:WORD ret ;平衡棧 MY_ADD endp
nn1 參數名 : 類型(也就是大小)
注意:
1.我們保存寄存器環境在參數定義的左邊,如果要加參數,需要加個逗號隔開
2.定義參數的時候,類型名(大小)一定要大寫
使用:
我們以前使用都是bp -xxx ,現在可以直接用這些參數代替了,如果計算一個結果,放到ax當中
匯編代碼:
mov ax,9 push ax ;傳遞參數,圧棧 mov bx,4 push bx CALL MY_ADD ;調用函數 MY_ADD proc near stdcall USES cx, nn1:WORD ,nn2:WORD mov cx,nn1 mov ax,nn2 ;這里只需要寫我們自己的代碼即可,偽指令對應的匯編都會自動完成 add ax,cx ret ;平衡棧 MY_ADD endp
這里使用了偽指令,所以都會翻譯成等價的匯編代碼了,我們看下反匯編,看下參數變為什么樣子了
它會自動的完成轉換
注意:
1.雖然變成了參數,但其實翻譯的匯編代碼還是 [bp-xxx],還是不能內存直接給內存
比如不能寫成這樣
mov nn1,nn2
這樣匯編代碼翻譯過來就是
mov [bp-xxx],[bp-xxx]
我們以前說過,想使用內存的值,必須經過中轉才可以,(也就是給雞存器保存一下,或者放到CPU的暫存器中)
5.局部變量的使用,以及注意的問題(重要)
局部變量以及開辟局部變量,以前都是棧頂-xx,(俗稱抬棧)
比如匯編代碼寫成
push bp ;保存棧底 mov bp,sp sub sp,4 ;開辟局部變量控件 push cx ;保存寄存器的環境 .....你的核心代碼 pop cx ;恢復寄存器的環境 mov sp,bp ;恢復局部變量空間(銷毀) pop bp ;恢復棧底 ret ;平衡棧
但是現在我們直接寫一下,看下會出現什么問題
匯編代碼:
mov ax,9 push ax ;傳遞參數,圧棧 mov bx,4 push bx CALL MY_ADD ;調用函數 MY_ADD proc near stdcall USES cx, nn1:WORD ,nn2:WORD sub sp,4 ;開辟局部變量空間 mov cx,nn1 mov ax,nn2 ;我們的核心代碼 add ax,cx ret ;平衡棧 MY_ADD endp
反匯編:
我已經畫出來了,大家看下會出現什么清空,這里給個提示
生成函數的步驟
我們發現了,我們應該先抬棧,在保存環境
以前的代碼都是這樣寫的,但是偽指令生成的匯編代碼我們沒辦法改,怎么辦,也就意味着,如果開辟局部變量空間
那么就會出錯的.
解決方法,用新的偽指令,定義局部變量
LOCAL 變量名字:類型(大小)
請看匯編代碼:
mov ax,9 push ax ;傳遞參數,圧棧 mov bx,4 push bx CALL MY_ADD ;調用函數 MY_ADD proc near stdcall USES cx, nn1:WORD ,nn2:WORD ;sub sp,4 ;開辟局部變量空間 LOCAL MyAAA:WORD ;申請局部變量空間 LOCAL MyBBB:WORD mov cx,nn1 mov ax,nn2 ;我們的核心代碼 add ax,cx ret ;平衡棧 MY_ADD endp
看下反匯編:
可以看出,這些正常了,先申請空間,然后保存環境,恢復的時候是先恢復環境,然后釋放局部變量空間
這里申請局部變量空間的時候,並沒有使用 sup sp,4 而是使用的加法指令 add sp-4 其實是一樣的
這里關於函數定義的偽指令調用就結束了,我們只需要寫上這些偽指令,那么我們就可以和C語言一樣,直接寫我們的
核心代碼了
6.函數調用的偽指令(定義講完了,該講調用了)
我們每次調用的時候,都要先 傳入參數,壓棧,然后Call
現在提供了一個偽指令,讓我們像C語言一樣的方式去掉用
Invoke 偽指令
關於這個偽指令文檔,課程資料里會帶 (文件名字是 masm32.chm)
看上面寫的,我們只需要
invoke 函數名 這樣是調用空參函數
invoke MY_ADD
invoke 函數名, 傳入的參數1,傳入的參數2.. 調用帶參函數
invoke MY_ADD ,1 ,2
我們的例子修改為這種調用
其實還是改為我們上一次調用的那種
注意:
1.使用invoke的時候,函數的定義必須放在前面,否在報錯
三丶更多的偽指令 (if if else if else if else while do while for )
這里只說幾個,具體的自己查下手冊看下使用即可
1.if的使用
看下語法就知道了
以前我們用匯編寫if語句
mov ax,0 cmp ax,0 jnz END ;不等於0,跳到結束 .............;等於0執行我們的代碼 END: .....;結束的代碼
而現在我們可以用偽指令寫成
.if ax == 0 mov ax,3 .endif
如果帶有else的則加個else 在.endif, 如果是 elise if 則同理
2.while的偽指令的用法
while ax == 0 ..... endm
這些很簡單了,編譯出的匯編代碼就是前幾天的作業,只要寫過就知道匯編代碼是什么了,不會的可以自己看下反匯編
四丶匯編中的有參宏,和無參宏,以及條件宏
1.條件宏偽指令(和C語言類似)
ifndef HELLO ;如果沒有定義HELLO HELLO EQU ;那就定義HELLO EQU是代表定義宏的意思,相當於C語言的#define endif
2.無參宏,無參宏就是EQU(#define)
定義的名字 EQU關鍵字 替換為 PI EQU 314 ;定義PI 它的值是314
3.帶參數的宏(這個比較有意思)
記得我們函數偽指令的時候的ret嗎,我們可以吧ret替換為return,和C語言一樣的使用
帶參宏的偽指令是 macro
return macro n1ret n1 endm
注意參數的時候后我們不用給類型了,也就是說不能寫成下面這樣
return macro n1:WORD
可以有多個參數
return macro n1,n2.....
五丶偽指令之匯編中的結構體
我們以前定義數據的時候都是在全局數據區去定義,但是這樣不好,如果數據一多就不好整理了,現在偽指令提供了一個struct的關鍵字,讓我們去定義
使用:
1.結構體的定義以及使用
結構體的定義是放在全局數據區的外面
定義:
結構體名稱 關鍵字 成員 大小 是否初始化 .......... 結構體名稱 結束標志 例子: MyData struct year dw ? ;都是不初始化的 month dw ? day dw ? MyData ends
使用:
使用的時候需要放在全局數據區的段里面
例如:
MyData segment
結構體變量名稱 結構體名稱 <初始化的值>
g_Data1 MyData<?>
MyData ends
訪問結構體的兩種方法
1.使用LEA訪問
2.使用假設(偽指令 這個偽指令叫做假設)
1.LEA訪問
lea bx,g_Data1
mov word ptr[bx+xxx],1 給結構體成員賦值,這樣需要自己去算
2.使用假設偽指令
看匯編代碼
lea bx,g_Data1 假設關鍵字 假設的事物 假設成什么 ASSUME bx:ptr MyData ;假設bx 是MyData的結構體的指針 mov [bx].year,1 mov [bx].month,2 ........... ;如果bx還有其他的作用,則取消假設 ;一般是配套使用 ASSUMW bx:nothing ;假設bx什么都不是
2.結構體套結構體的定義以及使用
直接看匯編代碼吧:
MyDatas struct year dw ? month dw ? ;定義日期結構體 day dw ? MyDatas ends Student struct sid dw ? sex dw ? ;定義學生 bbb MyDatas<?>;結構體套日期結構體,並且初始化,重要這里必看 Student ends MyData segment db 100 ;數據段 g_Stu Student<1, 2, <2017,2,3> > ;都初始化 MyData ends MyStack segment stack org 100 ;棧段 MyStack ends MyCode segment START: ;代碼段 mov ax,MyData mov ds,ax mov es,ax mov ax,MyStack mov ss,ax ;分段 ;使用結構體 lea bx,g_Stu ASSUME bx: ptr Student mov [bx].sid ,1 mov [bx].sex ,2 ;使用的時候都可以通過.訪問了 mov [bx].bbb.year ,3
ASSUME bx:nothing MyCode ends end START
反匯編代碼:
編譯器會自動幫我們尋址去翻譯
6.使用假設偽指令 訪問全局變量
以前我們訪問的時候 都是通過 lea 給基址寄存器,然后在通過內存訪問直接修改里面的值,例如
lea bx,g_number mov word ptr[bx],1 ;給它所在的內存復制
現在,我們只需要先把段假設一下,然后可以直接給ax
MyData segment db 100 ;數據段 g_number dw 1 ;給全局變量申請一個直接 MyData ends MyStack segment stack org 100 ;棧段 MyStack ends MyCode segment ASSUME cs:seg MyCode, DS:seg MyData, SS: MyStack ;假設一下 START: ;代碼段 mov ax,MyData mov ds,ax mov es,ax mov ax,MyStack mov ss,ax ;分段 ;使用 mov ax,g_number ;直接可以個ax了,不用再通過lea了 mov g_number,1 ;這里注意一下,我們是可以 mov mem,imm 的,以前的時候尋址方式都講過了
; 不能mov mem,mem ,也說過 內存和內存不能直接交換,必須通過中轉
MyCode ends end START
看下反匯編代碼:
想當與和我們手寫是一樣的
如果對你有幫助,請評論或者收藏,謝謝支持,碼字不易,寫一篇好的博客,最少4個小時,只因為想把最精彩,最有用的干活分享給你
課堂資料下載:
鏈接:http://pan.baidu.com/s/1i5zhYZj 密碼:jbsb