文章目录
- 前言
- 1. 存在的两个问题
- 2. 解决办法
- 3. 示例代码
- 3.1 程序说明
- 3.1.1 定义多个段的方法
- 3.1.2 对段地址的引用
- 3.1.3 各种段完全是我们的安排
- 4. 总结
- 结语
前言
📌
汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。
本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
1. 存在的两个问题
在前面的内容中,我们在程序中用到了数据和栈,将数据、栈和代码都放到了一个段里面。我们在编程的时候要注意何处是数据,何处是栈,何处是代码。
这样做显然有两个问题:
(1)把它们放到一个段中使程序显得混乱。
(2)前面程序中处理的数据很少,用到的栈空间也小,加上没有多长的代码,放到一个段里面没有问题。但如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中(一个段的容量不能大于 64KB,是我们在学习中所用的8086模式的限制,并不是所有的处理器都这样)。
2. 解决办法
所以,应该考虑用多个段来存放数据、代码和栈。
怎样做呢?
我们用和定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间。
3. 示例代码
具体做法如下面的程序所示,这个程序将数据、栈和代码放到了不同的段中。
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 2lh
code ends
end start
3.1 程序说明
下面对这段程序进行说明。
3.1.1 定义多个段的方法
这点,我们从程序中可明显地看出,定义一个段的方法和前面所讲的定义代码段的方法没有区别,只是对于不同的段,要有不同的段名。
3.1.2 对段地址的引用
现在,程序中有多个段了。
如何访问段中的数据呢?
当然要通过地址,而地址是分为两部分的,即段地址和偏移地址。
如何指明要访问的数据的段地址呢?
在程序中,段名就相当于一个标号,它代表了段地址。所以指令“mov ax,data
”的含义就是将名称为“data”的段的段地址送入 ax。一个段中的数据的段地址可由段名代表,偏移地址就要看它在段中的位置了。程序中“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.3 各种段完全是我们的安排
现在,我们以一个具体的程序来再次讨论一下所谓的“代码段”、“数据段”、“栈段”。在汇编源程序中,可以定义许多的段,比如在上面的示例程序中,定义了3个段,“code”、“data”和“stack”。我们可以分别安排它们存放代码、数据和栈。
那么我们如何让CPU按照我们的这种安排来执行这个程序呢?
下面来看看源程序中对这3个段所做的处理。
(1)
我们在源程序中为这3个段起了具有含义的名称。
-
用来放数据的段我们将其命名为“data”
-
用来放代码的段我们将其命名为“code”
-
用作栈空间的段命名为“stack”
这样命名了之后,CPU是否就去执行“code”段中的内容,处理“data”段中的数据,将“stack”当做栈了呢?
当然不是,我们这样命名,仅仅是为了使程序便于阅读。这些名称同“start”、“s0”等标号一样,仅在源程序中存在,CPU并不知道它们。
(2)
我们在源程序中用伪指令“assume cs:code,ds:data,ss:stack
”将cs、ds和ss分别和code、data、stack 段相连。
这样做了之后,CPU是否就会将cs指向code,ds 指向 data,ss指向 stack,从而按照我们的意图来处理这些段呢?
当然也不是,要知道assume是伪指令,是由编译器执行的,也是仅在源程序中存在的信息,CPU并不知道它们。我们不必深究assume的作用,只要知道需要用它将你定义的具有一定用途的段和相关的寄存器联系起来就可以了。
(3)
若要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)来存放 data 段中数据的偏移地址。
4. 总结
总之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的。
完全可以将之前的示例程序写成下面的样子,实现同样的功能。
assume cs:b,ds:a,ss:c
a segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
a ends
c segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
c ends
b segment
d: mov ax,c
mov ss,ax
mov sp,20h ;希望用c段当作栈空间,设置ss:sp指向c:20
mov ax,a
mov ds,ax ;希望用ds:bx访问a段中的数据,ds指向a段
mov bx,0 ;ds:bx指向a段中的第一个单元
mov cx,8
s: push [bx]
add bx,2
loop s ;以上将a段中的0~15单元中的8个字型数据依次入栈
mov bx,0
mov cx,8
s0: pop [bx]
add bx,2
loop s0 ;以上依次出栈8个字型数据到a段的0~15单元中
mov ax,4c00h
int 2lh
b ends
end d ;d处是要执行的第一条指令,即程序的入口
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!