汇编语言程序设计
用汇编语言写源程序
伪指令
没有对应的机器码的指令,最终不被CPU所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
常见的汇编程序格式如下:
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
将上述代码拆解
伪指令部分如下:
assume cs:codesg
codesg segment
....
codesg ends
end
程序返回部分如下:
....
....
mov ax,4c00H
int 21H
....
汇编指令部分如下:
....
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
....
程序中的三种伪指令
段定义
1)一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或者当做栈空间使用。
2)一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
3)定义程序中的段:每个段都需要段名
;分号为汇编语言的注释符,与C语言的 //和/**/同理
段名 segment ;段的开始
... ;汇编代码
段名 ends ;段的结束
end(不是ends)
汇编程序的结束标记。若程序结尾处不加end,编辑器在编译程序时,无法知道程序在何处结束。
assume(假设)
含义是假设某一段寄存器和程序中的某一个用segment ... ends定义的段相关联 -- assume cs:codesg指cs寄存器与codesg相关联,将定义的codesg当做程序的代码段使用。
编程求23
1)定义一个段
2)实现处理任务
3)指出程序在何处结束
4)段与段寄存器关联
5)加上程序返回的代码
代码实现如下
assume cs:code
code segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4C00H
int 21H
code ends
end
将原文件变成可执行文件
masm 文件名.mas 编译程序
link 文件名.ojb 链接程序
文件名 运行程序
Debug跟踪程序的执行
debug跟踪test程序

如图可以看到数据段从075AH开始,代码段从076AH开始
小结:
1)程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,这程序所在的内存区的地址为DS:0
2)这个内存区的前256个字节村PSP,DOS用来和程序进行通信
3)从256个字节处向后的空间存放的是程序,CS的值为DS + 10H
4)程序加载后,CX中存放代码的长度(字节)
[ ] 和 ( )
中括号:汇编语法的规定,表示一个内存单元
小括号:为了方便学习而做出的约定,表示了一个内存单元或寄存器中的内容
Loop指令
功能:实现循环
CPU执行loop指令进行的操作
1)(cx) = (cx) - 1
2)判断cx中的值
不为0则转至标号处执行程序
如果为0则向下执行
实例
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
用cx和loop指令向配合实现循环功能的三个要点:
1)在cx中存放循环此时
2)用标号指定循环开始的位置
3)在标号和loop指令的中间,写上要循环执行的程序段(循环体)
实现123*236
assume cs:code
code segment
mov ax,0
mov cx,123
l:add ax,256
loop l
mov ax,4c00H
int 21H
code ends
end
计算FFFF:0006字节单元中的数乘以3,结果存储在dx中(连加三次,等同乘三)
assume cs:code
code segment
mov ax,0ffffH
mov ds,ax
mov bx,6
mov al,[bx] ;ax低位寄存器初始化
mov ah,0 ;ax高位寄存器初始化
mov dx,0
mov cx,3
l:add dx,ax
loop l
mov ax,4c00H
int 21H
code ends
end
段前缀的使用
观察如下代码
assume cs:code
code segment
mov ax,2000H
mov ds,ax
mov al,[0]
mov bl,[1]
mov cl,[2]
mov dl,[3]
mov ax,4c00H
int 21H
code ends
end
代码中[ ]的含义是将段地址为2000偏移地址为[ ]中数据位置的值赋值给左侧的寄存器,但是在真正编译链接后,通过debug进行查看会发现,编译器只是将0赋值给al,1赋值给bl,以此类推。与想象中编译的结果不同。因此正确的书写格式如下:
assume cs:code
code segment
mov ax,2000H
mov ds,ax
mov al,ds:[0] ;添加段前缀
mov bl,ds:[1]
mov cl,ds:[2]
mov dl,ds:[3]
mov ax,4c00H
int 21H
code ends
end
计算ffff:0 -- ffff:b 字节单元中的数据的和,结果存储在dx中
assume cs:code
code segment
mov ax,0ffff
mov ds,ax ;为数据段地址赋值
mov dx,0 ;初始化为零,用于记录总和
mov ax,0 ;利用al记录字节数据
mov cx,12 ;循环次数
mov bx,0 ;作为地址的偏移
l:mov al,ds:[bx]
inc bx ;bx自行加一
add dx,ax ;记录总和
loop l
mov ax,c400H
int 21H
code ends
end
将内存ffff:0 -- ffff:b中的数据拷贝到 0020:0 -- 0020:000b单元中
代码一:
assume cs:code
code segment
mov cx,12 ;需要循环十二次复制操作
mov bx,0 ;偏移量
mov dx,0 ;当做中间变量
l:mov ax,0ffff
mov ds,ax
mov dl,ds:[bx] ;从ffff段位置取出一个数据
mov ax,20
mov ds,ax
mov ds:[bx],dl ;将中间变量赋值给20段地址的空间
inc bx ;偏移地址加一
loop l ;跳转到循环的开始,继续下一个空间的复制
mov 4c00H
int 21H
code ends
end
但是上述操作过于繁琐,每次复制一个字节空间,都需要对段地址进行更换两次,因此进行代码二的优化
代码二:
assume cs:code
code segment
mov ax,0ffff
mov ds,ax ;为第一个段寄存器赋值
mov ax,20
mov ex,ax ;为第二个段寄存器赋值
mov bx,0 ;初始化偏移量为0
mov cx,12 ;设置循环次数
mov ax,0 ;存储中间变量
l:mov al,ds:[bx] ;取出字节中数据
mov ex:[bx],al ;将al中数据存储到指定地址
inc bx ;偏移量自行加一
loop l
mov ax,4c00H
int 21H
code ends
end
在代码段中使用的数据
在上述代码二中的代码,存在一定的危险性
由于ds与ex寄存器被直接指定了物理地址,如果该物理地址恰好是其他程序运行所需要的空间,那么如果进行数据的更改,造成的结果是无法预见的。
因此衍生出来的对策
1)在程序的段中存放数据,运行时由操作系统分配空间
2)段的类别:数据段、代码段、栈段
3)各种段中均可以有数据
4)可以在单个的段中安置,也可以将数据、代码、栈放入不同的段中
应用案例
编程计算以下八个数据的和,结果存放在ax寄存器中
0123H 0456H 0789H 0ABCH 0DEFH 0FEDH 0CBAH 0987H
数据的定义
字型数据:dw(define word)
字节型数据:db(define byte)
双字型数据:dd(define double)
在代码段中定义字形数据(define word),代码如下:(下述代码存在问题)
assume cs:code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
mov bx,0 ;初始化偏移量为0
mov ax,0 ;在ax中记录总和,将ax初始化为0
mov cx,8 ;初始化循环次数
l:add ax,cs:[bx]
add bx,2
loop l
mov ax,4c00H
int 21H
code ends
end

可以观察到,下一条执行的指令不是mov bx,0,而是将定义的字型数据当做了指令来执行,那么逻辑上与我们构想的程序不相符,而程序执行的位置由cs:ip来决定,因此ip的位置如果跳过前八个双字的数据,直接指向执行命令的话,那么程序运行的过程与我们逻辑上设想的是一样的。
代码做如下改进
assume cs:code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
start: ;表示程序的开始
mov bx,0 ;初始化偏移量为0
mov ax,0 ;在ax中记录总和,将ax初始化为0
mov cx,8 ;初始化循环次数
l:add ax,cs:[bx]
add bx,2
loop l
mov ax,4c00H
int 21H
code ends
end start

在代码段中使用栈
利用栈,将下述八个数据逆序存放
0123H 0456H 0789H 0ABCH 0DEFH 0FEDH 0CBAH 0987H
代码实现如下
assume cs:code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H ;cs:0至cs:F
dw 0,0,0,0,0,0,0,0 ;定义栈空间cs:10至cs:1F
start: ;定义程序的开始
mov ax,cx
mov ss,ax ;为栈段寄存器赋初值
mov sp,20 ;定义栈顶指针
mov bx,0 ;初始化偏移量
mov cx,8 ;定义循环次数
pushstack:
push cs:[bx] ;入栈操作
add bx,2 ;由于数据是字型,因此偏移量一次加二
loop pushstack ;进行循环
mov bx,0 ;将偏移量重新置零
mov cx,8 ;设置循环次数
popstack:
pop cs:[bx] ;将出栈的值赋值到指定地址空间
add bx,2
loop popstack ;进行循环
mov ax,4c00H
int 21H
code 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
stack ends
code segment
start: ;定义程序的开始
mov ax,cx
mov ss,ax ;为栈段寄存器赋初值
mov sp,20 ;定义栈顶指针
mov ax,data
mov ds,ax ;初始化数据段
mov bx,0 ;初始化偏移量
mov cx,8 ;定义循环次数
pushstack:
push cs:[bx] ;入栈操作
add bx,2 ;由于数据是字型,因此偏移量一次加二
loop pushstack ;进行循环
mov bx,0 ;将偏移量重新置零
mov cx,8 ;设置循环次数
popstack:
pop cs:[bx] ;将出栈的值赋值到指定地址空间
add bx,2
loop popstack ;进行循环
mov ax,4c00H
int 21H
code ends

浙公网安备 33010602011771号