1. 程式人生 > 實用技巧 >彙編基礎之三 -- 彙編指令

彙編基礎之三 -- 彙編指令

指令

詳細的指令介紹:https://blog.csdn.net/bjbz_cxy/article/details/79467688

常用的彙編指令有:

  • mov 指令,在記憶體中寫入以一個數,將暫存器的值寫入記憶體,將記憶體中的值寫入暫存器。暫存器使用名稱指定,記憶體使用一個記憶體地址編號指定。

指令 寬度    指定記憶體地址     指定值
MOV DWORD PTR DS:[地址編號], 值             // 值寫入記憶體
MOV DWORD PTR DS:[0x1840FF93], EAX         // eax暫存器中的值寫入記憶體
MOV EAX DWORD PTR DS:[0x0012FF45
] // 記憶體值寫入EAX暫存器

  記憶體地址可以有以下 的幾種書寫方式

 PTR DS:[0x10ff93dd]               // 直接指定地址
  PTR DS:[reg]                      // reg為8個暫存器的名稱, reg中儲存的是一個記憶體地址
  PTR DS:[reg + 立即數]             // reg值 + 一個數字得到記憶體地址,實現地址偏移
  PTR DS:[reg + reg*{1,2,4,8}]     // 陣列地址的快速定址
  PTR DS:[reg + reg*{1,2,4,8} + 立即數]  //
利用地址的偏移,這是記憶體可以快速定址原因
  • MOVS指令:預設將ESI暫存器中儲存的記憶體地址處的資料,拷貝到EDI暫存器中儲存的記憶體地址上去,每次複製後,ESI和EDI中儲存的地址值自動偏移複製的資料寬度值,可以向大地址和小地址偏移,由EFL標誌暫存器上的DF 標誌位決定。
將ESI儲存的記憶體地址的值,複製到EDI暫存器中儲存的記憶體地址上去。並且ESI和EDI的值會自動加1,也就是記憶體地址向後移動了一位。
MOVS BYTE PTR ES:[EDI], BYTE PTR DS:[ESI]
  
ESI和EDI是可以省略的,預設都是從這兩個暫存器中存放的記憶體地址拷貝,更具每次拷貝的資料寬度,有MOVSB, MOVSW, MOVSD三個快捷的指令,表示移動一個位元組,兩個位元組和四個位元組資料寬度。
  • STOS指令:將AI,AX,EAX暫存器的值儲存到EDI指定的記憶體單元中。LODS是STOS的逆過程
    STOS BYTE PTR ES:[EDI]       // byte對應AI,word對應AX,dword對應EAX
    STOS WOED PTR ES:[EDI]       // AX => EDI 指定 的地址處
    STOS DWORD PTR ES:[EDI]      // EAX => EDI指定的地址處
  • REP指令:按計數暫存器(ECX)中指定的重複次數執行指定的指令。
    MOV ECX 12     // 指定重複次數
    REP MOVSD      // 重複18次,12是16進位制,每次執行後,ECX-1
    REP STOSD     // d代表資料寬度為 dword
  • 簡單的運算類指令: ADD(加法) SUB(減法) AND(與) NOT(非) XOR(異或), 簡單示例

    ADD DWORD EAX, EBX       // 將EAX暫存器中的值與EBX中的值相加,結果儲存到EAX中
    AND DWORD EAX, PTR DS:[0x10ff93dd] // 將EAX暫存器中的值與記憶體地址為0x10ff93dd的值位於,結果存EAX中
  • CPM:和SUB相同對兩個值相減, SUB eax eax 會將計算結果存入eax,然後更改EFL標誌暫存器中的ZF標誌位,而CPM只會更具結果更改ZF標誌位,不儲存該結果。通常用於比較兩個值是否相同。如果相同,則CMP(兩數之差)結果為0,ZF標誌位將會1,否則ZF位將位0,通過獲取ZF為值得到是否相等。
  • TEST:AND相同, 對兩個值進行為與運算,但不儲存結果,只會更改ZF標誌位為0或1。

JCC指令組

上面使用CPM和TEST進行兩值得比較,並通過標誌位得結果,得到比較得結果,而JCC指令組也是同樣得原理。

J是指jump。CC是條件,該條件與EFL標誌暫存器中的標誌位息息相關。通過獲取EFL標誌位的值,判斷是否滿足條件而是否執行JUMP操作。對應了高階語言中的比較運算。

JCC指令中文含義檢查符號位典型C應用
JZ/JE 若為0則跳轉;若相等則跳轉 ZF=1 if (i == j);if (i == 0);
JNZ/JNE 若不為0則跳轉;若不相等則跳轉 ZF=0 if (i != j);if (i != 0);
JS 若為負則跳轉 SF=1 if (i < 0);
JNS 若為正則跳轉 SF=0 if (i > 0);
JP/JPE 若1出現次數為偶數則跳轉 PF=1 (null)
JNP/JPO 若1出現次數為奇數則跳轉 PF=0 (null)
JO 若溢位則跳轉 OF=1 (null)
JNO 若無溢位則跳轉 OF=0 (null)
JC/JB/JNAE 若進位則跳轉;若低於則跳轉;若不高於等於則跳轉 CF=1 if (i < j);
JNC/JNB/JAE 若無進位則跳轉;若不低於則跳轉;若高於等於則跳轉; CF=0 if (i >= j);
JBE/JNA 若低於等於則跳轉;若不高於則跳轉 ZF=1或CF=1 if (i <= j);
JNBE/JA 若不低於等於則跳轉;若高於則跳轉 ZF=0或CF=0 if (i > j);
JL/JNGE 若小於則跳轉;若不大於等於則跳轉 SF != OF if (si < sj);
JNL/JGE 若不小於則跳轉;若大於等於則跳轉; SF = OF if (si >= sj);
JLE/JNG 若小於等於則跳轉;若不大於則跳轉 ZF != OF 或 ZF=1 if (si <= sj);
JNLE/JG 若不小於等於則跳轉;若大於則跳轉 SF=0F 且 ZF=0 if(si>sj)ag

堆疊指令

ESP:棧頂指標暫存器,記錄當前使用的地址,棧的記憶體空間是存大地址開始使用的 EBP:棧底指標暫存器,記錄棧開始的位置。

我們可以使用MOV指令,將資料MOV到ESP暫存器指定的位置,然後使用SUB或者ADD指令,將ESP暫存器的值偏移對應的資料寬度值,實現資料的入棧和出棧操作。基於這樣的原理,彙編中提供了PUSH和POP命令

MOV BYTE PTR DS:[ESP], 0xFF          // 向棧中寫入一個位元組
SUB ESP, 1                           // 棧頂指標偏移一位元組

以上兩個操作等同於一個push操作
PUSH 0xFF                           // 將0xFF寫入到ESP儲存的地址處,同時ESP偏移一個數據寬度
PUSH EAX                // EAX中的值入棧
PUSH DWORD PTR DS:[地址]             // 該地址處的值入棧

出棧同理 
POP DWORD EAX                     // 將棧頂的元素取出放入EAX暫存器中儲存,同時ESP偏移一個DWORD資料寬度

跳轉指令

  • JMP:將程式跳轉到指定的地址執行,通過改變EIP中的值實現

  • CALL:將程式跳轉到指定的地址,跳轉前將下一次執行的地址儲存到棧中,用來記錄跳轉前的位置。然後改變EIP中的值使得cpu去執行其他地址的指令。當執行結束需要返回原來的地址時,從棧中取出跳轉簽到的地址,賦值到EIP暫存器中即可回到跳轉前的狀態。

  • RETN:從棧中取出儲存的地址,賦值給EIP,用作下次執行

使用這兩個指令需要理解EIP暫存器的作用。當一個程式被編譯完成之後,程式的執行方法即已經確定,即A=>B=>C的順序進行執行,且他們使用的記憶體空間說連續的,計算機執行一行指令後,下一次要執行的內容都儲存在EIP暫存器儲存的地址處。所以EIP暫存器指向的內容,就是程式下一次執行的指令。也就是說,A執行時,EIP暫存器中儲存的是B的地址,這樣執行A後將會獲取B的資訊並執行。如果A時一個JMP或者CALL指令,執行依次JMP EIP D,這條指令表示將EIP中的值改為D的地址,所以下一次執行的即為D指令。由此實現了跳轉。這樣的方式當程式跳轉到D指令後,程式無法返回B指令了,因為我們找不到B的地址,想要重新回到B指令,需要將B的地址儲存,使用時取回即可,通常是將其入棧。這樣就和CALL指令的使用方式相同了。

JMP EIP, 地址   // 修改EIP暫存器中的值。
CALL 地址    // 跳轉到指定地址,執行該地址的指令,跳轉前,將下一行地址入棧,

CALL指令的詳細使用在下一章函式中講解,同時會涉及到堆疊提升與堆疊平衡。

JCC指令中文含義英文原意檢查符號位典型C應用
JZ/JE 若為0則跳轉;若相等則跳轉 jump if zero;jump if equal ZF=1 if (i == j);if (i == 0);
JNZ/JNE 若不為0則跳轉;若不相等則跳轉 jump if not zero;jump if not equal ZF=0 if (i != j);if (i != 0);
JS 若為負則跳轉 jump if sign SF=1 if (i < 0);
JNS 若為正則跳轉 jump if not sign SF=0 if (i > 0);
JP/JPE 若1出現次數為偶數則跳轉 jump if Parity (Even) PF=1 (null)
JNP/JPO 若1出現次數為奇數則跳轉 jump if not parity (odd) PF=0 (null)
JO 若溢位則跳轉 jump if overflow OF=1 (null)
JNO 若無溢位則跳轉 jump if not overflow OF=0 (null)
JC/JB/JNAE 若進位則跳轉;若低於則跳轉;若不高於等於則跳轉 jump if carry;jump if below;jump if not above equal CF=1 if (i < j);
JNC/JNB/JAE 若無進位則跳轉;若不低於則跳轉;若高於等於則跳轉; jump if not carry;jump if not below;jump if above equal CF=0 if (i >= j);
JBE/JNA 若低於等於則跳轉;若不高於則跳轉 jump if below equal;jump if not above ZF=1或CF=1 if (i <= j);
JNBE/JA 若不低於等於則跳轉;若高於則跳轉 jump if not below equal;jump if above ZF=0或CF=0 if (i > j);
JL/JNGE 若小於則跳轉;若不大於等於則跳轉 jump if less;jump if not greater equal SF != OF if (si < sj);
JNL/JGE 若不小於則跳轉;若大於等於則跳轉; jump if not less;jump if greater equal SF = OF if (si >= sj);
JLE/JNG 若小於等於則跳轉;若不大於則跳轉 jump if less equal;jump if not greater ZF != OF 或 ZF=1 if (si <= sj);
JNLE/JG 若不小於等於則跳轉;若大於則跳轉 jump if not less equal;jump if greater SF=0F 且 ZF=0 if(si>sj)ag