title: 第6章 包含多个段的程序
date: 2021-08-01 14:48:03
tags: 汇编语言笔记
categories: 汇编语言笔记
在工作室好哥哥们的影响下,也开始喜欢用博客记录自己的学习过程了。
https://afanbird.github.io/
个人博客的地址,由于可能不久之后会换电脑,暂且就不更新在个人博客上了(怕迁移麻烦),用CSDN更新,后面转载到个人博客上面的时候,也会方便很多。笔记是自己在学习中的汇编语言第三版书上的重点知识。之所以记录下来,也是为了自己以后复习知识的时候,能方便的复习。如有不对的地方,欢迎各位师傅指出,若有什么想法,也可以讨论交流,一起学习进步。
第6章 包含多个段的程序
前言
在操作系统的环境中,合法地通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突。在操作系统允许的情况下,程序可以取得任意容量的空间。
程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,二就是程序在执行的过程中向系统申请。在我们的课程中,不讨论第二种方法。
我们若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出说明。我们通过在源程序中定义段来进行内存空间的获取。
6.1 在代码段中使用数据
考虑这样一个问题,编程计算以下8个数据的和,结果存在ax寄存器中:
0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h
在前面的课程中,我们都是累加某些内存单元中的数据,并不关心数据本身。可现在要累加的就是已经给定了数值的数据。我们可以将它们一个一个地加到ax寄存器中,但是,我们希望可以用循环的方法来进行累加,所以在累加前,要将这些数据存储在一组地址连续的内存单元中。
如何将这些数据存储在一组地址连续的内存单元中呢?
我们可以用指令一个一个地将它们送入地址连续的内存单元中,可是这样又有一个问题,到哪里去找这段内存空间呢?
从规范的角度来讲,我们是不能自己随便决定哪段空间可以使用的,应该让系统来为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。
程序6.1
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
mov bx ,0
mov ax,0
mov cx,8
s:add ax,cs:【bx】
add bx ,2
loop s
mov ax ,4c00h
int 21h
code ends
end
解释一下,程序第一行中的“dw”的含义是定义字型数据。dw 即“define word”。在这里,使用dw定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16个字节。
程序中的指令就要对这8个数据进行累加,可这8个数据在哪里呢?由于它们在代码段中,程序在运行的时候CS 中存放代码段的段地址,所以可以从CS 中得到它们的段地址。它们的偏移地址是多少呢?因为用dw定义的数据处于代码段的最开始,所以偏移地址为0,这8个数据就在代码段的偏移0、2、4、6、8、A、C、E处。程序运行时,它们的地址就是CS:0、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指向下一个数据所在的字单元。
图6.1中,通过“DS=0B2D”,可知道程序从0B3D:0000开始存放。(前256个字节是PSP,是DOS跟程序通信的)
用u命令从0B3D:0000查看程序,却看到了一些让人读不懂的指令。
为什么没有看到程序中的指令呢?实际上用u命令从0B3D:0000查看到的也是程序中的内容,只不过不是源程序中的汇编指令所对应的机器码,而是源程序中,在汇编指令前面,用dw定义的数据。实际上,在程序中,有一个代码段,在代码段中,前面的 16个字节是用“dw”定义的数据,从第16个字节开始才是汇编指令所对应的机器码。
从图6.2和6.3中,我们可以看到程序加载到内存中后,所占内存空间的前16个单元存放在源程序中用“dw”定义的数据,后面的单元存放源程序中汇编指令所对应的机器指令。
怎样执行程序中的指令呢?用Debug 加载后,可以将IP设置为10h,从而使CS:IP指向程序中的第一条指令。然后再用t命令、p命令,或者是g命令执行。
可是这样一来,我们就必须用Debug 来执行程序。程序6.1编译、连接成可执行文件后,在系统中直接运行可能会出现问题,因为程序的入口处不是我们所希望执行的指令。如何让这个程序在编译、连接后可以在系统中直接运行呢?我们可以在源程序中指明程序的入口所在,具体做法如下。
程序6.2
assume cs:codecode segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987hstart : mov bx ,0 mov ax,0 mov cx,8 s:add ax,cs:【bx】 add bx ,2 loop s mov ax ,4c00h int 21hcode endsend start
end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。
我们在程序6.2中,用伪指令end 描述了程序的结束和程序的入口。在编译、连接后,由“end start”指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。在程序6.2生成的可执行文件中,这个入口地址的偏移地址部分为:10H。当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP。这样CPU就从我们希望的地址处开始执行。
归根结底,我们若要CPU从何处开始执行程序,只要在源程序中用“end标号”指明就可以了。
6.2 在代码段中使用栈
程序运行时,定义的数据存放在cs:0~cs:F单元中,共8个字单元。依次将这8个字单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。
问题是,我们首先要有一段可当作栈的内存空间。如前所述,这段空间应该由系统来分配。
可以在程序中通过定义数据来取得一段空间,然后将这段空间当作栈空间来用。程序如下。
程序6.3
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;用dw定义16个字型数据,在程序加载后,将取得16个字的内存空间,存放这16个数据。在后面的程序中将这段空间当作栈来使用。
start: mov ax ,cs
mov ss ,ax
mov sp,30h ;将设置栈顶ss:sp指向cs:30
mov bx,0
mov cx ,8
s:push cs:【bx】
add bx,2
loop s ;以上代码段0-15单元中的8个字节型数据依次入栈
mov bx ,0
mov cx, 8
s0: pop cs:【bx】
add bx,2
loop s0 ;依次取出栈8个字型数据到代码段0-15单元中
mov ax,4c00h
int 21h
codesg ends
end start ;指明程序的入口在start处
我们要将cs:10-cs:2F的内存空间当作栈来使用,初始状态下栈为空,所以ss:sp要指向栈底,则设置ss:sp指向cs:30。(这一块是,栈空的情况下,cs:sp会指向,栈底的下一位,因为cs:2F是栈底,所以加一,就是30咯)
在代码段中定义了16个字型数据,它们的数值都是0。这 16个字型数据的值是多少,对程序来说没有意义。
我们用dw定义16 个数据,即在程序中写入了16个字型数据,而程序在加载后,将用32个字节的内存空间来存放它们。
这段内存空间是我们所需要的,程序将它用作栈空间。可见,我们定义这些数据的最终目的是,通过它们取得一定容量的内存空间。
所以我们在描述dw 的作用时,可以说用它定义数据,也可以说用它开辟内存空间。
比如对于:dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
可以说,定义了8个字型数据,也可以说,开辟了8个字的内存空间,这段空间中每个字单元中的数据依次是:0123h、0456h、0789h、0abch、0defh、0fedh、Ocbah、0987h。因为它们最终的效果是一样的。
6.3 将数据、代码、栈放入不同的段
程序6.4:
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
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 ;结束code段
end start ;整个程序的结束,并告诉start的起始地址
对程序6.4做出说明
(1)定义多个段的方法和前面所讲的定义代码段的方法没有区别,只是对于不同的段,要有不同的段名。
(2)在程序中,段名就相当于一个标号,它代表了段地址。
一个段中的数据的段地址可由段名表示,偏移地址就要看它在段中的位置了。程序中“data”段中的数据“0abch”的地址就是:data:6.要将它送入bx中,就要用如下代码:
mov ax,data
mov ds,ax
mov bx,ds:【6】
我们不能用下面的指令:
mov ds,data
mov bx,ds:【6】
其中指令“mov ds,data”是错误的,因为8086CPU不允许将一个数值直接送入段寄存器。
程序中对段名的引用,如指令“mov ds,data”中的"data",将被编译器处理为一个表示段地址的数值。
(3)“代码段”、“数据段”、“栈段”完全是我们自己的安排
1.用assume将定义的具有一定用途的段和相关的寄存器联系起来。
2.若要CPU按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令是CPU要执行的内容。
CPU如何知道去执行它们?我们在源程序的最后用“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序中的第一条指令。
标号“start”在“code”段中,这样CPU 就将code 段中的内容当作指令来执行了。我们在code 段中,使用指令:
mov ax,stack
mov ss,ax
mov sp,20h
设置ss指向stack,设置ss:sp指向stack:20,CPU执行这些指令后,将把stack当作栈空间来用。CPU若要访问data段中的数据,则可用ds指向data段用其他的寄存器(如bx)来存放段中数据的偏移地址。
总之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的。