我们先采用 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的值
;以下计算1到100的和
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