【汇编语言】汇编语言程序
文章目录
- 【汇编语言】汇编语言程序
- 前言
- 一、用汇编语言写的源程序
- 汇编语言编写程序的工作过程
- 程序中的三种伪指令
- 源程序编译链接后变为机器码
- 汇编程序的结构
- 如何写出一个程序来
- 程序中可能的错误
- 二、由源程序到程序运行
- 由源程序到执行可执行文件的过程
- 编译
- 提示语法错误
- 连接
- 执行可执行程序
- 小结
- 三、运行及跟踪
- Debug装载程序
- 用Debug 单步执行
- P命令和G命令
- 执行的不同方式
- 在这里插入图片描述
- 四、[……]和(……)
- [……]与(……)的约定
- 符号idata表示常量
- 五、Loop指令
- 用Loop指令编程实例
- t命令和p命令的区别
- 循环是否越界
- 六、段前缀的使用
- 引入段前缀:一个“异常”现象及对策
- 访问连续内存单元---loop和[bx]联手
- 段前缀的使用
- 七、在代码段中使用数据
- 应用案例
- 八、在代码段中使用栈
- 在代码段中使用栈:以数据逆序存放为例
- 数据逆序存放程序
- 九、将数据、代码、栈放入不同段
- 总结
前言
本篇文章将详细讲到用汇编语言写的源程序,由源程序到程序运行的过程,用Debug跟踪程序的执行,[……]和(……)的区别,Loop指令的详细介绍和使用,段前缀的使用,如何在代码段中使用数据,在代码段中使用栈以及将数据,代码,栈放入不同段。
一、用汇编语言写的源程序
汇编语言编写程序的工作过程
汇编程序:包含汇编指令和伪指令的文本。
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end
程序中的三种伪指令
源程序编译链接后变为机器码
汇编程序的结构
- 在Debug中直接写入指令编写的汇编程序:
1、适用于功能简单、短小精悍的程序
2、只需要包含汇编指令即可 - 单独编写成源文件后再编译为可执行文件的程序:
1、适用于编写大程序
2、需要包括汇编指令,还要有指导编译器工作的伪指令
3、源程序由一些段构成,这些段存放代码、数据,或将某个段当作栈空间 - ; —注释
assume cs:code,ds:data,ss:stack
data segment
dw 0123H,0456H,0789H,0abcH,0defH
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
mov ax,stack
mov ss,ax
mov sp,20h ;设置栈段
mov ax,data
mov ds,ax ;设置数据段
mov bx,0
mov cx,8
s: push [bx]
add bx,2
loop s
…
code ends
end
代码段不用设置。
如何写出一个程序来
例:编程求2^3。
1、定义一个段
2、实现处理任务
3、指出程序在何处结束
4、段与段寄存器关联
5、加上程序返回代码
1、
abc segment
abc ends
2、
abc segmentmov ax,2
add ax,ax
add ax,axabc ends
3、
abc segmentmov ax,2
add ax,ax
add ax,axabc ends
end
4、
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,axabc ends
end
5、
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,axmov ax 4c00h
int 21habc ends
end
程序中可能的错误
- 语法错误
程序在编译时被编译器发现的错误;
容易发现下面程序中错误
aume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,sx
end
1.assume写错2.没有段结束3.没有程序结束
- 逻辑错误
程序在编译时不能表现出来的、在运行时发生的错误;
不容易发现下面程序中的错误
求2^3
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,bx ;寄存器写错
mov ax,4c10H;结束应为4c00h
int 21H
abc ends
end
二、由源程序到程序运行
由源程序到执行可执行文件的过程
编译
目标文件(*.OBJ)是我们对一个源程序进行编译要得到的最终结果。
列表文件(*.LST)是编译器将源程序编译为目标文件的过程中产生的中间结果。
交叉引用文件(*.CRF)同列表文件一样,是编译器将源程序编译为目标文件过程中产生的中间结果。
对源程序的编译结束,编译器输出的最后两行告诉我们这个源程序没有警告错误和必须要改正的错误。
提示语法错误
两类错误:
1.Severe Errors。
2.找不到给出的源程序文件。
命令后加;以简化过程。
连接
可执行文件(.EXE)是我们对一个程序进行连接要得到的最终结果。
映像文件(.MAP)是连接程序将目标文件连接为可执行文件过程中产生的中间结果。
库文件(.LIB)里包含了一些可以调用的子程序,如果我们的程序中调用了某一个库文件中的子程序,就需要在连接的时候,将这个库文件和我们的目标文件连接到一起,生成可执行文件。
no stack segment,一个“没有栈段”的警告错误 ,可以不理会这个错误。
连接中可能会遭遇错误例:object nor found —— 找不到对象
命令后加 ; 以简化过程
执行可执行程序
我们的程序没有像显示器输出任何信息。程序只是做了一些将数据送入寄存器和加法的操作,而这些事情,我们不可能从显示屏上看出来。
程序执行完成后,返回,屏幕上再次出现操作系统的提示符。
小结
三、运行及跟踪
Debug装载程序
程序被装入内存的什么地方?
- 程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为 0 ,则程序所在的内存区的地址为:DS:0。
- 这个内存区的前256 个字节存PSP,DOS用来和程序进行通信。
- 从 256字节处向后的空间存放的是程序,CS的值为DS+10H。
- 程序加载后,CX中存放代码的长度(字节)。
用Debug 单步执行
P命令和G命令
- 继续命令P(Proceed):类似T命令,逐条执行指令、显示结果。但遇子程序、中断等时,直接执行,然后显示结果。
- 运行命令G(Go):从指定地址处开始运行程序,直到遇到断点或者程序正常结束。
执行的不同方式
四、[……]和(……)
[……]与(……)的约定
[…]——(汇编语法规定)表示一个内存单元
指令 | 段地址 | 偏移地址 | 操作单位 |
mov ax,[0] | 在DS中 | 在[0]中 | 字 |
mov al,[0] | 在DS中 | 在[0]中 | 字节 |
mov ax,[bx] | 在DS中 | 在[bx]中 | 字 |
mov ax,[0] | 在DS中 | 在[bx]中 | 字节 |
(…)——(为学习方便做出的约定)表示一个内存单元或寄存器中的内容;…只能用寄存器及物理地址
描述对象 | 描述方法 | 描述对象 | 描述方法 |
ax中的内容为0010H | (ax)=0010H | 2000:1000处的内存为0010H | (21000H)=0010H |
mov ax,[2]的功能 | (ax)=((ds)*16+2) | mov [2],ax的功能 | ((ds)*16+2)=(ax) |
add ax,2 的功能 | (ax)=(ax)+2 | add ax,bx的功能 | (ax)=(ax)+(bx) |
push ax的功能 | (sp) = (sp)-2((ss)*16+(sp))=(ax) | pop ax的功能 | (ax)=((ss)*16+(sp) |
((ss)*16+(sp))=(ax) | (sp)=(sp)+2 |
符号idata表示常量
例:
mov ax,[idata]:代表mov ax,[1]、mov ax,[2]、mov ax,[3]…
mov bx,idata:代表mov bx,1、mov bx,2、mov bx,3…
mov ds,idata:代表mov ds,1、mov ds,2…(都是非法指令)
五、Loop指令
-
功能:实现循环(计数型循环)
指令的格式:
loop 标号 -
CPU 执行loop指令时要进行的操作
① (cx)=(cx)-1;
② 判断cx中的值
不为零则转至标号处执行程序如果为零则向下执行。 -
要求
cx 中要提前存放循环次数,因为(cx)影响着loop指令的执行结果
要定义一个标号
例:本程序功能2->4->8->16->32->…
用Loop指令编程实例
编程计算2^16
用cx和loop 指令相配合实现循环功能的三个要点:
(1)在cx中存放循环次数;
(2)用标号指定循环开始的位置;
(3)在标号和loop 指令的中间,写上要循环执行的程序段(循环体)。
t命令和p命令的区别
继续命令P(Proceed):类似T命令,逐条执行指令、显示结果。但遇子程序、中断等时,直接执行,然后显示结果。
运行命令G(Go):从指定地址处开始运行程序,直到遇到断点或者程序正常结束;G命令还可以指定执行到的代码地址。
循环是否越界
问题:计算ffff:0006字节单元中的数乘以3,结果存储在dx中
六、段前缀的使用
引入段前缀:一个“异常”现象及对策
Debug 中,mov al,[0]的功能是–将DS:0存储单元的值传给AL。
编译(masm)并连接(link)后…
编译好的程序中,mov al,[0]变成了将常量0传给AL。
-
对策:
在[idata]前显式地写上段寄存器
mov ax,2000h
mov ds,ax
mov bx,0
mov al,ds:[bx]
mov ax,2000h
mov ds,ax
mov al,ds:[0] -
小结(在程序中):
mov al,[0]:(al)=0,同mov al,0
mov al,ds:[0]:(al)=((ds)*16+0)
mov al,[bx]:(al)=((ds)*16+(bx))
mov al,ds:[bx]:与mov al,[bx]相同
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”或“es:”,在汇编语言中称为段前缀。
访问连续内存单元—loop和[bx]联手
问题:计算ffff:0~ffff:b字节单元中的数据的和,结果存储在dx中。
- 分析:
(1)运算后的结果是否会超出 dx 所能存储的范围?
ffff:0~ffff:b内存单元中的数据是字节型数据,范围在0~255之间,12个
这样的数据相加,结果不会大于 65535,可以在dx中存放下。
(2)是否可以将 ffff:0~ffff:b中的数据直接累加到dx中?
add dx, ds:[addr] ;(dx)=(dx)+?
期望:取出内存中的8位数据进行相加
实际:取出的是内存中的16位数据
(3)是否可以将 ffff:0~ffff:b中的数据直接累加到dl中?
add dl, ds:[addr] ;(dl)=(dl)+?
期望:取出内存中的8位数据相加
实际:取出的是内存中的8位数据,但很有可能造成进位丢失。 - 对策:取出8位数据,加到16位的寄存器。
mov al, ds:[addr]
mov ah, 0
add dx, ax
这样写的程序显然是太笨了,如和改进?
改进:用loop循环 - 方法:
循环次数由CX控制
循环中要访问的内存单元的偏移地址放到 bx中,随循环递增,访问连续的内存单元。
段前缀的使用
问题:将内存ffff:0~ ffff:b中的数据拷贝到 0:200~0:20b单元中。
七、在代码段中使用数据
上面说到的将内存ffff:0~ ffff:b中的数据拷贝到 0:200~0:20b单元中。这样做是非常危险的。
-
问题:
程序中直接写地址,危险!
安全”位置存放数据,存哪里? -
对策:
在程序的段中存放数据,运行时由操作系统分配空间。
段的类别:数据段、代码段、栈段
各种段中均可以有数据
可以在单个的段中安置,也可以将数据、代码、栈放入不同的段中
应用案例
- 问题:编程计算以下8个数据的和,结果存在ax 寄存器中
0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
在代码段中定义数据
dw: define word,定义字型数据
db 定义一个字节
dd 定义一个双字
问题:
真正的代码从0010开始
解决问题的关键:让数据从CS:0000开始,让代码从CS:0010开始!
改进:
效果:程序加载后,CS:IP指向要执行的第一条指令在start处!
八、在代码段中使用栈
在代码段中使用栈:以数据逆序存放为例
问题:完成下面的程序,利用栈,将程序中定义的数据逆序存放。
- 思路:
程序运行时,定义的数据存放在cs:0~cs:F单元中,共8个字单元。
依次将这8个字单元中的数据入栈,然后再依次出栈到这 8 个字单元中,从而实现数据的逆序存放。
栈需要的内存空间,在程序中通过定义“空”数据来取得。
数据逆序存放程序
九、将数据、代码、栈放入不同段
- 评价此方法:
特点:数据,栈,代码都在一个段。
问题:程序显得混乱,编程和阅读时都要注意何处是数据,何处是栈,何处是代码。
只应用于要处理的数据很少,用到的栈空间也小,加上没有多长的代码。
对策:数据,栈,和代码放在不同段。
总结
到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦!