【学习笔记】汇编学习笔记(付各种资料及习题答案)

[huayang]

本文的学习资料完全取自于王爽—《汇编语言(第三版)》

也就相当于读书笔记,基本全文就是我择抄的重点(我认为的重点)和一些个人的理解

自我感觉勉强能够入门,请轻喷,也请别一天天大佬大佬的叫。

仅供复习使用!!!

推荐教学视频:https://www.bilibili.com/video/BV1mt411R7Xv?p=46

一些重要的东西

先看书后看这个

mov ax,123 //把123给ax

add ax,123 //ax=ax+123

sub ax,123  //ax=ax-123

jmp 2AE3:3  //CS=2AE3H,IP=0003H

push ax //将寄存器ax中的数据送入栈中

pop ax  // 从栈顶取出数据送入ax

inc bx //bx=bx+1

dw 123h,0456h  //定义字型数据

db 'flag'  //定义单字节数据段 ,意思是编译时flag为数据而不是指令

mov al,0101110B
and al,1000110B
//al=0000110B //简单来说就是一假为假


mov al,0101110B
or al,1000110B
//al=1101110B  //简单来说就是一真为真
8086 有 4 个段寄存器:CS、DS、SS、ES

通用寄存器ax,bx,cx,dx

CS 为代码段寄存器,IP 为指令指针寄存器

8086CPU 有 14 个寄存器,每个寄存器有一个名称。分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

8086CPU 中的 DS 寄存器通常用来访问数据的地址段

mov al,[bx]也可以写为mov al,ds:[bx]  主要用来查看地址 //可查看《debug 和汇编编译器 masm 对指令的不同处理》图片

[address] 表示一个偏移地址为 address 的内存单元

任何时刻,SS:SP 指向栈顶元素

栈顶的地址存放在ss中,偏移地址存放在sp中

栈段,将它的段地址放在 SS 中

bx 中存放的数据作为一个偏移地址 EA,段地址 SA 默认在 ds 中

通常我们使用 loop 指令来执行循环功能,cx 存放循环次数

内存单元是字节单元(一个单元存放一个字节)

两个内存单元用来储存一个字,这两个单元可以看作为一个字单

segment //定义一个段的开始

ends  //定义一个段的结束

assume cs:code //将用作代码段的段code和CPU中的段寄存器cs联系起来

end是一个汇编程序结束标记

end 标号 //指明CPU从何处开始执行程序(建议参照‘在代码段中使用数据’进行理解)

cs:[bx]是什么意思呢,cs是因为在cs的代码段里,assume cs:codesg定义的,[bx]偏移地址

mov ax,data将名称为data的段的段地址送入ax,其实就相当于送入ds的信息

mov ax,[bx] //(ax)=((ds)*16+(bx))

mov [bx],ax //((ds)*16+(bx))=(ax)

si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用

[bx+si]和[bx+di]含义相近

储存单元

8个bit组成一个Byte,也就是通常讲的一个字节。微型机储存器的存储单元可以储存一个Byte,及8个二进制单位。一个存储单元的储存器有128个储存单元,它可以储存128个Byte。

上面的是对于微型储存器

对于大容量的储存器一般还是用以下单位进行计算容量(以下用B来代表Byte)

1KB=1024B  1Mb=1024KB  1GB=1024MB 1TB=1024GB

CPU对储存器的读写

在计算机中专门有连接cpu和其他芯片的导线,通常称为总线,总线从逻辑上又分为三类,地址总线、控制总线和数据总线

地址总线

10根导线可以传送10位二进制数据,也就是2的10次方(2^10)

最小数位0,最大数为1023

一个CPU有N根地址导线,可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。

地址总线的宽度决定了CPU的寻址能力

数据总线

CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的传输速度,8根数据总线一次可传送一个8位二进制数据(一个字节)

8086CPU的数据总线宽度为16

数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量

控制总线

控制总线的宽度决定了CPU对系统中其他器件的控制能力

检测点1.1

  1. 13
  2. 1024、0、1023
  3. 8192、1024
  4. 10^30、10^20、10^10
  5. 64、1、16、4
  6. 1、1、2、2、4
  7. 512、256
  8. 二进制

一些别的

2^4  //16B

2^5  //32B

2^6 //64B

2^10  //1KB

2^20  //1MB

2^30  //1GB

寄存器—2021.5.8(核心)

8086CPU有14个寄存器,每个寄存器有一个名称。分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW

通用寄存器

8086CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这四个寄存器通常用来存放一般性的数据,被称为通用寄存器。

8086CPU的AX、BX、CX、DX这4个寄存器都可以分为两个可独立使用的8位寄存器来用:

  • AX可分为AH和AL
  • BX可分为BH和BL
  • CX可分为CH和CL
  • DX可分为DH和DL

以AX为例,8086CPU的16位寄存器分为两个8位寄存器的情况

AX的低8位构成了AL寄存器,高8位构成了AH寄存器。

AH和AL寄存器是可以独立使用的8位寄存器。

字在寄存器中的储存

  • 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
  • 字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和地位字节

几条汇编指令

注:在写一条汇编指令或一个寄存器的名称时不区分大小写。

如:mov ax,18和MOV AX,18的含义相同;bx和BX的含义相同

思考:

注:存多少位数据则是2的几次方,比如:
十六进制可以存四位则是2^4
八进制可以存两位则是2^2

如过改为al或ah也是一样的

al或ah为8位寄存器只能放两位十六进制数据

所以也要把最高位1丢失,ax中的数据为:0058H

注意:操作对象的位数不一致不能传数据,如下面的就是错误的:

检测点2.1

要记住ax分为ah与al,ah与al分别代表前两位与后两位,后面的以此类推

位数只有四位多的要去掉

注意进制!!!

(1):

  1. F4A3H
  2. 31A3H
  3. 3123H
  4. 6246H
  5. 826CH
  6. 6246H
  7. 826CH
  8. 04D8H
  9. 0482H
  10. 6C82H
  11. D882H
  12. D888H
  13. D810H
  14. 6246H

(2):

mov ax,2
add ax,ax
add ax,ax
add ax,ax

物理地址

CPU访问内存单元时会给出内存单元地址,每一个内存单元都有唯一的地址,我们称其为物理地址

16位结构的cpu

8086是16位机

具有以下特点

  • 运算器以此最多可以处理16位的数据
  • 寄存器的最大宽度为16位
  • 寄存器和运算器之间的通路为16位

8086CPU给出物理地址的方法

8086CPU有20位地址总线但8086CPU又是16位结构

所以8086CPU采用了一种内部用两个16位地址合成的方法来形成一个20位的物理地址

地址加法器

段地址 * 10H = 基础地址

基础地址 + 偏移地址(0~FFFFH) =物理地址

比如:

F230H * 10H + C8H = F23C8H

这里有个思考问题简单的说一下

物理地址    段地址   偏移地址
21F60H     1F00H   2F60H

怎么得来的呢

1F00H * 10H + 2F60H = 21F60H

1F000
 2F60
21F60 //因为F为15加2进一位所以就是1F + 2 = 21

有点简单但一下子真没转过来

检测点2.2

  • 00010H 、1000fH
  • 0001H 、2000H

段寄存器

8086有4个段寄存器:CS、DS、SS、ES

CS和IP

CS为代码段寄存器,IP为指令指针寄存器

这段还是看书里的图比较好理解

总的来说

CPU将CS:IP所指向的内容全部当做指令来执行

  1. CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
  2. IP=IP+所读取指令的长度,从而指向下一条指令
  3. 执行指令。转到步骤1,重复这个过程。

内存中的一段信息曾被CPU执行过的话,它所在的内存单元必然被CS:IP指向过。

修改CS、IP的指令

ax等等的值可以用mov进行设置

但mov指令不能用于设置cs、ip的值

若想同时修改CS、IP的内容,可用形如“jmp段地址:偏移地址”

jmp 2AE3:3  //CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令

若想仅修改IP的内容,可用“jmp某一合法寄存器”的指令完成

jmp ax, 指令执行前:ax=1000H,CS=2000H,IP=0003H
        指令执行后:ax=1000H,CS=2000H,IP=1000H

其实就是用寄存器中的值修改IP

jmp ax,就好似:mov IP,ax

注意!!!是好似没说真有mov IP,ax这样的指令

代码段

比如:

mov ax,0000 (B8 00 00)
add ax,0123H (05 23 01)
mov bx,ax (8B D8)
jmp bx (FF E3)

上面123B0H~123B9H这段内存是用来存放代码的,是一个代码段,段地址为123BH,长度为10个字节

对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将进行我们定义的的代码段中的指令

要使代码段中的指令被执行就必须要将CS:IP指向所定义的代码段中的第一条指令的首地址

x想要这段代码得到执行让CS=123BH、IP=0000H

检测点2.3

4

1.mov ax,bx 2.sub ax,ax 3.jmp ax 4.执行jmp ax(不懂看上面)

0

预备知识

Debug

需要windows虚拟机一台

常用debug功能

  • -r 查看改变cpu寄存器的内容
  • -d 查看内存中的内容
  • -e 改写内存中的内容
  • -u 将内存中的机器指令翻译成汇编指令
  • -t 执行一条机器指令
  • -p 一次执行完
  • -a 以汇编指令的格式在内存中写入一条机械指令
  • -g 直接从一个地址进行跟踪
  • -q 退出debug

r命令

若想改变一个寄存器的值,比如ax的值可用r后面接寄存器名进行修改

我们想把ax改为1234

首先r ax回车,会出现如下页面

在冒号后面输入想要修改的数值,比如这里我们修改为1234,然后再用r即可查看

当然r也可以单独修改CS和IP

d命令

我们想要知道内存1000H处的内容,可以用“d 段地址:偏移地址”

如果紧接着再用d即可看见后续的内容

如过想查看内存单元

e命令

比如要将内存1000:0~1000:9单元中的内容分别写为0、1、2、3、4、5、6、7、8、9

如下图

然后用d 1000:0查看一下

如果直接输入e 1000:0回车则是以提问的方式进行输入

u命令

t命令

e命令写入机械码完后要用t命令进行执行

还可以继续用t继续执行命令

a命令

按两下回车推出编辑x

g命令

一直执行到这里

然后用t继续进行跟踪

寄存器(内存访问)

内存中字的储存

在内存中储存时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,低字节存放在低地址单元,高字节存放在高地址单元

两个内存单元用来储存一个字,这两个单元可以看作为一个字单元

比如

上图0、1为一个字单元,这里我们就称为0号单元0为低字节单元,1为高字节单元

0号单元的地址就为4E20H

0为低位,1为高位,后面2为低位,3为高位以此类推(这里说的是上图)

(这里可能有点绕)

任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元

DS和[address]

8086CPU中的DS寄存器通常用来访问数据的地址段

mov也可以将一个内存单元的内容送入一个寄存器

用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中

[address]表示一个偏移地址为address的内存单元

mov al,[0]

[0]表示一个内存单元,里面的0表示内存单元的偏移地址

只有偏移地址是不能定位一个内存单元的。

又点疑惑就看看这个

字的传送

偏移0

偏移1

以此类推

回头再把内存中字的储存看熟

mov、add、sub

  • mov 寄存器,数据 比如:mov ax,8
  • mov 寄存器,寄存器 比如:mov ax,bx
  • mov 寄存器,内存单元 比如:mov ax,[0]
  • mov 内存单元,寄存器 比如:mov [0],ax
  • mov 段寄存器,寄存器 比如:mov ds,ax
  • add 寄存器,数据 比如:add ax,8
  • add 寄存器,寄存器 比如:add ax,bx
  • add 寄存器,内存单元 比如:add ax,[0]
  • add 内存单元,寄存器 比如:add[0],ax
  • sub 寄存器,数据 比如:sub ax,9
  • sub 寄存器,寄存器 比如:sub ax,bx
  • sub 寄存器,内存单元 比如:sub ax,[0]
  • sub 内存单元,寄存器 比如:sub [0],ax

mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。

数据段

将一组长度为N(N<=64KB)、地址连续、起始地址为16的倍数的内存单元当做专门储存数据的内存空间,从而定义了一个数据段

对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问

上面的理解了这个不难

检测点3.1

是看下面的那段

2662H
E626H
E626H
2662H
D6E6H
FD48H
2C14H
0000H
00E6H
0000H
0026H
000CH

栈是一种具有特殊的访问方式的储存空间,特殊性就在于最后进入这个空间的数据最先出去

CPU提供的栈机制

基于8086CPU编程的时候可以将一段内存当做栈来使用

PUSH(入栈)POP(出栈)

任何时刻,SS:SP 指向栈顶元素

任何时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址

栈顶超界的问题

当栈满的时候在使用push指令入栈,或栈空的时候在使用pop指令出栈都将发生栈顶超界的问题

8086CPU只考虑当前的情况:当前的栈在何处、当前要执行的指令是哪一条

push、pop指令

  • push 寄存器 //将一个寄存器中的数据入栈
  • pop 寄存器 //出栈,用一个寄存器接收出栈的数据
  • push 段寄存器 //将一个段寄存器中的数据入栈
  • pop 段寄存器 //出栈,用一个段寄存器接收出栈的数据
  • push 内存单元 //将一个内存单元处的字入栈(注意:栈操作都是以字为单位)
  • pop 内存单元 //出栈,用一个内存单元接收出栈的数据

比如:

  • mov ax,1000H
  • mov ds,ax //内存单元的段地址要放在ds中
  • push [0] //将1000:0处的字压如栈中
  • pop [2] //出栈,出栈的数据送入1000:2处

栈段

长度为N(N<=64KB)的一组连续、起始地址为16的倍数的内存单元,当做栈空间来用,从而定义一个栈段

对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当做栈空间来用

要想使得如push、pop等栈操作命令访问我们定义的栈段就要将SS:sp指向我们定义的栈段

一个栈段最大容量为64KB

第一个程序

终于度过了漫长枯燥的大理论时间了

动手操作才能学得更快

源程序

伪指令

汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。

汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。

伪指令没有对应的机器指令,最终不被CPU所执行

伪指令是由编译器来执行的指令

segment和ends是一对成对使用的伪指令

segment //定义一个段的开始

ends  //定义一个段的结束

一个汇编程序是由多个段组成,这些段被用来存放代码、数据、或当做栈空间来使用。

end

注意这里的end不要和上面的ends搞混了

end是一个汇编程序结束标记

我们在写程序的时候要在结尾处加上伪指令end,否则在编译的时候无法知道程序在哪里结束

assume

assume cs:code //将用作代码段的段code和CPU中的段寄存器cs联系起来

这个指令有点麻烦,以后复习的时候再来概括

源程序中的”程序“

源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据称为程序

标号

程序的结构

2^3运算程序

assume cs:abc
    abc segment
    mov ax,2
    add ax,ax
    add ax,ax
abc ends
end

程序返回

一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们成这个过程为:程序返回

mov ax,4c00H
int 21H

这两条指令所实现的功能就是程序返回

这里暂时不必去理解这两天指令

语法错误和逻辑错误

上面我们写的运算程序没有返回。当然这个错误在编译的时候不会表现出来

也就是说这个程序对编译器来说是正确的的程序

assume cs:abc
    abc segment
    mov ax,2
    add ax,ax
    add ax,ax
abc ends
end

程序发生语法错误很容易被发现,因为变异的时候会出错

而逻辑错误则不容易发现

我们把上面的逻辑错误更正过来

assume cs:abc
    abc segment
    mov ax,2
    add ax,ax
    add ax,ax
    mov ax,4c00H
    int 21H
abc ends
end

编译&连接

工具在已给出

分别会用到编译工具(masm)和连接工具(link)

首先去win-xp把文件后缀打开

在工具目录下创建一个1.asm文件

最好把工具都搞到一起免得麻烦

写入代码

cd进工具目录

使用masm进行编译

masm 文件名

注:这里文件名要注意一下,一定要是tab出来或是一个一个打出来的才行,拖进去的无法执行

一直回车就行,会发现目录下多出个OBJ文件

然后使用link进行连接

link.exe obg文件

注:这里文件名要注意一下,一定要是tab出来或是一个一个打出来的才行,拖进去的无法执行

然后同样一直回车就行

出来了

我们运行1.exe竟发现任何结果,程序当然是运行了的,只不过我们程序根本没有向显示器输出任何信息

输出信息后面会讲

谁将可执行文件中的程序中的程序装载进入内存并使它运行

程序执行过程的跟踪

可以用Debug来进行跟踪一个程序的运行过程。

对于简单的错误仔细检查源程序即可发现,对于隐藏较深的错误就必须对程序的执行过程进行跟踪分析才容易发现

下面以1.exe为列说说如何用debug对程序执行过程进行跟踪

具体操作

用r看看各个寄存器的情况

dos系统中exe文件中的程序的加载过程有点难

这里我们要知道一个东西叫psp

有点晦涩难懂,我则抄了一些网上的东西方便大家进一步理解

原文地址:https://blog.csdn.net/sinat_34938176/article/details/78077022

物理地址=段地址×10H+偏移地址

明白了吧?PSP区的物理地址就是SA×10H,程序区的物理地址就是(SA+10H)×10H,即SA×10H+100H,刚好比PSP高了100H(即256)个字节。其实就是把偏移地址本来应该负责的100H的偏移量转移到了段地址上面,这样就能尽可能扩充程序区的大小了。
所以我们可以看出来,其实上面这个公式相当重要,它贯穿了全书,在不同的章节看到它都会有不同的体会,掌握它也会让我们更加容易地理解很多问题。

用t我们可以清晰的看见执行的结果

注意看ax

我这里多执行了一步,退出来重新搞

当执行到int 21的时候执行p命令

注意这里必须要使用p命令执行int 21,至于为什么书上

这里说一下-p命令和t命令的区别

p命令是全部执行完,t命令是一个一个的执行

显示这个表示程序正常结束

所以程序加载顺序为:command加载debug,debug加载1.exe。返回的顺序是:从1.exe中的程序返回到debug,从debug返回到command

实验3 编程、编译、连接、跟踪

(1)看上面

(2)这题要把CPU提供的栈机制完全搞懂才好做

assume cs:codesg

codesg segment

    mov ax, 2000h ;把2000h地址上的值给ax
    mov ss, ax ;令2000h为段地址
    mov sp, 0h ;栈长为0
    add sp, 10h ;栈长为16
    pop ax ;弹出到ax,然后sp = 10H + 2 = 12H
    pop bx ;弹出到bx 然后sp = 12H + 2 = 14H
    push ax ;入栈  然后sp = 14H - 2 = 12H
    push bx ;入栈  然后sp = 12H - 2 = 10H
    pop ax ;出栈 然后sp = 10H + 2 = 12H
    pop bx ;出栈 然后sp = 12H + 2 = 14H
    ;后面的语句就实现了交换

    mov ax, 4c00h
    int 21h

codesg ends

end

我们来说说第五行那个怎么来的

pop ax //sp = 10H + 2 = 12H

ss为栈的段地址,sp为栈的偏移地址

因为ax=2000H,pop弹出了两个字节多了两个空位所以sp向下偏移两位,下加上减

感觉这理解有问题,后续复习的时候再想想

(3)着重看看上面关于psp的问题

[BX]和loop指令

[bx]

mov ax,[bx]

bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中,即:

(ax)=((ds)*16+(bx))

mov [bx],ax

bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处,即:

((ds)*16+(bx))=(ax)

问题

这里不是很懂,后面会写

Loop指令

loop指令的执行格式是:loop 标号

CPU执行loop指令的时候进行如下两步的操作

  • (cx)=(cx)-1
  • 判断cx中的值

通常我们使用loop指令来执行循环功能,cx存放循环次数

我们来看看下列代码

assume cd:code
code segment
 mov ax,2
 mov cx,11
s:add ax,ax
 loop s
 mov ax,4c00h
 int 21h
code ends
end

总的来说loop s其实就是条件,s:add ax,ax则为触发条件要执行的指令

在debug中跟踪用loop指令实现的循环程序

assume cs:codesg
 
codesg segment
 
start:
    mov ax,0ffffh//编译器要求数字不能以ABCDEF开头,需加上0
    mov ds,ax
    mov bx,6h
    mov dx,0h
    mov al,[bx]
    mov ah,0h //mov ax,[bx]是否可以替代
    mov cx,3h //非2h,loop指令会先-1再判断
s:    add dx,al
    loop s
 
    mov ax,4c00h
    int 21h
codesg ends
end start

注意程序中的第一条指令 mov ax,0ffffh。大于9fffh的十六进制数据A000H、A001H、FFFFH等在书写的时候都是以字母开头的

而在汇编源程序中,数据不能以字母开头所以要在前面加上0

比如9138h可以直接写为9138h,而A000h在汇编中要写为0A000h。

这里不好写得用t慢慢看就行了

debug和汇编编译器masm对指令的不同处理

在debug使用类似指令:

mov ax,[0]

表达的意思是将ds:0处的数据送入到ax中

但是在汇编源程序中,指令’mov ax,[0]’会被当做‘mov ax,0’执行

在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用[…]表示内存单元
如果在[]里用一个常量idata直接给出内存单元的偏移地址,就要在[]的前面显式地给出段地址所在的寄存器

比如:

mov al,ds:[0]

如果在[]里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中,当然也可以显式地给出段地址所在的寄存器

loop和bx的联合应用

把ffff:0~ffff:b中的8位数据累加到16位寄存器dx中我们有如下几种方法

  • (dx)=(dx)+内存中的8位数据
  • (dl)=(dl)+内存中的8位数据

第一个方法中的问题是两个运算对象的类型不匹配,第二种结果则有可能超界

目前有一种方法就是得用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界

这样写固然很麻烦,所以我们用到loop

难度不高,inc bx 看做是bx++就行了

段前缀

指令mov ax,[bx]中,内存单元的偏移地址由bx给出,而段地址默认在ds中。

用于显式地指明内存单元的段地址的ds、 c s、ss、 es,在汇编语言中被称为段前缀

一段安全的空间

汇编源程序中的指令mov ds:[26h],ax和debug中的汇编指令mov [0026],ax同义

dos方法下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码

段前缀的使用

优化后

实验4 bx和loop的使用

1.2题

assume cs:codes
codes segment
         mov ax,20h
         mov ds,ax
         mov cx,64
         mov bx,0
     s: mov ds:[bx],bl
         inc bx
         loop s
         mov ax,4c00h
         int 21h
codes ends
end

包含多个段的程序

在代码段中使用数据

dw 123h,0456h //定义字型数据

assume cs :code
code segment
	dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
	mov ax,0
	mov bx,0
	mov cx,8
s:	add ax,cs:[bx]
	add bx,2
	loop s
	mov ax,4c00H
	int 21H
code ends
end
assume cs :code
code segment
	dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
start:	mov ax,0
	mov bx,0
	mov cx,8
s:	add ax,cs:[bx]
	add bx,2
	loop s
	mov ax,4c00H
	int 21H
code ends
end  start

注意对比上下两段代码

在程序的第一条指令前面加上一个标号start,而这个标号在伪指令end的后面出现

end除了通知编译器程序技术外,还可以通知编译器程序的入口在什么地方

也就是说‘mov bx,0’是程序的第一条指令

我们若要知道CPU从何处开始执行程序只需要在‘end 标号’指明就可以了

我们可以这样安排框架:

assume cs:code
code segment
         :
         :
        数据 
         :
         :
start:
         :
         :
        代码
         :
         :
code ends
end start

在代码段中使用栈

利用栈将程序中定义的数据逆序存放

assume cs:codesg
codesg segment
        dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
           ?
codesg ends
end

解题

assume cs:codeseg

codeseg segment

        dw 0123H,0456H,0789H,0abcH,0defH,0fdeH,0cbaH,0987H
        dw 0,0,0,0,0,0,0,0      ;用dw来定义8个字型数据,在程序加载后,
                                ;将取得8个字的内存空间,存放这8个数据,在后面的
                                ;程序中,将这段空间作为栈来使用
start:  mov ax,cs
        mov ss,ax
        mov sp,32               ;设置栈顶指向cs:32
        mov bx,0                ;初始化bx
        mov cx,8
s:      push cs:[bx]
        add bx,2
        loop s                  ;以上代码将0~15单元中的数据依次压栈

        mov bx,0
        mov cx,8
s0:     pop cs:[bx]
        add bx,2
        loop s0                 ;以上代码将0~15单元中的数据依次出栈

codeseg ends

end start

这里需要借助视频进行理解《10.2 在代码段中安排自己定义的栈空间》

检测点6.1

下面程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据

assume cs:codesg
codesg segment

    dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h  # 定义数据
start: mov ax,0
       mov ds,ax         # 定义内存起始段
       mov bx,0          # 定义内存偏移

       mov cx,8          # 定义循环次数
    s: mov ax,[bx]       # 将ds:[bx]内存单元中的数据写入ax寄存器
       mov cs:[bx],ax    # 将ax寄存器中的数据写入cs:[bx]代码单元
       add bx,2          # 相邻两个字节单元为一个字单元,因此偏移2
       loop s

       mov ax,4c00h
       int 21h

codesg ends
end start

(2)下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,数据的传送用栈来进行

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   ; 10个字单元用作栈空间
start: mov ax,cs         ; 定义代码段
       mov ss,ax         ; 定义栈段
       mov sp,36h        ; 定义栈底为36h (8*2 + 10*2)
       
       mov ax,0
       mov ds,ax         ; 定义内存起始段
       mov bx,0          ; 定义内存偏移
       mov cx,8          ; 定义循环次数
       
       s: push ds:[bx]         ; 将ds:[bx]内存单元中的数据写入栈单元ss:sp(sp=36h) -> sp=36h - 2h = 34h

       pop cs:[bx]       ; 将ss:sp(sp=34h)为栈顶的字单元中的数据写入cs:[bx]中 -> sp=34h + 2h = 36h

       add bx,2          ; 相邻两个字节单元为一个字单元,因此偏移2
       loop s

       mov ax,4c00h
       int 21h

codesg ends
end start    

将数据、代码、栈放入不同的段

我们应该考虑用多个段来存放数据、代码和栈

我们用个定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间

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    ;8个字节用于缓存
stack ends
	
code segment
start:
    
    mov ax,stack    ;mov ss,cs,不能这么写
    mov ss,ax
    mov sp,16   ;巨坑!!!,单独有一个栈段后,sp的值应重新考虑
	mov ax,data
	mov ds,ax
	mov bx,0
    mov cx,8
	
  s:push [bx]
    add bx,2
    loop s
	
    mov bx,0
    mov cx,8
 s0:pop [bx]
    add bx,2
    loop s0
	
    mov ax,4c00h
    int 21h
code ends
end start

mov ax,data将名称为data的段的段地址送入ax

一个段中的数据的段地址可由段名代表,偏移地址就要看他在段中的位置了

程序中‘data’段中的数据‘0abch’的地址是:data:6。要将他送入bx中,代码如下:

mov ax,data
mov ds,ax
mov bx,ds:[6]

这样写则是错误的:

mov ds,data
mov bx,ds:[6]

mov ds,data指令是错误的,因为8086CPU不允许将一个数值直接送入段寄存器

多看几遍,有点晦涩难懂

实验5编写、调试具有多个段的程序

一、

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
stack ends
 
code segment
start:	mov ax, stack
	mov ss, ax
	mov sp, 16	;ss:sp stack
 
	mov ax, data
	mov ds, ax	;ds data
 
	push ds:[0]
	push ds:[2]
	pop ds:[2]
	pop ds:[0]
 
	mov ax, 4c00h
	int 21h
 
code ends
end start

(1)0123, 0456, 0789, 0abc, 0def, 0fed, 0cba, 0987

(2)CS=0B3F 、SS=0B3E、DS=0B3D(这题每个人的都不一样,以自己的为准)

(3)X-2,X-1

二、

assume cs:code, ds:data, ss:stack
 
data segment
	dw 0123h, 0456h
data ends
 
stack segment
	dw 0, 0
stack ends
 
code segment
start:
	mov ax, stack
	mov ss, ax
	mov sp, 16	;ss:sp stack
 
	mov ax, data
	mov ds, ax
 
	push ds:[0]
	push ds:[2]
	pop ds:[2]
	pop ds:[0]
 
	mov ax, 4c00h
	int 21h
code ends
end start

(1)0123、0456

(2)cs=0b3f ss=0b3e ds=0b3d

(3)X-2,X-1

后面的再来分析

建议看看这篇文章:汇编实验五 编写,调试具有多个段的程序

更灵活的定位内存地址的方法

and和or指令

and

mov al,0101110B
and al,1000110B
//al=0000110B

or

mov al,0101110B
or al,1000110B
//al=1101110B

以字符形式给出的数据

在汇编程序中可以用’…’的方式指明数据是以字符的形式给出的,编译器会转换为对应的ascii码

assume cs:code,ds:data
 
data segment
    db 'unIX'
	db 'foRK'
data ends
 
code segment
start:
    mov ax,'a'
	mov bx,'b'
    
	mov ax,4c00h
	int 21h
code ends
 
end start

在上面程序中db ‘unIX’ 相当于 db 75H,6EH,49H,58H(u,n,I,X)是16进制的ascii码

大小写转换的问题

我们先来看看同一个字母的大写和小写有什么规律

可以看到小写字母的ascii码值比大写字母的ascii码值打了20h

所以最终的规律就是:大写字母+20H=小写字母,小写字母-20H=大写字母

我们要如何判断是大小写呢

一个字母不管它原来是大写还是小写,它的第5位置是0他就必将是大写字母(从右到左)

同理它的第5位置是1他就必将是小写字母

用or和and指令的完整程序

assume cs:codesg,ds:datasg

datasg segment
 db 'BaSiC'
 db 'iNfOrMaTiOn'
datasg ends
codesg segment
 start:mov ax,datasg
       mov ds,ax   ;设置ds指向datasg段
       mov bx,0   ;设置(bx)=0,ds:bx指向BaSiC的第一个字母
       mov cx,5   ;设置循环次数5,因为BaSiC有5个字母
     s:mov al,[bx] ;将ASCII码从ds:bx所指向的单元取出
       and al,11011111B ;将al中的ASCII码的第5位置变为0,
       mov [bx],al ;将转变后的ASCII码写回原单位
       inc bx  ;(bx)加1,ds:bx指向下一个字母
       ioop s  
       mov bx,5 ;设置(bx)=5,ds:bx指向iNfOrMaTiOn的一个字母
       mov cx,11 ;设置循环次数11,因为iNfOrMaTiOn有11个字母
     s0:mov al,[bx]
        or al,00100000B ;将al的ASCII码的第5位置为1,变为小写字母
        mov [bx],al
        inc bx
        loop s0
        mov ax,4c00h
        int 21h
codesg ends
end start

[bx+idata]

mov ax,[bx+200]的含义:

(ax)=((ds)*16+(bx)+200)

也可以写作下面的格式:

mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200

用[bx+idata]的方式进行数组的处理

assume cs:codesg,ds:datasg

datasg segment
 db 'BaSiC'
 db 'MinIX'
datasg ends

codesg segment
 start:mov ax,datasg
 mov ds,ax
 mov bx,0
 mov cx,5
S:mov al,[bx]
  and al,11011111b
  mov [bx],al
  mov al,[a+bx]
  or al,00100000b
  mov [5+bx],al
  inc bx
  loop s
codesg ends
end start

理解这段代码即可

SI和DI

si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用

[bx+si]和[bx+di]

[bx+si]和[bx+di]含义相近

mov ax,[bx+si]的含义如下:

(ax)=((ds)*16+(bx)+(si))

也可以写成如下格式:

mov ax,[bx][si]

不同的寻址方式的灵活应用

[idata]用一个常量来表示地址,可用于直接定位一个内存单元

[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元

[bx+idata]用一个变量和常量表示地址,可在一个其起始地址的基础上用变量间接定位一个内存单元

[bx+si]用两个变量表示地址

[bx+si+idata]用两个变量和一个常量表示地址

将datasg段中每个单词的头一个字母改变为大写字母

assume cs:codesg,ds:datasg
 
datasg segment
  db '1. file         '
  db '2. edit         '
  db '3. search       '
  db '4. view         '
  db '5. options      '
  db '6. help         '
datasg ends
 
codesg segment
start:
    mov ax,datasg
	mov ds,ax
    mov cx,6
	mov bx,0
	mov si,0
	mov dx,0
  s:
    mov dl,[bx + si + 3] ;这里操作的是字节,借助dl保存
    and dl,11011111B
	mov [bx + si + 3],dl
    add si,10h
	loop s
	
	mov ax,4c00h
	int 21h
codesg ends
 
end start

本题的难点在于在只有cx一个循环计数器的情况下如何实现双重循环。这里可以把cx中的值暂时保存在内存中再取出

assume cs:codesg,ds:datasg
 
datasg segment
  db 'ibm             '
  db 'dec             '
  db 'dos             '
  db 'vax             '
  dw 0
datasg ends
 
codesg segment
start:
    mov ax,datasg
    mov ds,ax
    mov bx,0   ;行
	
    mov cx,4
  s:
    mov ds:[40h],cx
    mov si,0   ;列
    mov cx,3
    s0:
        mov al,[bx+si]
        and al,11011111b
        mov [bx+si],al
        inc si
        loop s0
	add bx,10h
	mov cx,ds:[40h]
	loop s
	
	mov ax,4c00h
	int 21h
codesg ends
 
end start

理解即可

[/huayang]

2021.5.25

(第七章已正式完结,因比赛后面的已经没那么重要了,咱们0day见)

==>转载请注明来源哦<==
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇