1. 程式人生 > >彙編總結(3)——程式設計初步

彙編總結(3)——程式設計初步

堆疊的作用

組合語言中的堆疊就是高階語言中的棧。

堆疊主在彙編程式設計中主要有三個作用:

  • 過程呼叫&返回指令
  • 引數傳遞
  • 區域性變數

過程呼叫&返回指令

過程呼叫中的過程指什麼?

組合語言中的過程就是高階語言裡面說的子程式,呼叫子程式(過程、函式)的本質就是控制轉移,它與無條件轉移的區別是呼叫子程式需要考慮返回

過程呼叫指令用於由主程式轉移到子程式;

過程返回指令用於由子程式返回到主程式。

指令 中文名 格式 解釋 備註 location
CALL 過程呼叫指令 CALL LABEL 段內直接呼叫LABEL 與jmp的區別在於call指令會在呼叫label之前儲存返回地址(call 中return之後主程式還可以繼續執行,jmp 當label執行完畢後不能返回主程式繼續執行) p61
RET 段內過程返回指令 RET 使子程式結束,繼續執行主程式 P61

call指令的背後

段內直接呼叫的背後操作其實是兩步:

(1)把返回地址(EIP內容)壓入堆疊

(2)使得EIP內容為目標地址偏移,從而實現轉移

返回地址:緊隨過程呼叫指令的下一條指令的地址(有效地址)

目標地址:子程式開始處的地址(有效地址)

與無條件轉移相比,過程呼叫指令call只是多了第一步(保護現場)。

ret指令的背後

過程返回指令的執行其實進行的是如下操作:

從堆疊中彈出地址偏移,送到指令指標暫存器EIP中,這個返回地址通常就是在執行相應的呼叫指令時所壓入堆疊的返回地址。

引數傳遞

入口引數:主程式傳給子程式的引數

出口引數:子程式傳給主程式的引數

引數傳遞的方法主要有:暫存器傳遞法堆疊傳遞法、約定記憶體單元傳遞法、call後續區傳遞法等。具體情況需要事先約定好。

一般c語言的習慣是使用堆疊傳遞入口引數,使用暫存器傳遞出口引數,因為一般入口引數比較多,出口引數比較少。

區域性變數

這裡就一個結論,堆疊可以用於安排動態區域性變數。

算術邏輯運算指令

乘除指令

無符號數乘法指令(MUL)

指令格式:

MUL OPRD

該指令實現兩個無符號數的乘法運算,乘數是OPRD,被乘數位於AL、AX或EAX中(由OPRD的尺寸決定)。

需要注意的是乘積之後尺寸翻倍,兩個8位的數乘積為16位,結果存放在AX中,類似的,兩個16位數的乘積結果為32位,放在DX:AX中,最後,64位的乘積放在EDX:EAX中。

運算元OPRD可以是通用暫存器、儲存單元,但是不能是立即數

有符號數乘法指令(IMUL)

有符號乘法指令有三種使用形式:

IMUL OPRD;
IMUL DEST,SRC;
IMUL DEST,SRC1,SEC2;

具體解釋如下:

  • IMUL OPRD

    單運算元乘法指令和無符號數乘法的規則差不多,只是在乘的時候把乘數和被乘數都當成有符號數。

  • IMUL DEST,SRC

    資料流方向是DEST<=DEST*SRC

    要求目的運算元DEST只能是16位或32位通用暫存器,源運算元SRC可以是通用暫存器或儲存單元,需與目的運算元尺寸一致,可以是一個立即數(尺寸不能超過目的運算元)。

    乘數和被乘數均作為有符號數。

  • IMUL DEST,SRC1,SEC2

    資料流方向為DEST<=SRC1*SRC2

    目的運算元DEST只能是16位或32位通用暫存器。

    SRC1可以是通用暫存器或者儲存單元,須與目的運算元尺寸一致,但不能是立即數

    SRC2只能是一個立即數,尺寸不能超過目的運算元

    被乘數和乘數均為有符號數。

無符號數除法指令(DIV)

一般格式:

DIV OPRD

OPRD是除數,被除數位於AX、DX:AX或EDX:EAX中,由OPRD的尺寸決定,被除數的尺寸翻倍,商在AL、AX或者EAX中,餘數在AH、DX或者EDX中,商和餘數的尺寸和OPRD相同。

運算元OPRD可以是通用暫存器,可以是儲存單元,但不能是立即數

注意使用DIV指令時要防止除溢位,比如:

image-20181113211830792

比如上圖,除完之後應該商300餘0,可是300超出了AL的表示範圍,這時候就產生了溢位情況,在實際使用中要注意防範類似情況。

有符號數除法指令(IDIV)

指令格式:

IDIV OPRD

基本原則和DIV一樣,不同之處在於:

  • 除法時有符號的
  • 如果不能整除,餘數的符號與被除數符號一致,而且餘數的絕對值小於除數的絕對值。

總結

指令 中文名
MUL 無符號數乘法指令
IMUL 有符號數乘法指令
IMUL DEST,SRC 有符號數乘法指令
IMUL DEST,SRC1,SRC2 有符號數乘法指令
DIV 無符號數除法指令
IDIV OPRD 有符號數除法指令

符號拓展指令

符號拓展指令的實質是用被拓展暫存器的符號位佔據目標拓展暫存器。

指令 中文名 格式 解釋
CBW 位元組轉化為字指令 CBW 把暫存器AL中的符號拓展到暫存器AH;如果AL最高有效位、為0,則AH=0,如果AL最高位為1,則AH=FFH
CWD 字轉化為雙字指令 CWD 把暫存器AX中的符號拓展到暫存器DX;AX最高位0和1不同情況的拓展策略同CBW
CDQ 雙字轉化為四字指令 CDQ 把暫存器EAX中的符號拓展到EDX;AX最高位0和1不同情況的拓展策略同CBW
CWDE 字轉化為雙字指令 CWDE 把AX中的符號拓展到EAX的高16位;AX最高位0和1不同情況的拓展策略同CBW

使用舉例:

image-20181113213232393

符號拓展傳送指令(MOVSX)

一般格式:

MOVSX DEST,SRC

把SRC符號拓展後送到DEST。

目的運算元的尺寸必須大於源運算元的尺寸。源運算元的尺寸可以是8位或16位,目的運算元的尺寸可以是8位或16位。

使用舉例:

image-20181113213459899

零拓展傳送指令(MOVZX)

一般格式:

MOVZX DEST,SRC

把SRC零拓展後送到DEST。

源運算元可以是8位或16位,目的運算元可以是16位或32位。

使用舉例:

image-20181113213639331

邏輯運算指令

需要注意:

只有通用暫存器或者儲存單元可作為目的運算元,用於存放運算結果。

指令 中文名 格式 解釋 備註
NOT 否運算指令 NOT OPRD 把運算元OPRD按位取反,然後送回OPRD
AND 與運算指令 AND DEST,SRC 把兩個運算元進行與運算之後結果送回DEST 同1得1,否則得0
OR 或運算指令 OR DEST,SRC 把兩個運算元進行或運算之後結果送回DEST 同0得0,否則得1
XOR 異或運算 XOR DEST,SRC 把兩個運算元進行異或運算之後結果送回DEST 相同得0不同得1
TEST 測試指令 TEST DEST,SRC 與AND指令類似,將各位相與,但是結果不送回DEST,僅影響狀態位標誌,指令執行後,ZF、PF、SF反映運算結果,CF和OF被清零 通常用於檢測某些位是否為1,但又不希望改變運算元的值

test使用舉例:

判斷AL中的位6和位2是否有一位為1:

test al,01000100B;

隨後,判斷標誌位ZF,如果ZF為0,說明al第6位和第2位都為0,否則說明二者有一個為1.

移位指令

一般移位指令

指令 中文名 格式 解釋 備註
SAL 算術左移 SAL OPRD,count 把運算元oprd左移count位,右邊補0 與shl指令一樣 通過擷取count的低5位,實際的移位數被限於0到31之間。
SHL 邏輯左移 SHL OPRD,count 把運算元oprd左移count位,右邊補0 與sal指令一樣 通過擷取count的低5位,實際的移位數被限於0到31之間。
SAR 算術右移 SAR OPRD,count 把運算元oprd右移count位,同時每右移一位,左邊補符號位,移出的最低位進入標誌位CF 通過擷取count的低5位,實際的移位數被限於0到31之間。
SHR 邏輯右移 SHR OPRD,count 把運算元oprd右移count位,左邊補0,移出的最低位進入標誌位CF 通過擷取count的低5位,實際的移位數被限於0到31之間。

迴圈移位指令

指令 中文名 格式 解釋 備註
ROL 左迴圈移位指令 ROL OPRD,count 左迴圈移一位之後最高位移到最低位的同時也進入CF 通過擷取count的低5位,實際的移位數被限於0到31之間。
ROR 右迴圈移位指令 ROR OPRD,count 右迴圈移一位之後最低位移到最高位的同時也進入CF 通過擷取count的低5位,實際的移位數被限於0到31之間。
RCL 帶進位左迴圈移位 RCL OPRD,count 相當於CF在最高位直接參與迴圈移位 大迴圈左移 通過擷取count的低5位,實際的移位數被限於0到31之間。
RCR 帶進位右迴圈移位 RCR OPRD,count 相當於CF在最高位直接參與迴圈移位 大迴圈右移 通過擷取count的低5位,實際的移位數被限於0到31之間。

使用例項:

實現把al的最低位送到bl的最低位,仍保持al不變。

ror bl,1;//bl迴圈右移一位
ror al,1;//al迴圈右移一位,最低位進入cf
rcl bl,1;//bl帶進位左移,帶進了來自al的最低位(cf)
rol al,1;//恢復al

雙精度移位指令

雙精度移位指令是為了方便地把一個運算元的部分內容通過移位複製到另一個運算元。

格式:

  • 雙精度左移:SHLD OPRD1,OPRD2,count
  • 雙精度右移:SHRD OPRD1,OPRD2,count

解釋:

  • SHLD OPRD1,OPRD2,count

    將OPRD1左移指定的count位,在低端空出的位用運算元OPRD2高階的count位填補,但是OPRD2內容保持不變,運算元OPRD1中最後移出的位保留在進位標誌CF中。

  • SHRD OPRD1,OPRD2,count

    將OPRD1右移指定的count位,空出的位用OPRD2低端的count位填補,但是OPRD2內容保持不變,運算元OPRD1中最後移出的位保留在進位標誌CF中。

分支程式設計

無條件和條件轉移指令

段內轉移和段間轉移

  • 段內轉移(近轉移):僅僅重新設定指令指標暫存器EIP的轉移,由於沒有調整CS,所以轉移後繼續執行的指令仍在同一程式碼段中。
  • 段間轉移(遠轉移):不僅重新設定EIP,而且重新設定程式碼段暫存器CS的轉移,由於重置了CS,轉移後繼續執行的指令在另一程式碼段中。

對於段內轉移和段間轉移需要注意:

  • 條件轉移指令迴圈指令只能實現段內轉移
  • 無條件轉移指令過程呼叫指令以及返回指令,既可以是段內轉移,也可以是段間轉移
  • 軟中斷指令中斷返回指令一定是段間轉移

直接轉移和間接轉移

  • 直接轉移:轉移指令中直接給出轉移目標地址的轉移;
  • 間接轉移:轉移指令中給出包含轉移目標地址的暫存器或者儲存單元的轉移;

需要注意無條件轉移指令和過程呼叫指令集可以是直接轉移也可以是間接轉移。

無條件轉移指令

無條件轉移指令分為4種:

  • 段內直接轉移
  • 段內間接轉移
  • 段間直接轉移
  • 段間間接轉移

需要說明的是無條件轉移指令均不影響標誌暫存器的狀態標誌。

無條件段內直接轉移
JMP LABEL;

標號LABEL表示要轉移的目標位置(轉移目的地)。

無條件段內直接轉移的機器碼構成如下:

操作碼OP  地址差rel

地址差rel實際上是LABEL所指定的指令的地址偏移與緊跟JMP指令的下一條指令的地址偏移之間的差值,rel可正可負,這樣才可以實現前後的跳轉。地址差rel可以用一個位元組表示,也可用4個位元組或2位元組表示,如果只用一個位元組表示,就稱之為短(short)轉移,否則稱為近(near)轉移。一般如果當彙編器彙編到某條轉移指令時可以計算出地址差rel,彙編器會自動判斷出應該用1位元組表示rel還是4位元組或2位元組,否則彙編器會使用較多的位數來表示地址差。所以,當程式設計師在寫程式時能顧及出用8位就可以表示出地址差,那麼可以在標號前加一個彙編器操作符**“SHORT”**來指定用一個位元組表示地址差,表示轉移的目的地就在附近。

無條件段內間接轉移
JMP OPRD

OPRD是32位通用暫存器或者雙字儲存單元,比如:

JMP ECX;
JMP DWORD PTR [EBX];
無條件段間轉移指令JMP

段間轉移指令和段內轉移指令差不多,只是涉及到改變程式碼段暫存器CS的內容,情況較為複雜,在之後的文章中介紹。

條件轉移指令

條件轉移指令在前一篇文章已經介紹,這裡不再贅述,只是需要特別明確一下rel偏移量的概念。

多路分支的實現

多路分支實際上指的就是switch-case的彙編實現,這裡的實現原理主要是通過無條件間接轉移指令和目標地址表來實現多路分支,舉個例子:

考慮一下多路分支程式:

int  cf319(int x,  int operation)
{
    int  y;
    //多路分支
    switch ( operation ) {
        case 1:
            y = 3*x;
            break;
        case 2:
            y = 5*x+6;
            break;
        case 4:
        case 5:
            y = x*x ;
            break;
        case 8:
            y = x*x+4*x;
            break;
        default:
            y = x ;
    }
    if ( y > 1000 )
        y = 1000;
    return  y;
}

上面的程式為了更加具有一般性,刻意沒有安排連續的case值,其彙編的實現如下:

 push  ebp
 mov   ebp, esp
                                   ; switch ( operation ) {
 mov   eax, DWORD PTR [ebp+12]  ;取得引數operation(case值)
 dec   eax                      ;0開始計算,所以先減去1
 cmp   eax, 7                   ;0開始計算,最多就是7
 ja    SHORT LN2cf319           ;超過,則轉default(LN2cf319對應defalut處理語句)
 ;
 jmp   DWORD  PTR  LN12cf319[ eax*4 ]     ;這句是關鍵,這裡實現了多路分支
 ;
    

LN12cf319:                           ;多向分支目標地址表
    DD    LN6cf319                   ; case 1
    DD    LN5cf319                   ; case 2
    DD    LN2cf319                   ; default
    DD    LN4cf319                   ; case 4
    DD    LN4cf319                   ; case 5
    DD    LN2cf319                   ; default
    DD    LN2cf319                   ; default
    DD    LN3cf319                   ; case 8

看上面的彙編實現,其核心思想是巧妙地運用了無條件段內間接轉移和目標地址表,因為目標地址表每項佔4個位元組,所以跳轉的地址是目標地址表的起始地址加case物件(這裡是operation)乘以4,如果operation為0,跳到目標地址表的首地址,即就是LN6c,如果operation為1,跳到LN5c,以此類推就實現了多路分支的巧妙跳轉。

使用該方法的一般建議是:當多路分支數超過5時,考慮無條件間接轉移方式和目標地址表結合實現多路分支會更高效。

迴圈程式設計

迴圈指令

迴圈指令類似於條件轉移指令,其採用的是段內相對轉移的方式,是通過在指令指標暫存器EIP上加一個地址差的方式實現的轉移,需要注意的是迴圈指令中的這個地址差只用了一個位元組(8位)來表示,所以轉移範圍僅在-128-127之間。在保護方式(32位程式碼段)下,ECX作為迴圈計數器,實方式下,以CX為迴圈計數器,迴圈指令不影響各標誌位。

指令 中文名 格式 解釋 備註
LOOP 計數迴圈指令 LOOP LABEL 使ECX的值減1,當ECX的值不為0的時候跳轉至LABEL,否則執行LOOP之後的語句
LOOPE 等於迴圈指令 LOOPE LABEL 使ECX的值減1,如果結果不等於0並且零標誌ZF等於1(表示相等),那麼就轉移到LABEL,否則執行LOOPE之後的語句 ECX的減1並不影響標誌位,ZF是否為1取決於迴圈指令之前指令對其的影響。
LOOPZ 零迴圈指令 LOOPZ LABEL 使ECX的值減1,如果結果不等於0並且零標誌ZF等於1(表示相等),那麼就轉移到LABEL,否則執行LOOPZ之後的語句 ECX的減1並不影響標誌位,ZF是否為1取決於迴圈指令之前指令對其的影響。
LOOPNE 不等於迴圈指令 LOOPE LABEL 使ECX的值減1,如果結果不等於0並且零標誌ZF等於0(表示不相等),那麼就轉移到LABEL,否則執行LOOPNE之後的語句 ECX的減1並不影響標誌位,ZF是否為1取決於迴圈指令之前指令對其的影響。
LOOPNZ 非零迴圈指令 LOOPNZ LABEL 使ECX的值減1,如果結果不等於0並且零標誌ZF等於0(表示不相等),那麼9就轉移到LABEL,否則執行LOOPNZ之後的語句 ECX的減1並不影響標誌位,ZF是否為1取決於迴圈指令之前指令對其的影響。
JECXZ 計數轉移指令 JECXZ LABEL 當暫存器ECX的值為0時轉移到LABEL,否則順序執行 注意與LOOP的關係是JECXZ是直接判斷ECX,沒有先減ECX通常在迴圈開始之前使用該指令,所以迴圈次數為0時,就可以跳過迴圈體

子程式設計

呼叫約定

  • -_cdecl被稱為 C 呼叫約定。預設呼叫約定。引數按照從右至左的順序入堆疊,函式本身不清理堆疊。

  • _stdcall被稱為 pascal 呼叫約定。引數按照從右至左的順序入堆疊,函式自身清理堆疊。

  • _fastcall 是快速呼叫約定。通過 暫存器傳遞引數。前兩個引數由 ECXEDX 傳送,其他引數按照從右至左的順序入堆疊,函式自身清理堆疊。