X86架构(五)——栈操作与寻址操作

我们先采用 X86架构(四) 所学知识,在显示器上显示 1+2+3+...+100=

	;代码清单7-1
	;文件名:c07_mbr.asm
	;文件说明:硬盘主引导扇区代码
	;创建日期:2011-4-13 18:02
	
	jmp near __start
	message db '1+2+3+...+100='
__start:
	mov ax, 0x7c0				;数据段基地址
	mov ds, ax
	mov ax, 0xb800				;显存段地址
	mov es, ax
	mov si, message     		;源数据起始地址
	mov di, 0           		;目的地址偏移
	mov cx, start - message		;计算字符数量,并存储在cx
__loop_1:
	mov al, [si]				;获取源地址数据
	mov [es:di], al				;将数据传送到目的地址[es:di]
	inc di						;段偏移加1
	mov byte [es:di], 0x07		;显示属性跟随存储
	inc di						;段偏移加1
	inc si						;下一个字符的地址
	loop __loop_1				;cx不为0,继续执行__loop_1
	;cx寄存器又为计数寄存器,处理器执行loop时,自动计算cx的值
	;以下计算1100的和
	xor ax,ax					;清空ax寄存器,存放结果
    mov cx,1
__loop_2:
	add ax,cx					;ax = ax + cx
	inc cx						;cx + 1
	cmp cx,100
	jle __loop_2				;小于等于时继续执行__loop_2

栈和栈段的初始化

栈(Stack)是一种后进先出(Last In First Out,LIFO)的数据存储结构,数据的存取只能从一端进行,如下图所示。
栈示意图
和代码段、数据段和附加段一样,栈也是一个内存段,叫栈段(Stack Segment),由段寄存器SS指向。栈的操作有两种,分别是压栈(push)和出栈(pop)。压栈和出栈只能在一端进行,所以需要用栈指针寄存器SP (Stack Pointer)来指示下一个数据应当压入栈内的位置,或者数据从哪里出栈。
定义栈需要两个连续的步骤,即初始化段寄存器SS和栈指针SP的内容。

	;以下计算累加和的每个数位 
	xor cx,cx		;cx清零
	mov ss,cx		;设置ss寄存器
	mov sp,cx		;设置sp寄存器,堆栈段从高地址向低地址生长

	mov bx,10		;除数
	xor cx,cx
__loop_3:
	inc cx			;压栈中用cx记录一共压入栈元素个数,以便之后出栈时能及时停止pop
	xor dx,dx		;dx清零,寄存器ax中保存着被除数,32位被除数[dx:ax]
	div bx			;除数bx
	or dl,0x30		;余数在dx中,但是余数最多到9,因此在dl中就够了,加0x30得到ASCII码
	push dx			;dx中只有dl有意义,但是压栈的单位必须是字(两个字节)
	cmp ax,0
	jne __loop_3	;ax不为0跳转,商为0时,不需要再除

NOTE
8086 处理器压栈时只能压入一个字
处理器在执行push指令时,首先将栈指针寄存器SP的内容减去操作数的字长(以字节为单位的长度,在16位处理器上是2),然后,把要压入栈的数据存放到逻辑地址SS:SP 所指向的内存位置(和其他段的读写一样,把栈段寄存器SS 的内容左移4 位,加上栈指针寄存器SP 提供的偏移地址)。如下图所示,当push指令第一次执行时,SP的内容减2,即0x0000-0x0002=0xFFFE,借位被忽略。于是,被压入栈的数据,在内存中的位置实际上是0x0000:0xFFFE。push指令的操作数是字,而且Intel 处理器是使用低端字节序的,故低字节在低地址部分,高字节在高地址部分,正好占据了栈段的最高两个字节位置。
段分配
NOTE
不同于代码段,代码段在处理器上执行时,是由低地址端向高地址端推进的,而压栈操作则正好相反,是从高地址端向低地址端推进的。
压栈状态
经过上方的各个程序,1~100的累加和被分解并存储到栈中,下面我们将位数从栈中取出并显示到显示器上

__loop_4:
	pop dx			;出栈,栈顶元素是千位,百位,十位,个位
	mov [es:di], dl ;dl保存低位,位数不超过10
	inc di			;偏移地址加1
	mov byte [es:di],0x07	;显示属性
	inc di					;__loop_4
	loop __loop_4	;cx在上段程序中已经完成赋值

	jmp near $ 		;死循环

NOTE

  • push 指令的操作数可以是16位寄存器或者16位内存单元
  • 栈在本质上也只是普通的内存区域,只是使用push和pop指令来访问比较方便
  • 要注意保持栈平衡
  • 在编写程序前,必须充分估计所需要的栈空间,以防止破坏有用的数据

8086处理器的寻址方式

1. 寄存器寻址
指令的操作数位于寄存器中,如

mov ax, cx
add bx, 0xf000
inc dx

2. 立即寻址
立即寻址也叫立即数寻址,指令的操作数是一个立即数

add bx, 0xf000  ;操作数0xf000为立即数
mov dx, label_a ;操作label_a是一个标号,也是立即数

3. 内存寻址
8086处理器访问内存时,采用的是段地址左移4位,然后加上偏移地址,来形成20位物理地址的模式,段地址由4 个段寄存器之一来提供,偏移地址要由指令来提供。所以内存寻址就是如何在指令中提供偏移地址,供处理器访问内存时使用。
3.1 直接寻址
使用该寻址方式的操作数是一个偏移地址,而且给出了该偏移地址的具体数值。

mov ax, [0x5c0f] 			;逻辑地址[DS:0x5c0f]
add word [0x0230], 0x5000 	;逻辑地址[DS:0x0230]
xor byte [es:label_b], 0x05	;ES为段超越前缀

3.2 基址寻址
基址寻址,就是在指令的地址部分使用基址寄存器BX或者BP来提供偏移地址。

;DS的内容左移4位,加上基址寄存器BX中的内容,形成20位的物理地址。
mov [bx], dx		;逻辑地址[DS:bx],
add byte [bx], 0x55	;逻辑地址[DS:bx]

使用基址寻址可以使代码边的更简洁高效,如

buffer:
	dw 0x20, 0x100, 0x0f, 0x300, 0xff00

	mov bx, buffer	;数据的偏移地址
	mov cx, 4
__loop:
	inc word [bx]
	add bx, 2		;字操作,所以加2
	loop __loop

BP用作基址寻址的寄存器时,在形成20位的物理地址时,默认的段寄存器是SS。所以,它经常用于访问栈。

;处理器将栈段寄存器SS的内容左移4位,加上寄存器BP的内容,形成20位的物理地址,
;然后将该地址处的一个字传送到寄存器AX中。
mov ax, [bp]

同时,利用此方式可以访问栈中的内容而不破坏栈的状态,尤其是SP寄存器中的内容。

mov ax, 0x5000
push ax
mov bp, sp		;保存sp的内容
mov ax, 0x7000
push ax
mov dx, [bp]	;访问前面保存的SP对应的栈中的内容

3.3 变址寻址
变址寻址类似于基址寻址,但变址寻址使用的是变址寄存器(或称索引寄存器)SI和DI。例如:

;默认使用段寄存器DS指向的数据段,偏移地址由寄存器SI或者DI提供
mov [si], dx
add ax, [di]
xor word [si], 0x8000
;使用段超越前缀
add bx, [es:si]

3.4 基址变址寻址
使用基址变址的操作数可以使用一个基址寄存器(BX 或者BP),外加一个变址寄存器(SI 或者DI)。

;肯定也是默认使用DS指向的数据段啦
mov ax, [bx+si]
add word [bx+di], 0x3000

哈哈哈哈哈,下面就要开始学习更多内容啦
让我们一起进步吧 Bro

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值