匯編語言的準(zhǔn)備知識(shí)-給初次接觸匯編者2

字號(hào):

匯編指令的操作數(shù)可以是內(nèi)存中的數(shù)據(jù), 如何讓程序從內(nèi)存中正確取得所需要的數(shù)據(jù)就是對(duì)內(nèi)存的尋址。
    INTEL 的CPU 可以工作在兩種尋址模式:實(shí)模式和保護(hù)模式。 前者已經(jīng)過時(shí),就不講了, WINDOWS 現(xiàn)在是32位保護(hù)模式的系統(tǒng), PE 文件就基本是運(yùn)行在一個(gè)32位線性地址空間, 所以這里就只介紹32位線性空間的尋址方式。
    其實(shí)線性地址的概念是很直觀的, 就想象一系列字節(jié)排成一長隊(duì),第一個(gè)字節(jié)編號(hào)為0, 第二個(gè)編號(hào)位1, 。。。。 一直到4294967295(十六進(jìn)制FFFFFFFF,這是32位二進(jìn)制數(shù)所能表達(dá)的值了)。 這已經(jīng)有4GB的容量! 足夠容納一個(gè)程序所有的代碼和數(shù)據(jù)。 當(dāng)然, 這并不表示你的機(jī)器有那么多內(nèi)存。 物理內(nèi)存的 管理 和分配是很復(fù)雜的內(nèi)容, 初學(xué)者不必在意, 總之, 從程序本身的角度看, 就好象是在那么大的內(nèi)存中。
    在INTEL系統(tǒng)中, 內(nèi)存地址總是由"段選擇符:有效地址"的方式給出。段選擇符(SELECTOR)存放在某一個(gè)段寄存器中, 有效地址則可由不同的方式給出。 段選擇符通過檢索段描述符確定段的起始地址, 長度(又稱段限制), 粒度, 存取權(quán)限, 訪問性質(zhì)等。 先不用深究這些, 只要知道段選擇符可以確定段的性質(zhì)就行了。 一旦由選擇符確定了段, 有效地址相對(duì)于段的基地址開始算。 比如由選擇符1A7選擇的數(shù)據(jù)段, 其基地址是400000, 把1A7 裝入DS中, 就確定使用該數(shù)據(jù)段。 DS:0 就指向線性地址400000。 DS:1F5278 就指向線性地址5E5278。 我們?cè)谝话闱闆r下, 看不到也不需要看到段的起始地址, 只需要關(guān)心在該段中的有效地址就行了。 在32位系統(tǒng)中, 有效地址也是由32位數(shù)字表示, 就是說, 只要有一個(gè)段就足以涵蓋4GB線性地址空間, 為什么還要有不同的段選擇符呢? 正如前面所說的, 這是為了對(duì)數(shù)據(jù)進(jìn)行不同性質(zhì)的訪問。 非法的訪問將產(chǎn)生異常中斷, 而這正是保護(hù)模式的核心內(nèi)容, 是構(gòu)造優(yōu)先級(jí)和多任務(wù)系統(tǒng)的基礎(chǔ)。 這里有涉及到很多深層的東西, 初學(xué)者先可不必理會(huì)。
    有效地址的計(jì)算方式是: 基址+間址*比例因子+偏移量。 這些量都是指段內(nèi)的相對(duì)于段起始地址的量度, 和段的起始地址沒有關(guān)系。 比如, 基址=100000, 間址=400, 比例因子=4, 偏移量=20000, 則有效地址為:
    100000+400*4+20000=100000+1000+20000=121000。 對(duì)應(yīng)的線性地址是400000+121000=521000。 (注意, 都是十六進(jìn)制數(shù))。
    基址可以放在任何32位通用寄存器中, 間址也可以放在除ESP外的任何一個(gè)通用寄存器中。 比例因子可以是1, 2, 4 或8。 偏移量是立即數(shù)。 如: [EBP+EDX*8+200]就是一個(gè)有效的有效地址表達(dá)式。 當(dāng)然, 多數(shù)情況下用不著這么復(fù)雜, 間址,比例因子和偏移量不一定要出現(xiàn)。
    內(nèi)存的基本單位是字節(jié)(BYTE)。 每個(gè)字節(jié)是8個(gè)二進(jìn)制位, 所以每個(gè)字節(jié)能表示的的數(shù)是11111111, 即十進(jìn)制的255。 一般來說, 用十六進(jìn)制比較方便, 因?yàn)槊?個(gè)二進(jìn)制位剛好等于1個(gè)十六進(jìn)制位, 11111111b = 0xFF。 內(nèi)存中的字節(jié)是連續(xù)存放的, 兩個(gè)字節(jié)構(gòu)成一個(gè)字(WORD), 兩個(gè)字構(gòu)成一個(gè)雙字(DWORD)。 在INTEL架構(gòu)中, 采用small endian格式, 即在內(nèi)存中,高位字節(jié)在低位字節(jié)后面。 舉例說明:十六進(jìn)制數(shù)803E7D0C, 每兩位是一個(gè)字節(jié), 在內(nèi)存中的形式是: 0C 7D 3E 80。 在32位寄存器中則是正常形式,如在EAX就是803E7D0C。 當(dāng)我們的形式地址指向這個(gè)數(shù)的時(shí)候,實(shí)際上是指向第一個(gè)字節(jié),即0C。 我們可以指定訪問長度是字節(jié), 字或者雙字。 假設(shè)DS:[EDX]指向第一個(gè)字節(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
    在段的屬性中,有一個(gè)就是缺省訪問寬度。如果缺省訪問寬度為雙字(在32位系統(tǒng)中經(jīng)常如此),那么要進(jìn)行字節(jié)或字的訪問,就必須用byte/word ptr顯式地指明。
    缺省段選擇:如果指令中只有作為段內(nèi)偏移的有效地址,而沒有指明在哪一個(gè)段里的時(shí)候,有如下規(guī)則:
    如果用ebp和esp作為基址或間址,則認(rèn)為是在SS確定的段中;
    其他情況,都認(rèn)為是在DS確定的段中。
    如果想打破這個(gè)規(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),嚴(yán)格地應(yīng)該叫做“棧”?!岸选笔橇硪环N類似但不同的結(jié)構(gòu)。SS 和 ESP 是INTEL對(duì)棧這種數(shù)據(jù)結(jié)構(gòu)的硬件支持。push/pop指令是專門針對(duì)棧結(jié)構(gòu)的特定操作。SS指定一個(gè)段為棧段,ESP則指出當(dāng)前的棧頂。push xxx 指令作如下操作:
    把ESP的值減去4;
    把xxx存入SS:[ESP]指向的內(nèi)存單元。
    這樣,esp的值減小了4,并且SS:[ESP]指向新壓入的xxx。 所以棧是“倒著長”的,從高地址向低地址方向擴(kuò)展。pop yyy 指令做相反的操作,把SS:[ESP]指向的雙字送到y(tǒng)yy指定的寄存器或內(nèi)存單元,然后把esp的值加上4。這時(shí),認(rèn)為該值已被彈出,不再在棧上了,因?yàn)樗m然還暫時(shí)存在在原來的棧頂位置,但下一個(gè)push操作就會(huì)把它覆蓋。因此,在棧段中地址低于esp的內(nèi)存單元中的數(shù)據(jù)均被認(rèn)為是未定義的。
    最后,有一個(gè)要注意的事實(shí)是,匯編語言是面向機(jī)器的,指令和機(jī)器碼基本上是一一對(duì)應(yīng)的,所以它們的實(shí)現(xiàn)取決于硬件。有些看似合理的指令實(shí)際上是不存在的,比如:
    mov DS:[edx], ds:[ecx] ;內(nèi)存單元之間不能直接傳送
    mov DS, 1A7 ;段寄存器不能直接由立即數(shù)賦值
    mov EIP, 3D4E7 ;不能對(duì)指令指針直接操作。