8086汇编学习之代码段、数据段、栈段与段地址寄存器

同类学习笔记总结:
(一)、8086汇编学习之基础知识、通用寄存器、CS/IP寄存器与Debug的使用
(二)、8086汇编学习之DS寄存器、SS/SP寄存器
(三)、8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等
我们主要分析一下在单个段的程序与多个段的程序中,每个段寄存器的值是如何安排的,段的位置关系,内存大小等问题。

一、只有一个段的程序:

程序实例:
利用栈将程序中数据段中前8个word的数据按字型进行逆转。

assume cs:codeseg

codeseg segment
    dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H  ;8word=16Byte
    dw 0,0,0,0,0,0,0,0,0,0  ;栈设置的比实际要用的大10word=20Byte

start:
        mov ax,cs
        mov ss,ax
        mov sp,36   ;给栈设置初始值

        mov bx,0
        mov cx,8    ;给CX循环计数寄存器设置初值

        loop_push:
            push cs:[bx]
            add bx,2
            loop loop_push

        mov bx,0
        mov cx,8

        loop_pop:
            pop cs:[bx]
            add bx,2
            loop loop_pop

        mov ax,4C00H
        int 21H

codeseg ends

end start

对于上面这段代码的分析:
由于这段程序只有一个段,该段为代码段,但是代码段前面有一部分数据,其中第一行为数据(dw命令开辟空间以word为单位,一个数据2Byte),第二行为为栈开辟的空间,之后的便是指令。由于指令寄存器CS:IP存放第一条指令的地址,CPU根据CS:IP的值开始解析并执行指令,但是默认情况下只有一个段的程序,其第一行指令/数据的地址就是CS:IP的值,在编译连接生成可执行程序时,就将程序的入口写入可执行文件描述信息中。但是这里第一行是数据而不是指令,将数据解析成指令其程序是不能正确运行的。而这时,start标识就标识了一个地址,end伪指令与start标识结合后,编译器根据该伪指令的信息就将satrt标识的地址写入可执行文件的描述信息中,根据可执行文件描述信息获取正确的指令其实地址存入CS:IP寄存器,那么CPU读取CS:IP时就能从正确的入口地址读取解析并执行指令了。

这里写图片描述
上图为例子程序的debug调试结果与对应的寄存器内存图,CS:IP初始值为start的标识地址(IP不为0,因为有数据段、栈段存在于代码段),这就将我们CS:IP=CS:0为程序入口地址的传统观念就打破,在指令前存在数据总是与指令存在于一个段显得格格不入。
我们可以看到当数据段、代码段、栈段存在于一个段时,会显得比较混乱,并且如果数据+栈+代码的量大于64K时就不能放在一个段中,所以我们将数据、代码、栈分别置于不同的段显得尤为迫切。

最终运行结果:
这里写图片描述

二、多个段的程序:

如何将code、data、stack置于不同段。其实很简单,我们再拿一个例子来说明,例子程序下:
目的:通过栈将0:0~0:F的值覆盖到给定数据段(程序中的数据段)的位置

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  ;36个字节的栈
stack ends
;代码段
code segment
    start:
    ;设置数据段与栈段寄存器
    mov ax,data
    mov ds,ax ;设置数据段的段地址
    mov ax,stack
    mov ss,ax ;设置栈段的栈顶地址
    mov sp,20H

    mov bx,0
    mov cx,8

    mov ax,0
    mov es,ax

    push_pop_loop:
        push es:[bx]
        pop ds:[bx]
        add bx,2
    loop push_pop_loop

    mov ax,4C00H
    int 21H
code ends
end start

分析:段名相当于一个标号,它标识(标记)了该段的段地址,所以mov ax,stack、mov ax,data的含义就是:将栈段/或者数据段的段地址送入ax寄存器中,在mov ss,ax、mov ds,ax设置SS寄存器与DS寄存器,CPU就能识别内存中的一系列二进制哪些是数据、哪些是栈段、哪些是指令。
而在code段用end start标识CS:IP从start开始为程序的入口且是指令,这个入口在编译器下被编译器将伪指令end解析,然后将start的地址存放到文件的描述信息中去,可执行程序在被加载到内存中去时先设置CS:IP,那么CPU就从CS:IP即我们设置的start开始执行执行程序指令。对于这种情况,我们可以看到CS:0就是指令首地址,而不是只有一个段时的数据首地址,数据首地址在ds:0开始的地址,栈在ss:sp,这是,我们发现cs、ss、ds的特点意义显示了出来。

具体的寄存器的值与内存分布数据存储如下图所示:

这里写图片描述

测试结果:
这里写图片描述

(1)、关于一个段的大小问题:

一个段的数据的大小为N个字节,那么程序在加载的过程中为该段分配的内存地址大小为:((N-1)/16+1)*16。例如:

某code segment大小为16字节,那么它的装载占有空间为:16-1=15 ==> 15/16=0 ==> 0+1=1 ==> 1*16=16
某stack segment大小为15字节,那么它的装载占有空间为:15-1=14 ==> 14/16=0 ==> 0+1=1 ==> 1*16=16
某data segment大小为17字节,那么它的装载占有空间为:17-1=16 ==> 16/16=1 ==> 1+1=2 ==> 2*16=32
小总结:实际大小为N,装载大小为M(M=n*16),则:M-16<=N<=M
所以说一个程序加载到内存以后,它所占用的总内存一定是16字节的倍数。

(2)关于end start:

如果修改最后一行的end start为end。因为start只是标识地址,而end start则是告诉编译器start是入口,如果修改最后一行的end start为end的话,那么编译器就不会理睬start,将默认的入口地址写入可执行文件描述信息。而默认的入口地址就是codeseg+dataseg+stackseg三段地址中最前面的地址(低地址)如果代码段(code)在我们编码时本来就在第一段,那么不加end start,编译器将首段地址(cs)写入可执行文件描述信息。但是如果stack/data段是第一段,那写入可执行文件描述信息的地址就是(ss)/(ds),将数据解析成指令,程序肯定是无法正常运行的,所以只有代码段在前面才能正确运行,因此为了不出现默认与实际编程段顺序安排偏离的情况,我们就要记得设置start与end start。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值