贺利坚汇编课程笔记2 访问寄存器和内存
文章目录
- 贺利坚汇编课程笔记2 访问寄存器和内存
- 0201 寄存器及数据存储
- CPU的组成
- 寄存器是CPU内部的信息存储单元
- 通用寄存器--以AX为例
- “字”在寄存器中的存储
- 0202 mov 和 add指令
- 0203 确定物理地址的方法
- 物理地址
- 8086CPU给出物理地址的方法
- 例:8086CPU访问地址为123C8H的内存单元
- “段地址×16+偏移地址=物理地址”的本质含义
- 0204 内存的分段表示法
- 用分段的方式管理内存
- 同一段内存,多种分段方案
- 用不同的段地址和偏移地址形成同一个物理地址
- 0205 Debug的使用
- Debug是什么?
- Debug能做什么
- 启动Debug
- 用R命令查看、改变CPU寄存器的内容
- 用D命令查看内存中的内容
- 用E命令改变内存中的内容
- 用U命令将内存中的机器指令翻译成汇编指令
- 用A命令以汇编指令的格式在内存中写入机器指令
- 用T命令执行机器指令
- 用Q命令退出Debug
- 0206 CS、IP与代码段
- 两个关键的寄存器 CS IP
- 例示:在CS和IP指示下代码的执行
- 在Debug中实证演示上面的示例
- 0207 jmp指令
- 修改CS、IP的指令
- 转移指令jmp
- 例
- 用Debug实操一下
- 0301 内存中字的存储
- 字
- 字单元
- 0302 用DS和[address]实现字的传送
- 要解决的问题
- 字的传送
- 案例
- debug实操
- 0303 DS与数据段
- 对内存单元中数据的访问
- 例 将123B0H~123BAH的内存单元定义为数据段
- 用mov指令操作数据
- 加法add和减法sub指令
- 用DS和[address]形式访问内存中数据段方法小结
- 0304 栈及栈操作的实现
- 栈结构
- 栈的操作
- 例:把10000H~1000FH内存当作栈来使用
- 例
- debug验证
- 栈顶超界问题
- 栈的小结
- 0305 关于”段“的总结
- 三种段
- 综合示例:按要求设置段
- debug演示
- 综合示例:三个段地址可以一样
0201 寄存器及数据存储
CPU的组成
- 运算器进行信息处理;
- 寄存器进行信息存储;
- 控制器协调各种器件进行工作;
- 内部总线实现CPU内各个器件之间的联系
寄存器是CPU内部的信息存储单元
- 8086CPU有14个寄存器
- 通用寄存器:AX BX CX DX
- 变址寄存器:SI DI
- 指针寄存器:SP BP
- 指令指针寄存器:IP
- 段寄存器:CS SS DS ES
- 标志寄存器:PSW
- 共性
- 8086CPU所有寄存器都是16位的,可以存放两个字节
通用寄存器–以AX为例
-
一个16位寄存器存储一个16位的数据,能保存的数据的最大值为 2 16 − 1 ( F F F F H ) 2^{16}-1(FFFFH) 216−1(FFFFH)
-
例:在AX中存储18D
-
12H
-
10010B
-
-
问题 :8086上一代CPU中的寄存器都是8位 的,如何保证程序的兼容性?
- 通用寄存器均可以分为两个独立的 8位寄存器使用
- AX可以分为AH和AL
- BX可以分为BH和BL
- CX可以分为CH和CL
- DX可以分为DH和
- 通用寄存器均可以分为两个独立的 8位寄存器使用
“字”在寄存器中的存储
- 8086是16位CPU ; 8086的字长(word size)为16bit :
- 一个字(word)可以存在一个16位寄存器中 ;
- 这个字的高位字节存在这个寄存器的高8位寄存器 ;
- 这个字的低位字节存在这个寄存器的低8位寄存器
0202 mov 和 add指令
-
例1
看到老师演示windows自带计算器的程序员模式,感觉还挺好玩的
功能很强大
居然还有这个 BYTE WORD DWORD QWORD(四字)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
注意溢出
-
例2
看清楚 l 和 h ,注意溢出
最后那个158, 1要丢弃
0203 确定物理地址的方法
物理地址
- CPU访问内存单元时要给出内存单元的地址。
- 所有的内存单元构成的存储空间是一个一维的线性空间。
- 每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为**物理地址**。
- 事实 :
- 8086有20位地址总线,可传送20位地址,寻址能力为1M。
- 8086是16位结构的CPU
- 运算器一次最多可以处理16位的数据,寄存器的最大宽度为16位。
- 在8086内部处理的、传输、暂存的地址也是16 位,寻址能力也只有64KB!
- 问题:8086如何处理在寻址空间上的这个矛盾? 👇
8086CPU给出物理地址的方法
-
8086CPU的解决方法
- 用两个16位地址(段地址、偏移地址) 合成一个20位的物理地址。
-
地址加法器合成物理地址的方法
-
物理地址=段地址×16+偏移地址
(段地址左移4位)
-
例:8086CPU访问地址为123C8H的内存单元
老师这里演示的动画非常棒!
- 段地址:1230,偏移地址:00c8
- 段地址:123C,偏移地址:0008
- 段地址:123B,偏移地址:0018
- …
- 这些都是可以的
“段地址×16+偏移地址=物理地址”的本质含义
哈哈哈,老师举的这个姚明的例子还挺好玩的
- 要解决的问题
- 用两个16位的地址(段地址、偏移地址), 相加得到一个20位的物理地址
- 本质含义
- CPU在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址
0204 内存的分段表示法
用分段的方式管理内存
同一段内存,多种分段方案
-
段地址×16 必然是 16的倍数,所以一个段的起始地址也一定是16的倍数
-
偏移地址为16位,16 位地址的寻址能力为 64K,所以一个段的长度最大为64K
-
例
起始地址(基础地址)为10000H,段地址为1000H,大小为100H
[
段地址取前16位
起始地址(基础地址)为10000H和10080H,段地址为1000H和1008H,大小均为80H
用不同的段地址和偏移地址形成同一个物理地址
0203 里面已经举过例子
-
再举个例子 物理地址:21F60H
段地址 偏移地址 2000H 1F60H 2100H 0F60H 21F0H 0060H 21F6H 0000H 1F00H 2F60H - 在8086PC机中存储单元地址的表示方法
下面两种说法等价
-
偏移地址16位,变化范围为0~FFFFH,用偏移地址最多寻址64KB
- 例:给定段地址2000H,寻址范围是20000H~2FFFFH,共64KB
-
段地址非常重要
- 偏移地址可以用多种方法提供——8086丰富的取址方式
0205 Debug的使用
Debug是什么?
医学中的内窥镜
Debug能做什么
- 用R命令查看、改变CPU寄存器的内容
- 用D命令查看内存中的内容
- 用E命令改变内存中的内容
- 用U命令将内存中的机器指令翻译成汇编指令
- 用A命令以汇编指令的格式在内存中写入机器指令
- 用T命令执行机器指令
- … and so on
启动Debug
- 把debug所在的路径挂载到c上面,然后敲入debug
用R命令查看、改变CPU寄存器的内容
r
:查看寄存器内容
r 寄存器名
(r和寄存器中可以没有空格):改变指定寄存器内容
用D命令查看内存中的内容
D
列出预设地址内存处的 128个字节的内容
-
D 段地址:偏移地址
列出内存中指定地址处的内容
-
D 段地址:偏移地址 结尾偏移地址
列出内存中指定地址范 围内的内容
用E命令改变内存中的内容
E 段地址:偏移地址 数据1 数据2 ...
-
E 段地址:偏移地址
-
逐个询问式修改
-
空格 - 接受,继续
-
回车 - 结束
-
用U命令将内存中的机器指令翻译成汇编指令
-
例
-
有汇编指令
-
mov ax 0123H
-
mov bx, 0003H
-
mov ax, bx
-
add ax, bx
-
-
对应的机器码为
- B8 23 01
- BB 03 00
- 89 D8
- 01 D8
-
e 地址 数据
- 写入 -
d 地址
- 查看 -
u 地址
- 查看代码
-
神奇捏 可以看作指令 也可以看作数据
用A命令以汇编指令的格式在内存中写入机器指令
- 例
-
有汇编指令
mov ax, 0123H mov bx, 0003H mov ax, bx add ax, bx
-
对应的机器码为
B8 23 01 BB 03 00 89 D8 01 D8
-
a 地址 - 写入汇编指令
写入cs:ip处
a 073F:0100
-
d 地址 - 查看数据
-
u 地址 - 查看代码
-
用T命令执行机器指令
- t - 执行CS:IP处的指令
用Q命令退出Debug
q - 退出Debug
0206 CS、IP与代码段
两个关键的寄存器 CS IP
- CS:代码段寄存器
- IP: 指令指针寄存器
- CS:IP:CPU将内存中CS:IP 指向的内容当作指令执行
例示:在CS和IP指示下代码的执行
-
8086CPU当前状态:CS中内容为2000H,IP中内容为0000H
-
内存20000H~20009H处存放着可执行的机器代码
https://www.bilibili.com/video/BV1pi4y1P76P?p=13&spm_id_from=pageDriver&vd_source=0e8431ba6fd78bb2215c36307a75ac1a
视频 P13 5min处 的演示动画 好棒!
- 8086PC工作过程的简要描述
- 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器
- IP = IP + 所读取指令的长度,从而指向下一条指令
- 执行指令。转到步骤1,重复此过程
在Debug中实证演示上面的示例
上面那个图
-
先把CS改为2000, IP改为0
r cs :2000 r ip :0 r
-
用A命令把代码写入 , U命令查看代码
mov ax, 0123H mov bx, 0003H mov ax, bx add ax, bx
-
t执行cs:ip处的代码
-
前面的问题, 当指令使用还是当数据使用?
d命令显示的是“狭义”的数据
u显示了“广义”的数据:数据和指令
- 取决于程序员,如果是当指令使用, 那设置为CS:IP指向这一段, CPU就会把这一段当作指令使用
0207 jmp指令
修改CS、IP的指令
-
执行何处的指令,取决于CS:IP
-
可以通过改变CS、IP中的内容,来控制CPU要执行的目标指令 :
-
那么如何改变CS、IP的值?
-
方法1:Debug 中的 R 命令可以改变寄存器的值——rcs, rip
- Debug是调试手段,并非程序方式!
-
方法2:用指令修改
- jmp指令
-
转移指令jmp
-
可以同时修改cs、ip的内容
jmp 段地址:偏移地址
jmp 2AE3:3 jmp 3:0B16
功能:用指令中给出的段地址修改CS,偏移地址修改IP
-
也可以仅修改ip的内容
jmp某一合法寄存器
jmp ax jmp bx
(类似于mov IP,ax,但是注意mov IP,ax是不允许的!)
例
-
从20000H开始,执行的指令序列是
mov ax,6622H jmp 1000:3 mov ax,0000 mov bx,ax jmp bx ;修改IP IP变为0 mov ax,0123H ;转到第(3)步mov ax,0000执行 开始循环
用Debug实操一下
-
环境准备
-
执行指令
0301 内存中字的存储
字
-
对8086CPU,16位作为一个字
- 问题:16位的字存储在一个16位的寄存器中,如何存储?
- 回答:高8位放高字节,低8位放低字节 :
- 问题: 16位的字在内存中需要2个连续字节存储,怎么存放?
- 回答 ; 低位字节存在低地址单元,高位字节存在高地址单元
- 问题:16位的字存储在一个16位的寄存器中,如何存储?
-
例:20000D(4E20H)存放0、1两个单元,18D (0012H)存放在2、3两个单元
书中一般是最左边那个,
注意描述方式 0012H的起始地址是2 4E20H的起始地址是0
字单元
-
字单元:由两个地址连续的内存单元组成,存放一个字型数据(16位)
-
框出来的方式都可以组成一个字单元
-
-
原理:在一个字单元中,低地址单元存放低位字节,高地址单元存放高位字节 ; 在起始地址为0的单元中,存放的是4E20H ; 在起始地址为2的单元中,存放的是001
- 问题:
- (1)0地址单元中存放的字节型数据是(20H)
- (2)0地址字单元中存放的字型数据是(4E20H)
- (3)2地址单元中存放的字节型数据是(12H)
- (4)2地址字单元中存放的字型数据是(0012H )
0302 用DS和[address]实现字的传送
要解决的问题
- 要求 :CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址;
- 原理 ;:在8086PC中,内存地址由段地址和偏移地址组成(段地址:偏移地址)
- 解决方案:DS和[address]配合
- 用 DS寄存器存放要访问的数据的段地址
- 偏移地址用[…]形式直接给出
-
例1 将10000H(1000:0)中的数据读到al中
mov bx,1000H mov ds,bx mov al,[0] ;[0]这种方式给出,段地址默认在DS
-
例2 将al中的数据写到10000H(1000:0)中
mov bx,1000H mov ds,bx mov [0],al
cs:ip是执行,ds:[address] 是拿数据。
字的传送
-
8086CPU可以一次性传送一个字(16位的数据)
-
例
mov bx, 1000H mov ds, bx mov ax, [0] ;1000:0处的字型数据送入ax mov [0], cx ;cx中的16位数据送到1000
案例
debug实操
-
准备数据和指令
写入之后
-
执行指令
0303 DS与数据段
对内存单元中数据的访问
-
对于8086PC机,可以根据需要将一组内存单元定义为一个段
- 物理地址=段地址×16+偏移地址
- 将一组长度为N(N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段
-
例:用123B0H~123B9H的空间来存放数据
-
段地址:123BH 起始偏移地址:0000H 长度:10字节
-
段地址:1230H 起始偏移地址:00B0H 长度:10字节
-
and so on …
将哪段内存当作数据段,段地址如何定,在编程时安排
-
-
处理方法:(DS)😦[address])
- 用DS存放数据段的段地址
- 用相关指令访问数据段中的具体单元,单元地址由[address]指出
- mov add sub等
例 将123B0H~123BAH的内存单元定义为数据段
-
例:累加数据段中的前3个单元中的数据
mov ax, 123BH mov ds, ax mov al,0 ;al清零 add al,[0] add al,[1] add al,[2]
-
例:累加数据段中的前3个字型数据
mov ax, 123BH mov ds, ax mov ax, 0 add ax,[0] add ax,[2] add ax,[4]
用mov指令操作数据
指令形式 | 示例 |
---|---|
mov 寄存器,数据 | mov ax, 8 |
mov 寄存器,寄存器 | mov ax, bx |
mov 寄存器,内存单元 | mov ax, [0] |
mov 内存单元,寄存器 | mov [0], ax |
mov 段寄存器,寄存器 | mov ds, ax |
-
学习方法:大胆假设,小心求证
推测1~3 可以,推测4不行
加法add和减法sub指令
段寄存器不能参与add运算
两个内存单元也不能直接相加
用DS和[address]形式访问内存中数据段方法小结
mov ax,1000H
mov ds,ax
mov ax,11316
mov [0],ax
mov bx,[0]
sub bx,[2]
mov [2],bx
-
字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中
-
用mov指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中
-
[address]表示一个偏移地址为address的内存单元
-
在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应
-
mov、add、sub是具有两个操作对象的指令,访问内存中的数据段
(对照:jmp是具有一个操作对象的指令,对应内存中的代码段)
-
可以根据自己的推测,在Debug中实验指令的新格式
0304 栈及栈操作的实现
栈结构
- 栈是一种只能在一端进行插入或删除操作的数据结构。
- 栈有两个基本的操作:入栈和出栈。
- 入栈:将一个新的元素放到栈顶;
- 出栈:从栈顶取出一个元素
- 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
- 栈的操作规则:LIFO(Last In First Out,后进先出)
- CPU提供的栈机制
- 现今的CPU中都有栈的设计。
- 8086CPU提供相关的指令,支持用栈的方式访问内存空间。
- 基于8086CPU的编程,可以将一段内存当作栈来使用。
栈的操作
- PUSH(入栈)和 POP(出栈)指令
- push ax:将ax中的数据送入栈中
- pop ax:从栈顶取出数据送入ax (以字为单位对栈进行操作)
例:把10000H~1000FH内存当作栈来使用
-
视频里很棒的动画演示
没有问题,栈(地址)增长方向和内存单元地址增长方向相反,且8086为小端存储,低位放到低地址
-
However,CPU如何知道一段内存空间被当作栈使用的捏?
moreover,执行push和pop的时候,如何知道哪个单元是栈顶单元?
-
栈段寄存器SS:存放栈顶的段地址
-
栈顶指针寄存器SP:存放栈顶的偏移地址
-
任意时刻,SS:SP指向栈顶指针
-
例
-
push ax 时发生了啥
- SP = SP - 2
- 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶
这里一开始有个疑惑,可能是来自于最早开始学栈的时候,拿一个框子举例子,先放进去一本书,再放进去一本书,书越堆越高,让我会有种误解,越往上去地址越高,
但是栈地址增加的方向和内存单元地址增长方向是相反滴! 记住这一点就ok啦
push 就是栈地址增加,那么就是内存地址减少 ,所以SP是减
-
pop ax时又发生了啥
pop是栈地址减少,那么就是内存地址增加,所以SP是加
- 将SS:SP指向的内存单元处的数据送入ax中
- SP=SP+2, SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶
debug验证
实现了AX和BX的数据交换
栈顶超界问题
- 问题来了,如何保证在入栈、出栈时,栈顶不超出栈空间?
- 哦莫,得编程时自己小心
栈的小结
-
push、pop 实质上就是一种内存传送指令,可以在寄存器和内存 之间传送数据,与mov指令不同的是,push和pop指令访问的内 存单元的地址不是在指令中给出的,而是由SS:SP指出的。
-
执行push和pop指令时,SP 中的内容自动改变。
-
8086CPU提供的栈操作机制:
- 在SS,SP中存放栈顶的段地址和偏移地址,入栈和出栈指 令根据SS:SP指示的地址,按照栈的方式访问内存单元。
- push指令的执行步骤:
- SP=SP-2
- 向SS:SP指向的字单元中送入数据
- pop指令的执行步骤:
- 从SS:SP指向的字单元中读取数据
- SP=SP+2。
0305 关于”段“的总结
-
编程时,可以根据需要将一组内存单元定义为一个段。
-
可以将起始地址为16的倍数,长度为 N(N ≤64K )的一组地址连续的内存单元,定义为一个段。
- 物理地址=段地址×16+偏移地址
- 段的最大长度为64K
-
将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元——在程序中可以完全由程序员安排
三种段
- 数据段
- 将段地址放在 DS中
- 用mov、add、sub等访问内存单元的指令时,CPU将我们定义的数据段中的内容当作数据来访问;
- 代码段
- 将段地址放在 CS
- 将段中第一条指令的偏移地址放在IP中
- CPU将执行我们定义的代码段中的指令;
- 栈段
- 将段地址放在SS中,
- 将栈顶单元的偏移地址置放在 SP中
- CPU在需要进行栈操作(push、pop)时,就将我们定义的栈段当作栈空间来用
综合示例:按要求设置段
mov bx,1000H
mov ds,bx
mov bx,1001H
mov ss,ax
mov sp,10H
mov ax,[0]
mov bx,[2]
push ax
push bx
pop ax
pop bx
mov [0],ax
mov [2],bx
debug演示
- 用a写入指令,用u查看
综合示例:三个段地址可以一样
mov bx,1000H
mov ds,bx
mov bx,1001H
mov ss,ax
mov sp,10H
mov ax,[0]
mov bx,[2]
push ax
push bx
pop ax
pop bx
mov [0],ax
mov [2],bx
写入指令
执行指令