16位匯編中的偽指令


 

 

 

                匯編中的偽指令(基於匯編編譯器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.偽指令函數關鍵字,以及用法

  
Syntax: label PROC [distance] [langtype] [visibility]
[<prologuearg>]
[USES reglist] [,parameter [:tag]]...
[LOCAL varlist]
statements
label ENDP
看到上面的用法和調用是不是懵逼了,下面一點一點講解
(只講解常用的,如果想看,可以查看幫助文檔 masm.chm 這個我會放在每天的資料雲盤中,最后提供鏈接,下載即可)
帶有中括號[] 代表可選的意思
distance: 調用方式,你是段間Call調用,還是段內Call調用,注意這個地方默認的是near(段內調用)
langtype  調用約定:你是Std標准調用約定,還是C約定,這里給了則編譯器會自動根據我們給的調用約定平棧
USES  reglist(寄存器) : 這個代表你要使用的寄存器,上面我們手寫的時候,寄存器信息是我們自己保存的,這里寫上,則編譯器會自動的幫我們保存
parameter:tag  參數,和參數類型,比如我們尋找參數的時候是BP-XXX,這里直接給參數名,他會自動尋找
下面具體看我怎么寫
ret返回指令: 在偽指令中,不要在使用retn,retf等等指令去平棧了,這里寫ret即可,ret會根據你給的調用方式
自動選擇使用那個指令去平棧
Laber ENDP: 標號  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM