指令
詳細的指令介紹:
常用的匯編指令有:
-
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 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操作。對應了高級語言中的比較運算。
中文含義 | 檢查符號位 | 典型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:棧頂指針寄存器,記錄當前使用的地址,棧的內存空間是存大地址開始使用的
我們可以使用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指令的詳細使用在下一章函數中講解,同時會涉及到堆棧提升與堆棧平衡。