贺利坚汇编课程笔记2 访问寄存器和内存

Posted 临风而眠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贺利坚汇编课程笔记2 访问寄存器和内存相关的知识,希望对你有一定的参考价值。

贺利坚汇编课程笔记2 访问寄存器和内存

文章目录

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) 2161(FFFFH)

  • 例:在AX中存储18D

    • 12H

    • 10010B

  • 问题 :8086上一代CPU中的寄存器都是8位 的,如何保证程序的兼容性?

    • 通用寄存器均可以分为两个独立的 8位寄存器使用
      • AX可以分为AH和AL
      • BX可以分为BH和BL
      • CX可以分为CH和CL
      • DX可以分为DH和

“字”在寄存器中的存储

  • 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

    段地址偏移地址
    2000H1F60H
    2100H0F60H
    21F0H0060H
    21F6H0000H
    1F00H2F60H
    • 在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工作过程的简要描述
    1. 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器
    2. IP = IP + 所读取指令的长度,从而指向下一条指令
    3. 执行指令。转到步骤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:用指令修改

    - 方法3 👇
    • 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个连续字节存储,怎么存放?
      • 回答 ; 低位字节存在低地址单元,高位字节存在高地址单元
  • 例: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

写入指令

执行指令

以上是关于贺利坚汇编课程笔记2 访问寄存器和内存的主要内容,如果未能解决你的问题,请参考以下文章

汇编语言导学篇---01

汇编语言 第三章 寄存器(内存访问)

汇编语言——寄存器(内存访问 ds数据段寄存器,ss栈段寄存器)

第三章 寄存器(内存访问)

汇编语言课堂总结3——内存访问

汇编语言第三章总结