汇编学习笔记(5) -- 包含多个段的程序
前文说过,0:200 ~ 0:2FF是相对安全的段
但这段空间容量只有256个字节
如果我们需要的空间超过256个字节该怎么办?
在操作系统的环境中,合法地通过操作系统取得的空间都是安全的
因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间相冲突
在操作系统允许的情况下,程序可以取得任意容量的空间
主要的办法是:
加载程序的时候为程序分配空间
我们若要一个程序在被加载的时候取得所需的空间则
必须要在源程序中做出说明
我们通过在源程序中定义段来进行内存空间的获取。
上面是从内存空间获取的角度上,谈定义段的问题
我们再从程序规划的角度来谈一下定义段的问题。
大多数有用的程序,都要处理数据,使用栈空间
当然也都必须有指令
为了程序设计上的清晰和方便,我们一般也都定义不同的段来存放它们。
在代码段中使用数据
考虑这样一个问题,编程计算以下8个数据的和,结果存在ax寄存器中:
0123h、0456h、0789h、Oabch、Odefh、Ofedh、Ocbah、0987h
在之前,我们都是累加某些内存单元中的数据,并不关心数据本身
可现在要累加的就是已经给定了数值的数据
我们可以将它们一个一个地加到ax寄存器中
但是,我们希望可以用循环的方法来进行累加
所以在累加前,要将这些数据存储在一组地址连续的内存单元中。
如何将这些数据存储在一组地址连续的内存单元中呢?
我们可以用指令一个一个地将它们送入地址连续的内存单元中
可是这样又有一个问题,到哪里去找这段内存空间呢?
从规范的角度来讲,我们是不能自己随便决定哪段空间可以使用的,应该让系统来为我们分配
我们可以在程序中,定义我们希望处理的数据
这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中
当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中
与此同时,我们要处理的数据也就自然而然地获得了存储空间。
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
mov ax,4c00h int 21h code ends end
程序第一行中的“dw”的含义是定义字型数据。
在这里,使用dw定义了8个字型数据(数据之间以逗号分隔)
它们所占的内存空间的大小为16个字节
程序中的指令就要对这8个数据进行累加,这8个数据在代码段中
程序在运行的时候CS中存放代码段的段地址,所以可以从CS中得到它们的段地址
它们的偏移地址是为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 指向下一个数据所在的字单元。
编译连接 为1.exe,debug中查看
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200919193146508-266347505.png)
u命令查看的指令和源程序中的不一样
因为在最前面的不是指令,而是dw定义的数据
所以前16个字节是dw定义的数据,之后才是我们的程序
所以我们从076c:10后开始查看,跳过dw定义的数据
![](https://img2020.cnblogs.com/blog/2114977/202009/2114977-20200919193234062-377594734.png)
但是这样怎么执行程序中的指令呢?
用debug加载后,可以将ip设置为10h
从而使cs:ip指向程序中的第一条指令
但这样就只能用debug执行程序了
所以修改代码
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h start: 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 start
加了两个start
end后面也有个start
end除了通知编译器程序结束外
还可以通知编译器程序的入口在什么地方
上面程序中,我们用end指明了程序的入口在标号start处
所以我们就有了一个框架
assume cs:code code segment 数据 start: 代码 mov ax,4c00h int 21h code ends end start
在代码段中使用栈
完成下面的程序,利用栈,将程序中定义的数据逆序存放
assume cs:code code segment dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ??? code ends end start
思路大致如下:
程序运行时,定义的数据存放在cs:0 ~cs:F 单元中
一共8个字单元
依次将这8个字单元中的数据入栈
再依次出栈这8个字单元中
从而实现数据的逆序存放
问题来了
我们需要一段可以当作栈的 安全的 空间
这段空间应该是由系统来分配
可以通过定义数据来取得一段空间
然后将这段空间当作栈空间来用
assume cs:code code 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 code ends end start
注意,这里先定义了8个字节数据(或者说是8个字)
后来又定义了16个字节数据,一共24个字节数据
一个字节数据就是两个字节
一个字节占8位,也就是16进制的00 00
一个内存单元就是一个字节
所以24个字占了48(16进制是30个)个内存单元
由于cs:10 - cs:2F 这段栈空间为空,所以ss:sp指向栈底
所以第一个程序的位置在 cs:30
将数据代码栈放入不同的段
由于将数据和栈放到一个段中容易使程序显得混乱
并且一个段只有64kb(因为8086是16位的,其它处理器不一定)
所以应该使用多个段存放数据、代码和栈
以下代码和上面的代码功能一样,利用栈,将程序中定义的数据逆序存放
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 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 end end start
说明:
(1)定义多个段的方法
定义一个段的方法和前面一样
只是对于不同的段,要有不同的段名。
(2)对段地址的引用
现在,程序中有多个段了,如何访问段中的数据呢?当然要通过地址,而地址是分为
两部分的,即段地址和偏移地址。如何指明要访问的数据的段地址呢?在程序中,段名就
相当于一个标号,它代表了段地址。所以指令“mov ax,data”的含义就是将名称为
“data”的段的段地址送入ax。一个段中的数据的段地址可由段名代表,偏移地址就要看
它在段中的位置了。程序中“data”段中的数据“Oabch”的地址就是:data:6。要将它送
入bx中,就要用如下的代码:
mov ax,data mov ds,ax mov bx,ds:[6]
不能用:
mov ax,data mov ds,ax mov bx,ds:[6]
因为8086不允许直接将一个数值直接送入段寄存器中
(3)“代码段” 、“数据段”、“栈段” 完全是我们的安排
我们在源程序中为这 3个段起了具有含义的名称
用来放数据的段我们将其命名为“data”,
用来放代码的段我们将其命名为“code"
用作栈空间的段命名为“stack”
我们这样命名,仅仅是为了使程序便于阅读。
这些名称同“start” 、“s”、“s0” 等标号一样,仅在源程序中存在,CPU并不知道它们
我们在源程序中 用伪指令“assume cs:code, ds:data, ss:stack ”
将cs、ds 和ss分别和code、data、 stack 段相连。
这样做了之后,CPU也不会将cs指向code, : ds指向data,ss指向stack
assume 是伪指令,是由编译器执行的,也是仅在源程序中存在的信息,CPU并不知道它们
我们不必深究assume的作用,只要知道需要用它将你定义的具有一定用途的段和相关的寄存器联系起来就可以了
若要 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)来存放data段中数据的偏移地址
总之,CPU处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间
完全是靠程序中具体的汇编指令
和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的
参考: 王爽 - 汇编语言 和 小甲鱼零基础汇编