一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

常用的匯編指令都有哪些?

 phoenixcyan 2013-04-14

常用的匯編指令都有哪些?

MOV(MOVe)   傳送指令

PUSH     入棧指令
POP     出棧指令
XCHG(eXCHanG)   交換指令
XLAT(TRANSLATE)   換碼指令
LEA (Load Effective Address) 有效地址送寄存器指令
LDS(Load DS with pointer) 指針送寄存器和DS指令
LES(Load ES with pointer) 指針送寄存器和ES指令
LAHF(Load AH with Flags) 標志位送AH指令
SAHF(Store AH into Flgs) AH送標志寄存器指令
PUSHF(PUSH the Flags)   標志進棧指令
POPF(POP the Flags)   標志出棧指令
ADD     加法指令
ADC     帶進位加法指令
INC     加1指令
SUB(SUBtract)   不帶借位的減法指令
SBB(SuVtrach with borrow) 帶借位的減法指令
DEC(DECrement)   減1指領
NEG(NEGate)   求補指令
CMP(CoMPare)   比較指令
MUL(unsinged MULtiple) 無符號數(shù)乘法指令
IMUL(sIgned MUL tiple) 有符號數(shù)乘法指令
DIV(unsigned DIVide)   無符號數(shù)除法指令
IDIV(sIgned DIVide)   有符號數(shù)除法指令
CBW(Count Byte to Word) 字節(jié)轉(zhuǎn)換為字指令
CWD(Count Word to Doble word) 字轉(zhuǎn)換為雙字指令
DAA   壓縮的BCD碼加法十進制調(diào)整指令
DAS   壓縮的BCD碼減法十進制調(diào)整指令
AAA   非壓縮的BCD碼加法十進制調(diào)整指令
AAS   非壓縮的BCD碼加法十進制調(diào)整指令
AND     邏輯與指令
OR     邏輯或指令
XOR     邏輯異或指令
NOT     邏輯非指令
TEST     測試指令
SHL(SHift logical Letf)   邏輯左移指令
SHR(SHift logical Right)   邏輯右移指令
ROL(Rotate Left )   循環(huán)左移指令P58
ROR(Rotate Right)   循環(huán)右移指令P58
RCL(Rotate Left through Carry) 帶進位循環(huán)左移
RCR(Rotate Right through Carry) 帶進位循環(huán)左移
MOVS(MOVe String)   串傳送指令
STOS(STOre into String) 存入串指令
LODS(LOad from string) 從串取指令
REP(REPeat)   重復操作前
CLD(CLear Direction flag) 清除方向標志指令
STD(SeT Direction flag)   設置方向標志指令
CMPS(CoMPare String)   串比較指令
SCAS(SCAn String)   串掃描指令
REPE/REPZ(REPeat while Equal/Zero)相等/為零時重復操作前綴
REPNE/REPNZ(REPeat while Not Equal/Zero)不相等/不為零進重復前綴
IN(INput)   輸入指令
OUT(OUTput)   輸出指令
JMP(JuMP)   無條件轉(zhuǎn)移指令
JZ,JNZ,JS,JNS,JO,JNO,JP,JNP,JB,JNB,JBE,JNBE,JL,JNL,JLE,JNLE,JCXZ   條件轉(zhuǎn)移指令
LOOP     循環(huán)指令P70
LOOPZ/LOOPE   為零/相等時循環(huán)指令
LOOPNZ/LOOPNE   不為零/不相等時循環(huán)指令
CALL     子程序調(diào)用指令
RET(RETun)   子程序返回指令
CLC(CLear Carry)   進位位置0指令
CMC(CoMplement Carry) 進位位求反指令
SRC(SeT Carry)   進位位置1指令
NOP(No OPeretion)   無操作指令
HLT(HaLT)   停機指令
OFFSET   返回偏移地址
SEG     返回段地址
EQU(=)   等值語句
PURGE   解除語句
DUP     操作數(shù)字段用復制操作符
SEGMENT,ENDS   段定義指令
ASSUME   段地址分配指令
ORG     起始偏移地址設置指令
$     地址計數(shù)器的當前值
PROC,ENDP   過程定義語句
NAME,TITLE,END   程序開始結(jié)束語句
MACRO,ENDM   宏定義指令

JZ   OPR //結(jié)果為零轉(zhuǎn)移
JNZ   OPR //結(jié)果不為零轉(zhuǎn)移
JS   OPR //結(jié)果為負轉(zhuǎn)移
JNS   OPR //結(jié)果為正轉(zhuǎn)移
JO   OPR //溢出轉(zhuǎn)移
JNO   OPR //不溢出轉(zhuǎn)移
JP   OPR //結(jié)果為偶轉(zhuǎn)移
JNP   OPR //結(jié)果為奇轉(zhuǎn)移
JC   OPR //有進位轉(zhuǎn)移
JNC   OPR //無進位轉(zhuǎn)移

 

 

 

 

 

 

 

 

 

匯編語言和CPU以及內(nèi)存,端口等硬件知識是連在一起的. 這也是為什么匯編語言沒有通用性的原因. 下面簡單講講基本知識(針對INTEL x86及其兼容機)
============================
x86匯編語言的指令,其操作對象是CPU上的寄存器,系統(tǒng)內(nèi)存,或者立即數(shù). 有些指令表面上沒有操作數(shù), 或者看上去缺少操作數(shù), 其實該指令有內(nèi)定的操作對象, 比如push指令, 一定是對SS:ESP指定的內(nèi)存操作, 而cdq的操作對象一定是eax / edx.

在匯編語言中,寄存器用名字來訪問. CPU 寄存器有好幾類, 分別有不同的用處:

1. 通用寄存器:
EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(這個雖然通用,但很少被用做除了堆棧指針外的用途)

這些32位可以被用作多種用途,但每一個都有”專長”. EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是”基地址”(base)寄存器, 在內(nèi)存尋址時存放基地址. ECX 是計數(shù)器(counter), 是重復(REP)前綴指令和LOOP指令的內(nèi)定計數(shù)器. EDX是…(忘了..哈哈)但它總是被用來放整數(shù)除法產(chǎn)生的余數(shù). 這4個寄存器的低16位可以被單獨訪問,分別用AX,BX,CX和DX. AX又可以單獨訪問低8位(AL)和高8位(AH), BX,CX,DX也類似. 函數(shù)的返回值經(jīng)常被放在EAX中.

ESI/EDI分別叫做”源/目標索引寄存器”(source/destination index),因為在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目標串.

EBP是”基址指針”(BASE POINTER), 它最經(jīng)常被用作高級語言函數(shù)調(diào)用的”框架指針”(frame pointer). 在破解的時候,經(jīng)??梢钥匆娨粋€標準的函數(shù)起始代碼:

push ebp ;保存當前ebp
mov ebp,esp ;EBP設為當前堆棧指針
sub esp, xxx ;預留xxx字節(jié)給函數(shù)臨時變量.

這樣一來,EBP 構(gòu)成了該函數(shù)的一個框架, 在EBP上方分別是原來的EBP, 返回地址和參數(shù). EBP下方則是臨時變量. 函數(shù)返回時作 mov esp,ebp/pop ebp/ret 即可.

ESP 專門用作堆棧指針.

2. 段寄存器:
CS(Code Segment,代碼段) 指定當前執(zhí)行的代碼段. EIP (Instruction pointer, 指令指針)則指向該段中一個具體的指令. CS:EIP指向哪個指令, CPU 就執(zhí)行它. 一般只能用jmp, ret, jnz, call 等指令來改變程序流程,而不能直接對它們賦值.
DS(DATA SEGMENT, 數(shù)據(jù)段) 指定一個數(shù)據(jù)段. 注意:在當前的計算機系統(tǒng)中, 代碼和數(shù)據(jù)沒有本質(zhì)差別, 都是一串二進制數(shù), 區(qū)別只在于你如何用它. 例如, CS 制定的段總是被用作代碼, 一般不能通過CS指定的地址去修改該段. 然而,你可以為同一個段申請一個數(shù)據(jù)段描述符”別名”而通過DS來訪問/修改. 自修改代碼的程序常如此做.
ES,FS,GS 是輔助的段寄存器, 指定附加的數(shù)據(jù)段.
SS(STACK SEGMENT)指定當前堆棧段. ESP 則指出該段中當前的堆棧頂. 所有push/pop 系列指令都只對SS:ESP指出的地址進行操作.

3. 標志寄存器(EFLAGS):

該寄存器有32位,組合了各個系統(tǒng)標志. EFLAGS一般不作為整體訪問, 而只對單一的標志位感興趣. 常用的標志有:

進位標志C(CARRY), 在加法產(chǎn)生進位或減法有借位時置1, 否則為0.
零標志Z(ZERO), 若運算結(jié)果為0則置1, 否則為0
符號位S(SIGN), 若運算結(jié)果的最高位置1, 則該位也置1.
溢出標志O(OVERFLOW), 若(帶符號)運算結(jié)果超出可表示范圍, 則置1.

JXX 系列指令就是根據(jù)這些標志來決定是否要跳轉(zhuǎn), 從而實現(xiàn)條件分枝. 要注意,很多JXX 指令是等價的, 對應相同的機器碼. 例如, JE 和JZ 是一樣的,都是當Z=1是跳轉(zhuǎn). 只有JMP 是無條件跳轉(zhuǎn). JXX 指令分為兩組, 分別用于無符號操作和帶符號操作. JXX 后面的”XX” 有如下字母:

無符號操作: 帶符號操作:
A = “ABOVE”, 表示”高于” G = “GREATER”, 表示”大于”
B = “BELOW”, 表示”低于” L = “LESS”, 表示”小于”
C = “CARRY”, 表示”進位”或”借位” O = “OVERFLOW”, 表示”溢出”
S = “SIGN”, 表示”負”
通用符號:
E = “EQUAL” 表示”等于”, 等價于Z (ZERO)
N = “NOT” 表示”非”, 即標志沒有置位. 如JNZ “如果Z沒有置位則跳轉(zhuǎn)”
Z = “ZERO”, 與E同.

如果仔細想一想,就會發(fā)現(xiàn) JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, ….

4. 端口

端口是直接和外部設備通訊的地方。外設接入系統(tǒng)后,系統(tǒng)就會把外設的數(shù)據(jù)接口映射到特定的端口地址空間,這樣,從該端口讀入數(shù)據(jù)就是從外設讀入數(shù) 據(jù),而向外設寫入數(shù)據(jù)就是向端口寫入數(shù)據(jù)。當然這一切都必須遵循外設的工作方式。端口的地址空間與內(nèi)存地址空間無關,系統(tǒng)總共提供對64K個8位端口的訪 問,編號0-65535. 相鄰的8位端口可以組成成一個16位端口,相鄰的16位端口可以組成一個32位端口。端口輸入輸出由指令IN,OUT,INS和OUTS實現(xiàn),具體可參考 匯編語言書籍。

 

匯編指令的操作數(shù)可以是內(nèi)存中的數(shù)據(jù), 如何讓程序從內(nèi)存中正確取得所需要的數(shù)據(jù)就是對內(nèi)存的尋址。

INTEL 的CPU 可以工作在兩種尋址模式:實模式和保護模式。 前者已經(jīng)過時,就不講了, WINDOWS 現(xiàn)在是32位保護模式的系統(tǒng), PE 文件就基本是運行在一個32位線性地址空間, 所以這里就只介紹32位線性空間的尋址方式。

其實線性地址的概念是很直觀的, 就想象一系列字節(jié)排成一長隊,第一個字節(jié)編號為0, 第二個編號位1, 。。。。 一直到4294967295(十六進制FFFFFFFF,這是32位二進制數(shù)所能表達的最大值了)。 這已經(jīng)有4GB的容量! 足夠容納一個程序所有的代碼和數(shù)據(jù)。 當然, 這并不表示你的機器有那么多內(nèi)存。 物理內(nèi)存的管理和分配是很復雜的內(nèi)容, 初學者不必在意, 總之, 從程序本身的角度看, 就好象是在那么大的內(nèi)存中。

在INTEL系統(tǒng)中, 內(nèi)存地址總是由”段選擇符:有效地址”的方式給出。段選擇符(SELECTOR)存放在某一個段寄存器中, 有效地址則可由不同的方式給出。 段選擇符通過檢索段描述符確定段的起始地址, 長度(又稱段限制), 粒度, 存取權(quán)限, 訪問性質(zhì)等。 先不用深究這些, 只要知道段選擇符可以確定段的性質(zhì)就行了。 一旦由選擇符確定了段, 有效地址相對于段的基地址開始算。 比如由選擇符1A7選擇的數(shù)據(jù)段, 其基地址是400000, 把1A7 裝入DS中, 就確定使用該數(shù)據(jù)段。 DS:0 就指向線性地址400000。 DS:1F5278 就指向線性地址5E5278。 我們在一般情況下, 看不到也不需要看到段的起始地址, 只需要關心在該段中的有效地址就行了。 在32位系統(tǒng)中, 有效地址也是由32位數(shù)字表示, 就是說, 只要有一個段就足以涵蓋4GB線性地址空間, 為什么還要有不同的段選擇符呢? 正如前面所說的, 這是為了對數(shù)據(jù)進行不同性質(zhì)的訪問。 非法的訪問將產(chǎn)生異常中斷, 而這正是保護模式的核心內(nèi)容, 是構(gòu)造優(yōu)先級和多任務系統(tǒng)的基礎。 這里有涉及到很多深層的東西, 初學者先可不必理會。

有效地址的計算方式是: 基址+間址*比例因子+偏移量。 這些量都是指段內(nèi)的相對于段起始地址的量度, 和段的起始地址沒有關系。 比如, 基址=100000, 間址=400, 比例因子=4, 偏移量=20000, 則有效地址為:

100000+400*4+20000=100000+1000+20000=121000。 對應的線性地址是400000+121000=521000。 (注意, 都是十六進制數(shù))。

基址可以放在任何32位通用寄存器中, 間址也可以放在除ESP外的任何一個通用寄存器中。 比例因子可以是1, 2, 4 或8。 偏移量是立即數(shù)。 如: [EBP+EDX*8+200]就是一個有效的有效地址表達式。 當然, 多數(shù)情況下用不著這么復雜, 間址,比例因子和偏移量不一定要出現(xiàn)。

內(nèi)存的基本單位是字節(jié)(BYTE)。 每個字節(jié)是8個二進制位, 所以每個字節(jié)能表示的最大的數(shù)是11111111, 即十進制的255。 一般來說, 用十六進制比較方便, 因為每4個二進制位剛好等于1個十六進制位, 11111111b = 0xFF。 內(nèi)存中的字節(jié)是連續(xù)存放的, 兩個字節(jié)構(gòu)成一個字(WORD), 兩個字構(gòu)成一個雙字(DWORD)。 在INTEL架構(gòu)中, 采用small endian格式, 即在內(nèi)存中,高位字節(jié)在低位字節(jié)后面。 舉例說明:十六進制數(shù)803E7D0C, 每兩位是一個字節(jié), 在內(nèi)存中的形式是:0C 7D 3E 80。 在32位寄存器中則是正常形式,如在EAX就是803E7D0C。 當我們的形式地址指向這個數(shù)的時候,實際上是指向第一個字節(jié),即0C。 我們可以指定訪問長度是字節(jié), 字或者雙字。 假設DS:[EDX]指向第一個字節(jié)0C:

mov AL, byte ptr DS:[EDX] ;把字節(jié)0C存入AL
mov AX, word ptr DS:[EDX] ;把字7D0C存入AX
mov EAX, dword ptr DS:[EDX] ;把雙字803E7D0C存入EAX

在段的屬性中,有一個就是缺省訪問寬度。如果缺省訪問寬度為雙字(在32位系統(tǒng)中經(jīng)常如此),那么要進行字節(jié)或字的訪問,就必須用byte/word ptr顯式地指明。

缺省段選擇:如果指令中只有作為段內(nèi)偏移的有效地址,而沒有指明在哪一個段里的時候,有如下規(guī)則:

如果用ebp和esp作為基址或間址,則認為是在SS確定的段中;
其他情況,都認為是在DS確定的段中。

如果想打破這個規(guī)則,就必須使用段超越前綴。舉例如下:

mov eax, dword ptr [edx] ;缺省使用DS,把DS:[EDX]指向的雙字送入eax
mov ebx, dword ptr ES:[EDX] ;使用ES:段超越前綴,把ES:[EDX]指向的雙字送入ebx

堆棧:

堆棧是一種數(shù)據(jù)結(jié)構(gòu),嚴格地應該叫做“棧”。“堆”是另一種類似但不同的結(jié)構(gòu)。SS 和 ESP 是INTEL對棧這種數(shù)據(jù)結(jié)構(gòu)的硬件支持。push/pop指令是專門針對棧結(jié)構(gòu)的特定操作。SS指定一個段為棧段,ESP則指出當前的棧頂。push xxx 指令作如下操作:

把ESP的值減去4;
把xxx存入SS:[ESP]指向的內(nèi)存單元。

這樣,esp的值減小了4,并且SS:[ESP]指向新壓入的xxx。 所以棧是“倒著長”的,從高地址向低地址方向擴展。pop yyy 指令做相反的操作,把SS:[ESP]指向的雙字送到y(tǒng)yy指定的寄存器或內(nèi)存單元,然后把esp的值加上4。這時,認為該值已被彈出,不再在棧上了,因 為它雖然還暫時存在在原來的棧頂位置,但下一個push操作就會把它覆蓋。因此,在棧段中地址低于esp的內(nèi)存單元中的數(shù)據(jù)均被認為是未定義的。

最后,有一個要注意的事實是,匯編語言是面向機器的,指令和機器碼基本上是一一對應的,所以它們的實現(xiàn)取決于硬件。有些看似合理的指令實際上是不存在的,比如:

mov DS:[edx], ds:[ecx] ;內(nèi)存單元之間不能直接傳送
mov DS, 1A7 ;段寄存器不能直接由立即數(shù)賦值
mov EIP, 3D4E7 ;不能對指令指針直接操作。

 

“匯編語言”作為一門語言,對應于高級語言的編譯器,我們需要一個“匯編器”來把匯編語言原文件匯編成機器可執(zhí)行的代碼。高級的匯編器如MASM, TASM等等為我們寫匯編程序提供了很多類似于高級語言的特征,比如結(jié)構(gòu)化、抽象等。在這樣的環(huán)境中編寫的匯編程序,有很大一部分是面向匯編器的偽指令, 已經(jīng)類同于高級語言?,F(xiàn)在的匯編環(huán)境已經(jīng)如此高級,即使全部用匯編語言來編寫windows的應用程序也是可行的,但這不是匯編語言的長處。匯編語言的長 處在于編寫高效且需要對機器硬件精確控制的程序。而且我想這里的人學習匯編的目的多半是為了在破解時看懂反匯編代碼,很少有人真的要拿匯編語言編程序吧? (汗……)

好了,言歸正傳。大多數(shù)匯編語言書都是面向匯編語言編程的,我的帖是面向機器和反匯編的,希望能起到相輔相成的作用。有了前面兩篇的基礎,匯編語言 書上對大多數(shù)指令的介紹應該能夠看懂、理解了。這里再講一講一些常見而操作比較復雜的指令。我這里講的都是機器的硬指令,不針對任何匯編器。

無條件轉(zhuǎn)移指令jmp:

這種跳轉(zhuǎn)指令有三種方式:短(short),近(near)和遠(far)。短是指要跳至的目標地址與當前地址前后相差不超過128字節(jié)。近是指跳 轉(zhuǎn)的目標地址與當前地址在用一個段內(nèi),即CS的值不變,只改變EIP的值。遠指跳到另一個代碼段去執(zhí)行,CS/EIP都要改變。短和近在編碼上有所不同, 在匯編指令中一般很少顯式指定,只要寫 jmp 目標地址,幾乎任何匯編器都會根據(jù)目標地址的距離采用適當?shù)木幋a。遠轉(zhuǎn)移在32位系統(tǒng)中很少見到,原因前面已經(jīng)講過,由于有足夠的線性空間,一個程序很少 需要兩個代碼段,就連用到的系統(tǒng)模塊也被映射到同一個地址空間。

jmp的操作數(shù)自然是目標地址,這個指令支持直接尋址和間接尋址。間接尋址又可分為寄存器間接尋址和內(nèi)存間接尋址。舉例如下(32位系統(tǒng)):

jmp 8E347D60 ;直接尋址段內(nèi)跳轉(zhuǎn)
jmp EBX ;寄存器間接尋址:只能段內(nèi)跳轉(zhuǎn)
jmp dword ptr [EBX] ;內(nèi)存間接尋址,段內(nèi)跳轉(zhuǎn)
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;內(nèi)存間接尋址,段間跳轉(zhuǎn)

解釋:
在32位系統(tǒng)中,完整目標地址由16位段選擇子和32位偏移量組成。因為寄存器的寬度是32位,因此寄存器間接尋址只能給出32位偏移量,所以只能是段內(nèi) 近轉(zhuǎn)移。在內(nèi)存間接尋址時,指令后面是方括號內(nèi)的有效地址,在這個地址上存放跳轉(zhuǎn)的目標地址。比如,在[00903DEC]處有如下數(shù)據(jù):7C 82 5900 A7 01 85 659F 01

內(nèi)存字節(jié)是連續(xù)存放的,如何確定取多少作為目標地址呢?dword ptr 指明該有效地址指明的是雙字,所以取
0059827C作段內(nèi)跳轉(zhuǎn)。反之,fward ptr 指明后面的有效地址是指向48位完全地址,所以取19F:658501A7 做遠跳轉(zhuǎn)。

注意:在保護模式下,如果段間轉(zhuǎn)移涉及優(yōu)先級的變化,則有一系列復雜的保護檢查,現(xiàn)在可不加理會。將來等各位功力提升以后可以自己去學習。

條件轉(zhuǎn)移指令jxx:只能作段內(nèi)轉(zhuǎn)移,且只支持直接尋址。

=========================================
調(diào)用指令CALL:

Call的尋址方式與jmp基本相同,但為了從子程序返回,該指令在跳轉(zhuǎn)以前會把緊接著它的下一條指令的地址壓進堆棧。如果是段內(nèi)調(diào)用(目標地址是 32位偏移量),則壓入的也只是一個偏移量。如果是段間調(diào)用(目標地址是48位全地址),則也壓入下一條指令的完全地址。同樣,如果段間轉(zhuǎn)移涉及優(yōu)先級的 變化,則有一系列復雜的保護檢查。

與之對應retn/retf指令則從子程序返回。它從堆棧上取得返回地址(是call指令壓進去的)并跳到該地址執(zhí)行。retn取32位偏移量作段 內(nèi)返回,retf取48位全地址作段間返回。retn/f 還可以跟一個立即數(shù)作為操作數(shù),該數(shù)實際上是從堆棧上傳給子程序的參數(shù)的個數(shù)(以字計)返回后自動把堆棧指針esp加上指定的數(shù)*2,從而丟棄堆棧中的參 數(shù)。這里具體的細節(jié)留待下一篇講述。

雖然call和ret設計為一起工作,但它們之間沒有必然的聯(lián)系。就是說,如果你直接用push指令向堆棧中壓入一個數(shù),然后執(zhí)行ret,他同樣會把你壓入的數(shù)作為返回地址,而跳到那里去執(zhí)行。這種非正常的流程轉(zhuǎn)移可以被用作反跟蹤手段。

中斷指令INT n

在保護模式下,這個指令必定會被操作系統(tǒng)截獲。在一般的PE程序中,這個指令已經(jīng)不太見到了,而在DOS時代,中斷是調(diào)用操作系統(tǒng)和BIOS的重要 途徑。現(xiàn)在的程序可以文質(zhì)彬彬地用名字來調(diào)用windows功能,如 call user32!getwindowtexta。從程序角度看,INT指令把當前的標志寄存器先壓入堆棧,然后把下一條指令的完全地址也壓入堆棧,最后根據(jù) 操作數(shù)n來檢索“中斷描述符表”,試圖轉(zhuǎn)移到相應的中斷服務程序去執(zhí)行。通常,中斷服務程序都是操作系統(tǒng)的核心代碼,必然會涉及到優(yōu)先級轉(zhuǎn)換和保護性檢 查、堆棧切換等等,細節(jié)可以看一些高級的教程。

與之相應的中斷返回指令IRET做相反的操作。它從堆棧上取得返回地址,并用來設置CS:EIP,然后從堆棧中彈出標志寄存器。注意,堆棧上的標志 寄存器值可能已經(jīng)被中斷服務程序所改變,通常是進位標志C, 用來表示功能是否正常完成。同樣的,IRET也不一定非要和INT指令對應,你可以自己在堆棧上壓入標志和地址,然后執(zhí)行IRET來實現(xiàn)流程轉(zhuǎn)移。實際 上,多任務操作系統(tǒng)常用此伎倆來實現(xiàn)任務轉(zhuǎn)換。

廣義的中斷是一個很大的話題,有興趣可以去查閱系統(tǒng)設計的書籍。

 ===========================================
裝入全指針指令LDS,LES,LFS,LGS,LSS

這些指令有兩個操作數(shù)。第一個是一個通用寄存器,第二個操作數(shù)是一個有效地址。指令從該地址取得48位全指針,將選擇符裝入相應的段寄存器,而將 32位偏移量裝入指定的通用寄存器。注意在內(nèi)存中,指針的存放形式總是32位偏移量在前面,16位選擇符在后面。裝入指針以后,就可以用DS:[ESI] 這樣的形式來訪問指針指向的數(shù)據(jù)了。

============================================
字符串操作指令

這里包括CMPS,SCAS,LODS,STOS,MOVS,INS和OUTS等。這些指令有一個共同的特點,就是沒有顯式的操作數(shù),而由硬件規(guī)定 使用DS:[ESI]指向源字符串,用ES:[EDI]指向目的字符串,用AL/AX/EAX做暫存。這是硬件規(guī)定的,所以在使用這些指令之前一定要設好 相應的指針。
這里每一個指令都有3種寬度形式,如CMPSB(字節(jié)比較)、CMPSW(字比較)、CMPSD(雙字比較)等。
CMPSB:比較源字符串和目標字符串的第一個字符。若相等則Z標志置1。若不等則Z標志置0。指令執(zhí)行完后,ESI 和EDI都自動加1,指向源/目標串的下一個字符。如果用CMPSW,則比較一個字,ESI/EDI自動加2以指向下一個字。
如果用CMPSD,則比較一個雙字,ESI/EDI自動加4以指向下一個雙字。(在這一點上這些指令都一樣,不再贅述)
SCAB/W/D 把AL/AX/EAX中的數(shù)值與目標串中的一個字符/字/雙字比較。
LODSB/W/D 把源字符串中的一個字符/字/雙字送入AL/AX/EAX
STOSB/W/D 把AL/AX/EAX中的直送入目標字符串中
MOVSB/W/D 把源字符串中的字符/字/雙字復制到目標字符串
INSB/W/D 從指定的端口讀入字符/字/雙字到目標字符串中,端口號碼由DX寄存器指定。
OUTSB/W/D 把源字符串中的字符/字/雙字送到指定的端口,端口號碼由DX寄存器指定。

串操作指令經(jīng)常和重復前綴REP和循環(huán)指令LOOP結(jié)合使用以完成對整個字符串的操作。而REP前綴和LOOP指令都有硬件規(guī)定用ECX做循環(huán)計數(shù)器。舉例:

LDS ESI,SRC_STR_PTR
LES EDI,DST_STR_PTR
MOV ECX,200
REP MOVSD

上面的代碼從SRC_STR拷貝200個雙字到DST_STR. 細節(jié)是:REP前綴先檢查ECX是否為0,若否則執(zhí)行一次MOVSD,ECX自動減1,然后執(zhí)行第二輪檢查、執(zhí)行……直到發(fā)現(xiàn)ECX=0便不再執(zhí)行MOVSD,結(jié)束重復而執(zhí)行下面的指令。

LDS ESI,SRC_STR_PTR
MOV ECX,100
LOOP1:
LODSW
…. (deal with value in AX)

LOOP LOOP1
…..

從SRC_STR處理100個字。同樣,LOOP指令先判斷ECX是否為零,來決定是否循環(huán)。每循環(huán)一輪ECX自動減1。

REP和LOOP 都可以加上條件,變成REPZ/REPNZ 和 LOOPZ/LOOPNZ. 這是除了ECX外,還用檢查零標志Z. REPZ 和LOOPZ在Z為1時繼續(xù)循環(huán),否則退出循環(huán),即使ECX不為0。REPNZ/LOOPNZ則相反。

高級語言程序的匯編解析

在高級語言中,如C和PASCAL等等,我們不再直接對硬件資源進行操作,而是面向于問題的解決,這主要體現(xiàn)在數(shù)據(jù)抽象化和程序的結(jié)構(gòu)化。例如我們 用變量名來存取數(shù)據(jù),而不再關心這個數(shù)據(jù)究竟在內(nèi)存的什么地方。這樣,對硬件資源的使用方式完全交給了編譯器去處理。不過,一些基本的規(guī)則還是存在的,而 且大多數(shù)編譯器都遵循一些規(guī)范,這使得我們在閱讀反匯編代碼的時候日子好過一點。這里主要講講匯編代碼中一些和高級語言對應的地方。

1. 普通變量。通常聲明的變量是存放在內(nèi)存中的。編譯器把變量名和一個內(nèi)存地址聯(lián)系起來(這里要注意的是,所謂的“確定的地址”是對編譯器而言在編譯階段算出 的一個臨時的地址。在連接成可執(zhí)行文件并加載到內(nèi)存中執(zhí)行的時候要進行重定位等一系列調(diào)整,才生成一個實時的內(nèi)存地址,不過這并不影響程序的邏輯,所以先 不必太在意這些細節(jié),只要知道所有的函數(shù)名字和變量名字都對應一個內(nèi)存的地址就行了),所以變量名在匯編代碼中就表現(xiàn)為一個有效地址,就是放在方括號中的 操作數(shù)。例如,在C文件中聲明:

int my_age;

這個整型的變量就存在一個特定的內(nèi)存位置。語句 my_age= 32; 在反匯編代碼中可能表現(xiàn)為:

mov word ptr [007E85DA], 20

所以在方括號中的有效地址對應的是變量名。又如:

char my_name[11] = “l(fā)ianzi2000″;

這樣的說明也確定了一個地址,對應于my_name. 假設地址是007E85DC,則內(nèi)存中[007E85DC]=’l',[007E85DD]=’i', etc. 對my_name的訪問也就是對這地址處的數(shù)據(jù)訪問。

指針變量其本身也同樣對應一個地址,因為它本身也是一個變量。如:

char *your_name;

這時也確定變量”your_name”對應一個內(nèi)存地址,假設為007E85F0. 語句your_name=my_name;很可能表現(xiàn)為:

mov [007E85F0], 007E85DC ;your_name的內(nèi)容是my_name的地址。

2. 寄存器變量

在C和C++中允許說明寄存器變量。register int i; 指明i是寄存器存放的整型變量。通常,編譯器都把寄存器變量放在esi和edi中。寄存器是在cpu內(nèi)部的結(jié)構(gòu),對它的訪問要比內(nèi)存快得多,所以把頻繁使用的變量放在寄存器中可以提高程序執(zhí)行速度。

3. 數(shù)組

不管是多少維的數(shù)組,在內(nèi)存中總是把所有的元素都連續(xù)存放,所以在內(nèi)存中總是一維的。例如,int i_array[2][3]; 在內(nèi)存確定了一個地址,從該地址開始的12個字節(jié)用來存貯該數(shù)組的元素。所以變量名i_array對應著該數(shù)組的起始地址,也即是指向數(shù)組的第一個元素。 存放的順序一般是i_array[0][0],[0][1],[0][2],[1][0],[1][1],[1][2] 即最右邊的下標變化最快。當需要訪問某個元素時,程序就會從多維索引值換算成一維索引,如訪問i_array[1][1],換算成內(nèi)存中的一維索引值就是 1*3+1=4.這種換算可能在編譯的時候就可以確定,也可能要到運行時才可以確定。無論如何,如果我們把i_array對應的地址裝入一個通用寄存器作 為基址,則對數(shù)組元素的訪問就是一個計算有效地址的問題:

; i_array[1][1]=0×16

lea ebx,xxxxxxxx ;i_array 對應的地址裝入ebx
mov edx,04 ;訪問i_array[1][1],編譯時就已經(jīng)確定
mov word ptr [ebx+edx*2], 16 ;

當然,取決于不同的編譯器和程序上下文,具體實現(xiàn)可能不同,但這種基本的形式是確定的。從這里也可以看到比例因子的作用(還記得比例因子的取值為 1,2,4或8嗎?),因為在目前的系統(tǒng)中簡單變量總是占據(jù)1,2,4或者8個字節(jié)的長度,所以比例因子的存在為在內(nèi)存中的查表操作提供了極大方便。

4. 結(jié)構(gòu)和對象

結(jié)構(gòu)和對象的成員在內(nèi)存中也都連續(xù)存放,但有時為了在字邊界或雙字邊界對齊,可能有些微調(diào)整,所以要確定對象的大小應該用sizeof操作符而不應 該把成員的大小相加來計算。當我們聲明一個結(jié)構(gòu)變量或初始化一個對象時,這個結(jié)構(gòu)變量和對象的名字也對應一個內(nèi)存地址。舉例說明:

struct tag_info_struct
{
int age;
int sex;
float height;
float weight;
} marry;

變量marry就對應一個內(nèi)存地址。在這個地址開始,有足夠多的字節(jié)(sizeof(marry))容納所有的成員。每一個成員則對應一個相對于這 個地址的偏移量。這里假設此結(jié)構(gòu)中所有的成員都連續(xù)存放,則age的相對地址為0,sex為2, height 為4,weight為8。

; marry.sex=0;

lea ebx,xxxxxxxx ;marry 對應的內(nèi)存地址
mov word ptr [ebx+2], 0
……

對象的情況基本相同。注意成員函數(shù)具體的實現(xiàn)在代碼段中,在對象中存放的是一個指向該函數(shù)的指針。

5. 函數(shù)調(diào)用

一個函數(shù)在被定義時,也確定一個內(nèi)存地址對應于函數(shù)名字。如:

long comb(int m, int n)
{
long temp;
…..

return temp;
}

這樣,函數(shù)comb就對應一個內(nèi)存地址。對它的調(diào)用表現(xiàn)為:

CALL xxxxxxxx ;comb對應的地址。這個函數(shù)需要兩個整型參數(shù),就通過堆棧來傳遞:

;lresult=comb(2,3);

push 3
push 2
call xxxxxxxx
mov dword ptr [yyyyyyyy], eax ;yyyyyyyy是長整型變量lresult的地址

這里請注意兩點。第一,在C語言中,參數(shù)的壓棧順序是和參數(shù)順序相反的,即后面的參數(shù)先壓棧,所以先執(zhí)行push 3. 第二,在我們討論的32位系統(tǒng)中,如果不指明參數(shù)類型,缺省的情況就是壓入32位雙字。因此,兩個push指令總共壓入了兩個雙字,即8個字節(jié)的數(shù)據(jù)。然 后執(zhí)行call指令。call 指令又把返回地址,即下一條指令(mov dword ptr….)的32位地址壓入,然后跳轉(zhuǎn)到xxxxxxxx去執(zhí)行。

在comb子程序入口處(xxxxxxxx),堆棧的狀態(tài)是這樣的:

03000000 (請回憶small endian 格式)
02000000
yyyyyyyy <–ESP 指向返回地址

前面講過,子程序的標準起始代碼是這樣的:

push ebp ;保存原先的ebp
mov ebp, esp;建立框架指針
sub esp, XXX;給臨時變量預留空間
…..

執(zhí)行push ebp之后,堆棧如下:

03000000
02000000
yyyyyyyy
old ebp <—- esp 指向原來的ebp

執(zhí)行mov ebp,esp之后,ebp 和esp 都指向原來的ebp. 然后sub esp, xxx 給臨時變量留空間。這里,只有一個臨時變量temp,是一個長整數(shù),需要4個字節(jié),所以xxx=4。這樣就建立了這個子程序的框架:

03000000
02000000
yyyyyyyy
old ebp <—- 當前ebp指向這里
temp

  所以子程序可以用[ebp+8]取得第一參數(shù)(m),用[ebp+C]來取得第二參數(shù)(n),以此類推。臨時變量則都在ebp下面,如這里的temp就對應于[ebp-4].

子程序執(zhí)行到最后,要返回temp的值:

mov eax,[ebp-04]
然后執(zhí)行相反的操作以撤銷框架:

mov esp,ebp ;這時esp 和ebp都指向old ebp,臨時變量已經(jīng)被撤銷
pop ebp ;撤銷框架指針,恢復原ebp.

這是esp指向返回地址。緊接的retn指令返回主程序:

retn 4

該指令從堆棧彈出返回地址裝入EIP,從而返回到主程序去執(zhí)行call后面的指令。同時調(diào)整esp(esp=esp+4*2),從而撤銷參數(shù),使堆 ?;謴偷秸{(diào)用子程序以前的狀態(tài),這就是堆棧的平衡。調(diào)用子程序前后總是應該維持堆棧的平衡。從這里也可以看到,臨時變量temp已經(jīng)隨著子程序的返回而消 失,所以試圖返回一個指向臨時變量的指針是非法的。

為了更好地支持高級語言,INTEL還提供了指令Enter 和Leave 來自動完成框架的建立和撤銷。Enter 接受兩個操作數(shù),第一個指明給臨時變量預留的字節(jié)數(shù),第二個是子程序嵌套調(diào)用層數(shù),一般都為0。enter xxx,0 相當于:

push ebp
mov ebp,esp
sub esp,xxx

leave 則相當于:

mov esp,ebp
pop ebp

=============================================================
好啦,我的學習心得講完了,謝謝各位的抬舉。教程是不敢當?shù)?,因為我也是個大菜鳥。如果這些東東能使你們的學習輕松一些,進步快一些,本菜鳥就很開心了。

 

 

 

計算機匯編語言的一個突出優(yōu)點就是利用符號(Symbol)來代替目標碼,也即大量的二進制代碼用符號來表示,使匯編語言源程序容易理解,便于記憶。

在宏匯編語言中所有變量名、標號名、記錄名、指令助記符和寄存器名等統(tǒng)稱符號.這些符號可通過匯編控制語句的偽操作命令重新命名,也可以通過指令給 它定義其它名字及新的類型屬性,因而給程序設計帶來很大的靈活性.符號是程序員在程序中用來代表某個存儲單元、數(shù)據(jù)、表達式和名字等所定義的標識符,可分 為寄存器、標號、變量、數(shù)字、名字五類.

匯編語句形式:
START:?。粒模摹。粒?BUFFER
DATA?。樱牛牵停牛危?br> BUFFER?。模隆?1H, 02H
DATA  ENDS
JMP?。樱裕粒遥云渲校樱裕粒遥?BUFFER,DATA均為符號,它們分別表示標號,變量名,段名,它們具有完全不同的特定含意.

標號
標號(LABEL)是為一組機器指令所起的名字.標號可有可無,只有當需要用符號地址來訪問該語句時,才給此語句賦予標號.標號是程序的目標標志,總是和某地址相聯(lián)系,供轉(zhuǎn)移或循環(huán)指令控制轉(zhuǎn)移使用.

2 1 標號的屬性

因標號表示的是指令地址,所以它有三個屬性,即段屬性、偏移屬性和類型屬性.段屬性即段地址,標號的段必須在CS中.偏移屬性是表示該標號到段首地 址的距離,單位是字節(jié),是16位無符號整數(shù).類型屬性是距離屬性,指標號和轉(zhuǎn)移指令的距離,該標號在本段內(nèi)引用,距離在-128~+127之間時稱短標 號,距離屬性為SHORT,當標號在本段,距離在-32768~+32767之間時稱近標號,距離屬性為NEAT,當引用標號的指令和標號不在同一段時稱 遠標號,距離屬性為FAR.

2 2 標號的定義

標號的定義有三種方法:
2 2 1 隱含說明標號距離屬性為SHORT和NEAR的標號可以使用隱含說明,即在代碼段中定義,標識符后加冒號,放在一條匯編指令的操作符前面.例:
NEXT:?。停希帧 。粒?BX
———-
LOOP?。危牛兀?br> - - - - - - - - -      
NEXT1:?。茫停小 。粒?BX
JA ?。危牛兀?
其中NEXT和NEXT1都是標號名.
2 2 2 用LABEL定義標號
對于屬性為NEAR和FAR的標號均可以用這種定義.格式是:
標號名?。蹋粒拢牛獭  。危牛粒?FAR
例如:NEXT?。蹋粒拢牛?nbsp;NEAR/FAR
- - - - - - - - -  ?。J
LOOP   NEXT
2 2 3 用EQU定義標號
對于屬性為NEAR和FAR的標號也可用EQU定義.格式是:
標號名?。牛眩铡。裕龋桑印。危牛粒?FAR
例如:
NEXT?。牛眩铡。裕龋桑印。危牛粒?br> - - - - - - - - -      ?。J
LOOP ?。危牛兀?br> 2 3 標號的使用
2 3 1 無條件轉(zhuǎn)移指令中標號作為轉(zhuǎn)移地址
格式:
JMP   標號
其中標號可以是短標號,近標號或遠標號
. 2 3 2 循環(huán)指令中,標號作為轉(zhuǎn)移地址
格式:LOOP   標號
其中標號只能是短標號
2 3 3 條件轉(zhuǎn)移中標號作為轉(zhuǎn)移地址
格式:
條件轉(zhuǎn)移指令   標號
其中標號只能用短標號
2 3 4 屬性分離符
2 .3. 4. 1 取段地址算符SEG
例如:MOV?。粒?SEG NEXT
SEG?。危牛兀?nbsp;就是取標號NEXT所在段的段地址.
2 3 4 2 取偏移量算符OFFSET
例如:MOV BX,?。希疲疲樱牛浴。危牛?br> 其中OFFSET?。危牛兀跃褪侨颂枺危牛兀缘挠行У刂?該語句等效于:LEA?。拢??。危牛兀?br> 2 3 4 3 取類型算符TYPE
例如:
MOV?。粒??。裕伲校拧。危牛兀?br> 若NEXT為近標號,則TYPE?。危牛兀灾禐椋疲疲疲疲?-1),若NEXT為遠標號TYPE?。危牛兀灾禐椋疲疲疲牛?-2).其中-1和-2無真正的物理意義,僅以數(shù)值表示標號類型而已.

變量

變量(Variable)代表存放在某些存儲單元的數(shù)據(jù),這些數(shù)據(jù)在程序運行期間可以隨時被修改.變量是通過變量名在程序中引用,變量名實際上是存 儲區(qū)中一個數(shù)據(jù)區(qū)的名字,以變量名數(shù)據(jù)的方式供程序員使用,作為指令或偽.指令的操作數(shù),大大方便了程序設計者.由于變量是在邏輯段中定義.這就決定了變 量和標號一樣具有段屬性、偏移屬性和類型屬性,前兩個和標號的屬性相同,而類型屬性是指出數(shù)據(jù)區(qū)的數(shù)據(jù)項的存取單位是字節(jié)(BYTE),字(WORD)或 數(shù)字(DWORD)等.可見變量和標號的主要區(qū)別在于變量指的是數(shù)據(jù),而標號則對應的是指令。

.3 1 變量的定義
變量通常也有三種定義法

.3 1 1 用偽指令DB,DW,DD等來定義

格式:[變量名] 定義數(shù)據(jù)偽指令〈表達式〉

其中變量名可有可無,若沒有名字則該變量為無名變量.表達式可以是常數(shù)、保留符號”?”、ASCII碼字符串(只能用DB定義)、地址表達式(不能用DB定義)、預置數(shù)據(jù)表格和用DUP定義的重復值.變量名可在任一邏輯段中定義,其后邊不緊跟冒號而是加一空格。

.例如:A?。模隆?00;A為一個字節(jié),值為100.
B DB 100,2 3;B值為100,B+1的值為6.
C?。模隆 粒拢谩?C的值為41H,C+1的值為42H,C+2的值為43H.D?。模隆?;
D是一個字節(jié),預留一個字節(jié),可以置入任何內(nèi)容.
E?。模隆?3?。模眨?0);定義23個0,每一個0占一個字節(jié).
F?。模隆??。模眨?1,2 DUP(0));定義9個數(shù),順序為:1,0,0,1,0,0,1,0,0.
G?。模住 粒隆?’CD’;G的值為4142H,G+2的值為4344H.
H?。模住? 3;H為一個字,存放順序為06,00H
I?。模住? 預留一個字,占兩個字節(jié)單元,

3 1 2 用偽指令LABEL定義變量

格式:
變量名?。蹋粒拢牛獭。拢伲裕?WORD/DWORD
例如:
BUF?。蹋粒拢牛獭。拢伲裕?br> DB 21
它等價于?。拢眨啤。模隆?1
3 1 3 用偽指令EQU定義變量
格式:變量名?。牛眩铡。裕龋桑印。拢伲裕?WORD/DWORDTHIS是定義任意類型算符,它同LABEL一樣用于建立變量或標號類型屬性,而其段屬性為語句所在段的段地址,偏移屬性為所在位置的下一個能分配到的可用偏移地址.例如:
STACK SEGMENT
DW 100?。模眨??
TOP?。牛眩铡。裕龋桑印。祝希遥?或TOP LABEL?。祝希遥?
STACK?。牛危?br> 變量TOP被定義為字類型,它的偏移量應為STACK段定義100個字后的下一個字的偏移量,它恰就是堆棧指針SP的初值,因此經(jīng)常用這種方法為SP賦初值.

3 1 4 雙重定義變量名利用隱含方式和顯示方式的雙重方式,可以對同一位置定義為雙重變量.

格式
〈變量名〉?。牛眩铡。裕龋桑印搭愋汀?br> 〈變量名〉?。模?DW/DD…
例如:
AB EQU?。裕龋桑印。拢伲裕?br> (或AB LABEL?。拢伲裕?
AW?。模住?0?。模眨?0)AW定義為字變量,在AW前使用了THIS?。拢伲裕?定義了一個字節(jié)類型變量,訪問同一個位置,用AB按字節(jié)訪問,用AW則按字訪問.

3 2 變量的訪問

3 2 1 變量名作為存儲單元的直接地址

變量名用直接尋址時,變量的類型必須與指令的要求相符合.
例如:AB已定義字節(jié)變量,AW定義為字變量,用變量名作直接尋址形式如下:
MOV?。粒?AB
MOV?。粒?AW
3 2 2 用合成運算符PTR臨時改變變量類型
接上例用
MOV CX,WORD?。校裕摇。粒?br> MOV?。茫?BYTE PTR?。粒?br> 則可臨時把AB變?yōu)樽诸愋?AW變?yōu)樽止?jié)類型,但段和偏移屬性不變.
3 2 3 變量名作為相對尋址中的偏移量
例如:
MOV?。粒?AB〔SI〕
MOV?。粒?AW[BX][SI]
在這里AB,AW分別表示它們的偏移量而不是它們所表示的數(shù)據(jù),常用于數(shù)組或表格操作中,AB[SI]就表示AB數(shù)組中第SI個元素.
3 2 4 屬性分離符

其中SEG和OFFSET用法和標號相同,分別表示取變量所在段的段地址和變量的偏移地址.而TYPE運算符,將回送該變量類型所表示的字節(jié)數(shù).
例如:設AB為字節(jié)變量,AW為字變量,則:
MOV?。粒?TYPE AB即MOV?。粒?1
MOV?。粒?TYPE AW即MOV?。粒?2

3 2 5 取變量數(shù)據(jù)項個數(shù)運算符LENGTH對于變量定義時使用DUP的情況,匯編程序?qū)⒒厮停模眨星暗闹貜痛螖?shù),即分配給該變量的單元數(shù),若表達式有多個DUP,則取第一個DUP項,其它情況則回送1.
例如:ARRAY?。模住?0 DUP(0)則
MOV?。茫?LENGTH ARRAY即MOV?。茫?50
ARRAY1,DW1,2,3 則
MOV?。茫?LENGTH?。粒遥遥粒?
即MOV CX,1
可見LENGTH表示數(shù)組元素個數(shù),而不管其類型.

3 2 6 取變量數(shù)據(jù)項長度算符SIZE

SIZE算符,匯編程序?qū)⒒厮头峙浣o該變量的字節(jié)數(shù),即
SIZE=LENGTH TYPE

例如:
ARRAY?。模住?0 DUP(0) 則
SIZE?。粒遥遥粒?50 2=100

要注意:對字符串變量求其長度,使用SIZE不能達到目的.
例如:
ST?。模隆 粒拢茫模牛疲恰t
SIZE?。樱灾禐?而不是7,欲求字符串長可用COUNT EQU $-ST,則COUNT值為7,其中$為定義ST一串字符后下一個可用的偏移地址.

3 2 7 變量名僅對應數(shù)據(jù)區(qū)第一個數(shù)據(jù)項
例如:
WORD?。模住?0?。模眨??)
MOV AX,WORD;第一個元素送AX,
MOV?。粒?WORD+38;第20個元素送AX.
其它符號

除標號和變量外,符號還可表示常量、段名、過程名、寄存器名和指令助記符等.

(1)符號常數(shù)常數(shù)也常以符號形式出現(xiàn),使之更具有通用性且便于修改.例:
COUNT?。牛眩铡?00 則COUNT就表示常數(shù)100.
(2)符號表示指令助記符.例:
MOV EQU?。停希謩tMOVE就表示指令MOV
(3)符號表示寄存器,例:COUNT EQU?。茫貏tCOUNT就代表寄存器CX.
(4)符號作為段名,例:
DATA?。樱牛牵停牛危?br> - – - — – - – - – - – - – -
DATA ENDS
DATA 是段名,引用DATA表示段地址.
(5)符號作為過程名,例:SUBR?。校遥希谩。危牛粒?FAR
- – - — – - – - – - – - – -
SUB?。牛危模?br> SUB為過程名,它同樣具有段、偏移量和距離類型三個屬性
.(6)符號作為宏指令名
宏定義格式宏指令名?。停粒茫遥稀形式參數(shù)]
- – - — – - – - – - – - – -
      ENDM
宏調(diào)用格式:
宏指令名 [實參數(shù)]
每當引用宏指令名則匯編程序?qū)暾{(diào)用作宏展開,就是用宏定義體取代源程序中的宏指令并用實參數(shù)取代宏定義中的形式參數(shù)

 

 

匯編語言是各種計算機語言中與硬件關系最為密切、最直接的語言,是時空效率最高的語言,它能夠利用計算機所有硬件特性并能直接控制硬件,所以在計算 機應用系統(tǒng)設計和過程控制中是必不可少的.目前教學中采用8086/8088匯編語言系統(tǒng)組織教學仍是最佳選擇.其中子程序技術是一種解決重復性問題的重 要設計方法,采用子程序結(jié)構(gòu)可以簡化源程序書寫、提高程序存儲效率、減少出錯率、增加程序的易讀性和可維護性,并且有利用子程序資源的組織和使用.設計子 程序時,除了必需要考慮的程序調(diào)用、返回和完成特定功能的指令序列外,還必須注意解決子程序設計中帶有的共性的一些問題,即:現(xiàn)場保護、參數(shù)傳遞、子程序 的嵌套與遞歸調(diào)用、編寫子程序說明文檔等.
1 現(xiàn)場保護
現(xiàn)場保護的目的是調(diào)用子程序之后,能夠返回主程序繼續(xù)執(zhí)行.因此要對子程序中用到的寄存器,堆棧進行必要的保護.
1 1 寄存器保護因為匯編語言程序中的主要操作對象是CPU中的各寄存器,對那些主程序和子程序中都會用到的一些寄存器要在子程序使用之前進行保護.寄 存器保護最好是在子程序中進行,并且在子程序中進行恢復,這樣子程序顯得更完整.其方法是使用堆棧,由于指令系統(tǒng)中制定了規(guī)范的進棧指令PUSH和出棧指 令POP,并會自動修改堆棧指針,只要在程序設計中注意8086/8088的堆棧是否按”后進先出”的原則組織的.
1 2 堆棧保護子程序是利用調(diào)用(CALL)指令和返回(RET)指令來實現(xiàn)正確的調(diào)用和返回的.因為CALL命令執(zhí)行時壓入堆棧的斷點地址就是供子程 序返回主程序時的地址,編程時一定要注意子程序的類型屬性,即是段內(nèi)調(diào)用還是段間調(diào)用.段內(nèi)調(diào)用和返回為NEAR屬性,段間調(diào) 王艷玲,等談談匯編語言中 子程序的設計方法37用和返回為FAR屬性.8086/8088的匯編程序用子程序定義PROC的類型屬性來確定CALL和RET指令的屬性.如果所定義 的子程序是FAR屬性,那么對它的調(diào)用和返回一定都是FAR屬性;如果所定義的子程序是NEAR屬性,那么對它的調(diào)用和返回也一定是NEAR屬性.這樣用 戶只是在定義子程序時考慮它的屬性,而CALL和RET指令的屬性就可以由匯編程序來確定了.另外,進入子程序后再使用堆棧時也必須保證壓入和彈出字節(jié)數(shù) 一致,如果在這里堆棧存取出錯,必然會導致返回地址的錯誤.
2 參數(shù)傳遞
主程序在調(diào)用子程序時,經(jīng)常要向子程序傳遞一些參數(shù)或控制信息,子程序執(zhí)行后,也常需要把運行的結(jié)果返回調(diào)用程序.這種信息傳遞稱為參數(shù)傳遞,其常用的方法有寄存器傳遞、內(nèi)存固定單元傳遞、堆棧傳遞.
2 1 寄存器傳遞由主程序?qū)⒁獋鬟f的參數(shù)裝入事先約定的寄存器中,轉(zhuǎn)入子程序后再取出進行處理,這種方法受CPU內(nèi)部寄存器數(shù)量限制,因此只適于傳遞少量參數(shù)的場合,如一些常見的軟件延時子程序,均是利用某寄存器傳遞循環(huán)計數(shù)器初值.
2 2 通過內(nèi)存固定單元的傳遞此方法適于大量傳遞參數(shù)時使用,它是在內(nèi)存中開辟特定的一片區(qū)域用于傳遞參數(shù).主程序和子程序都按事先約定在指定的存儲單元中進行數(shù)據(jù)交換,這種方法要占用一定數(shù)量的存儲單元.不足之處是信息易被修改,不利于模塊化設計.
2 3 通過堆棧實現(xiàn)參數(shù)傳遞這種方法是先在主程序中把參數(shù)和參數(shù)地址壓入堆棧,在子程序中取出使用,由于堆棧操作不占用寄存器,并且堆棧單元使用后可自 動釋放,反復使用,便于實現(xiàn)數(shù)據(jù)隔離和模塊化設計.使用這種方法時,當子程序返回后,這些參數(shù)就不在有用了,應當丟棄.這時可以利用帶立即數(shù)的返回指令修 改指針,使其指向參數(shù)入棧以前的值.
3 子程序嵌套與遞歸調(diào)用
匯編語言中子程序的嵌套只要堆??臻g允許,一般不受嵌套層次限制.嵌套子程序設計中,應注意寄存器的保護和恢復,避免各層子程序之間寄存器沖突.遞歸子程 序的設計必須保證每次調(diào)用都不破壞以前調(diào)用時所用的參數(shù)和中間結(jié)果.為保證每次調(diào)用的正確,一般把每次調(diào)用的參數(shù)、有關寄存器的內(nèi)容以及中間結(jié)果進棧保 存.
4 子程序說明文檔
一般來說子程序是要反復使用或提供用戶使用,所以編寫子程序時應盡量采用較好的算法,使子程序運行速度比較快,又節(jié)省內(nèi)存.同時還應最大限度地滿足今后程 序維護與使用的需要.在設計子程序的同時就應當建立相應的說明文檔,清楚地描述子程序的功能和調(diào)用方法.通常子程序說明文檔應包括:子程序名稱、子程序功 能、入口參數(shù)、出口參數(shù)、工作寄存器、工作單元及最后修改日期等

 

 

ARMC和匯編混合編程及示例

在嵌入式系統(tǒng)開發(fā)中,目前使用的主要編程語言是C和匯編,C++已經(jīng)有相應的編譯器,但是現(xiàn)在使用還是比較少的。在稍大規(guī)模的嵌入式軟件中,例如含 有OS,大部分的代碼都是用C 編寫的,主要是因為C 語言的結(jié)構(gòu)比較好,便于人的理解,而且有大量的支持庫。盡管如此,很多地方還是要用到匯編語言,例如開機時硬件系統(tǒng)的初始化,包括CPU 狀態(tài)的設定,中斷的使能,主頻的設定,以及RAM的控制參數(shù)及初始化,一些中斷處理方面也可能涉及匯編。另外一個使用匯編的地方就是一些對性能非常敏感的 代碼塊,這是不能依靠C編譯器的生成代碼,而要手工編寫匯編,達到優(yōu)化的目的。而且,匯編語言是和CPU 的指令集緊密相連的,作為涉及底層的嵌入式系統(tǒng)開發(fā),熟練對應

匯編語言的使用也是必須的。

 

單純的C 或者匯編編程請參考相關的書籍或者手冊,這里主要討論C 和匯編的混合編程,包括相互之間的函數(shù)調(diào)用。下面分四種情況來進行討論,暫不涉及C++。

 

1. 在C 語言中內(nèi)嵌匯編

在C 中內(nèi)嵌的匯編指令包含大部分的ARM 和Thumb 指令,不過其使用與匯編文件中的指令有些不同,存在一些限制,主要有下面幾個方面:

 

不能直接向PC寄存器賦值,程序跳轉(zhuǎn)要使用B或者BL指令

在使用物理寄存器時,不要使用過于復雜的C 表達式,避免物理寄存器沖突

R12和R13 可能被編譯器用來存放中間編譯結(jié)果,計算表達式值時可能將R0 到R3、R12及R14用于子程序調(diào)用,因此要避免直接使用這些物理寄存器

一般不要直接指定物理寄存器,而讓編譯器進行分配

內(nèi)嵌匯編使用的標記是 _asm或者asm關鍵字,用法如下:

 

_asm

{

instruction [; instruction]

[instruction]

}

asm(“instruction [; instruction]“);

 

下面通過一個例子來說明如何在C 中內(nèi)嵌匯編語言,

 

#include <stdio.h>

void my_strcpy(const char *src, char *dest)

{

char ch;

_asm

{

loop:

ldrb ch, [src], #1

strb ch, [dest], #1

cmp ch, #0

bne loop

}

}

int main()

{

char *a = “forget it and move on!”;

char b[64];

my_strcpy(a, b);

printf(“original: %s”, a);

printf(“copyed: %s”, b);

return 0;

}

 

在這里C 和匯編之間的值傳遞是用C 的指針來實現(xiàn)的,因為指針對應的是地址,所以匯編中也可以訪問。

 

2. 在匯編中使用C定義的全局變量

內(nèi)嵌匯編不用單獨編輯匯編語言文件,比較簡潔,但是有諸多限制,當匯編的代碼較多時一般放在單獨的匯編文件中。這時就需要在匯編和C 之間進行一些數(shù)據(jù)的傳遞,最簡便的辦法就是使用全局變量。

 

/* cfile.c

* 定義全局變量,并作為主調(diào)程序

*/

#include <stdio.h>

int gVar_1 = 12;

extern asmDouble(void);

int main()

{

printf(“original value of gVar_1 is: %d”, gVar_1);

asmDouble();

printf(” modified value of gVar_1 is: %d”, gVar_1);

return 0;

}

 

對應的匯編語言文件:

 

;called by main(in C),to double an integer, a global var defined in C is used.

AREA asmfile, CODE, READONLY

EXPORT asmDouble

 

IMPORT gVar_1

asmDouble

ldr r0, =gVar_1

ldr r1, [r0]

mov r2, #2

mul r3, r1, r2

str r3, [r0]

mov pc, lr

END

 

3. 在C 中調(diào)用匯編的函數(shù)

在C 中調(diào)用匯編文件中的函數(shù),要做的主要工作有兩個,一是在C 中聲明函數(shù)原型,并加extern關鍵字;二是在匯編中用EXPORT 導出函數(shù)名,并用該函數(shù)名作為匯編代碼段的標識,最后用mov pc, lr返回。然后,就可以在C 中使用該函數(shù)了。從C的角度,并不知道該函數(shù)的實現(xiàn)是用C還是匯編。更深的原因是因為C 的函數(shù)名起到表明函數(shù)代碼起始地址的左右,這個和匯編的label是一致的。

 

/* cfile.c

* in C,call an asm function, asm_strcpy

* Sep 9, 2004

*/

#include <stdio.h>

extern void asm_strcpy(const char *src, char *dest);

int main()

{

const char *s = “seasons in the sun”;

char d[32];

asm_strcpy(s, d);

printf(“source: %s”, s);

printf(” destination: %s”,d);

return 0;

}

 

;asm function implementation

AREA asmfile, CODE, READONLY

EXPORT asm_strcpy

asm_strcpy

loop

ldrb r4, [r0], #1 address increment after read

cmp r4, #0

beq over

strb r4, [r1], #1

b loop

over

mov pc, lr

END

 

在這里,C 和匯編之間的參數(shù)傳遞是通過ATPCS(ARM Thumb Procedure Call Standard)的規(guī)定來進行的。簡單的說就是如果函數(shù)有不多于四個參數(shù),對應的用R0-R3來進行傳遞,多于4個時借助棧,函數(shù)的返回值通過R0來返 回。

 

4. 在匯編中調(diào)用C的函數(shù)

在匯編中調(diào)用C的函數(shù),需要在匯編中IMPORT 對應的C函數(shù)名,然后將C 的代碼放在一個獨立的C 文件中進行編譯,剩下的工作由連接器來處理。

 

;the details of parameters transfer comes from ATPCS

;if there are more than 4 args, stack will be used

EXPORT asmfile

AREA asmfile, CODE, READONLY

IMPORT cFun

ENTRY

mov r0, #11

mov r1, #22

mov r2, #33

BL cFun

END

 

/*C file, called by asmfile */

int cFun(int a, int b, int c)

{

return a + b + c;

}

 

在匯編中調(diào)用C 的函數(shù),參數(shù)的傳遞也是通過ATPCS來實現(xiàn)的。需要指出的是當函數(shù)的參數(shù)個數(shù)大于4時,要借助stack,具體見ATPCS規(guī)范。

 

 

ARM匯編指令的一些總結(jié)

ARM匯編指令很多,但是真正常用的不是很多,而且需要認真琢磨的又更少了。

比較有用的是MOV B BL LDR STR

還是通過具體匯編代碼來學習吧。

      @ disable watch dog timer     

   mov   r1, #0×53000000   //立即數(shù)尋址方式

   mov   r2, #0×0

   str   r2, [r1]       

MOV沒有什么好說的,只要掌握幾個尋址方式就可以了,而且ARM的尋址方式比386的簡單很多。立即數(shù)尋址方式,立即數(shù)要求以“#”作前綴,對于十六進制的數(shù),還要求在#后面加上0x或者&。0x大家很好理解。有一次我碰到了&ff這個數(shù),現(xiàn)在才明白跟0xff是一樣的。

STR是比較重要的指令了,跟它對應的是LDR。ARM指令集是加載/存儲型的,也就是說它只處理在寄存器中的數(shù)據(jù)。那么對于系統(tǒng)存儲器的訪問就經(jīng)常用到STR和LDR了。STR是把寄存器上的數(shù)據(jù)傳輸?shù)街付ǖ刂返拇鎯ζ魃稀K母袷轿覀€人認為很特殊:

STR(條件) 源寄存器,<存儲器地址>

比如 STR R0, [R1] ,意思是R0-> [R1],它把源寄存器寫在前面,跟MOV、LDR都相反。

LDR應該是非常常見了。LDR就是把數(shù)據(jù)從存儲器傳輸?shù)郊拇嫫魃?。而且有個偽指令也是LDR,因此我有個百思不得其解的問題。看這段代碼:

mov r1, #GPIO_CTL_BASE

   add   r1, r1, #oGPIO_F

   ldr   r2,=0x55aa   // 0x55aa是個立即數(shù)啊,前面加個=干什么?

   str   r2, [r1, #oGPIO_CON]

   mov   r2, #0xff

   str   r2, [r1, #oGPIO_UP]

   mov   r2, #0×00

   str   r2, [r1, #oGPIO_DAT]

對于當中的ldr 那句,我就不明白了,如果你把=去掉,是不能通過編譯的。我查了一些資料,個人感覺知道了原因:這個=應該表示LDR不是ARM指令,而是偽指令。作為偽指令的時候,LDR的格式如下:

LDR 寄存器, =數(shù)字常量/Label

它的作用是把一個32位的地址或者常量調(diào)入寄存器。嗬嗬,那大家可能會問,

“MOV r2,#0x55aa”也可以啊。應該是這樣的。不過,LDR是偽指令啊,也就是說編譯時編譯器會處理它的。怎么處理的呢?——規(guī)則如下:如果該數(shù)字常量 在MOV指令范圍內(nèi),匯編器會把這個指令作為MOV。如果不在MOV范圍中,匯編器把該常量放在程序后面,用LDR來讀取,PC和該常量的偏移量不能超過 4KB。

這么一說,雖然似懂非懂,但是能夠解釋這個語句了。

 

 

然后說一下跳轉(zhuǎn)指令。ARM有兩種跳轉(zhuǎn)方式。

(1) mov pc <跳轉(zhuǎn)地址〉

這種向程序計數(shù)器PC直接寫跳轉(zhuǎn)地址,能在4GB連續(xù)空間內(nèi)任意跳轉(zhuǎn)。

(2)通過 B BL BLX BX 可以完成在當前指令向前或者向后32MB的地址空間的跳轉(zhuǎn)(為什么是32MB呢?寄存器是32位的,此時的值是24位有符號數(shù),所以32MB)。

B是最簡單的跳轉(zhuǎn)指令。要注意的是,跳轉(zhuǎn)指令的實際值不是絕對地址,而是相對地址——是相對當前PC值的一個偏移量,它的值由匯編器計算得出。

BL非常常用。它在跳轉(zhuǎn)之前會在寄存器LR(R14)中保存PC的當前內(nèi)容。BL的經(jīng)典用法如下:

   bl NEXT   ; 跳轉(zhuǎn)到NEXT

       ……

    NEXT

       ……

       mov pc, lr    ; 從子程序返回。

 

最后提一下Thumb指令。ARM體系結(jié)構(gòu)還支持16位的Thumb指令集。Thumb指令集是ARM指令集的子集,它保留了32位代碼優(yōu)勢的同時 還大大節(jié)省了存儲空間。由于Thumb指令集的長度只有16位,所以它的指令比較多。它和ARM各有自己的應用場合。對于系統(tǒng)性能有較高要求,應使用32 位存儲系統(tǒng)和ARM指令集;對于系統(tǒng)成本和功耗有較高要求,應使用16位存儲系統(tǒng)和ARM指令集。

ARM異常(Exceptions)的理解

分類:技術筆記

畢設筆記

1.對ARM異常(Exceptions)的理解

所有的系統(tǒng)引導程序前面中會有一段類似的代碼,如下:

.globl _start                    ;系統(tǒng)復位位置

_start: b       reset            ;各個異常向量對應的跳轉(zhuǎn)代碼

ldr     pc, _undefined_instruction ;未定義的指令異常

ldr     pc, _software_interrupt     ;軟件中斷異常

ldr     pc, _prefetch_abort          ;內(nèi)存操作異常

ldr     pc, _data_abort               ;數(shù)據(jù)異常

ldr     pc, _not_used                  ;未使用

ldr     pc, _irq                       ;慢速中斷異常

ldr     pc, _fiq                       ;快速中斷異常

從中我們可以看出,ARM支持7種異常。問題時發(fā)生了異常后ARM是如何響應的呢?第一個復位異常很好理解,它放在0×0的位置,一上電就執(zhí)行它, 而且我們的程序總是從復位異常處理程序開始執(zhí)行的,因此復位異常處理程序不需要返回。那么怎么會執(zhí)行到后面幾個異常處理函數(shù)呢?

看看書后,明白了ARM對異常的響應過程,于是就能夠回答以前的這個疑問。

當一個異常出現(xiàn)以后,ARM會自動執(zhí)行以下幾個步驟:

(1)把下一條指令的地址放到連接寄存器LR(通常是R14),這樣就能夠在處理異常返回時從正確的位置繼續(xù)執(zhí)行。

(2)將相應的CPSR(當前程序狀態(tài)寄存器)復制到SPSR(備份的程序狀態(tài)寄存器)中。從異常退出的時候,就可以由SPSR來恢復CPSR。

(3) 根據(jù)異常類型,強制設置CPSR的運行模式位。

(4)強制PC(程序計數(shù)器)從相關異常向量地址取出下一條指令執(zhí)行,從而跳轉(zhuǎn)到相應的異常處理程序中。

至于這些異常類型各代表什么,我也沒有深究。因為平常就關心reset了,也沒有必要弄清楚。

ARM規(guī)定了異常向量的地址:

b       reset            ; 復位 0×0

ldr pc, _undefined_instruction ;未定義的指令異常 0×4

ldr     pc, _software_interrupt     ;軟件中斷異常    0×8

ldr     pc, _prefetch_abort          ;預取指令    0xc

ldr     pc, _data_abort               ;數(shù)據(jù)        0×10

ldr     pc, _not_used                  ;未使用      0×14

ldr     pc, _irq                       ;慢速中斷異常   0×18

ldr   pc, _fiq                       ;快速中斷異常    0x1c

這樣理解這段代碼就非常簡單了。碰到異常時,PC會被強制設置為對應的異常向量,從而跳轉(zhuǎn)到相應的處理程序,然后再返回到主程序繼續(xù)執(zhí)行。

這些引導程序的中斷向量,是僅供引導程序自己使用的,一旦引導程序引導Linux內(nèi)核完畢后,會使用自己的中斷向量。

嗬嗬,這又有問題了。比如,ARM發(fā)生中斷(irq)的時候,總是會跑到0×18上執(zhí)行啊。那Linux內(nèi)核又怎么能使用自己的中斷向量呢?原因在于Linux內(nèi)核采用頁式存儲管理。開通MMU的頁面映射以后,CPU所發(fā)出的地址就是虛擬地址而不是物理地址。就Linux內(nèi)核而言,虛擬地址0×18經(jīng)過映射以后的物理地址就是0xc000 0018。所以Linux把中斷向量放到0xc000 0018就可以了。

另外,說一下MMU。說句實話,還不是很明白這個MMU機理。參加Intel培訓的時候,李眈說了MMU的兩個主要作用:

(1)安全性:規(guī)定訪問權(quán)限

(2) 提供地址空間:把不連續(xù)的空間轉(zhuǎn)換成連續(xù)的。

第2點是不是實現(xiàn)頁式存儲的意思?

2005年6月9日晚

補充一下:  05/06/14

.globl _start ;系統(tǒng)復位位置
_start: b reset ;各個異常向量對應的跳轉(zhuǎn)代碼
ldr pc, _undefined_instruction ;未定義的指令異常

……

_undefined_instruction :
.word undefined_instruction

也許有人會有疑問,同樣是跳轉(zhuǎn)指令,為什么第一句用的是 b reset;
而后面的幾個都是用ldr?

為了理解這個問題,我們以未定義的指令異常為例。

當發(fā)生了這個異常后,CPU總是跳轉(zhuǎn)到0×4,這個地址是虛擬地址,它映射到哪個物理地址
取決于具體的映射。
ldr pc, _undefined_instruction
相對尋址,跳轉(zhuǎn)到標號_undefined_instruction,然而真正的跳轉(zhuǎn)地址其實是_undefined_instruction的內(nèi)容——undefined_instruction。那句.word的相當于:
_undefined_instruction dw undefined_instruction (詳見畢設筆記3)。
這個地址undefined_instruction到底有多遠就難說了,也許和標號_undefined_instruction在同一個頁面,也許在 很遠的地方。不過除了reset,其他的異常是MMU開始工作之后才可能發(fā)生的,因此undefined_instruction 的地址也經(jīng)過了MMU的映射。
在剛加電的時候,CPU從0×0開始執(zhí)行,MMU還沒有開始工作,此時的虛擬地址和物理地址相同;另一方面,重啟在MMU開始工作后也有可能發(fā)生,如果reset也用ldr就有問題了,因為這時候虛擬地址和物理地址完全不同。

因此,之所以reset用b,就是因為reset在MMU建立前后都有可能發(fā)生,而其他的異常只有在MMU建立之后才會發(fā)生。用b reset,reset子程序與reset向量在同一頁面,這樣就不會有問題(b是相對跳轉(zhuǎn)的)。如果二者相距太遠,那么編譯器會報錯的。

 

 

在ARM模式下,任何一條數(shù)據(jù)處理指令可以選擇是否根據(jù)操作的結(jié)果來更新CPSR寄存器中的ALU狀態(tài)標志位。在數(shù)據(jù)處理指令中使用S后綴來實現(xiàn)該功能。

不要在CMP,CMN,TST或者TEQ指令中使用S后綴。這些比較指令總是會更新標志位。

在Thumb模式下,所有數(shù)據(jù)處理指令都更新CPSR中的標志位。有一個例外就是:當一個或更多個高寄存器被用在MOV和ADD指令時,此時MOV和ADD不能更新狀態(tài)標志.

幾乎所有的ARM指令都可以根據(jù)CPSR中的ALU狀態(tài)標志位來條件執(zhí)行。參見表2-1條件執(zhí)行后綴表。

在ARM模式下,你可以:

· 根據(jù)數(shù)據(jù)操作的結(jié)果更新CPSR中的ALU狀態(tài)標志;

· 執(zhí)行其他幾種操作,但不更新狀態(tài)標志;

· 根據(jù)當前狀態(tài)標志,決定是否執(zhí)行接下來的指令。

在Thumb模式,大多數(shù)操作總是更新狀態(tài)標志位,并且只能使用條件轉(zhuǎn)移指令(B)來實現(xiàn)條件執(zhí)行。該指令(B)的后綴和在ARM模式下是一樣的。其他指令不能使用條件執(zhí)行。

2.5.1 ALU狀態(tài)標志

CPSR寄存器包含下面的ALU狀態(tài)標志:

2.5.2 執(zhí)行條件

N,Z,C,V相關的條件碼后綴如下表所列:

舉例說明:

 

示例1:

ADD            r0, r1, r2              ; r0 = r1 + r2, 不更新標志位

ADDS         r0, r1, r2      ; r0 = r1 + r2, 后綴S表示更新標志位

ADDCSS     r0, r1, r2      ; If C 標志為1,則執(zhí)行r0 = r1 + r2, 且更新標志,

CMP             r0, r1            ; CMP指令肯定會更新標志.

 

示例2:(請自行分析)

gcd        CMP r0, r1

BEQ end

BLT less

SUB r0, r0, r1

B gcd

less

SUB r1, r1, r0

B gcd

end

 

 

在 ARM 匯編語言程序里,有一些特殊指令助記符,這些助記符與指令系統(tǒng)的助記符不同,沒有相對應的操作碼,通常稱這些特殊指令助記符為偽指令,他們所完成的操作稱 為偽操作。偽指令在源程序中的作用是為完成匯編程序作各種準備工作的,這些偽指令僅在匯編過程中起作用,一旦匯編結(jié)束,偽指令的使命就完成。

 

在 ARM 的匯編程序中,有如下幾種偽指令:符號定義偽指令、數(shù)據(jù)定義偽指令、匯編控制偽指令、宏指令以及其他偽指令。

 

符號定義( Symbol Definition )偽指令

符號定義偽指令用于定義 ARM 匯編程序中的變量、對變量賦值以及定義寄存器的別名等操作。

常見的符號定義偽指令有如下幾種:

— 用于定義全局變量的 GBLA 、 GBLL 和 GBLS 。

— 用于定義局部變量的 LCLA 、 LCLL 和 LCLS 。

— 用于對變量賦值的 SETA 、 SETL 、 SETS 。

— 為通用寄存器列表定義名稱的 RLIST 。

1、 GBLA、GBLL 和GBLS

語法格式:

GBLA ( GBLL 或 GBLS ) 全局變量名

GBLA 、 GBLL 和 GBLS 偽指令用于定義一個 ARM 程序中的全局變量,并將其初始化。其中:

GBLA 偽指令用于定義一個全局的數(shù)字變量,并初始化為 0 ;

GBLL 偽指令用于定義一個全局的邏輯變量,并初始化為 F (假);

GBLS 偽指令用于定義一個全局的字符串變量,并初始化為空;

由于以上三條偽指令用于定義全局變量,因此在整個程序范圍內(nèi)變量名必須唯一。

使用示例:

GBLA Test1 ;定義一個全局的數(shù)字變量,變量名為 Test1

Test1 SETA 0xaa ;將該變量賦值為 0xaa

GBLL Test2 ;定義一個全局的邏輯變量,變量名為 Test2

Test2 SETL {TRUE} ;將該變量賦值為真

GBLS Test3 ;定義一個全局的字符串變量,變量名為 Test3

Test3 SETS “ Testing ” ;將該變量賦值為 “ Testing ”

 

2、 LCLA、LCLL 和LCLS

語法格式:

LCLA ( LCLL 或 LCLS ) 局部變量名

LCLA 、 LCLL 和 LCLS 偽指令用于定義一個 ARM 程序中的局部變量,并將其初始化。其中:

LCLA 偽指令用于定義一個局部的數(shù)字變量,并初始化為 0 ;

LCLL 偽指令用于定義一個局部的邏輯變量,并初始化為 F (假);

LCLS 偽指令用于定義一個局部的字符串變量,并初始化為空;

以上三條偽指令用于聲明局部變量,在其作用范圍內(nèi)變量名必須唯一。

使用示例:

LCLA Test4 ;聲明一個局部的數(shù)字變量,變量名為 Test4

Test3 SETA 0xaa ;將該變量賦值為 0xaa

LCLL Test5 ;聲明一個局部的邏輯變量,變量名為 Test5

Test4 SETL {TRUE} ;將該變量賦值為真

LCLS Test6 ;定義一個局部的字符串變量,變量名為 Test6

Test6 SETS “ Testing ” ;將該變量賦值為 “ Testing ”

 

3、 SETA、SETL 和SETS

語法格式:

變量名 SETA ( SETL 或 SETS ) 表達式

偽指令 SETA 、 SETL 、 SETS 用于給一個已經(jīng)定義的全局變量或局部變量賦值。

SETA 偽指令用于給一個數(shù)學變量賦值;

SETL 偽指令用于給一個邏輯變量賦值;

SETS 偽指令用于給一個字符串變量賦值;

其中,變量名為已經(jīng)定義過的全局變量或局部變量,表達式為將要賦給變量的值。

使用示例:

LCLA Test3 ;聲明一個局部的數(shù)字變量,變量名為 Test3

Test3 SETA 0xaa ;將該變量賦值為 0xaa

LCLL Test4 ;聲明一個局部的邏輯變量,變量名為 Test4

Test4 SETL {TRUE} ;將該變量賦值為真

 

4 、 RLIST

語法格式:

名稱 RLIST { 寄存器列表 }

RLIST 偽指令可用于對一個通用寄存器列表定義名稱,使用該偽指令定義的名稱可在 ARM 指令 LDM/STM 中使用。在 LDM/STM 指令中,列表中的寄存器訪問次序為根據(jù)寄存器的編號由低到高,而與列表中的寄存器排列次序無關。

使用示例:

RegList RLIST {R0-R5 , R8 , R10} ;將寄存器列表名稱定義為 RegList ,可在 ARM 指令 LDM/STM中通過該名稱訪問寄存器列表。

 

數(shù)據(jù)定義( Data Definition )偽指令

數(shù)據(jù)定義偽指令一般用于為特定的數(shù)據(jù)分配存儲單元,同時可完成已分配存儲單元的初始化。

常見的數(shù)據(jù)定義偽指令有如下幾種:

— DCB 用于分配一片連續(xù)的字節(jié)存儲單元并用指定的數(shù)據(jù)初始化。

— DCW ( DCWU ) 用于分配一片連續(xù)的半字存儲單元并用指定的數(shù)據(jù)初始化。

— DCD ( DCDU ) 用于分配一片連續(xù)的字存儲單元并用指定的數(shù)據(jù)初始化。

— DCFD ( DCFDU )用于為雙精度的浮點數(shù)分配一片連續(xù)的字存儲單元并用指定的數(shù)據(jù)初始

化。

— DCFS ( DCFSU ) 用于為單精度的浮點數(shù)分配一片連續(xù)的字存儲單元并用指定的數(shù)據(jù)初

始化。

— DCQ ( DCQU ) 用于分配一片以 8 字節(jié)為單位的連續(xù)的存儲單元并用指定的數(shù)據(jù)初始

化。

— SPACE 用于分配一片連續(xù)的存儲單元

— MAP 用于定義一個結(jié)構(gòu)化的內(nèi)存表首地址 &

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    日韩精品视频香蕉视频| 日韩综合国产欧美一区| 都市激情小说在线一区二区三区| 91插插插外国一区二区婷婷| 午夜久久精品福利视频| 日本精品最新字幕视频播放| 久久本道综合色狠狠五月| 精品视频一区二区三区不卡| 毛片在线观看免费日韩| 久久一区内射污污内射亚洲 | 中日韩免费一区二区三区| 亚洲一区二区福利在线| 国产视频福利一区二区| 国产精品免费自拍视频| 国产成人亚洲欧美二区综| 中文字幕av诱惑一区二区| 国产欧美性成人精品午夜| 成人日韩在线播放视频| 国产激情一区二区三区不卡| 懂色一区二区三区四区| 国产丝袜女优一区二区三区| 亚洲欧美日本国产不卡 | 激情五月天免费在线观看| 99视频精品免费视频播放| 国产乱淫av一区二区三区| 视频一区二区 国产精品| 五月的丁香婷婷综合网| 91精品国产综合久久不卡| 亚洲一区二区亚洲日本 | 久久香蕉综合网精品视频| 亚洲一区二区三区四区性色av| 日韩一区二区三区观看| 欧美人妻少妇精品久久性色 | 日韩精品第一区二区三区| 中文字幕佐山爱一区二区免费| 国自产拍偷拍福利精品图片| 色播五月激情五月婷婷| 日韩精品福利在线观看| 黄色三级日本在线观看| 91欧美视频在线观看免费| 在线九月婷婷丁香伊人|