x86 16位实模式 04——完善程序

镇楼图

x86 16位实模式 04——完善程序

Pixiv:岸Yasuri



〇、BX寄存器的特性

通用寄存器中也有一类特殊的,BX寄存器可以像数据段地址的变量一样来使用

/*比如*/
mov [0],1234h
mov [2],5678h
/*为数据段ds:0~ds:3赋值*/
mov bx,0
mov ax,[bx]

add bx,2
mov ax,[bx]

其他大部分寄存器都无法做到这样,而bx寄存器却可以使用这样的语法

基于bx寄存器的特性,我们不能再简单地将bx寄存器理解为一个通用寄存器,它还常常作为数据段的一个地址变量


一、inc指令、loop指令与CX寄存器

inc

这个指令类似于C语言的++

inc bx等效于add bx,1

loop

这个指令允许我们使用循环结构

比如求解\(2^{15}\)(目前还没学乘除,汇编语言也没有pow这样一个函数)

assume cs:code
	code segment
		mov ax,1
		
		mov cx,15
	s:	mov ax,2
		add ax,ax
		loop s
		/*这是一个循环结构*/
		mov ax,4c00h
		int 21h
	code ends
end

标号

你在某一代码处设置标号s,在下面再写上loop <标号>即可设置一个循环段

从s的这一行代码开始到loop结束为要循环的代码

这里不断循环的代码是

mov ax,2
add ax,ax

循环次数如何确定?

在8086汇编语言中,次数完全由CX来定,我们已经知道CX寄存器可以用来计算一个汇编程序的代码行数,但同时CX寄存器还担当了作为循环次数的一个作用

你设置CX的值为多少就循环多少次

mov cx,15/*设置次数为15*/
...
loop s/*会隐含地让cx-1*/

/*当cx为0时则退出循环
因此你设置cx为多少就循环就执行多少次
*/

循环结构框架

mov cx,次数

循环体

loop 标号

二、debug与masm的一个不同的细微的处理方式

debug是用来调试代码

masm是用来编译代码

在处理某一方面不太一样

assume cs:code
	code segment
		mov ax,2000
		mov al,[0]
	code ends
end

我们首先看一下从test.asm到test.obj再到test.exe经过masm编译器运行后的代码

x86 16位实模式 04——完善程序

再对比一下我在debug上写的

x86 16位实模式 04——完善程序

可以发现debug会处理成[0]而masm会处理成0

[0]的含义是偏移地址

0的含义是一个内存单元

就效率而言[0]更高,我们可以将代码mov al,[0]改成mov al,ds:[0]

这样可以达到与debug一样的处理方式,效率也更高


三、一个完整的程序

在汇编程序中你要详细地设置好各个段以及作用,一个段最多只能容纳64KB的内存

我们利用assume设置了代码段,同理我们也可以设置栈段和数据段

设置段

assume cs:code,ss:stack,ds:data
	stack segment
		...
	stack ends
	
	data segment
		...
	data ends
	
	code segment
		...
	code ends
end

这里我定义code与cs链接起来,ss与stack链接起来,ds与data链接起来

这些段寄存器的作用不用多说了

其内存空间是由系统自动分配好的

我们无需再关心它到底是在哪一段,只需要知道其偏移地址即可,我们可以用BX寄存器索引数据段,sp索引栈段,ip索引代码段。

不过一般情况下我们只会去索引数据段

设置程序从哪开始

当设置的段多起来后我们需要一个标号来确定程序从哪开始,结束的是哪一段

assume cs:code,ss:stack,ds:data
	stack segment
		...
	stack ends
	
	data segment
		...
	data ends
	
	code segment
	start:	...
			...
	code ends
end start

这里我设置标号start,同时在下面的end伪指令表明我结束的是从start开始的那一段代码

dw指令——定义字型数据

dw指令类似于C语言里面的定义变量,不过有些不一样

在汇编里是定义一个整数或者更准确点你是定义一个4位十六进制数据

dw是定义一个字型数据(也就是定义一个占2字节的数据)

dw 1234
/*
  定义字型数据1234H
  类似于C语言的int a = 0x1234;
*/
dw 12
/*定义字型数据1200H*/

习惯上会在栈段和代码段那一块专门定义数据


四、现在能做点什么?

虽然现在学得有限,但还是能从内存角度实现一些功能

①内存数据COPY

下面代码能将data段的数据copy至stack段

assume cs:code,ds:data,ss:sg

data segment
	dw 3031h,3233h,3435h,3637h,3839h
	/*注意加上h,否则会默认为十进制的3031*/
data ends

sg segment stack
	dt 1 dup (0)
	/*这块的语法再下一节会有讲
	  可以忽略这行代码
	*/
sg ends

code segment
start:	
		mov ax,data
		mov ds,ax
		
		mov bx,0
		
		mov cx,10
		s:	
			push ds:[bx]
			add bx,2
		loop s
		
		mov ax,4c00h
		int 21h
code ends
end start

x86 16位实模式 04——完善程序

x86 16位实模式 04——完善程序

同理依据内存操作,你还能完成其他的操作,比如清空内存、数据交换等


LAST、关于寄存器

我们发现通用寄存器并不普通,似乎每个寄存器都具有特殊功能一样,但总体上就分为段寄存器、偏移地址寄存器、通用寄存器和标志寄存器(后续会讲)。

这三类具有一个最基本的功能,通用寄存器就是用来存储数据,段寄存器就是用来指向段,偏移地址寄存器就是指向段的偏移地址的内存

其余的功能如果你不使用你就可以只用它的基本功能

如果你在某一个块不用循环结构,那么CX就可以用来存储数据而不是专门去存储循环次数

如果在某一个程序不需要使用栈段,你就可以把SS指向的内存当作一个普通的内存,可以当成一个代码段也可以当成一个数据段


参考书籍

《汇编语言 第四版》——王爽

参考网站

https://fishc.com.cn/forum-39-1.html

https://b23.tv/qUfphX

上一篇:汇编语言第六章包含多个段的程序


下一篇:JavaScript Replace 多个字符