汇编笔记[03]
汇编笔记[03]
1 用汇编语言写的源程序
程序员 -> 汇编程序 -> 编译器 -> 机器码 -> 计算机
- 伪指令
没有对应的机器码的指令,最终不被CPU所执行。 - 谁来执行伪指令?
伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。 - 程序返回(套路!):程序结束运行后,将CPU的控制权交还给使他得已运行的程序(常为DOS系统)。
1.1 程序中的三种伪指令
- 段定义
一个汇编程序是由多个段组成的,这些段用来存放代码、数据或当做栈空间来使用。
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
定义程序中的段:每个段都需要有段名
段名 segment --段的开始
…
段名 ends --段的结束 - end(不是ends)
汇编程序的结束标记。若程序结尾处不加end,编译器在编译程序时,无法知道程序在何处结束。 - assume(假设)
含义是假设某一段寄存器和程序中的某一个用segment …ends定义的段相关联 – assume cs:codesg指CS寄存器与codesg关联,将定义的codesg当做程序的代码段使用。
1.2 源程序经编译连接后变为机器码
源程序文件.asm -> 编译和连接 -> 可执行文件.exe
1.3 汇编程序的结构
- 在Debug中直接写入指令编写的汇编程序
1.适用于功能简单、短小精悍的程序
2.只需要包含汇编指令即可 - 单独编写成源文件后再编译为可执行文件的程序
1.适用于编写大程序
2.需要包括汇编指令,还要有指导编译器工作的伪指令。
3.源程序由一些段构成,这些段存放代码,数据,或将某个段当成栈空间。
4.注释
1.4 汇编程序编写
- 例:编程求
2
3
2^3
23
1.定义一个段
2.实现处理任务
3.指出程序在何时结束
4.段与段寄存器关联
5.加上程序返回的代码
1.5 程序中可能的错误
- 语法错误
1.程序在编译时被编译器发现的错误;
2.容易发现下面程序中错误 - 逻辑错误
1.程序在编译时不能表现出来的,在运行时发生的错误;
2.不容易发现下面程序中的错误。
2 由源程序到程序运行
2.1 由写出源程序到执行科执行文件的过程
程序员文件编辑 -> 源程序文件.asm -> 程序员编译 -> 目标文件.obj
-> 程序员连接 -> 可以执行文件.exe -> 程序员运行程序 -> 计算机
2.2 编辑源程序
EditPlus
2.3 编译
- 目标文件(*.OBJ)是我们对一个源程序进行编译要得到的最终结果。
- 列表文件(*.LST)是编译器将源程序编译为目标文件的过程中产生的中间结果。
- 交叉引用文件(*.CRF)同列表文件一样,是编译器将源程序编译为目标文件过程中的中间结果。
- 对源程序的编译结束,编译器输出的最后两行告诉我们这个源程序没有警告和必须要改正的错误。
2.4 提示语法错误
- 两类错误
Severe Errors
找不到所给的源程序文件
命令后加";"以简化过程
2.5 连接
- 可执行文件(.exe)是我们对一个程序进行连接要得到的最终结果。
- 映像文件(.MAP)是连接程序将目标文件连接为可执行文件过程中产生的中间结果。
- 库文件(.LIB)是包含了一些可以调用的子程序,如果我们的程序中调用了某一个库文件中的子程序,就需要在连接的时候,将这个库文件和我们的目标文件连接到一起,生成可执行文件。
- no stack segment, 一个 "没有栈段"的警告错误,可以不理会这个错误。
2.6 执行可执行程序
- 我们的程序没有向显示器输出任何信息。程序只是做了一些将数据送入寄存器和加法的操作,而这些事情,我们不可能从显示屏上看出来。
2.7 小结
源文件.asm -> 目标文件.obj -> 可执行文件.exe
3 运行及跟踪
3.1 回顾
link 命令: .obj文件转化为.exe
3.2 用Debug装载程序
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
- 3.2.1 先用-r查看一下寄存器的值
DS=075AH
CS=076AH
程序被装入内存的什么地方?
一共256(100H)字节程序段前缀(PSP),作为数据区 - 3.2.2 先用-u查看
- 3.2.3 小结
1.程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为:DS:0。
2.这个内存区的前256字节处向后的空间存放的是程序,CS的值为DS+10H。
3.从256字节处向后的空间存放的是程序,CS的值为DS+10H。
4.程序加载后,CX中存放代码的长度(字节)。
3.3 用Debug单步执行程序
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
到这步,IP的值已经指向其他地方了。
-
3.3.1 继续命令P(Proceed):
类似T命令,逐条执行指令,显示结果。但遇子程序、中断等时,直接执行,然后显示结果。
遇到中断指令,结束了。 -
3.3.2 运行命令G(Go):
从指定地址处开始运行程序,直到遇到断点或者程序正常结束。
3.4 程序执行的不同方式
- 3.4.1 在DOS中执行
程序执行的常态
DOS启动后,计算机由"命令解释器"(程序command.com)控制
运行可执行程序时,command将程序加载入内存,设置CPU的CS:IP指向程序的第一条指令(即程序的入口),使程序得以运行。
程序运行结束后,返回到"命令解释器",CPU继续运行command。 - 3.4.1 在Debug中执行
程序执行处于开发周期的运行方式;
运行Debug时,command程序加载Debug.exe,debug将程序加载入内存,程序运行结束后要返回到Debug中,使用Q命令退出Debug,将要返回到command中。
4 […]和(…)
4.1 …]的规定与(…)的约定
- 4.1.1 […] – (汇编语法规定)方括号表示一个内存单元
指令 | 段地址 | 偏移地址 | 操作单位 |
---|---|---|---|
mov ax, [0] | 在DS中 | 在[0]中 | 字 |
mov al, [0] | 在DS中 | 在[0]中 | 字节 |
mov ax, [bx] | 在DS中 | 在[bx]中 | 字 |
mov al, [bx] | 在DS中 | 在[bx]中 | 字节 |
- 4.1.2 (…) – (为学习方便做出的约定)表示一个内存单元或寄存器中的内容。
不是汇编语言的规定,而是我们方便学习的约定。
描述对象 | 描述方法 | 描述对象 | 描述方法 |
---|---|---|---|
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) 【sp -2 指的是栈顶偏移地址减2,移动;然后赋值压栈】 | pop ax的功能 | (ax) = ((ss)*16 + (sp)) (sp)=(sp)+2 |
4.2 再约定:符号idata表示常量
- 4.2.1 例
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...(都是非法指令)
4.3 案例分析
略
5 Loop指令
5.1 Loop指令
- 5.1.1 功能
实现循环(技术型循环) - 5.1.2 指令的格式
loop 标号
- 5.1.3 CPU执行loop指令时要进行的操作
1.(cx) = (cx)-1;
2.判断cx中的值
不为零则转至标号处执行程序,如果为零则向下执行。
- 5.1.4 要求
1.cx中要提前存放循环次数,因为(cx)影响着loop指令的执行结果
2.要定义一个标号
5.2 用Loop指令编程实例
- 5.2.1 任务1:编程计算 2 2 2^2 22
assume cs:code
code segment
mov ax,2
# 用2 + 2 实现2^2
add ax,ax
mov ax,4c00h
int 21h
code ends
end
- 5.2.2 任务2:编程计算 2 3 2^3 23
assume cs:code
code segment
mov ax,2
# 用2 + 2 实现2^3
add ax,ax
add ax,ax
mov ax,4c00h
int 21h
code ends
end
- 5.2.3 任务2:编程计算 2^12
assume cs:code
code segment
mov ax,2
mov cx,11
# 再做11次add ax,ax
s:add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end
用cx和loop指令相配合实现循环功能的三个要点:
1.在cx中存放循环次数;
2.用标号指定循环开始的位置;
3.在标号和loop指令中间,写上要循环执行的程序段(循环体)。
5.3 用Loop指令编程实例
- 5.3.1 问题:计算123x236,结果储存在ax中
方法:用加法实现乘法,将123连加236次
assume cs:code
code segment
mov ax,0
mov cx,236
s:add ax,123
loop s
mov ax,4c00h
int 21h
code ends
end
6 Loop指令使用再例
6.1 再例:用Loop指令编程
- 6.1.1 问题:计算ffff:0006字节单元中的数乘以3,结果储存在dx中
先将内存中数据取出,连加3次,即乘以3
一个字节单元是8位的,所以使用al【ax的低8位】来加
程序:
assume cs:code
code segment
mov ax,0ffffh # 在汇编程序中,数据不能以字母开头,要在ffff前面加0
mov ds,ax # 设置段地址
mov bx,6
mov al,[bx] #[bx]代表的是,段地址是ax,偏移地址是bx的地址在内存中的值
mov ah,0 # (ax)=((ds)*16+(bx))
mov dx,0
mov cx,3 # 设置循环次数
s:add dx,ax # 结果在dx中
loop s
mov ax,4c00h
int 21h
code ends
end
1.其他必要的考虑:运算后的结果是否会超出dx所能存储的范围?
2.分析:ffff:0006单元中的书是一个字节型的数据,范围是0~255之间,则用她和3相乘结果不会大于65535,不会出现超界。
7 段前缀的使用
7.1 引入段前缀:一个"异常"现象及对策
# ds:就是段前缀,显示段地址
mov al ds:[0]
mov al ds:[1]
mov al ds:[2]
mov al ds:[2]
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的
"ds:"、"cs:"、"ss:"、"es:",在汇编语言中称为端前缀。
7.2 访问连续的内存单元–loop和[bx]联手
- 7.2.1 问题:计算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
实际编码
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12 # 循环12次
s:mov al,[bx]
mov ah,0
add dx,ax
inc bx # bx加一
loop s
mov ax,4c00h
int 21h
code ends
end
7.3 段前缀的使用
- 7.3.1 问题:
将内存ffff:0~ffff:b中的数据拷贝到 0:200~0:20单元中。
实际代码
# 使用附加段寄存器
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12 # 循环12次
s:mov al,[bx]
mov ah,0
add dx,ax
inc bx # bx加一
loop s
mov ax,4c00h
int 21h
code ends
end
8 在代码段中使用数据
8.1 问题:这样做是危险的!
- 8.1.1 例:
将内存ffff:0~ffff:b中的数据拷贝到0:200~0:20b单元中。
8.2 应用案例
- 8.2.1 问题:编程计算以下8个数据的和,结果存在ax寄存器中0123H,0456H,0789H,0abcH,0defH,0cbaH,0987H
解决方案1:
assume cs:code
code segment
# dw:define word,定义字型数据
dw 0123H,0456H,0789H,0abcH,0defH,0cbaH,0987H # 在代码段中定义数据
mov bx,0
mov ax,0
mov cx,8
s:add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end
补充:
dw:定义一个字
db:定义一个字节
dd:定义一个双字
8.3 这个程序由问题
- 8.3.1 解决问题的关键:让数据从CS:0000开始,让代码从CS:0010开始!
8.4 这样改进
- 8.4.1 问题:编程计算以下8个数据的和,结果存在ax寄存器中0123H,0456H,0789H,0abcH,0defH,0cbaH,0987H
解决方案2:
assume cs:code
code segment
# dw:define word,定义字型数据
dw 0123H,0456H,0789H,0abcH,0defH,0cbaH,0987H # 在代码段中定义数据
start:mov bx,0 # 定义一个标号,指示代码开始的位置
mov ax,0
mov cx,8
s:add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start # end的作用:除了通知编译器程序结束外,还可以通知编译器程序入口的位置在什么地方。
以后程序的一般框架:
assume cs:code
code segment
# 数据部分
start:
# 代码部分
code ends
end start
9 在代码段中使用栈
9.1 在代码段中使用栈:以数据逆序存放为例
- 9.1.1 问题:完成下面的程序,利用栈,将程序中定义的数据逆序存放。
9.2 数据逆序存放
10 将数据、代码、栈放入不同段
10.1 将数据,代码,栈放入不同段
assume cs:code,ds:data,ss:stack
data segment
dw 0123H,0456H,0789H,0abcH,0defH,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:
# 初始化各段寄存器,cs已经默认给了,不用用mov给值
mov ax,stack # 将stack标号地址放入ax寄存器中
mov ss,ax # 栈段地址寄存器
mov sp,20h # 栈顶指针寄存器
mov ax,data # data所代表的地址移动到ax中
mov ds,ax
#入栈
mov bx,0 # 偏移地址设置为0
mov cx,8 # 循环8次
s:push [bx] #偏移地址是bx的,段地址是ds(即数据区,data所代表的地址),偏移地址为bx的地址数据送入栈中
add bx,2 # 偏移地址加2,移动到下一个位置
loop s
#出栈
mov bx,0
mov cx,8
s0:pop [bx] # 从栈顶取出数据送入 bx地址中
add bx,2
loop s0
mov ax,4c00h
int 21h
code ends
end start