【汇编语言】[BX]和loop指令(四)—— 汇编语言中的段前缀与内存保护:原理与应用解析

文章目录

  • 前言
  • [1. 段前缀](#1. 段前缀)
    • [1.1 示例演示](#1.1 示例演示)
    • [1.2 总结](#1.2 总结)
  • [2. 一段安全的空间](#2. 一段安全的空间)
    • [2.1 存在的问题](#2.1 存在的问题)
    • [2.2 示例演示](#2.2 示例演示)
      • [2.2.1 编译、链接、加载程序](#2.2.1 编译、链接、加载程序)
      • [2.2.2 运行程序](#2.2.2 运行程序)
    • [2.3 总结](#2.3 总结)
  • [3. 段前缀的使用](#3. 段前缀的使用)
    • [3.1 问题引入](#3.1 问题引入)
    • [3.2 分析问题](#3.2 分析问题)
    • [3.3 代码实现](#3.3 代码实现)
    • [3.4 程序的改进](#3.4 程序的改进)
      • [3.4.1 分析](#3.4.1 分析)
      • [3.4.2 代码实现](#3.4.2 代码实现)
  • 结语

前言

📌

汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。

本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。

1. 段前缀

指令"mov ax,[bx]"中,内存单元的偏移地址由bx给出,而段地址默认在 ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:

1.1 示例演示

(1)mov ax,ds:[bx]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。

(2)mov ax,cs:[bx]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在 bx中,段地址在cs中。

(3)mov ax,ss:[bx]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ss中。

(4)mov ax,es:[bx]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在es中。

(5)mov ax,ss:[0]

将一个内存单元的内容送入ax,i这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在ss中。

(6)mov ax,cs:[0]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为0,段地址在cs中。

1.2 总结

这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的"ds:"、"cs:"、"ss:"或"es:",在汇编语言中称为段前缀。

2. 一段安全的空间

2.1 存在的问题

在8086式中,随意向一段内存空间写入内容是很危险的,因为这段空间中可能存放着重要的系统数据或代码。比如下面的指令:

assembly 复制代码
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al

我们以前在Debug中,为了讲解上的方便,写过类似的指令。但这种做法是不合理的,因为之前我们并没有论证过1000:0中是否存放着重要的系统数据或代码。如果1000:0中存放着重要的系统数据或代码,"mov ds:[0],al"将其改写,将引发错误。

2.2 示例演示

比如下面的程序。

assembly 复制代码
assume cs:code
code segment

	mov ax,0
	mov ds,ax
	mov ds:[26h],ax
	
	mov ax,4c00h
	int 2lh

code ends

end

2.2.1 编译、链接、加载程序

将源程序编辑为p7.asm,编译、连接后生成p7.exe,用Debug加载,跟踪它的运行,如下图所示。

上图中,我们可以看到,源程序中的"mov ds:[26h],ax"被 masm 翻译为机器码"a3 26 00",而 Debug将这个机器码解释为"mov [0026],ax"。

可见,汇编源程序中的汇编指令"mov ds:[26h],ax"和 Debug 中的汇编指令"mov [0026],ax"同义。

2.2.2 运行程序

然后,我们看一下"mov [0026],ax"的执行结果,如下图所示。

上图中,是在 windows 2000 的 DOS 方式中,在 Debug 里执行"mov [0026],ax"的结果。

如果在实模式(即纯DOS方式)下执行程序p7.exe,将会引起死机。产生这种结果的原因是0:0026处存放着重要的系统数据,而"mov [0026],ax"将其改写。

2.3 总结

可见,在不能确定一段内存空间中是否存放着重要的数据或代码的时候,不能随意向其中写入内容。

不要忘记,我们是在操作系统的环境中工作,操作系统管理所有的资源,也包括内存。果我们需要向内存空间写入数据的话,要使用操作系统给我们分配的空间,而不应直接用地址任意指定内存单元,向里面写入。后面的内容我们会对"使用操作系统给我们分配的空间"有所认识。

但是,同样不能忘记,我们正在学习的是汇编语言,要通过它来获得底层的编程体验,理解计算机底层的基本工作机理。所以我们尽量直接对硬件编程,而不去理会操作系统。

这时,我们似乎面临一种选择,是在操作系统中安全、规矩地编程,还是自由、直接地用汇编语言去操作真实的硬件,了解那些早已被层层系统软件掩盖的真相?在大部分的情况下,我们选择后者,除非我们就是在学习操作系统本身的内容。

注意,我们在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编语言去操作真实的硬件,因为运行在CPU 实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。但在 Windows 2000、Unix 这些运行于 CPU 保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了。

在后面的课程中,我们需要直接向内存中写入内容,可我们又不希望发生上面图片中的那种情况。所以要找到一段安全的空间供我们使用。在一般的PC机中,DOS方式下,DOS 和其他合法的程序一般都不会使用0:200~0:2ff(00200h~002ffh)的 256 个字节的空间。所以,我们使用这段空间是安全的。不过为了谨慎起见,在进入DOS后,我们可以先用Debug查看一下,如果0:200~0:2ff单元的内容都是0的话,则证明DOS和其他合法的程序没有使用这里。

为什么DOS和其他合法的程序一般都不会使用0:200~0:2ff这段空间?我们将在以后的内容中讨论这个问题。




好了,我们总结一下:

(1)我们需要直接向一段内存中写入内容。

(2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发错误。

(3)DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码。

(4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2f这段空间。

3. 段前缀的使用

3.1 问题引入

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

3.2 分析问题

分析一下。

(1)0:200~0:20b单元等同于 0020:0~0020:b单元,它们描述的是同一段内存空间。

(2)复制的过程应用循环实现,简要描述如下。

初始化:

X=0

循环12次:

将ffff:x单元中的数据送入0020:x(需要用一个寄存器中转)

X=X+1

(3)在循环中,源始单元ffff:X和目标单元 0020:X的偏移地址X是变量。我们用 bx来存放。

(4)将0:200~0:20b用0020:0~0020:b 描述,就是为了使目标单元的偏移地址和源始单元的偏移地址从同一数值0开始。

3.3 代码实现

程序如下。

assembly 复制代码
assume cs:code

code segment
	mov bx,0			;(bx)=0,偏移地址从0开始
	mov cx,12			;(cx)=12,循环12次


s:	mov ax,0ffffh
	mov ds,ax			;(ds)=0ffffh
	mov dl,[bx]			;(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl


	mov ax,0020h
	mov ds,ax			;(ds)=0020h
	mov [bx],dl			;((ds)*16+(bx))=(dl),将中d1的数据送入0020:bx

	inc bx				;(bx)=(bx)+1
	loop s

	mov ax,4c00h
	int 2lh

code ends

end

3.4 程序的改进

3.4.1 分析

因源始单元ffff:X和目标单元0020:X相距大于64KB,在不同的64KB段里,上面的程序中,每次循环要设置两次ds。这样做是正确的,但是效率不高。我们可以使用两个段寄存器分别存放源始单元:ffffX和目标单元0020:X的段地址,这样就可以省略循环中需要重复做12次的设置ds的程序段。

3.4.2 代码实现

改进的程序如下。

assembly 复制代码
assume cs:code

code segment
	mov ax,0ffffh
	mov ds,ax			;(ds)=0ffffh
	
	mov ax,0020h
	mov es,aX			;(es)=0020h
	
	mov bx,0			;(bx)=0,此时ds:bx指向ffff:0,es:bx指向0020:0
	
	mov cx,12			;(cx)=12,循环12次
	
s:	mov dl,[bx]			;(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入d1
	mov es:[bx],dl		;((es)*16+(bx))=(dl),将dl中的数据送入0020:bx
	inc bx				;(bx)=(bx)+1
	1oop s

	mov ax,4c00h
	int 21h

code ends

end

改进后的程序中,使用es存放目标空间0020:0~0020:b的段地址,用ds存放源始空间ffff:0~ffff:b的段地址。在访问内存单元的指令"mov es:[bx],al"中,显式地用段前缀"es:"给出单元的段地址,这样就不必在循环中重复设置ds。

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

相关推荐
得物技术19 分钟前
MySQL单表为何别超2000万行?揭秘B+树与16KB页的生死博弈|得物技术
数据库·后端·mysql
isysc144 分钟前
面了一个校招生,竟然说我是老古董
java·后端·面试
黄林晴3 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我3 小时前
flutter 之真手势冲突处理
android·flutter
法的空间4 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止4 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭4 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
道可到4 小时前
Java 反射现代实践速查表(JDK 11+/17+)
java
AI小云4 小时前
【机器学习与实战】回归分析与预测:线性回归-03-损失函数与梯度下降
机器学习
jctech4 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源