一周干掉汇编语言 #Day1 #基础知识 #寄存器 #CPU #内存


基础不会,啥都白费;基础不牢,地动山摇。
汇编在基础中的地位举足轻重,学习汇编,可以帮助我们从CPU角度出发,理解程序,写出更好的高级语言 程序。可以帮助我们理解程序的运行机制,知道原理,解决一些隐蔽的BUG。
学习步骤: 看视频,看书,做笔记,理解为主。习题独立完成,全对,才能进入下一章的学习。
视频: 小甲鱼
教材: 《汇编语言(王爽)》(三版)
日期: 2020-06-25

进度:19/77

一、基础知识:5/77

1. 两个语言

机器语言: 一串二进制数。转变为电信号就可以控制计算机活动。


汇编语言: 汇编指令 + 伪指令 + 其它符号
 - 汇编指令: 机器语言助记符,和机器语言一一对应

注意: 机器型号不一样,所对应的汇编指令集也不一样

 - 伪指令/其它符号: 由编译器识别/执行,没有对应的机器语言


2. 存储单元&三类信息

存储单元: 一个存储单元,可以存储1B的数据


三类信息: CPU进行数据读写,需要进行三类信息的交互
 1. 地址信息: 数据所在地址
 2. 数据信息: 数据
 3. 控制信息: 读 or 写


读写流程: 得到地址,发出控制,找到数据


总线: 地址线 + 数据线 + 控制线
 1. 地址总线: 宽度表示CPU寻址能力(CPU位数)。假设宽度为N,那么最多可以寻找(A21)N({A^1_2})^N个内存单元(一个内存单元1B )

注意: 一个64位需要满足三条件(不满足的话,就是假64位):64位CPU + 64位操作系统 + 64位软件

 2. 数据总线: 宽度表示CPU一次能读写的数据大小。一根线一次传1b,8根线一次传1B。
 3. 控制总线: 宽度表示CPU最多能控制的外部器材的数量。

检测点 1.1

  1. 2132^{13}。寻址能力为8KB,即232102^3 * 2^{10}
  2. 2102^{10}, 0, 21012^{10} -1
  3. 2132^{13}, 2102^{10}
  4. 2302^{30}, 2202^{20}, 2102^{10}
  5. 262^6, 202^0, 242^4, 222^2216/210=262^{16}/2^{10} = 2^6, 以此类推。
  6. 1, 1, 2, 2, 4。除以8即可。
  7. 512, 256。210/212^{10}/2^1210/222^{10}/2^2
  8. 二进制数

3. 存储器

存储器: 随机存储器RAM(随机存储器,接口卡上的),只读存储器ROM(刷了BIOS的)
 - 随机存储器: 断电丢失数据,如主存(内存)
 - BIOS: 标准输入输出系统。用来检测硬件,加载操作系统的。

补充: 往显卡RAM中写入数据,就会显示在显示器中。


  • 抽象连接情况: (物理层面)
    在这里插入图片描述

  • 抽象连接情况: (逻辑层面)
    上述各部件在物理上是独立的,但是在逻辑上,都是与主线相连,可以把各部分存储器看做一个整体。在CPU看来,这些存储器是一个整体,从上往下线性地存储

注意: 运行程序的主体是CPU,所以,我们必须站在CPU的角度思考问题。

在这里插入图片描述

补充: 每个物理存储器,占用一个地址段(区间)
注意: 不同的计算机内存地址分配是不同的,《汇编语言(王爽)》(三版)使用的是Intel8086


8080PC内存地址空间分配:
在这里插入图片描述

二、 寄存器(CPU工作原理):10/77

1. 总论

CPU:运算器、控制器、寄存器 等组成,依靠内部总线 相连(连接主板上外部存储器的是外部总线

扩展: 可以看看《程序是怎样跑起来的》这本入门书


8086寄存器: 8086的寄存器都是16位(可存放两字节)。有以下14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

2. 通用寄存器

AX、BX、CX、DX 称为通用寄存器

在这里插入图片描述

注意: 8086上一代CPU的寄存器都是8位的,为了向下兼容,8086CUP上的AX、BX、CX、DX都可以拆成两个可独立使用的8位寄存器来用。
比如,AX 分为AHAL (其中H 是高, L 是低的意思),如果要向下兼容,就使用ALAH 全部填0。

3. 字与寄存器

字: 一个字 = 2B,一个字的高位字节存放在寄存器高八位,低位字节存放在低八位

补充: 阅读二进制不方便,所以我们一般写作16进制。4个二进制数就是一个十六进制。为了区分不同进制,二进制后加B,十六进制加H,八进制加O。

4. 汇编指令(部分)

注意: 汇编指令不区分大小写

汇编指令 CPU操作 对应高级语言
mov ax, 7 把7存入ax ax = 7
add ax, bx 把ax + bx的结果存入ax ax += bx

练习题2.1 :

在这里插入图片描述
练习题2.2:

在这里插入图片描述

注意: 进行运算时,位数须一致。比如8位到8位,16位到16位。

检测点 2.1

  1. F4A3H
    31A3H。F4是高位,被替换为31。
    3123H
    6246H
    826CH
    6246H
    826CH
    164CH
    04D8H。104D8H,最高位溢出
    0482H
    6C82H
    D882H
    D888H
    D810H。低位为110,作为一个独立的存储器使用,1不能交给高位,直接溢出舍弃。
    6246H
  2. mov ax, 2
    add ax, ax
    add ax, ax
    add ax, ax

5. 物理地址

物理地址: CPU认为,所有存储器是一个整体,每个存储单元都有其唯一地址,这个唯一地址 被称为物理地址


16位结构CPU: 具有以下特征

  1. 运算器一次最多可以处理16位数据
  2. 通用寄存器最大宽度为16位
  3. 寄存器和运算器之间的通路为16位

一个疑问? 8086有20位地址总线,寻址能力为1M。可是它是16位系统,只能传送16位地址,寻址能力只有64k。那么怎么能达到1M的寻址能力呢?

  • 答: 8086的CPU在内部用两个16位的地址合成的方法来合成一个20位的地址。
    在这里插入图片描述
    具体的步骤见下节。

6. 段地址&偏移地址 【重点】

=16+物理地址 = 段地址*16 + 偏移地址

  • 例子: 段地址1230H,偏移地址00C8H。物理地址 = 1230H*16 + 00C8H = 12300H + 00C8H = 123C8H
  • 解释: 段地址*16,对于二进制而言,就是往左移4位。4+16正好20位。

  • 段地址: 内存并不是被分为一段一段的,内存就是一条。段地址是源于8086的无奈之举,与真正的分段无关系,真正的分段是由程序员为了使用方便而在心里上的一种划分,可根据需要,把地址连续,起始地址为16的倍数的一组内存单元定义为一个段

偏移地址为16位,16位的寻址能力为64K,所以一个段的最大长度为64K。


CPU可以通过不同的段地址和偏移地址到达同一个物理地址,这就是条条大路通罗马在这里插入图片描述


补充: 数据A13C6H在8086中可以表述为A000:13C6单元中;也可以表述为在A000段中的13C6单元中。

检测点 2.2

  1. 0010H, 1000FH。SA16+EA=0001H16+EA=0010H+(0000HSA*16 + EA = 0001H*16 +EA = 0010H + (0000H ~ FFFFH)FFFFH)
  2. 1001H, 2000H,。SA=SA = (20000HSE20000H - SE)/$16
    最小:SE = FFFFH时,算出10001H/16, 向上取整地1001H。
    最大:SE = 0000H时。算出2000H。

7. 段寄存器

8086CPU有4个段寄存器:

CS DS SS ES
英文 code statement data … stack… extra…
解释 代码段(寄存器) 数据… 堆栈… 附加段

CSIP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。

  • CS: 代码段寄存器
  • IP: 指令指针寄存器

注意: 此IP非彼IP

在这里插入图片描述
步骤:

  1. CS和IP经过地址加法器,变为20000H
  2. 通过20位地址总线,找到20000H,这个指令是3位,所以一下读取3位
  3. 组成B80123的指令(B8是mov ax的意思。23和01是数据,因为下面的01是高位,上面的23是低位,所以是0123H。
  4. 通过数据总线传递给指令缓冲器,然执行控制器执行
  5. 执行完毕后, 把0123的值赋给AX
  6. 由于一次索引了三个地址,所以IP加3

附加: 8086按下电源键的一刻,CS被设置为FFFFH,IP被设置为0000H。所以,FFFF0H单元中的指令是8086开机后执行的第一条指令。

8. 修改CS/IP的指令

  • 在CPU中,程序员能够用指令控制的只有寄存器,程序员可以通过改变寄存器内的内容实现对CPU的控制
  • CPU从何处执行指令是由CS、IP的值决定的,程序员可以通过改变CS、IP的值来控制CPU来执行目标指令
  • 那么如何修改CS、IP的指令

  • mov指令可以改变8086中大部分寄存器的值,被称为传送指令 。(但,它不能用来修改CS、IP的值。

转移指令: 可以改变CS、IP的值
用法: jmp 段地址:偏移地址


只修改IP的值: jmp 某一合法寄存器

例子: jmp ax
相当于: mov IP, ax
功能: 把ax的值赋值给IP


对于8086来说,可以根据需要将一组内存定义为一个段(地址连续、起始地址为16的倍数、长度为N(N <= 64k))。这段内存是用来存放代码的,从而定义了一个代码段。

注意: CPU并不认代码段,只认CS、IP。所以要让CS:IP指向代码段第一行的首地址。
任意时刻,CPU将CS、IP指向的内存地址当做指令执行

检测点 2.3

补充: sub ax, bx
含义: ax -= bx

四次:

  1. 第一个语句
  2. 第二个语句
  3. 第三个语句
  4. 跳转到ax

最后IP = ax = 0000H

9. 实验

9.1 实验1

下个DOSBox,设定一个目录,把这三个文件扔进去在这里插入图片描述

  • 命令:发送

- r 查看/修改 寄存器

在这里插入图片描述

- a 以汇编格式输入命
- d 查看内存中的内容
- e 改写内存中的内容
- u 将机器指令转变为汇编指令
- d 段地址:偏移地址 查看从物理地址开始的内存
- u 查看汇编代码
- t 执行命令CS/IP


在这里插入图片描述
在这里插入图片描述
把CS\IP改了,开始执行


按t一步步执行
在这里插入图片描述

9.2 实验2

在这里插入图片描述

9.3 实验3

注意: 内存区域的值是ASCII值

在这里插入图片描述

ASCII码 16进制
48 0 30
50 2 32
47 / 2f

在这里插入图片描述
会发现!不让改!

9.4 实验4

在这里插入图片描述
这个是显存的地址,所以显示了图像

三、寄存器(内存访问):19/77

1. 内存中字的存储

字单元: 任意两个连续的内存单元可以被称为一个字单元
在这里插入图片描述

注意: 0号是低地址单元。两个16进制数是一个字(2字节)

8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址。


mov可以将一个内存单元中的数据送入一个寄存器:

  • mov寄存器名, [偏移地址]
    mov ax, [0]
    我们执行指令时,8086CPU会自动取DS中数据为内存单元的段地址

注意: 8086cpu有一个缺陷(硬件问题),不能直接把值赋给DS,需要靠一个通用寄存器转手
mov ax, 1000H
mov DS, ax


mov 通用寄存器, 段寄存器是可行的
在这里插入图片描述


不能直接对段寄存器进行操作:在这里插入图片描述


CS指向的段就是代码段,DS指向的段就是数据段

注意: mov ax, 1000[0]不会报错,但不会进行赋值。这个可能和机器型号有关,我用的是DOSBox。

检测点 3.1

  1. 2662H
    E626H
    E626H
    2662H
    D6E6H
    FD48H
    2C14H
    0000H
    00B6H
    0000H
    0026H。记录: 我之前一直以为一行是10个内存单元,看到这题,突然想起偏移地址是16进制,一行应该是16个内存单元。
    000CH
指令 ax bx CS IP DS
初始 0 0 2000H 0 1000H
mov ax, 6622H 6622H 3
jmp 0ff0:0100 0ff0 0100
变形 1000 0
mov ax, 2000H 2000H 3
mov ds, ax 2000H 5 2000H
mov ax, [0008] C389H 8
mov ax, [0002] EA66H B

数据和程序没区别,都是一堆二进制数。在CS段中就是指令,在DS段中就是数据。

2. 栈 【重点】

2.1 概念&用法

栈: stack,一种具有特殊的访问方式的存储空间,LIFO先进后出。

用处: 用处非常广泛,介绍一种最开始发明栈的原因。一个程序,如果只能从上至下进行,那么功能就太差了。为了复用性,需要函数 ,有一个问题就是,怎么回去?怎么回到调用处?于是发明了栈,把调用处的地址入栈,执行完函数后,再调用return(汇编语言指令都是三个字的,不叫这名,这是《程序是怎样跑起来的》上面的说法),把栈中的地址弹给CS/IP。

注意: 把一段内存,当做什么来使用,是程序员的看法。CPU只认数据。


push axax中的数据入栈
pop ax把栈中的数据弹入ax

注意: 8086CPU入栈/出栈是以字为单位的(ax是16进制,当然是字,而不是字节)
补充: push CS/pop CS,push和pop可以对段寄存器 进行直接操作
补充: push [1]/pop [2],push和pop可以对内存进行直接操作


SS: 存放栈顶的栈地址
SP: 存放栈顶的偏移地址
SS:SP: 指向栈顶的元素


内存空间上面是低地址,下面是高地址。数据一个个存放进去,栈顶就向上移动,所以入栈又称为压栈 。那么栈顶向下移动,SS:SP又时刻指向栈顶,是怎么做到的呢?靠得是SP进行变化。
push ax: SP -= 2。减号表示向上移动;pop指令正好相反。

补充: 当栈是空的时候,不存在栈顶。SP指向SS:0的下面一个内存单元(更大的内存单元)
注意: pop后,原指向的内存中的数据不会消失,只是SP向下移动了。当再次push时,会对原存在的数据进行覆盖。(格式化不是真正的删除,只有覆盖重写才是真的删除,这就是数据恢复的原理)

2.2 栈顶越界

栈顶越界: 当栈满时仍push,当栈空时仍pop,那么操作就会脱离我们规划的栈空间。这就叫栈顶越界 (危险)

注意: java等语言有溢出检测,而像C/C++之类的就没有

解决方法: 弄两个寄存器,一个记录栈下限,一个记录栈上限。然而,这种CPU目前并不存在。我们只能自己小心不越界,合理预估所需栈的大小(在C中,程序员可以操作Heap堆,这是由一堆零散空间通过逻辑连接的。要注意使用后,及时释放内存)

2.3 一些问题的解答

问题3.7: 注意,sp是可以直接赋值的,初始赋值为下一个内存单元,即000fH的下一个0010H
问题3.8: 注意,清零的步骤,我使用的是mov ax, 0这样的机器码是3个字节。书上的sub ax, ax的机器码是2个字节(现在资源没有那么匮乏,不需要纠结这么一小点空间)。系统很喜欢用xor ax, ax异或,如果ax等于ax就将ax清零。
问题3.10: 重要的事情说N遍,CPU只认二进制数据!


补充: mov指令涉及一个操作。push/pop涉及两个操作,push先改变SP的值,再放入值;pop先取出值,再改变SP
注意: 因为入栈是改变SP,所以栈的最大变化范围为0~ffff。当栈满,会由于SP溢出,从而又回到栈底部开始循环。


问题3.11: SP = 0000。当有程序入栈时,SP -= 2,会得到fffe。也就是利用了数据溢出 的原理


编译: 高级语言 -> 机器语言
反编译: 机器语言 -> 高级语言
汇编: 汇编语言 -> 机器语言
反汇编: 机器语言 -> 汇编语言

检测点 3.2

  1. mov ax, 2000H
    mov SS, ax
    mov SP, 0010H
  2. mov ax, 1000H
    mov SS, ax
    mov SP, 0000H

3. 实验2

- d 段地址:偏移地址可以查看内存单元的数据。
那么必须有一个寄存器来记录这个段地址,这个寄存器是DS(DS的值在操作前后,不会改变)。
D更多用法:

  • - d DS:0000H 查看当前数据段内存
  • - d CS:0000H 查看当前代码段内存
  • - d SS:0000H 查看当前栈段内存

  • 用e命令,批量修改内存:
    在这里插入图片描述
  • 用u命令,查看汇编形式的代码段:在这里插入图片描述
  • 用a命令,以汇编的形式写入栈: 在这里插入图片描述
  • Debug程序在执行修改寄存器SS时,下一条指令也会紧跟着执行 (一般情况下,t只能执行一条命令,这是个特例,涉及中断机制 ,以后再学)

实验任务1: 在这里插入图片描述
实验任务2:: SP = 10H,-2就变成e,再-2就变成c。(王爽老师说理解这里是很有悟性,我倒觉得没有理解就该回去复习了,显而易见到我不觉得这三个问题了)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章