《汇编语言》——王爽
第1章 基础知识
汇编语言是直接在硬件之上工作的编程语言。
PC机及CPU物理结构和编程结构的全面研究——《微机原理与接口》
计算机的一般结构、功能、性能的研究——《计算机组成原理》
如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作——《汇编语言》
1.1机器语言
机器语言是机器指令的集合。本质是一列二级制数字,计算机将之转变为一列高低电平,驱动计算机的电子器件,进行运算。
计算机是指由CPU和其他受CPU直接或间接控制的芯片、器件、设备组成的计算机系统。如:PC机。
每一种微处理器,(由于硬件设计和内部结构的不同,就需要不同的电平脉冲来控制、工作)都有自己的机器指令集,即机器语言。
书写和阅读机器码程序困难-->汇编语言
1.2汇编语言的产生
汇编语言的主体是汇编指令。
过程:程序员用汇编语言写出源程序,再用汇编编译器将其编译为机器码,有计算机最终执行。
1.3汇编语言的组成
汇编语言由3类指令组成:
- 汇编指令(
核心):机器码的助记符,有对应的机器码。 - 伪指令:无对应机器码,由编译器执行,计算机不执行。
- 其他符号:无对应机器码,由编译器识别,如:+、-、*、/ 等。
1.4存储器
CPU和内存的关系:要想让CPU工作,必须向它提供存储在内存中的指令和数据。
硬盘和内存的关系:硬盘中的数据或指令,必须读到内存中,才能被CPU调用。
CPU如何向内存中读写信息呢?
1.5指令和数据
指令和数据,在内存或硬盘上都是二进制信息。
CPU有时把信息看做指令,有时看做数据。
举例:
10001001110110000
89D8H#数据
mov ax,bx#指令
1.6存储单元
存储器被划分成若干存储单元,从0开始顺序编号。
一个存储单元的存储信息量:
-
单位:位(计算机存储信息的最小单位;bit)、字节(微型存储器容量的最小单位,Byte),1Byte = 8bit
-
微型计算机存储器的存储单元可以存储一个Byte(字节),即8个bit(二进制位)。
-
单位换算:1KB=1024B 、1MB=1024KB 1GB=1024B 1TB=1024GB
1.7 CPU对内存的读写
问题1:CPU如何对内存进行读写操作?
CPU要想进行数据的读写,必须和外部器件(芯片)进行3类信息的交互:
- 存储单元的地址(地址信息);
- 器件的选择,读或写的命令(控制信息);
- 读或写的数据(数据信息)。
问题2:CPU通过什么将地址、数据和控制信息传到存储器芯片中的呢?
电子计算机能处理、传输的信息都是电信号,电信号当然要用导线传送。计算机中专门连接CPU和其他芯片的导线,通常称为总线。总线,根据传送信息的不同,从逻辑上分为:地址总线、数据总线、控制总线。
问题3:如何命令计算机进行数据的读写呢?
要让计算机或微处理器工作,应向它输入能够驱动它进行工作的电平信息(机器码)。
读操作过程示意图:
1.8地址总线
作用:CPU通过地址总线来指定存储单元。
一个CPU有N根地址总线,则可以说地址总线宽度为N,寻址空间:2^N
1.9数据总线
作用:CPU与内存或其他器件之间的数据传送通过数据总线进行。
数据总线的宽度决定了CPU和外界的数据传送速度。 N根数据总线一次传送一个N/8B的数据。
1.10控制总线
作用:CPU对外部器件的控制是通过控制总线来进行的。
控制线是一群不同控制线的集合,控制总线的宽度决定了CPU对外部器件的控制能力。
1.11内存地址空间(概述)
1.12主板
每个PC机都有一个主板。
主板有一些核心器件和一些主要器件,这些器件通过总线相连。
器件:CPU、存储器、外围芯片组、扩展插槽(一般插有RAM内存条和各类接口卡)等
1.13接口卡
网卡、显卡、声卡
CPU对外部设备不能直接控制,如:显示器、音箱、打印机等。->接口卡
CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设进行工作。
1.14各类存储器芯片
读写属性分类:
-
随机存储器(RAM):带电存储,可读可写,关机丢失
-
只读存储器(ROM):只读不写,断电不丢失
1.15内存地址空间
存储器都和总线相连
CPU对他们进行读写时,都通过控制线发出内存读写命令
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,即内存地址空间
内存地址空间的大小受CPU地址总线宽度的限制。
第2章 寄存器
对CPU内部构造的理解:一个CPU由运算器、控制器、寄存器等器件构成,这些器件靠内部总线相连。
- 运算器,进行信息处理
- 寄存器,进行信息存储
- 控制器,控制各种器件进行工作
- 内部总线,连接各种器件,在他们之间进行内部传送
区分:内部总线与外部总线
- 内部总线,实现CPU内部各个器件的联系
- 外部总线,实现CPU和主板上其他器件的联系
不同的CPU,寄存器的个数、结构是不相同的。8086CPU有14个寄存器:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW
2.1通用寄存器
概念?
8086CPU的所有寄存器都是16位的,可以存放两个字节。AX,BX,CX,DX,这四个寄存器通常用来存放一般性的数据,被称为通用寄存器。
8086CPU的通用寄存器都可分为两个独立使用的8位寄存器来用。AX的低八位构成AL寄存器,高8位构成了AH寄存器。
一个16位寄存器所能存储的数据的最大值为多少?2^15-1
2.2字在寄存器中的存储
字:word,一个字由两个字节组成,高位字节和低位字节。
2.3几条汇编指令
mov ax,18
mov ah,78
add ax,8
mov ax,bx
add ax,bx
注意:八位寄存器的进位丢失问题
2.4 物理地址
CPU访问内存时,要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,即物理地址。
CPU通过地址总线送入存储器,必须是一个内存单元的物理地址。
不同的CPU可以有不同的形成物理地址的方法。
2.5 16位结构的CPU
16到底指什么呢?
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度为16位
2.6 8086给出物理地址的方法
2.7“段地址*16+偏移地址=物理地址”的本质含义
2.8段的概念
2.9段寄存器
2.10CS和IP
2.11修改CS和IP的指令
2.12代码段
实验1 查看CPU和内存,用机器指令和汇编指令编程
第3章 寄存器(内存访问)
第4章 第一个程序
第5章 [BX]和loop指令
1.[bx]和内存单元的描述
内存信息:①内存单元的地址;②内存单元的长度;
[0]:段地址在ds中,偏移地址为0
[bx]:偏移地址在寄存器中
2.loop指令和循环
3.描述性符号()
4.idata表示常量
5.1[BX]
mov ax,[bx]
mov [bx],ax
5.2 loop指令
loop实现循环,cx存放循环次数
;计算2^12
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
5.3在debug中跟踪用loop指令实现的循环程序
;将ffff:0006中的数据存入dx中
assume cs:code
code segment
mov ax,0ffffh ;汇编源程序中,数据不能以字母开头
mov ds,ax
mov bx,6 ;ffff:0006
mov al,[bx]
mov ah,0 ;将内存单元中的数据,存入ax中
mov dx,0
mov cx,3
s: add dx,ax
loop s ;累加
mov ax,4c00h
int 21h
code ends
end
g指令:一次执行完标号s前的指令
p指令:自动执行loop
5.4debug和汇编masm编译器对指令的不同
debug中:
mov al,[0]
;al中存放ds:[0]数据
masm中:
mov al,0
mov al,[0]
;等价
masm中正确写法:
mov ax,2000h
mov al,ds:[0]
mov al,ds:[bx]
mov al,[bx]
5.5 loop和[bx]的联合应用
用同一种方法处理地址连续的内存单元的问题:
;ffff:0 ~ffff:b单元的数据:EA C0 12 00 F0 30 31 2F 30 31 2F 39
;和为:dx:0405h
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0
mov dx,0
mov cx,12
s: mov al,[bx]
mov ah,0
add dx,ax
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
分析问题的流程:
- 寄存器是否可以计算存储结果?(结果越界)
- 运算时存储单位是否对应?(类型匹配)
- 如何实现计算?
注意点:
- 段寄存器不能直接赋值
mov ds,0;False
- 偏移地址在内存单元[x]
mov al,[0];写法不恰当
5.6段前缀
可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器:
mov ax,ds:[bx]
mov ax,ss:[0]
段前缀:这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的:ds:、 cs:、 ss: 、es: 标识符号
5.7一段安全的空间
;引起死机
assume cs:code
code segment
mov ax,0
mov ds,ax
mov ds:[26h],ax
mov ax,4c00h
int 21h
code ends
end
实模式和保护模式:
在纯DOS方式(实模式)下,可以不理会dos,直接用汇编语言去操作真实的硬件,因为运行在CPU实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。
但在Windows 2000、Unix这些运行与CPU保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了。
安全空间:0:200~0:2ff的256个字节的空间,不存放系统或其他程序的数据或代码。
5.8段前缀的使用
;将内存ffff:0~ffff:b单元中的数据复制到0:200~0:20b单元中
;ffff:0 ~ffff:b单元的数据:EA C0 12 00 F0 30 31 2F 30 31 2F 39
;0020:0 ~0020:b单元的数据:EA C0 12 00 F0 30 31 2F 30 31 2F 39
assume cs:code
code segment
mov cx,12
mov bx,0
s: mov ax,0ffffh
mov ds,ax
mov dl,[bx]
mov ax,0020h
mov ds,ax
mov [bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
引用两个段寄存器(ds、es),减少重复设置段寄存器地址运算:
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,12
s: mov dl,[bx];默认ds
mov es:[bx],dl
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
实验4 [bx]和loop的使用
;(1)向内存0:200~0:023f依次传送数据0~63(3fh)
assume cs:code
code segment
mov ax,0020h
mov ds,ax
mov bx,0
mov cx,64
s: mov [bx],bx
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end
第6章 包含多个段的程序
程序取得空间的方法:
- 在加载程序的时候为程序分配
- 在程序执行的时候向系统申请
6.1在代码段中使用数据
如何将数据存储在一组地址连续的内存单元中呢?到哪里去找这段内存空间呢?
;计算0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h的和,结果保存在ax中
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;定义字型数据,数据在代码段的最开始,大小16字节
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
-u之后:
cs:0 ~f 中为dw的8个16位的数据;
程序的运行,ip:10h,从而使cs:ip指向程序的第一条指令。
根据什么设置CPU的cs:ip指向程序的第一条要执行的指令?
- 汇编程序在经过汇编编译器编译后会形成可执行文件;
- 可执行文件由源程序和描述信息组成;
- end start 描述了程序的结束和程序的入口,程序的入口被转化为一个入口地址,储存在可执行文件的描述信息中;
- 当程序被程序加载到内存中之后,加载者从程序的描述信息中读到程序的入口地址,设置cs:ip,这样CPU就从我们希望的地址处开始执行。
;程序框架
assume cs:code
code segment
……
数据
start: ……
code ends
end start
6.2在代码段中使用栈
;将程序中定义的数据逆置
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;定义字型数据,数据在代码段的最开始,大小16字节
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;为什么一定呀开辟32字节的内存空间?
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
code ends
end start
6.3将数据、代码、栈放入不同的栈中
一个段的容量不得大于64kb,8086CPU模式限制
考虑用多个段,来存取数据、代码和栈:
段名就相当于有个标号,它代表了段地址
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;定义字型数据,数据在代码段的最开始,大小16字节
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
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
实验验证了data段、stack段、code段,地址的关系:
x,x+1,,x+2
;实验5-(5):将a段与b段中的数据相加,结果存入c段
assume cs:code
a segment
db 1,2,3,4,5,6,7,8
a ends
b segment
db 1,2,3,4,5,6,7,8
b ends
c segment
db 0,0,0,0,0,0,0,0
c ends
code segment
start: mov ax,a
mov ds,ax
mov ax,c
mov es,ax
mov bx,0
mov cx,8
s: mov al,[bx]
mov es:[bx],al
; mov es:[bx],[bx] ;这种写法不正确
inc bx
loop s
mov ax,b
mov ds,ax
mov bx,0
mov cx,8
s0: mov al,[bx]
add es:[bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
code ends
end start
;实验5-(6):将a段中的前8个字型数据,逆置入b段中
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a ends
b segment
dw 0,0,0,0,0,0,0,0
b ends
code segment
start: mov ax,b
mov ss,ax
mov sp,10h
mov ax,a
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
第7章 更灵活的定位内存地址的方法
定位内存单元地址的方法:
- [0]
- [bx]
7.1 and和or指令
-
and指令:逻辑与指令
作用:将操作对象的相应位设置为0,其他位不变
mov al,01100011B 将第6位设置为0 add al,11011111B -
or指令:逻辑或指令
作用:将操作对象的相应位设置为1,其他位不变
mov al,01100011B
将第6位设置为1
or al,00100000B
7.2关于ASCII码
(1)计算机的二进制信息的表示?
答:计算机对信息进行编码,再解码。只要规则相同,人可以理解的信息就可以存入计算机中,再从计算机中取出。
(2)文本编辑过程——键盘输入a,在屏幕上显示得我过程?
答:
- 键盘按下‘a’键,按键信息被送入计算机中,计算机采用ASCII码规则对信息进行编码,将其转化为61H存储在内存的指定空间中;
- 文本编辑软件从内存中取出61H,将其送到显卡的显存中;
- 工作在文本模式下的显卡,用ASCII码的规则,解释显存中的内容。
7.3 以字符形式给出的数据
汇编程序中,\'……\'指明数据以字符形式给出的,编译器将把他们转化为相对应的ASCII码
;汇编程序的字符操作
assume cs:code,ds:data
data segment
db \'unIX\'
db \'foRK\'
data ends
code segment
start: mov al,\'a\'
mov bl,\'b\'
mov ax,4c00h
int 21h
code ends
end start
7.4大小写转换问题
A:65d 01000001
a:97d 01100001
and: 11011111
结果: 01000001
assume cs:codesg,ds:datasg
datasg segment
db \'BaSiC\' ;转小写
db \'iNfOrMaTiOn\' ;转大写
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
; 如果(al)>61H,则为小写字母的ASCII码,则:sub al,20H
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
7.5\6 用[bx+idata]的方式进行数组的处理
assume cs:codesg,ds:datasg
datasg segment
db \'BaSiC\'
db \'MinIX\'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,datasg
mov bx,0
mov cx,5
s: mov al,[bx]
add al,11011111b
mov [bx],al
mov al,[bx+5]
mov al,5[bx]
or al,00100000b
mov [bx+5],al
loop s
mov ax,4c00h
int 21h
codesg ends
end start
#include<stdio.h>
int main(){
char a[5] = "BaSiC";
char b[5] = "MinIX";
int i = 0;
do
{
a[i] = a[i] & 0xDF;
b[i] = b[i] | 0x20;
i++;
} while (i < 5);
printf("%s %s\n",a,b);
getchar();
return 0;
}
7.7 SI和DI(bx)
SI和DI是8086CPU中和bx功能相近的寄存器,且不能够分成两个8位寄存器来用
将 welcome to masm! 复制到datasg:10h~1fh
assume cs:codesg,ds:datasg
datasg segment
db \'welcome to masm!\';16位
db \'................\'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,8
s: mov ax,[bx]
mov [bx+16],ax
; inc bx
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
assume cs:codesg,ds:datasg
datasg segment
db \'welcome to masm!\';16位
db \'................\'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0 ;用ds:si指向要复制的字符串
mov di,16 ;用ds:di指向复制的目的空间
mov cx,8
s: mov ax,[si] ;用16位寄存器进行内存单元的数据传送,一次复制2个字节,共复制8次
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
7.8 [bx+si]和[bx+di]
[si] [bx]
7.9 [bx+si+idata]和[bx+di+idata]
200 [bx] [si]
[bx] [si].200
7.10不同的寻址方式的灵活运用
深入理解下列几种寻址方式:
[idata]
[bx]
[bx+idata]
[bx+si]
[bx+si+idata]
[bx+idata]:
; 将每一个单词的头一个字母改成大写字母
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 bx,0
mov cx,6
s: mov al,[3+bx]
and al,11011111b
mov [3+bx],al
add bx,10h
loop s
mov ax,4c00h
int 21h
codesg ends
end start
[bx+si]:
; 将每一个单词的头一个字母改成大写字母
assume cs:codesg,ds:datasg
datasg segment
db \'ibm \'
db \'dec \'
db \'dos \'
db \'vax \'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4 ;设置外层循环计数值
s: mov dx,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,16
mov cx,dx ;用dx存储的外围循环的计数值恢复cx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
; 将每一个单词的头一个字母改成大写字母dx
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,16
mov cx,ds:[40h] ;用datasg:40h单元的值恢复cx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
; 使用栈来暂时存储数据
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db \'ibm \'
db \'dec \'
db \'dos \'
db \'vax \'
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16 ;栈段
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4 ;设置外层循环计数值
s0: push cx
mov si,0
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
[bx+si+idata]:
; 将每一个单词的前4个字母改成大写字母
assume cs:codesg,ss:stacksg,ds:datasg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db \'1. display \'
db \'2. brows \'
db \'3. replace \'
db \'4. modify \'
datasg ends
codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s: push cx
mov si,0
mov cx,4
s0: mov al,[bx+si+3]
and al,11011111b
mov [bx+si+3],al
inc si
loop s0
add bx,16
pop cx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
本章实践内容较多:
- 寻址方式的灵活运用—用更合理的结构来看待要处理的数据
- 二重循环:外层循环cx的保存和恢复:dx、dw、stack的方式
- 栈的应用:保存临时数据
- 大小写转换的方法:and al,111x(0)0000b(小写变大写/X-32)、or al,000x(1)1111b(大写转小写/X+32)