間接尋址
間接尋址允許寄存器像指針變量一樣運(yùn)作。要指出寄存器像一個(gè)指針一樣被間接使用,需要用方括號(hào)([])將它括起來。例如:
1 mov ax, [Data] ; 一個(gè)字的標(biāo)準(zhǔn)的直接內(nèi)存地址
2 mov ebx, Data ; ebx = & Data
3 mov ax, [ebx] ; ax = *ebx
因?yàn)锳X可以容納一個(gè)字,所以第三行代碼從EBX儲(chǔ)存的地址開始讀取一個(gè)字。如果用AL替換AX,那么只有一個(gè)字節(jié)會(huì)被讀取。認(rèn)識(shí)到寄存器不像在C中的變量一樣有類型是非常重要的。到底EBX具體指向什么完全取決于使用了什么指令。而且,甚至EBX是一個(gè)指針這個(gè)事實(shí)都完全取決于使用的指令。如果EBX錯(cuò)誤地使用了,通常不會(huì)有編譯錯(cuò)誤;但是,程序?qū)⒉粫?huì)正確運(yùn)行。這就是為什么相比于高級(jí)語(yǔ)言匯編程序較容易犯錯(cuò)的原因之一。
所有的32位通用寄存器(EAX,EBX,ECX,EDX)和指針寄存器(ESI,EDI)都可以用來間接尋址。一般來說,16位或8位的寄存器是不可以的。
子程序的簡(jiǎn)單例子
子程序是代碼中的一個(gè)的獨(dú)立的單元,它可以使用在程序的不同的地方。換句話說,一個(gè)子程序就像一個(gè)C中的函數(shù)。可以使用跳轉(zhuǎn)來調(diào)用子程序,但是返回會(huì)是一個(gè)問題。如果子程序要求能使用在程序中的任何地方,它必須要返回到調(diào)用它的代碼段處。因此,子程序的跳轉(zhuǎn)返回最好不要硬編碼為標(biāo)號(hào)。下面的代碼展示了如何使用JMP指令的間接方式來做這件事。此指令方式使用一個(gè)寄存器的值來決定跳轉(zhuǎn)到哪(因此,這個(gè)寄存器與C中的函數(shù)指針非常相似。) 下面使用子程序的方法來重寫第一章中的第一個(gè)程序。
子程序get_int使用了一個(gè)簡(jiǎn)單,基于寄存器的調(diào)用約定。它認(rèn)為EBX寄存器中存的是輸入雙字的儲(chǔ)存地址而ECX寄存器中存的是跳轉(zhuǎn)返回指令的地址。25行到28行,使用了ret1標(biāo)號(hào)來計(jì)算返回地址。在32行到34行,使用了$運(yùn)算子來計(jì)算返回的地址。$運(yùn)算子返回出現(xiàn)$這一行的當(dāng)前地址。$+7表達(dá)式計(jì)算在36行的MOV指令的地址。
這兩種計(jì)算返回地址的方法都是不方便的。第一種方法要求為每一次子程序調(diào)用定義一個(gè)標(biāo)號(hào)。第二種方法不需要標(biāo)號(hào),但是需要仔細(xì)的思量。如果使用了近跳轉(zhuǎn)來替代短跳轉(zhuǎn),那么與$相加的數(shù)就不會(huì)是7!幸運(yùn)的是,有一個(gè)更簡(jiǎn)單的方法來調(diào)用子程序。這種方法使用堆棧。
堆棧
許多CPU都支持內(nèi)置堆棧。堆棧是一個(gè)先進(jìn)后出(LIFO)的隊(duì)列。它是以這種方式組織的一塊內(nèi)存區(qū)域。PUSH指令添加一個(gè)數(shù)據(jù)到堆棧中而POP指令從堆棧中移除數(shù)據(jù)。移除的數(shù)據(jù)就是最后入棧的數(shù)據(jù)(這就是稱為先進(jìn)后
出隊(duì)列的緣故)。
SS段寄存器指定包含堆棧的段(通常它與儲(chǔ)存數(shù)據(jù)的段是一樣)。ESP寄存器包含將要移除出棧數(shù)據(jù)的地址。這個(gè)數(shù)據(jù)也被稱為棧頂。數(shù)據(jù)只能以雙字的形式入棧。也就是說,你不可以將一個(gè)字節(jié)推入棧中。
PUSH指令通過把ESP減4來向堆棧中插入一個(gè)雙字,然后把雙字儲(chǔ)存到[ESP]中。POP指令從[ESP]中讀取雙字,然后再把ESP加4.下面的代碼演示了這些指令如何工作,假定在ESP初始值為1000H。
堆??梢苑奖愕赜脕砼R時(shí)儲(chǔ)存數(shù)據(jù)。它同樣可以用來形成子程序調(diào)用和傳遞參數(shù)和局部變量。
80x86同樣提供一條PUSHA指令來把EAX,EBX,ECX,EDX,ESI,EDI和EBP寄存器的值推入棧中(不是以這個(gè)順序)。POPA指令可以用來將它們移除出棧。
更多建議: