汇编语言设计----汇编基础第一节


汇编语言设计


机器语言是机器指令集合

早期:打纸带:1打孔,0不打孔

汇编语言与汇编指令

汇编语言主体是汇编指令

  • 汇编指令是机器指令便于记忆书写格式

  • 汇编指令是机器指令助记符

  • 机器指令100110001100001100000

    汇编MOV,AX,BX

汇编语言编写程序工作过程

汇编指令--编译器--机器码

伪指令--编译器执行

汇编指令--机器码助记符

其他符号--编译器识别

计算机组成

cpu,总线,内存,扩展槽

指令和数据存放在内存或磁盘上,都是二进制

数据如何表示:二进制B,八进制O,十进制D,十六进制H

计算机中的存储单元

一个存储器有128单元

8086有20条数据总线,那有2的20次方的,1MB的空间

总线:链接cpu和其他芯片的导线,

逻辑上总线分:数据总线,地址总线,控制总线

地址总线

地址总线宽度,决定了可寻址总线的存储单元大小

数据总线

cou内存和其他器件之间的数据传送是通过数据总线进行的,数据总线宽度决定了cou的外界数据传输速度

8088 8位,8086,16位

控制总线

cpu对外部设备器件进行控制

内存的读写和地址空间

存储单元地址(地址信息)

器件的选择,读或写的命令(控制信息)

读或写的数据(数据信息)

什么是内存地址空间

RAM:能读能写,没电就没了,

ROM:只读,系统的BIOS

所有物理存储器看做一个由若干存储单元组成的逻辑存储器

每个物理存储器在逻辑存储器中占有一个地址段,即一段地址空间

汇编语言实践语言的环境搭建

目标理解计算机底层工作原理

DOS环境

链接:https://pan.baidu.com/s/1P62ldT7uXqKBAl3v4tXUOA?pwd=6fr9
提取码:6fr9
--来自百度网盘超级会员V5的分享

解压后的文件夹如下:
 
 其中:
 DOSBox0.74-win32-installer.exe是DOS模拟器的安装文件;
 MASM文件夹中是汇编程序设计中用到的命令;
 EX文件夹中提供了几个汇编程序作为示例。
 
 照下面的提示完成环境的配置。
 1. 将MASM文件夹拷贝到工作盘
 MASM中包含了8086汇编程序设计中要用到的几个必需的工具(masm.exe汇编、link.exe连接、Debug.exe调试,edit.com和edlin.com是两个编辑程序。),如下:
 
 将这个文件夹拷贝到你习惯的工作盘(我用D盘作工作盘,拷贝后,这些文件在D:\MASM中)。
 2. 安装DOSBox
 双击DOSBox0.74-win32-installer.exe,照提示安装,最后,桌面上有快捷方式:
  
 3. 启动DOSBox
 双击上面的图标,启动DOSBox0.74。启动后的界面如下:
 
 4.挂接MASM文件夹
 在Z:>提示符后输入命令mount c d:\masm。这个命令的意思是,将本机d:\masm文件夹作为模拟器的C盘。
 运行命令后的界面如下:
 
 注意:d:\masm是第1步拷贝到的工作目录,若你在第1步设置的是其他工作,请按你的实际设置改动。
 下面,将工作盘转换到C盘(输入C:并回车),再查看目录(dir再回车),可以看到类似下面的界面,标志着工作环境设置好了。
 
 在以后工作时,只需要第3、4步即可。

寄存器和数据存储


运算器进行信息处理

寄存器进行信息存储

控制器协调各种器件

内存总线实现CPU各器件之间的联系

14个寄存器

通用寄存器AX例子

在AX中存储18D

18D --十进制
12H --十六进制
10010 --二进制

问:8088 如何8086兼容?

分高低位

01001110 00100000
高位        低位

AH AL AX
BH BL BX
CH CL CX
DH DL DX

字:一个字可以存在一个16位的寄存器中

高位字节在高位寄存器高8位,低位字节在低8位寄存器

MOV add指令

mov ax.18 AX = 18 相当于赋值

mov ah.78 AH = 78 把78放到了高 8位

add ax.8 AX = AX +8 在AX数据加8

mov.ax.bx AX = BX BX数据送入AX中

add ax.bx AX = AX+BX 把AX,BX 内容相加

汇编指令不区分大小写

确定物理地址方法

川普访问内存单元都要给出内存单元地址

每一个内存单元在这个空间构成的空间都有唯一地址,这个地址叫物理地址

8086有20位地址总线,寻址能力是1mb

8086是16位cpu,寻址能力是64KB

如何解决寻址空间的矛盾

用两个16位地址和段地址,偏移地址合成一个20位的物理地址

物理地址 = 段地址X16+偏移地址

例子:

段地址: 1230 偏移1位

偏移地址 00C8

12300
+00C8
= 123C8

物理地址 = 123C8

本质含义

用两个16位地址相加得到20位物理地址

本质含义:cpu在访问内存时,用一个基础地址和一个相对于基础地址的偏移地址相加,给出一个内存单元的物理地址

内存的分段表示法

内存并没有分段,段的划分来自于cpu

同一段内存,多种分段方案

根据段地址计算偏移地址

4个段寄存器

CS-代码段DS数据段-SS栈段-ES段加

debug的使用

R查看寄存器的内容

R 寄存器名 改变指定寄存器内容(128字节)

RCS,RIP

D命令:查看内存中的内容

D段地址:偏移地址-列出内存中指定地址处的内容

D 1000:0

D段地址:偏移地址 结尾偏移地址-;列出内存中指定地址范围内容

E命令

E段地址:偏移地址 数据1

EE段地址:偏移地址

E 1000:0

逐个询问式修改

空格接收,继续

回车,结束

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
mav ax,bx
add ax,bx
对应机器码
B8 23 01
BB 03 00
89 D8
01 D8

a 地址-写入汇编指令

d 地址-查看数据

u 地址 -查看代码

T指令(执行机器命令)

t 执行CS:IP处的指令

mov ax,123H

两个关键的寄存器

CS:代码寄存器

IP:指令指针寄存器

CS:IP :cpu将内存中CS:IP指向的内容当做指令执行

CS内容是2000H,IP中内容是0000H

8086PC工作过程简要概述

1.CS:IP指向内存单元读取指令,读取的指令进入指令缓存器

2.IP = IP+ 所读取的指令的长度,从而指向下一条指令

3.执行指令,转到步骤1

jmp指令

事实:执行何处的指令,去取决于CS:IP

应用;可以通过改变CS IP的内容,来控制执行目标的指令

转移指令jmp

同时修改cs,ip内容

jmp段地址:偏移地址

jmp:2AE3:3

jmp:30B16

用指令中给出的段地址修改cs,偏移地址修改ip、

仅修改ip内容

jmp 某一合法寄存器

?	jmp ax (类似于mov IP,ax)

?	jmp bx

内存中字的存储

8086 16位作为一个字

高8位,低8位

低位字节低地址单元,高位字节高地址单元

0是低地址单元,1是高地址单元

字单元:由两个连续地址连续内存单元组成,存放一个字型数据

原理在一个字单元中,低地址单元存放低位字节,搞地质单元存放高位字节

用DS和[address]实现字的传送

要解决的问题:cpu从内存单元中要读取数据

在8086中,内存地址有段地址和偏移地址组成(段地址:偏移地址)

DS寄存器存放要访问的数据和段地址

偏移地址用[]形式

将段地址送入DS的方式
mov bx,1000H
mov bs, bx
套路是: 数据---一般寄存器---段寄存器

字的传送

8086可以一次性传送一个字

例子:

mov bx,1000H
mov ds ,bx
mov ax, [0] # 1000:0处的字型数据送入ax
mov[0],cx # cx中的16位数据送到1000:0处

[0] -- 1123
[1] -- 2211
[2] -- 1122

DS与数据段

对内存单元中数据的访问

对于8086 可以根据需要将一组内存单元定义为一个段

将一组长度N,地址连续,起始地址16的倍数内存单元当做专门存储数据内存空间,从而定义的一个数据段

段地址 123BH 起始偏移地址0000H 长度10字节

处理方法 DS([address])

用DS存放数据段的段地址

用相关指令访问数据段中的具体单元,单元地址由[address]

将123B0H--123BAH定义为数据段

累加数据段中前3个单元的数据

mov ax,123BH
mov ds,ax
mov al,[0]
add al,[0]
add,al,[1]
add al,[2]

用mov指令操作数据

add指令操作

栈结构

栈的基本操作

入栈和出栈

入栈:将一个新的元素放到栈顶

出栈:从栈顶取出一个元素

栈顶元素总是在最后入栈,需要出栈,又最先从栈中取出

cpu提供栈机制

8086cpu提供相关指令,支持用栈的方式访问内存空间

基于8086cpu编程,可以将一段内存当栈来使用

PUSH入栈和POP出栈指令
push ax:将ax从中的数据送入栈中
pop ax:从栈顶取出数据送入ax

问题:cpu如何知道一段内存空间被当做栈使用?

执行push和pop的时候,如何知道哪个单元是栈顶单元?

答案

8086中有两个与栈相关的寄存器

栈段寄存器SS--存放栈顶的段地址

栈顶指针寄存器SP--存放栈顶的偏移地址

栈的操作

mov ax,1000H

mov ss,ax

mov sp,0010H

mov ax,001AH
mov bx,001BH

push ax
push bx

pop ax
pop bx

执行入栈,栈顶超出栈空间

总结

push,pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push指令访问的内存单元地址不是在指令中给出的,而是有SS:SP指出的

段总结

物理地址 = 段地址x16 +偏移地址

数据段

  • 将段地址放在DS中
  • 用mov,add,sub等访问内存单元指令时,cpu将我们定义的数据段中内容当做数据段访问

代码段

  • 将段地址放在CS中,将段中第一条指令的偏移地址放在IP
  • CPU将执行我么定义四的代码段中指令

栈段

  • 将段地址放在SS,将栈顶单元偏移的放在SP中
  • CPU在需要我们进行栈操作push,pop,就将我们定义的栈段当做栈空间来使用

汇编语言程序


汇编语言编程序的工作过程

程序员--汇编程序--编译器--机器码--计算机

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

伪指令是编译器执行,编译器根据伪指令进行相关的编译工作

汇编程序:汇编程序好伪指令的文本

程序返回:程序结束后将cpu的控制权交还给使运行的程序系统

程序的三种伪指令

段定义:一个会变程序是多个段组成的,这些段用来被存放代码,数据或当做栈空间来使用

一个有意义的汇编程序中至少有一个段,这个段用来存放代码

段名 segment --- 段的开始

段名 ends --- 段的结束

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



'''



end

汇编程序结束标志

assume

含义是假设某一段寄存器中某一个使用segment,ends定义段相关联--assume cs:cdoesg指的是cs寄存器与codesg关联,将定义的codesg当做程序的代码来使用
'''

源程序经过编译连接后变为机器码

源程序文件 .asm ---可执行 .exe

汇编程序结构

在debug中直接写入指令编写汇编程序

适用于简单的短小精悍的程序

单独编写成源文件在编译可执行文件--编写大程序

注释

编写一个汇编程序

步骤

  • 定义一个段

  • 实现处理任务

  • 指出程序在何时结束

  • 段与段寄存器关联

  • 加上程序返回的代码

  • assume cs:abc
    abc segment
    abc segment
    	mov ax,2
    	add ax,ax
    	add ax,ax
    	
    	mov ax,4c00h
    	int 21h
    abc ends
    end
    

    程序中出现的错误

    语法错误

    写错了。。。

    逻辑错误

    脑子抽了。。。

写出源程序到可执行文件过程

编辑源程序

编译

? 对目标文件.OB对一个源程序进行编译要得到的结果

? 列表文件.LST是编译器将源程序编译为目标文件的过程中产生的中间结果

? 交叉引用文件。CRF和列表文件一样,是编译器将源程序编译为目标文件过程中产生的中间结果

对源程序编译结束,编译器输出最后两行告诉错误必须改正的错误

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



提示语法错误

severe errirs

找不到所给出的源文件

连接

可执行文件是对一个程序进行连接要得到的最终结果

映象文件:是连接程序将目标文件连接为可执行文件过程中产生中间结果

库文件包含了一些可以调用的子程序,如果我们的程序中调用了某一个库文件中的子程序,就需要连接的时候将这个库文件和我们的目标文件连接到一起,生成可执行文件、

no stack segment,一个 没有栈段的错误警告,可以不去理会

执行程序;直接输入文件名

程序的运行和跟踪


DEBUG装在程序EXE

程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址是0,程序内存区地址是DS:0

这个内存区前256字节存PSP,DOS用来程序进行通信

从256字节向后空间存放的是程序,CS值是DS+10H

程序加载后,CX存放代码长度

继续命令P

类似于T命令,逐条执行指令,显示结果,但遇到子程序,中断等,直接执行,然后显示结果

运行命令G

从指定地址处开始运行程序,知道断点结束或者程序正常结束

在DOS中执行

程序执行的常态

一个内存单元的描述:

  • ? 内存单元地址
  • ? 内存单元长度

[...]表示一个内存单元

(...)表示一个内存单元或寄存器中内容

LOOP指令

功能:实现循环

cpu执行loop指令时进行的操作

  1. (cx)=(cx)-1

  2. 判断cx的值

    1. 不为0则转到标号处执行程序

    2. 如果是0就向下执行

要求

cx要提前存放循环次数,(cx)影响着loop指令执行结果

要定义一个符号

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

例子:编程计算 2^2 , 2^3

cx和loop指令想配合实现循环功能的三个要点

? 1.在cx中存放循环次数

? 2.用标号指定循环开始的位置

? 3.在标号和loop指令的中间,写上要循环执行的程序段(循环体)

? 例子:计算 123x236

assume cs:code
code segment
	mov ax,0
	mov cx,236
	s: add ax,123
	loop s
	mov ax,4c00h
code ends
end

例子

编译:

masm p5-1.asm
link p5-1
debug p5-1.exe

assume cs:code

code segment
	mov ax,0ffffh
	mov ds,ax
	mov bx,6
	mov al,[bx]
	mov ah,0
	
	mov dx,0
	mov cx,3
	s: add dx,ax
	loop s
	
	
	mov ax,4c00h
	int 21h
code ends
end




引入段前缀,异常现象的对策

访问内存单元--loop和[bx]

问题:计算ffff:0-ffff:b字节单元中的数据的和,结果存储在dx中

分析:略

对策:取出8位数据,加到16位寄存器

mov al,ds:[addr]  取出8位数据
mov ah,0
add dx,ax

段前缀的使用

代码段中数据的使用

将内存ffff:0中数据拷贝到0:200-0:20b

问题,程序中直接写地址,很危险

对策

在程序中段中存放数据,运行时由操作系统分配空间

段的类别:数据段,代码段,栈段

各种段中均可以有数据

可以在单个段中安置,也可以将数据代码,栈放入不同的段中

实际应用

问题编程计算以下8个数据的和,结果存放到ax寄存器中

解决方案

assume cd:code
code segment
	dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中
	
	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:定义一个字

db:定义一个字节

dd:定义一个双字

代码有问题

改进

assume cs:code
code segment
	dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中

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 通知编译器程序结束外,还可以通知编译器入口在什么地方

在代码段中使用栈

程序运行时候。定义存放的数据cs:0--cs:f,一共8个单元

依次将八个单元中数据入栈,然后一次出栈找到8个字单元,从而实现数据的逆序存放

栈需要内存空间,在程序中通过定义:空数据来获得

数据逆序存放程序

assume cs:codesg
codesg segment
	dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987 ;在代码段中定义数据,只要求数据本身,并未指定在哪些单元中
	dw 0,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

将数据代码栈放入不同段



assume cs:code,ds:data,ss:stack
data segment
    dw 0123H,0456H,0789H,0ABCH,0DEFH,0CBAH,0987
data ends
stack segment
    dw 0,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
    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 

内存寻址方式


处理字符问题

汇编中,用‘..................'的方式指明数据是字符形式给出的,编译器把他们转化为响应的ASCII码

大小写字符问题

大写+20H --->小写 小写-20H --->大写

大小写转换

第一个字符串:小--大

第二个 大--小

assume cs:codesg,ds:datasg
datasg segment
    db 'BsSic'
    db 'INforMAtion'

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
    inc bx
    loop s
    ;第二个字符串
    mov bx,5
    mov,cx 11
    s0:mov al,[bx]
    or al,00100000b
    mov [bx],al
    inc bx
    loop s0
    mov ax,4c00h
    int 21h
codesg ends
end start

[bx+idata]含义

表示一个内存单元,它的偏移地址为(bx)+idata (bx中数值加上idata)

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

将一个内存单元的内容送入ax

这个内存单元的段地址在ds中,偏移地址在ds中,偏移地址为200加上bx的数值,数学化的描述是(ax) = ((ds)*16+200+(bx))

指令mov,ax,[bx+200]其他用法

mov ax,[200+bx]

mov ax,200[bx]

mov ax,[bx].200

用数组方法解决大小写转换问题

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,[5+bx]
    or al,00100000b
    mov [5+bx],al
    inc bx
    loop s

    mov ax,4c00h
    int 21h
codesg ends
end start


SI和DI寄存器


8086cpu有十四个寄存器

通用寄存器 AX,BX,CX,DX

变址寄存器 SI,DI

指针寄存器 SP,DP

指令指针寄存器 IP

段寄存器 CS,SS,DS,ES

标志寄存器 PSW

执行与地址有关操作

SI,DI和BX相近的寄存器

SI:源变址寄存器

DI目标变址寄存器

区别:SI,DI不能够分成两个8位寄存器来使用

1.mov bx,0

  1. mov s,0
  2. mov di 0

例子:用寄存器SI,SI实现将字符串‘welcome to masm’复制到特后面的数据区

源数据起始地址:datasg:0

目标数据起始地址:datasg:16

用ds:si指向要复制的原始字符串

用ds:di指向目的空间

然后用一个循环来完成复制

原地址:si

目标地址:di

assume cs:codesg,ds:datasg
datasg segment
    db 'welcome to masm'
    db '...............'
    datasg ends
    codesg segment
    start:
        mov ax,datasg
        mov ds,ax

        mov si,0
        mov di,16
        mov cx,8
        s: mov ax,[si]
        mov [di],ax
        add si,2
        add di,2
        loop s

        mov ax,4c00h
        int 21h
    codesg ends
    end start

[bx+si] [bx+di]指定地址

  • [bx+si] 表示一个内存单元
  • 偏移地址 bx +si 即是数值上加si中的数值

指令mov ax, bx+si 含义

  • 将一个内存单元的内容送入ax
  • 这个内存单元长度是2字节(字单元),存放一个字
  • 偏移地址是bx中数值加上si中的数值
  • 段地址 在ds中

指令mov ax,[bx+si]的数学化描述

  • (ax) = ((ds)*16+(bx)+(si))
  • mov ax,[bx+si]其他用法
  • mov ax,[bx] [si]

例子

mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+si]
inc si
mov cx,[bx+si]
inc si
mov di,si
mov ax,[bx+di]


[bx+si+idata] 和 [bx+di+idata]

偏移地址是 (bx) +(si)+idata 就是bx中的数值加上si中数值再加上idata

move ax,[bx+si+idata]含义

将一个内存单元的内容送入ax

这个内存单元长度是2字节,存放一个字

偏移地址中bx中数值加上idata,段地址在ds中

数字化描述

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

指令mov,ax,[bx+si+idata] 其他写法

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

例子

内存中数据 2000:1000 BE 00 06 00 6A 22

程序执行后

mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+2+si]
inc si
mov cx,[bx+2+si]
inc si
mov di,si
mov ax,[bx+2+di]

内存寻址方式的灵活应用

例子

例子

二重循环用了同一个cx,解决办法

但是存在问题是,cx占用寄存器资源,比较浪费

可以利用固定的储存单元,也可以用栈来存数据

直接寻址过程

寄存器间间接寻址过程

寄存器相对寻址过程

基址变址寻址过程

相对基址变址寻址过程