欢迎光临散文网 会员登陆 & 注册

计算机程序基础教程(03):x86读写数据指令

2023-02-09 03:53 作者:阿狸喜羊羊  | 我要投稿


【mov指令】


mov指令用于读写寄存器和内存地址空间,有两个地址码,分别指定写入地址与读取地址,寻址方式有3种:立即寻址、寄存器寻址、内存寻址。


 ● 立即寻址


也称为立即数寻址,写入的数据存储在地址码中,示例:mov ax,99,将99写入ax寄存器,其中99直接存储在地址码中。


立即数可以使用多种进制方式指定,如下:

99,默认为10进制

10B,添加B后缀,表示2进制

67Q,添加Q后缀,表示8进制

0CH,添加H后缀,表示16进制,若第一个数字为字母则需要额外添加前缀0

0xC,添加0x前缀,表示16进制,某些编译器不支持此方式


 ● 寄存器寻址


要操作的数据存储在寄存器中,示例:mov ax,bx,将bx中的数据写入ax。


 ● 内存寻址


要操作的数据在内存中,地址码指定数据的偏移地址,段地址默认存储在ds寄存器中,可以在内存与寄存器之间读写数据,也可以将一个立即数写入到内存,但是不能在两个内存地址之间读写数据。


         ★ 直接内存寻址


直接内存寻址使用一个立即数指定偏移地址,要操作的内存地址不能改变。

示例:

mov ax,[0x404020]      ;将0x404020地址处的数据写入ax寄存器,使用ds作为段地址寄存器。


在寄存器与内存之间读写时,处理器默认要读写的数据长度与寄存器长度相同,处理器通过寄存器的长度确定要读写多少个内存单元。

将一个立即数写入内存时,需要在地址码中指定数据的长度,处理器通过操作数类型码确定要写入几个内存单元。


指定数据长度关键词如下:

byte,1字节

word,2字节

dword,4字节

fword,6字节

qword,8字节


示例:

mov byte[0x404020],9   ;将9写入地址0x404020处,占1个存储单元

mov word[0x404020],9   ;占用2个存储单元


以上为nasm编译器语法,masm编译器需要额外添加ptr关键词:byte ptr。


         ★ 间接内存寻址


地址码中指定一个寄存器,使用寄存器中的数据作为偏移地址,寄存器中的数据也称为指针,意为它指向另一个数据,需要读写其他内存地址时,读写数据指令无需修改,只需要修改指针的值即可。


示例:

mov ax,[bx]     ;[]符号内指定一个寄存器,读取寄存器中的数据作为偏移地址

mov ax,[rcx]    ;在x86-64中,第二个地址码只能使用32位、64位寄存器


偏移地址也可以使用多个数据组合的方式指定:

mov eax, [rbx+2]        ;寄存器+立即数

mov eax, [rbx+rsi]      ;寄存器+寄存器

mov eax, [rbx+rsi+2]    ;寄存器+寄存器+立即数,两个寄存器长度必须相同

mov eax, [rbx*4+2]      ;寄存器×立即数+立即数

mov eax, [rbx*4+rcx]    ;寄存器×立即数+寄存器


 ● 字节序


对于长度超过一字节的数据,需要使用多个存储单元存储,不同处理器对数据字节的排序方式不同,x86处理器规定数据的低位存储在低地址中,高位存储在高地址中,比如 12345678H 需要占用4个存储单元,拆分为 12H、34H、56H、78H 四个字节,78H是低位,放在低地址中,12H放在高地址中,这种排序方式称为小端序,反之则称为大端序。




【读写并运算】


lea指令的原意为将一个指针写入一个寄存器,功能与mov类似,但是lea不能使用内存寻址,只能使用寄存器寻址、立即数寻址。


lea rax,[0x4]    ;将一个立即数写入rax,数据放在[]符号内,但这并非表示内存寻址

lea rax,[rbx]    ;将rbx中的数据写入rax,在x86-64中不能使用32位以下寄存器


lea的上述功能与mov指令重合,这并非lea的全部使用方式,lea支持读取数据的同时对数据进行运算,比如:lea rax,[rbp+4],rbp中的数据+4写入rax,等同于如下指令的组合:

mov rax, rbp

add rax, 4


lea支持以下数据运算方式:

lea  rax, [rbx+2]

lea  rax, [rbx+rcx]

lea  rax, [rbx+rcx+2]

lea  rax, [rbx*4+2]

lea  rax, [rcx+rbx*4]




【读写并扩展】


 ● movzx


movzx指令用于将一个无符号数扩展长度并写入指定寄存器,扩展的长度由写入寄存器的长度决定,具体行为是将扩展后的高位全部使用0填充,需要扩展的数据可以使用寄存器寻址、内存寻址,若使用内存寻址则需要指定数据长度。


movzx ax, al               ;al扩展为ax,ax高位全部设置为0

movzx eax, al

movzx eax, ax

movzx eax, bx

movzx rax, byte[0x404020]


movzx不能用于将32位寄存器数据扩展为64位长度,比如 movzx rax,eax 这样是错误的,因为写入eax时rax的高位会清0,无需扩展,直接使用即可,但是写入ax时eax的高位不会清0,这是x86-64与x86的一个区别。


 ● movsx


movsx指令用于读取并扩展一个有符号数的长度,具体行为是将扩展长度数据的符号位写入扩展后高位的每一位,若是正数,则高位全部使用0填充,若是负数,则高位全部使用1填充,其中最高位的1表示符号位,其余高位的1表示负数补码,将扩展高位全部设置为1即可满足两个补数相加产生进位的规则。


movsx ax, al                ;ax的高8位为al的符号位,若al为负数,则ax的高8位全部为1

movsx eax, al

movsx eax, bx

movsx rax, ebx

movsx rax, byte[0x404020]




【数据扩展指令】


数据扩展指令用于将ax系列寄存器中的有符号数扩展长度。


 ★ cbw

将al寄存器中的8位有符号数扩展为16位,使用ax寄存器存储,al存储低位,ah存储高位,具体行为是将al寄存器中的符号位写入ah的每一位,若al为负数则ah存储负数补码的高8位。


 ★ cwde

将ax中的16位有符号数扩展为32位,使用eax存储。


 ★ cdqe

将eax中的32位有符号数扩展为64位,使用rax存储。


 ★ cwd

将ax中的16位有符号数扩展为32位,使用dx+ax存储,dx存储高位,ax存储低位。


 ★ cdq

将eax中的32位有符号数扩展为64位,使用edx+eax存储,edx存储高位,eax存储低位。


 ★ cqo

将rax中的64位有符号数扩展为128位,使用rdx+rax存储,rdx存储高位,rax存储低位。




【数据交换】


xchg指令用于将两个地址码中的数据进行交换,可以使用寄存器寻址、内存寻址,但是不能使用两个内存地址,这一点与mov相同。


xchg ax,bx

xchg ax,[0x404020]




【读写栈空间】


使用一段内存空间存储数值数据时,若数据比较零散,可以随意的安排读写位置和顺序,而有些数据需要按固定顺序读写,常用的操作顺序有两种:队列和栈。


队列管理方式,最先存储的数据最先使用,最后存储的数据最后使用,就像排队一样,可以通过如下指令实现:

mov rax,0x404020     ;定义读写指针

mov rbx,[rax]        ;通过指针读取队列中的数据

add rax,4            ;指针+x,定位到下一个数据,x为数据长度

循环执行以上指令就可以通过队列的方式管理数据。


栈管理方式,最先存储的数据最后使用,最后存储的数据最先使用,就像弹夹一样,最后压入的子弹最先被击发。

若通过多条指令实现栈管理方式会很复杂,执行效率也不高,为了增加效率,CPU提供了专用的寄存器和指令实现栈功能,SS+SP寄存器为栈空间的地址,这个地址称为栈顶指针,栈操作指令通过此地址进行读写数据操作,栈指令读写数据的长度与CPU位宽相同,在8086处理器中,读写操作2个字节,在x86-64处理器中,读写操作8个字节。


 ● push 入栈指令


push指令用来将数据写入到栈中,有一个地址码,设置入栈的数据,使用立即数寻址、寄存器寻址、内存寻址,示例:push ax。

push对栈的操作是从高地址向低地址的顺序使用的,SP存储栈中末尾数据的地址(若没有数据则存储栈的起始地址),执行push时,CPU首先将SP减去CPU位宽,之后将数据写入SP指定的地址处。


 ● pop 出栈指令


pop指令用来读取栈中末尾的数据,有一个地址码,设置保存数据的地址,使用寄存器寻址、内存寻址。

pop对栈的操作是从低地址到高地址的顺序使用的,SP存储栈中末尾数据的地址,执行pop时,CPU首先从SP指定的地址处读取数据,读取完毕之后将SP的值增加CPU位宽,定位到下一个数据。


 ● 使用mov读写栈空间


程序执行时,操作系统会为其分配一段内存当做栈空间使用,栈空间的地址是连续的,读写速度比碎片化的内存更快,但是通过栈指令操作数据不灵活,只能按固定顺序、固定长度进行读写。

为了更灵活的使用栈空间,我们需要使用mov指令自由的读写栈,将一段内存空间当做栈空间使用是我们自己安排的,CPU并不会限制这段空间只能由push/pop指令操作。


mov rax,[rsp]

mov rbx,[rsp+8]

mov rcx,[rsp+16]


使用SP寄存器指定偏移地址时,CPU默认读取SS寄存器作为段地址。


当栈空间需要大量的同时使用mov和push/pop操作时容易导致混乱,为了避免混乱,使用mov操作栈空间时,一般首先将sp的值写入bp,之后将sp减去一个数值,此时sp和bp将栈空间分割为两部分,push/pop通过sp确定数据地址,mov通过bp确定数据地址,分别操作栈空间的不同范围。


mov rbp,rsp

sub rsp,0x60

mov word[rbp-2],1

mov word[rbp-4],2

push rax

push rbx


使用BP指定偏移地址时,CPU也会默认使用SS作为段地址寄存器。




【读写IO地址空间】


内存之外的存储器通过IO地址空间与CPU相连,IO地址空间使用专用的指令进行读写。


 ● in - 读


in al,0x60    ;读取0x60地址中的数据写入al,只能使用al、ax、eax接收数据,分别表示读取1字节、2字节、4字节

in eax,dx     ;可以使用间接寻址,但是只能使用dx寄存器存储IO空间地址


 ● out - 写


out 0x60,al

out dx,al


计算机程序基础教程(03):x86读写数据指令的评论 (共 条)

分享到微博请遵守国家法律