[SingleSele]汇编语言(x86)学习笔记(未完)
第3章 寄存器
3.1 内存中字的存储
大端法——2字节存储一个字,高字节存储高8位,低字节存储低8位。
将起始地址位N的字单元称为N地址字单元
例:
| 地址 | 内容 |
|---|---|
| 0 | 20H |
| 1 | 4EH |
| 2 | 12H |
| 3 | 00H |
| 0地址存储的字节型数据为20H | |
| 2地址存储的字型数据为0012H |
3.2 DS和[address]
DS寄存器存放要访问内存单元的段地址
[]用来表示内存单元的偏移量
如:
mov bx,1000H
mov ds,bx
mov al,[0]
表示将1000:0的数据读到al中
8086CPU不支持将立即数送入寄存器ds,只能从其他寄存器送入ds
3.3 字的传送
只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了
3.4 mov add sub指令
三者都可以对段寄存器进行读写操作
3.5 数据段
将一组长度位N<=64kB,地址连续,且起始地址为16倍数的内存单元当作专门存储数据的内存空间,即数据段
可以将ds设为数据段的段地址,再根据需要访问其中的具体单元
如:
;累加123B0H到123B2H的数据
mov ax,123BH
mov ds,as
mov al,0
add al,[0]
add al,[1]
add al,[2]
3.6 栈
3.7 CPU提供的栈机制
8086CPU提供入栈和出栈指令
栈是虚拟结构,CPU不记录栈底位置和栈的大小
SS:SP指向栈顶
需要程序员自行判定是否越界
3.8 栈顶超界的问题
3.9 push pop指令
访问内存段中数据时,系统自动从ds中获取段地址
3.10 栈段
指ss寄存器指向的地址,是逻辑结构,系统并不会记录栈的地址
第4章 第一个程序
4.1 一个源程序从写出到执行的过程
①编写汇编源程序
产生了一个存储源程序的文本文件
②对源程序进行编译链接
使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件,再用连接程序对目标文件进行链接,生成可执行文件
可执行文件包括
- 程序和数据
- 程序的描述信息
③执行可执行文件
4.2 源程序
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
下面对程序进行说明
(1)伪指令
没有对应的机器指令,最终不被CPU执行,而是被编译器执行,进行相关的编译指令
①segment
XXX segment
......
......
XXX ends
定义一个段,XXX为段名
一个源程序中所有将被计算机处理的信息:指令、数据、栈,被划分到了不同的段中
②end
end是一个汇编程序的结束标记,编译器再编译汇编程序的过程中,如果碰到了end就结束对源程序的编译
③assume
假设某一段寄存器和程序中的某一个用segment定义的段相关联,通过assume说明这种关联
如 assume cs:codesg 表示cs寄存器与codesg代码段有关
(2)源程序中的“程序”
指源程序中最终由计算机执行,处理的指令或数据
最终被转换为二进制机器码,存储在可执行文件中
(3)标号
比如codesg,作为一个段的名称,指代了一个地址,该名称最终被编译、连接程序处理为一个段的段地址
(4)程序的结构
源程序是由一些段构成的,我们可以在这些段中存放代码、数据、或作为栈空间。
例:编程运算2^3
①定义一个段,名为abc
abc segment
abc ends
②在这个段中写入汇编指令
mov ax,2
add ax,ax
add ax,ax
③指出程序在何处结束
end
④将abc和cs联系起来
assume cs:abc
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
(5)程序返回
P1将P2加载入内存->CPU控制权交给P2->P2运行完毕->CPU控制权交还给P1
mov ax,4c00H
int 21H
两条程序代表程序返回
(6)语法错误和程序错误
程序在编译时被编译器发现的错误为语法错误,运行时发生的错误是逻辑错误
4.3 编辑源程序
使用任何文本编辑器均可,将结果保存为.asm格式
4.4 编译
进入c:\masm目录,运行masm.exe
若编译c:\masm\p1.asm
:p1
:p1.asm
:C:\masm\p1.asm
可以指定输出目录,默认绝对目录,默认.obj
可以指定生成列表文件的名称,也可不生成
可以指定生成交叉引用文件的名称,也可不生成
提示 0 Warnings,0 Errors
产生编译1.obj
两类错误无法编译
- 程序中有"Severe Errors"
- 找不到所给出的源程序文件
4.5 连接
需要对.o文件进行连接产生可执行文件
使用微软 Overlay Linker 3.60 连接器,文件名为link.exe
进入c:\masm目录,运行link.exe
若连接c:\masm\p1.obj
:p1
:p1.obj
:C:\masm\p1.obj
可以指定输出目录,默认绝对目录,默认.exe
可以指定生成映像文件的名称,也可不生成
可以指定输入库文件的名称,若没有调用其他库中的子程序可不输入
提示no stack segment,忽略
连接的作用
- 当源程序很大时,可以分为多个源程序分开编译,再连接成一个可执行文件
- 程序调用了某个库中的子程序,需要将两个文件连接到一起
- 连接程序将目标文件处理为可执行文件
4.6 以简化的方式进行编译和连接
masm c:\1;
link c:\1;
自动忽略中间文件的生成
4.7 1.exe的执行
4.8 谁将可执行文件中的程序装载进入内存并使它运行?
操作系统的外壳
任何的操作系统都要提供shell程序,用户使用这个程序操作计算机系统进行工作
DOS中有一个程序command.com,称为命令解释器,即DOS的shell
4.9 程序执行过程的跟踪
debug 1.exe
源程序加载后寻找一段SA:0000的空闲内存区
创建一个大小为256字节的PSP区(程序前缀区)
程序起始点为SA+10H:0
用T指令单步执行,直到int 21H,用P执行
第5章 [BX]和loop指令
5.0.1 [bx]和内存单元的描述
要完整描述一个内存单元,需要两种信息
- 内存单元的地址
- 内存单元的长度(类型)
mov ax,[0]表示将一个长度为2的内存单元的内容送入ax,端地址在ds中,偏移量为0
mov ax,[bx]表示将一个长度为2的内存单元的内容送入ax,段地址在ds中,偏移量在bx中
5.0.2 loop
和循环有关
5.0.3 我们定义的描述性的符号:“()”
为了描述简洁,用(U)表示U中的内容
如:
- (ax)表示ax中的内容,(al)表示al中的内容
- ((ds)*16+(bx))表示ds中的address1乘以16加上bx中的address2作为新的address中的内容
- 也可以理解为address1:address2中的内容
注:
()中的元素可以有三种类型
- 寄存器名 如(ax) (bl)
- 段寄存器名 如(ds)
- 内存单元的物理地址 如(20000H)
不可以出现(2000:0)((ds):1000H)等用法
5.0.4 约定符号idata表示常量
mov ax,[idata]代表 mov ax,[1] mov ax,[2] 等用法
mov bx,idata代表 mov bx,1 mov bx,2 等用法
5.1 [BX]
mov ax,[bx] 将ds:bx中的数据送入ax中,即(ax)=((ds)16+(bx))
mov [bx],ax 将ax中的数据送入ds:bx中,即((ds)16+(bx))=(ax)
5.2 Loop指令
格式:loop 标号
执行时进行两步操作 ①(cx)=(cx)-1 ②若(cx)!=0 执行标号处程序
例:编程计算2^2,结果存在ax中
assume cs:code
code segment
mov ax,2
add ax,ax
mov ax,4c00H
int 21H
code ends
end
编程计算2^3
assume cs:code
code segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
code ends
end
编程计算2^12
assume cs:code
code segment
mov ax,2
;做11次add ax,ax
mov cx,11
s: add ax,ax
loop s
mov ax,4c00H
int 21h
code ends
end
cx和loop指令相配合实现循环功能的3个要点:
- 在cx中存放循环次数
- loop指令中的标号所标识地址要在前面
- 要循环执行的程序段,要写在标号和loop指令的中间
用cx和loop指令相配合实现循环功能的程序框架如下
mov cx,循环次数
s:
循环执行的程序段
loop s
例:用加法计算123*236
assume cs:code
code segment
mov ax,123
mov cx,235
s: add ax,123
loop s
mov ax,4c00H
int 21H
code ends
end
注:将236加123次有助于提高运行速度
5.3 在Debug中跟踪用loop指令实现的循环程序
计算ffff:0006单元中的数乘以3,结果存储在dx中
(1)运算后结果是否会上溢出
ffff:0006是一个字节型数据,范围在0~255之间,用它和3相乘结果不会大于65535,可在dx中存放下
(2)用循环累加实现乘法,用哪个寄存器累加
ax存放ffff:6,dx累加ax
(3)ffff:6是一个字节单元,ax是一个16位寄存器,长度不同如何赋值
(ah)=0, (al)=ffff6H
程序如下:
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax ;ds不能直接改写
mov bx,6H
mov ah,0
mov al,[bx]
mov dx,0
mov cx,3
s: add dx,ax
loop s
mov ax,4c00H
int 21H
code ends
end
注:在汇编源程序中,数据不能以字母开头,所以FFFF要写成0ffffh
循环指令的执行
-p 执行直到跳出循环
-g 0016 执行到CS:0016的指令
5.4 Debug和汇编编译器masm对指令的不同处理
对于mov ax,[idata],Debug将其视为mov ax,(idata),masm将其视为mov ax,idata
若需要在源程序中将内存单元中的数据送入寄存器,需要将偏移地址送入bx,再用[bx]访问内存
或者以mov ax,ds:[0]的形式给出段地址所在的段寄存器
5.5 loop和[bx]的联合应用
例:计算ffff:0~ffff:b单元中数据的和,结果存储在dx中
(1)运算后的结果是否会超出dx所能存储的范围
不会
(2)能否直接将ffff:0~ffff:b中的数据累加到dx中
不能,内存中数据是8位的,不能直接加到16位寄存器中
(3)能否直接将ffff:0~ffff:b中的数据累加到dl中
不能,dl只有8位,可能造成上溢出
(4)如何将ffff:0~ffff:b中的8位数据累加到16位寄存器中
使用一个16位寄存器做中介
程序如下:
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0 ;将ds:bx指向ffff:0
mov dx,0 ;存储结果
mov cx,12 ;循环次数
s: mov al,[bx]
mov ah,0
add dx,ax
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
5.6 段前缀
指令mov ax,[bx]中,内存单元的偏移地址由bx给出,而段地址默认在ds中,我们同样可以以其他段寄存器作为段地址访问内存
如:
mov ax,cs:[bx]
mov ax,es:[bx]
mov ax,ss:[0]
这些出现在访问内存单元的指令中,用于显式指明内存单元的段地址的“ds:”“cs:”在汇编语言中称为段前缀
5.7 一段安全的空间
在8086模式中,随意地向一段内存空间写入内容是非常危险的,因为这段空间中可能存放着重要的系统数据或代码。
例:
mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al
如果1000:0中存放着重要的系统数据或代码,mov ds:[0],al将其改写,将引发错误
例:
assume cs:code
code segment
mov ax,0
mov ds,ax
mov ds:[26h],ax
mov ax,4c00h
int 21h
code ends
end
在Debug里执行mov [0026],ax时报错,在实模式下运行将会引起死机
可见,在不能确定一段内存空间中是否存放着重要的数据或代码的时候,不能随意向其中写入内容
DOS和其他合法的程序一般不会使用0:2000:2ff这段空间,如果在Debug模式下查看0:2000:2ff单元的内容都是0的话,则证明DOS和其他合法的程序没有使用这里
5.8 段前缀的使用
将内存ffff:0ffff:b中的数据复制到0:2000:20b单元中
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,12
s: mov dl,cs:[bx]
mov es:[bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
第6章 包含多个段的程序
在操作系统的环境中,合法地通过操作系统取得的空间都是安全的。
程序取得所需空间的情况有两种
- 加载程序的时候为程序分配
- 程序在执行的过程中向系统申请
在本课程中只讨论第一种方法
若要一个程序在被加载的时候取得所需的空间,则要在源程序中定义段。
大多数有用的程序都要处理数据,使用栈空间,也都要有指令。为了程序设计清晰和方便,我们也都定义不同的段来存放他们。
讨论顺序:
- 在一个段中存放数据、代码、栈
- 将数据、代码、栈放入不同的段中
6.1 在代码段中使用数据
例:编程计算以下八个数据的和,结果存在ax寄存器中
在程序中定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存时。这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,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的含义是定义字型数据 define word。
程序中的指令对8个数据进行累加,每个数据地址为指令地址+偏移量。若dw的地址为CS:0
则八个数据的地址分别为 CS:0 CS:2 CS:4 CS:6 CS:8 CS:A CS:C CS:E
然而这样做会使程序入口处不是我们想要执行的指令
可以在源程序中指明程序入口
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,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:指明程序入口start
根据什么设置CPU的CS:IP指向程序的第一条要执行的指令?
由可执行文件中的描述信息指明。
有了这种方法,就可以这样来安排程序的框架
assume cs:code
code segment
数据
数据
数据
start:
代码
代码
代码
代码
code ends
end start
6.2 在代码段中使用栈
例:完成下面的程序,利用栈,将程序中定义的数据逆序存放
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
?
?
codesg ends
end
思路大致如下:
程序运行时,定义的数据存放在cs:0~cs:F单元中,依次将单元中的数据入栈,再依次出栈到8个单元中,从而实现逆序存放
问题是,需要一段可当作栈的内存空间
可以在程序中通过定义数据来取得一段空间,然后将这段空间当作栈空间来用
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: mov ax,cs
mov ss,ax
mov sp,30h
mov bx,0
mov cx,8
s: push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0
mov ax,4c00h
int 21H
codesg ends
end start
在描述dw的作用时,可以说用它定义数据,也可以说用它开辟内存空间
6.3 将数据、代码、栈放入不同的段
- 把他们放到一个段中使程序显得混乱
- 前面程序中处理的数据很少,用到的栈空间也小。8086处理器限制一个段大小为64kB
应该考虑用多个段来存放数据代码和栈
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:bx指向data段中第一个单元
mov bx,0
mov cx,8
s: push[bx]
add bx,2
loop s
mov bx,0
mob cx,8
s0: pop [bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
code ends
end start
(1)定义多个段的方法
与定义代码段的方法没有区别,对于不同的段需要有不同的段名
(2)对段地址的引用
段名即可指代段地址
(3)“代码段”“数据段”“栈段”完全是我们的安排
- 我们在源程序中为这三个段起了具有含义的名称
- 我们在源程序中使用伪指令assume将cs ds和ss分别与code data和stack段相连
- 段的区别体现在汇编指令中段的作用而非名称

浙公网安备 33010602011771号