加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 一个小细节
    • 段前缀
    • 一段安全的存储空间
    • 段前缀的使用
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

简单聊聊什么是段

2023/02/20
2013
阅读需 9 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

这是 x86 汇编连载系列第六篇文章,前五篇文章见文末。

一个小细节

从开始到现在我们接触到了两种汇编指令的编写方式,一种是在 dosbox 上的 debug 模式下通过 debug -a 的方式来编写,如下图所示:

这种方式能让你在 dosbox 中直接编写汇编代码,简单直接,不需要写伪指令,方便快捷。

还有一种方式需要我们在 dosbox 外部编写汇编源文件,源文件中的代码经由 MASM 汇编编译、LINK 指令链接后一种,如下图所示:

乍一看这两种方式编写的汇编源代码应该都能正确的执行,于是我们分别用两种不同的方式写下了

 

这几条指令。这三条指令很简单,我们的目的很明确,我们想把内存地址为 ds:[1], ds[2], ds[3] 的数据分别送入 al,bl,cl 寄存器。下面我们执行一下:

使用 debug 方式的截图如下:

如图所示,在使用 debug 方式中,"[ ]" 内的指令会被直接当做内存地址进行 mov。

使用 masm 编译器方式的截图如下:

如图所示,当我们使用 MASM 进行编译和链接后,[ ] 号中的 1 会被直接编译为数值 01,而不是 [1] 这个内存地址。这个是编译规定,大家要记住这个细节

也就是说,诚如  [idata] 这种形式,debug 和 masm 汇编器对其有不同的解释,debug 认为 [1] 中的就是一个内存地址,而 masm 认为 [1] 就是一个 idata 立即数。

话又说回来了,如果我们想在汇编源文件中表示内存地址,该怎么办呢?

这就需要借助一个寄存器了 --- bx

比如下面这段汇编代码

首先将 ds 寄存器设置为 2000 ,也就是 ds = 2000h,然后把 0 放入 bx 中,最后的 mov al,[bx] 就会默认从内存地址ds:[0] 处提取数据进行移动。

这样当然是可以的,不过仍然比较繁琐,我们不想要每次 mov 内存数据还要经过 bx 中转,我们想要像 debug 那样直接 mov ,该怎么做呢?其实也比较简单,直接显示指出段寄存器:[内存偏移]即可

看下面这段汇编代码:

如果你想要通过 MASM 的方式来取得 ds:[0] 处内存地址的话,就需要显示指定段寄存器,如果没有显示指定的话,默认按照 01 数值来处理。

所以我们可以总结一下上面所探讨的内容(基于 MASM 汇编编译器下)

    mov al,[0] :将数值 0 送入 al 寄存器中,(al) = 0。mov al,ds:[0]:(al) = ((ds) * 16 + 0) , 将内存单元中的数据送入 al 中,段地址为 ds。mov al,[bx]:(al) = ((ds) * 16 + bx) , 将内存单元的数据送入 al 中,段地址为 ds。mov al,ds:[bx] :和 mov al,[bx] 含义相同

还可以更为精简的总结一点:

MASM 汇编编译器会将 [idata] 编译为 idata,若想访问内存地址,则必须显示指定段地址或者使用 bx 进行中转

上面这些内容在本人其他文章中已经涉及到了,不过讲的不太细致,这篇文章算是细致的讲了下。

段前缀

上面的内容多次提到了一个名词就是 ,段所表示的其实也是一段内存空间,不过这种划分的方式是由 CPU 来决定的,内存并不会分为多个段。段的划分是主要为了 CPU 能够更方便的寻址,要想寻找段内的每个地址和数据,都需要有两个概念:段基址和段内偏移

在汇编语言中,一般通过 [bx] 来给出偏移地址,它的段基础在 ds 中,ds 是默认的段寄存器。

不过,只有一个 ds 段显然是无法应对复杂程序的寻址方式的,所以还可能会有多个段,如下所示:

上面列举了四种不同的段寄存器和寻址方式。

第一条指令把段基址为 ds,偏移地址为 bx 的内存地址的内容送入 ax ,长度为 2 个字节单元,也就是一个字,16 位。

第二条指令把段基址为 cs,偏移地址为 bx 的内存地址的内容送入 ax ,长度为 2 个字节单元,一个字,16 位。

第三条指令把段基址为 ss,偏移地址为 bx 的内存地址的内容送入 ax ,长度为 2 个字节单元,一个字,16 位。

第四条指令把段基址为 es,偏移地址为 bx 的内存地址的内容送入 ax ,长度为 2 个字节单元,一个字,16 位。

由于 ds、cs、ss、es 都是显示指出的,所以 ds、cs、ss、es 又被称为段前缀

一段安全的存储空间

我们写出的程序经过编译连接后,会由操作系统分配内存空间,我们并不知道哪些内存空间是有用的,哪些内存空间是保留的,哪些内存空间是可以使用的,由于有些内存空间存储着重要的系统数据或代码,所以我们最好不要随意的向内存空间写入数据,这是很危险的,比如下面这几条指令:

之前为了方便,我们没有判断 1000:[0] 这个内存空间有没有存放重要代码或数据就将数据写入其中,这种做法是错误的,如果 1000:[0] 处刚好存放着文件系统的起始代码,那么 mov ds:[0],al 就会将其改写,引发系统崩溃。

再看一段程序:

我们编写好代码后,进行编译链接,debug 这段代码:

当我们执行完 mov ds:[26h],ax 后,说什么也执行不下去了。

并不是我不想执行了,而是系统不让我执行了,因为系统死机了。。。。。。大家可以试试。

所以,在不清楚这段内存空间是干什么的时候,最不好要随意向内存空间写入数据。由于内存空间是由操作系统直接分配的,所以要想向一段内存空间写入数据的话,要使用操作系统给我们分配的内存空间。

那么话又说回来了,操作系统给我们分配了哪些空间可以安全的写入数据呢?

在一般的 PC 机,DOS 方式下,DOS 和其他合法程序一般都不会使用 0:200 ~ 0:2ff(00200h ~ 002ffh) 这段 256 个字节的空间,可以认为这段内存区域是安全的。

不过为了谨慎起见,我们写入的时候,最好使用 debug -d 来看一下这段内存区域有没有存储数据。

段前缀的使用

考虑一个问题,如何将内存 ffff:0 ~ ffff:b 单元中的数据复制到 0:200 ~ 0:20b 单元中?

需要考虑以下几点:

    0:200 ~ 0:20b 其实就是 200:0 ~ 200:b ,这就是对同一段内存空间的两种不同的描述。上面是两段不同的内存空间,所以需要两个段基址,通过一个寄存器 dl 来进行中转,把 ffff:0 ~ ffff:b 地址空间的数据复制到 dl 中,然后把 dl 中的数据再复制到 0:200 ~ 0:20b 中。一共复制 (b - 0) + 1 = 12 次。

开码!

从上面代码可以看到,我们显示使用了两种段前缀 ds 和 es ,这就是一个段前缀的使用案例。

我们分别将 0ffffh 和 200h 赋给了 ds 和 es 寄存器,然后设置 cx 循环次数为 12 次,s 是一个伪指令,表示循环的开始处,每个循环中都会把 0ffff:[bx] 中的数据赋值给 dl ,因为这是一个内存地址,所以使用 8 位寄存器就可以接收,然后将 dl 中的数据赋值给 200:[bx] 处,再执行循环。

相关推荐

电子产业图谱

cxuan 写的文章还不错。会分享计算机底层、计算机网络、操作系统,Java基础、框架、源码等文章。