計(jì)算機(jī)程序基礎(chǔ)教程(03):x86讀寫數(shù)據(jù)指令
【mov指令】
mov指令用于讀寫寄存器和內(nèi)存地址空間,有兩個(gè)地址碼,分別指定寫入地址與讀取地址,尋址方式有3種:立即尋址、寄存器尋址、內(nèi)存尋址。
?● 立即尋址
也稱為立即數(shù)尋址,寫入的數(shù)據(jù)存儲(chǔ)在地址碼中,示例:mov ax,99,將99寫入ax寄存器,其中99直接存儲(chǔ)在地址碼中。
立即數(shù)可以使用多種進(jìn)制方式指定,如下:
99,默認(rèn)為10進(jìn)制
10B,添加B后綴,表示2進(jìn)制
67Q,添加Q后綴,表示8進(jìn)制
0CH,添加H后綴,表示16進(jìn)制,若第一個(gè)數(shù)字為字母則需要額外添加前綴0
0xC,添加0x前綴,表示16進(jìn)制,某些編譯器不支持此方式
?● 寄存器尋址
要操作的數(shù)據(jù)存儲(chǔ)在寄存器中,示例:mov ax,bx,將bx中的數(shù)據(jù)寫入ax。
?● 內(nèi)存尋址
要操作的數(shù)據(jù)在內(nèi)存中,地址碼指定數(shù)據(jù)的偏移地址,段地址默認(rèn)存儲(chǔ)在ds寄存器中,可以在內(nèi)存與寄存器之間讀寫數(shù)據(jù),也可以將一個(gè)立即數(shù)寫入到內(nèi)存,但是不能在兩個(gè)內(nèi)存地址之間讀寫數(shù)據(jù)。
? ? ? ? ?★ 直接內(nèi)存尋址
直接內(nèi)存尋址使用一個(gè)立即數(shù)指定偏移地址,要操作的內(nèi)存地址不能改變。
示例:
mov ax,[0x404020]? ? ? ;將0x404020地址處的數(shù)據(jù)寫入ax寄存器,使用ds作為段地址寄存器。
在寄存器與內(nèi)存之間讀寫時(shí),處理器默認(rèn)要讀寫的數(shù)據(jù)長度與寄存器長度相同,處理器通過寄存器的長度確定要讀寫多少個(gè)內(nèi)存單元。
將一個(gè)立即數(shù)寫入內(nèi)存時(shí),需要在地址碼中指定數(shù)據(jù)的長度,處理器通過操作數(shù)類型碼確定要寫入幾個(gè)內(nèi)存單元。
指定數(shù)據(jù)長度關(guān)鍵詞如下:
byte,1字節(jié)
word,2字節(jié)
dword,4字節(jié)
fword,6字節(jié)
qword,8字節(jié)
示例:
mov byte[0x404020],9? ?;將9寫入地址0x404020處,占1個(gè)存儲(chǔ)單元
mov word[0x404020],9? ?;占用2個(gè)存儲(chǔ)單元
以上為nasm編譯器語法,masm編譯器需要額外添加ptr關(guān)鍵詞:byte ptr。
? ? ? ? ?★ 間接內(nèi)存尋址
地址碼中指定一個(gè)寄存器,使用寄存器中的數(shù)據(jù)作為偏移地址,寄存器中的數(shù)據(jù)也稱為指針,意為它指向另一個(gè)數(shù)據(jù),需要讀寫其他內(nèi)存地址時(shí),讀寫數(shù)據(jù)指令無需修改,只需要修改指針的值即可。
示例:
mov ax,[bx]? ? ?;[]符號(hào)內(nèi)指定一個(gè)寄存器,讀取寄存器中的數(shù)據(jù)作為偏移地址
mov ax,[rcx]? ? ;在x86-64中,第二個(gè)地址碼只能使用32位、64位寄存器
偏移地址也可以使用多個(gè)數(shù)據(jù)組合的方式指定:
mov eax, [rbx+2]? ? ? ? ;寄存器+立即數(shù)
mov eax, [rbx+rsi]? ? ? ;寄存器+寄存器
mov eax, [rbx+rsi+2]? ? ;寄存器+寄存器+立即數(shù),兩個(gè)寄存器長度必須相同
mov eax, [rbx*4+2]? ? ? ;寄存器×立即數(shù)+立即數(shù)
mov eax, [rbx*4+rcx]? ? ;寄存器×立即數(shù)+寄存器
?● 字節(jié)序
對(duì)于長度超過一字節(jié)的數(shù)據(jù),需要使用多個(gè)存儲(chǔ)單元存儲(chǔ),不同處理器對(duì)數(shù)據(jù)字節(jié)的排序方式不同,x86處理器規(guī)定數(shù)據(jù)的低位存儲(chǔ)在低地址中,高位存儲(chǔ)在高地址中,比如 12345678H 需要占用4個(gè)存儲(chǔ)單元,拆分為 12H、34H、56H、78H 四個(gè)字節(jié),78H是低位,放在低地址中,12H放在高地址中,這種排序方式稱為小端序,反之則稱為大端序。
【讀寫并運(yùn)算】
lea指令的原意為將一個(gè)指針寫入一個(gè)寄存器,功能與mov類似,但是lea不能使用內(nèi)存尋址,只能使用寄存器尋址、立即數(shù)尋址。
lea rax,[0x4]? ? ;將一個(gè)立即數(shù)寫入rax,數(shù)據(jù)放在[]符號(hào)內(nèi),但這并非表示內(nèi)存尋址
lea rax,[rbx]? ? ;將rbx中的數(shù)據(jù)寫入rax,在x86-64中不能使用32位以下寄存器
lea的上述功能與mov指令重合,這并非lea的全部使用方式,lea支持讀取數(shù)據(jù)的同時(shí)對(duì)數(shù)據(jù)進(jìn)行運(yùn)算,比如:lea rax,[rbp+4],rbp中的數(shù)據(jù)+4寫入rax,等同于如下指令的組合:
mov rax, rbp
add rax, 4
lea支持以下數(shù)據(jù)運(yùn)算方式:
lea? rax, [rbx+2]
lea? rax, [rbx+rcx]
lea? rax, [rbx+rcx+2]
lea? rax, [rbx*4+2]
lea? rax, [rcx+rbx*4]
【讀寫并擴(kuò)展】
?● movzx
movzx指令用于將一個(gè)無符號(hào)數(shù)擴(kuò)展長度并寫入指定寄存器,擴(kuò)展的長度由寫入寄存器的長度決定,具體行為是將擴(kuò)展后的高位全部使用0填充,需要擴(kuò)展的數(shù)據(jù)可以使用寄存器尋址、內(nèi)存尋址,若使用內(nèi)存尋址則需要指定數(shù)據(jù)長度。
movzx ax, al? ? ? ? ? ? ? ?;al擴(kuò)展為ax,ax高位全部設(shè)置為0
movzx eax, al
movzx eax, ax
movzx eax, bx
movzx rax, byte[0x404020]
movzx不能用于將32位寄存器數(shù)據(jù)擴(kuò)展為64位長度,比如 movzx rax,eax 這樣是錯(cuò)誤的,因?yàn)閷懭雃ax時(shí)rax的高位會(huì)清0,無需擴(kuò)展,直接使用即可,但是寫入ax時(shí)eax的高位不會(huì)清0,這是x86-64與x86的一個(gè)區(qū)別。
?● movsx
movsx指令用于讀取并擴(kuò)展一個(gè)有符號(hào)數(shù)的長度,具體行為是將擴(kuò)展長度數(shù)據(jù)的符號(hào)位寫入擴(kuò)展后高位的每一位,若是正數(shù),則高位全部使用0填充,若是負(fù)數(shù),則高位全部使用1填充,其中最高位的1表示符號(hào)位,其余高位的1表示負(fù)數(shù)補(bǔ)碼,將擴(kuò)展高位全部設(shè)置為1即可滿足兩個(gè)補(bǔ)數(shù)相加產(chǎn)生進(jìn)位的規(guī)則。
movsx ax, al? ? ? ? ? ? ? ? ;ax的高8位為al的符號(hào)位,若al為負(fù)數(shù),則ax的高8位全部為1
movsx eax, al
movsx eax, bx
movsx rax, ebx
movsx rax, byte[0x404020]
【數(shù)據(jù)擴(kuò)展指令】
數(shù)據(jù)擴(kuò)展指令用于將ax系列寄存器中的有符號(hào)數(shù)擴(kuò)展長度。
?★ cbw
將al寄存器中的8位有符號(hào)數(shù)擴(kuò)展為16位,使用ax寄存器存儲(chǔ),al存儲(chǔ)低位,ah存儲(chǔ)高位,具體行為是將al寄存器中的符號(hào)位寫入ah的每一位,若al為負(fù)數(shù)則ah存儲(chǔ)負(fù)數(shù)補(bǔ)碼的高8位。
?★ cwde
將ax中的16位有符號(hào)數(shù)擴(kuò)展為32位,使用eax存儲(chǔ)。
?★ cdqe
將eax中的32位有符號(hào)數(shù)擴(kuò)展為64位,使用rax存儲(chǔ)。
?★ cwd
將ax中的16位有符號(hào)數(shù)擴(kuò)展為32位,使用dx+ax存儲(chǔ),dx存儲(chǔ)高位,ax存儲(chǔ)低位。
?★ cdq
將eax中的32位有符號(hào)數(shù)擴(kuò)展為64位,使用edx+eax存儲(chǔ),edx存儲(chǔ)高位,eax存儲(chǔ)低位。
?★ cqo
將rax中的64位有符號(hào)數(shù)擴(kuò)展為128位,使用rdx+rax存儲(chǔ),rdx存儲(chǔ)高位,rax存儲(chǔ)低位。
【數(shù)據(jù)交換】
xchg指令用于將兩個(gè)地址碼中的數(shù)據(jù)進(jìn)行交換,可以使用寄存器尋址、內(nèi)存尋址,但是不能使用兩個(gè)內(nèi)存地址,這一點(diǎn)與mov相同。
xchg ax,bx
xchg ax,[0x404020]
【讀寫??臻g】
使用一段內(nèi)存空間存儲(chǔ)數(shù)值數(shù)據(jù)時(shí),若數(shù)據(jù)比較零散,可以隨意的安排讀寫位置和順序,而有些數(shù)據(jù)需要按固定順序讀寫,常用的操作順序有兩種:隊(duì)列和棧。
隊(duì)列管理方式,最先存儲(chǔ)的數(shù)據(jù)最先使用,最后存儲(chǔ)的數(shù)據(jù)最后使用,就像排隊(duì)一樣,可以通過如下指令實(shí)現(xiàn):
mov rax,0x404020? ? ?;定義讀寫指針
mov rbx,[rax]? ? ? ? ;通過指針讀取隊(duì)列中的數(shù)據(jù)
add rax,4? ? ? ? ? ? ;指針+x,定位到下一個(gè)數(shù)據(jù),x為數(shù)據(jù)長度
循環(huán)執(zhí)行以上指令就可以通過隊(duì)列的方式管理數(shù)據(jù)。
棧管理方式,最先存儲(chǔ)的數(shù)據(jù)最后使用,最后存儲(chǔ)的數(shù)據(jù)最先使用,就像彈夾一樣,最后壓入的子彈最先被擊發(fā)。
若通過多條指令實(shí)現(xiàn)棧管理方式會(huì)很復(fù)雜,執(zhí)行效率也不高,為了增加效率,CPU提供了專用的寄存器和指令實(shí)現(xiàn)棧功能,SS+SP寄存器為??臻g的地址,這個(gè)地址稱為棧頂指針,棧操作指令通過此地址進(jìn)行讀寫數(shù)據(jù)操作,棧指令讀寫數(shù)據(jù)的長度與CPU位寬相同,在8086處理器中,讀寫操作2個(gè)字節(jié),在x86-64處理器中,讀寫操作8個(gè)字節(jié)。
?● push 入棧指令
push指令用來將數(shù)據(jù)寫入到棧中,有一個(gè)地址碼,設(shè)置入棧的數(shù)據(jù),使用立即數(shù)尋址、寄存器尋址、內(nèi)存尋址,示例:push ax。
push對(duì)棧的操作是從高地址向低地址的順序使用的,SP存儲(chǔ)棧中末尾數(shù)據(jù)的地址(若沒有數(shù)據(jù)則存儲(chǔ)棧的起始地址),執(zhí)行push時(shí),CPU首先將SP減去CPU位寬,之后將數(shù)據(jù)寫入SP指定的地址處。
?● pop 出棧指令
pop指令用來讀取棧中末尾的數(shù)據(jù),有一個(gè)地址碼,設(shè)置保存數(shù)據(jù)的地址,使用寄存器尋址、內(nèi)存尋址。
pop對(duì)棧的操作是從低地址到高地址的順序使用的,SP存儲(chǔ)棧中末尾數(shù)據(jù)的地址,執(zhí)行pop時(shí),CPU首先從SP指定的地址處讀取數(shù)據(jù),讀取完畢之后將SP的值增加CPU位寬,定位到下一個(gè)數(shù)據(jù)。
?● 使用mov讀寫??臻g
程序執(zhí)行時(shí),操作系統(tǒng)會(huì)為其分配一段內(nèi)存當(dāng)做??臻g使用,??臻g的地址是連續(xù)的,讀寫速度比碎片化的內(nèi)存更快,但是通過棧指令操作數(shù)據(jù)不靈活,只能按固定順序、固定長度進(jìn)行讀寫。
為了更靈活的使用??臻g,我們需要使用mov指令自由的讀寫棧,將一段內(nèi)存空間當(dāng)做??臻g使用是我們自己安排的,CPU并不會(huì)限制這段空間只能由push/pop指令操作。
mov rax,[rsp]
mov rbx,[rsp+8]
mov rcx,[rsp+16]
使用SP寄存器指定偏移地址時(shí),CPU默認(rèn)讀取SS寄存器作為段地址。
當(dāng)??臻g需要大量的同時(shí)使用mov和push/pop操作時(shí)容易導(dǎo)致混亂,為了避免混亂,使用mov操作??臻g時(shí),一般首先將sp的值寫入bp,之后將sp減去一個(gè)數(shù)值,此時(shí)sp和bp將??臻g分割為兩部分,push/pop通過sp確定數(shù)據(jù)地址,mov通過bp確定數(shù)據(jù)地址,分別操作??臻g的不同范圍。
mov rbp,rsp
sub rsp,0x60
mov word[rbp-2],1
mov word[rbp-4],2
push rax
push rbx
使用BP指定偏移地址時(shí),CPU也會(huì)默認(rèn)使用SS作為段地址寄存器。
【讀寫IO地址空間】
內(nèi)存之外的存儲(chǔ)器通過IO地址空間與CPU相連,IO地址空間使用專用的指令進(jìn)行讀寫。
?● in - 讀
in al,0x60? ? ;讀取0x60地址中的數(shù)據(jù)寫入al,只能使用al、ax、eax接收數(shù)據(jù),分別表示讀取1字節(jié)、2字節(jié)、4字節(jié)
in eax,dx? ? ?;可以使用間接尋址,但是只能使用dx寄存器存儲(chǔ)IO空間地址
?● out - 寫
out 0x60,al
out dx,al