汇编语言-06包含多个段的程序
0:200~0:2FF是相对安全的,可这段空间的容量只有256个字节,如果我们需要的空间超过256个字节该怎么办呢?
在操作系统的环境中,合法地通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突。在操作系统允许的情况下,程序可以取得任意容量的空间。
程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,再就是程序在执行的过程中向系统申请。
加载程序的时候为程序分配空间,例如程序在加载的时候,取得了代码段中的代码的存储空间。
若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明。通过在源程序中定义段来进行内存空间的获取。
代码段中使用数据
编程计算以下8个数据的和,结果存在ax寄存器中:
0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
在程序的第一条指令的前面加上了一个标号start,而这个标号在伪指令end的后面出现。可见end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。
assume cs:code code segment #定义字型数据(2Byte),总共占用16B,有操作系统分配空间,分配的控件的CS的前16个字节 dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h start: mov bx,0 #start指令指明程序的第一条指令位置 mov ax,0 mov cx,8 #循环八次 s:add ax,cs:[bx] add bx,2 #一个字占两个字节 loop s mov ax,4c00h int 21h code ends end start #end后面加start指明程序入口
dw即“define word"。在这里,使用dw定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16 个字节。
由于它们在代码段中,程序在运行的时候CS中存放代码段的段地址,所以可以从CS中得到它们的段地址。它们的偏移地址是多少呢?因为用dw定义的数据处于代码段的最开始,所以偏移地址为0,这8 个数据就在代码段的偏移0 、2 、4 、6 、8 、A 、C 、E 处。程序运行时,它们的地址就是CS:O 、CS:2 、CS:4 、CS:6 、CS:8 、CS:A 、CS:C 、CS:E 。
程序中,用bx存放加2递增的偏移地址,用循环来进行累加。在循环开始前,设置(bx)=0,cs:bx指向第一个数据所在的字单元。每次循环中(bx)=(bx)+2,cs:bx指向下一个数据所在的字单元。
d查看dw写入的数据
u查看16位往后的代码
(1) 由其他的程序(Debug 、command 或其他程序)将可执行文件中的程序加载入内存;
(2) 设置CS:IP 指向程序的第一条要执行的指令(即程序的入口),从而使程序得以运行;
(3) 程序运行结束后,返回到加载者。
如何知道哪一条指令是程序的第一条要执行的指令?这一点,是由可执行文件中的描述信息指明的。我们知道可执行文件由描述信息和程序组成,程序来自于源程序中的汇编指令和定义的数据;描述信息则主要是编译、连接程序对源程序中相关伪指令进行处理所得到的信息。
用伪指令end描述了程序的结束和程序的入口。在编译、连接后,由“end start" 指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。这个入口地址的偏移地址部分为:10H。当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP。这样CPU就从我们希望的地址处开始执行。
总结:若要指明CPU从何处开始执行程序,只要在源程序中用“end 标号”指明就可以了。
end 标号
assume cs : code code segment #数据 start : #代码 code ends end start
在代码段中使用栈
利用栈,将程序中定义的数据逆序存放
dw 0123h,0456h , 0789h , 0abch, 0defh , 0fedh , 0cbah,0987h
思路:
程序运行时,定义的数据存放在cs:0 ~ cs:F单元中,共8个字单元。依次将这8个字单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。
首先要有一段可当作栈的内存空间。如前所述,这段空间应该由系统来分配。可以在程序中通过定义数据来取得一段空间,然后将这段空间当作栈空间来用。
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h #用dw定义16个字型数据,在程序加载后,将取得16个字的内存空间,存放这16个数据。在后面的程序中将这段空间当作栈来使用 dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 start:mov ax,cs mov ss,ax mov sp,30h #将设置栈顶ss:sp指向cs:30。栈空间为cs:0~cs:2F mov bx,0 mov cx,8 s:push cs:[bx] #将cs:[bx]中数据push到SS:SP中去 add bx,2 loop s #以上将代码段0~15单元的8个字形数据依次入栈 mov bx,0 mov cx,8 s0:pop cs:[bx] #将SS:SP中的数据pop到cs:[bx]中 add bx,2 loop s0 #以上依次出栈8个字形数据到到码段0~15单元中 mov ax,4c00h int 21h code ends end start
将cs:10 ~ cs:2F的内存空间当作栈来用,初始状态下栈为空,所以ss:sp要指向栈底,则设置ss:sp指向cs:30。
在代码段中定义了16个字型数据,它们的数值都是0。这16个字型数据的值是多少,对程序来说没有意义。我们用dw定义16个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存空间来存放它们。这段内存空间是我们所需要的,程序将它用作栈空间。
在描述dw的作用时,可以说用它定义数据,也可以说用它开辟内存空间。
将数据、代码、栈放入不同的段
在编程的时候要注意何处是数据,何处是栈,何处是代码。这样做显然有两个问题:
- 数据、栈、代码放到一个段中使程序显得混乱;
- 如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中(一个段的容量不能大千64KB,是我们在学习中所用的8086模式的限制,并不是所有的处理器都这样)。
用和定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间。
assume cs:code,ds:data,ss:stack # 分配好一个数据段内存空间 data segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ends # 分配好一个栈内存空间 stack segment dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 stack ends #代码段内存空间 code segment start:mov ax,stack mov ss,ax #ss指向stack段 mov sp,20h #设置栈顶ss:sp指向stack:20 mov ax,data mov ds,ax #ds指向data端 mov bx,0 #ds:bx指向data段中的第一个单元 mov cx,8 #循环8次 s:push [bx] add bx,2 loop s #以上将data段中的0~15单元中的8个字型数据依次入栈 mov bx,0 mov cx,8 s0:pop [bx] add bx,2 loop s0 #以上依次出栈8个字型数据到data段的0~15单元中 mov ax,4c00h int 21h code ends end start
定义多个段
定义一个段的方法和定义代码段的方法没有区别,只是对于不同的段,要有不同的段名,例如data、stack、code这三个segment。
对段地址的引用
在程序中,段名就相当于一个标号,它代表了段地址。所以指令“mov ax,data" 的含义就是将名称为"data"段的段地址送入ax。一个段中的数据的段地址可由段名代表,偏移地址就要看它在段中的位置了。
程序中“data" 段中的数据“0abch" 的地址就是: data:6 ,将它送入bx:
指令“mov ds,data"是错误的,因为8086CPU不允许将一个数值直接送入段寄存器中。程序中对段名的引用,如指令“mov ds,data"中的“data",将被编译器处理为一个表示段地址的数值。
”代码段”、“数据段”、“栈段“完全是我们的安排
在源程序中用伪指令“assume cs: code,ds: data,ss: stack"将cs、ds和ss分别和code、data、stack段相连。但实际上并没有把段的地址给cs、ds、ss。