第四章:第一个程序
书籍电子版 提取码: b62a
一个源程序从写出到执行的过程
-
编写汇编源程序
-
对源程序进行编译连接
编译产生目标文件,windows下生成.obj文件
连接生成可执行文件,windows下生成.exe文件
-
执行可执行文件中的程序
源程序
1. assume cs:codesg ;假设某一段寄存器和程序中的某一个用segment……ends定义的段相关联
1. codesg segment ;定义一个段,段名为codesg,这个段从此开始
2. mov ax, 0123
mov bx, 0456
add ax, bx
add ax, ax
mov ax, 4c00
int 21
1. codesg ends ;名称为codesg的段到此结束
1. end ;结束编译
- 伪代码
- 程序
- 标号:codesg这个标号指代了一个地址,这个段的名称最终会被编译、连接程序处理为一人段的段地址
结构
- 定义一段,名为abc
abc segment
……
abc ends
- 在这个段中写入汇编指令
abc segment
mov ax, 2
add ax, ax
abc ends
- 指出程序何时结束
abc segment
mov ax, 2
add ax, ax
abc ends
end
- 与寄存器相关联
assume cs:abc
abc segment
mov ax, 2
add ax, ax
abc ends
end
- 程序要返回
assume cs:abc
abc segment
mov ax, 2
add ax, ax
mov ax, 4c00
int 21
abc ends
end
windows下在DOSbox中使用masm软件
编译
源文件后缀应该是.asm。
masm 1(,asm)
生成.obj文件
连接
link 1
简化方式
masm 1;
link 1;
这样就不用总是点回车了
执行
1
Debug
debug 1.exe
DOS系统中.exe文件的加载过程:
- 程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,程序所有内存区的地址就为ds:0
- 这个内存区的前256个字节也就是16x16字节,放的是PSP,DOS用来和程序进行通信。这之后就是程序。
DS就是PSP的段地址SA,程序的段地址就是SA:0 + 256 = SA + 10 : 0
(段地址大小最后是要乘16的,所以16*16 = 256)
实验3:编程、编译、连接、跟踪
第五章:[bx]和loop指令
- [bx]和内存单元的描述
[bx]和[0]类似,段地址在ds中,偏移地址在[]中,
要完整的描述一个内存单元要两个信息
- 内存单元的地址
- 内存单元的长度
- 定义()
()表示一个寄存器或者一个内存单元中的内容
(ax)就是ax寄存器中的内容
()中的内容可以是三种类型
- 寄存器名 (ax)
- 段寄存器名 (ds)
- 内存的物理地址 ((ds)*16 + (bx))
- idata表示常量
mov ax, [1]
可以用idata来统一表示[]中的数字
[BX]
这其中,[bx]就是表示以ds为段地址,以bx为偏移地址的内存单元。
loop
此指令就是循环
s: add ax, ax
loop s ;cx = cx - 1
循环多少次是要有一个条件,这里用cx中的内容,如果cx为0就终止循环,而且每执行到loop s此条语句时cx = cx - 1
显然,如果cx = 1, 那就执行一次add
如果cx = 2,那就执行二次add
如果cx = n, 那就执行n次add
例题:用加法计算123*236
有两种加法方式:
- 将123加236次
- 将236加123次
显然第二种更快。
assume cs:code
code segment
mov ax, 0
mov cx, 123
s: add ax, 236
loop s
mov ax, 4c00h
int 21h
code ends
end
例题:计算ffff:0006单元中的数乘以3,结果放在dx中
这里要考虑的问题有以下几个:
- 能不能存的下
- 谁来存,怎么用循环
- ffff:0006是一个字节型数据,如果直接mov到dx中的话肯定会加上ffff:0007怎么才能让高8位不赋过去?或者说将高8位清0
assume cs:code
code segment
mov ax, 0ffffh ;在汇编源程序中,数据不能以字母开头
mov ds, ax
mov bx, 6
mov al, [bx]
mov ah, 0 ;不能保证ah就一定不是0
mov dx, 0
mov cx, 3
s: add dx, ax ;将ax中的值给dx
loop s
mov ax, 4c00h
int 21h
code ends
end
Debug命令
g IP CS:IP前的程序被执行
下一条语句是loop时,可以用p命令结束循环
Debug和汇编编译器masm对指令的不同处理
mov ax, [1]
Debug将它解释为[idata],idata是内存单元的偏移地址
masm将它解释为idata,就是一个普通的数字
- 如果要在编译器中实现Debug中的功能,可以用普通寄存器做一个过渡。
mov ax, 2000h
mov ds, ax
mov bx, 0
mov al, [bx] ;将ds:bx单元中的数据送入al中
- 也可以用在[idata]前面加上段地址:
mov ax, 2000h
mov ds, ax
mov al, ds:[0]
这两段代码实现同样的功能
loop和[bx]的联合使用
例题:计算ffff:0~ffff:b单元中数据的和,结果存在dx中
字节数据放到通用寄存器中,记得高位清0
用一个通用寄存器bx来增长
assum cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov bx, 0
mov cx, 12
mov dx, 0
s: mov cl, [bx] ;mov al, [bx]
mov ch, 0 ;mov ah, 0
mov dx, cx ;mov dx, ax
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
段前缀
mov ax, ds:[bx]
mov ax, cs:[bx]
这里cs,ds就是段前缀
一段安全的空间
我们面临一种选择,是在操作系统中安全、规矩的编程,还是自由、直接的用汇语言去操作真实的的硬件,了解那此早已被层层系统软件掩盖的真相?在大部分的情况下,我们选择后者。
段前缀的使用
例题:将内存ffff:0~ffff:b单元中的数据复制到0:200~020b单元中
assume cs:code
code segment
mov bx, 0
mov cx, 12
s: mov ax, 0ffffh
mov ds, ax
mov dl, [bx]
mov ax, 0020h
mov ds, ax
mov [bx], dl ;送入到的是一个字节空间,而不是一个字空间,所以就不用将dh清0
inc bx
loop s
mov ax, 4c00h
int 21h
code edns
end
每次设置ds太麻烦了
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]
mov es:[bx], dl
inc bx
loop s
mov ax, 4c00h
int 21h
code edns
end
实验4:[bx]和loop的使用
第六章:包含多个段的程序
前面的程序只有一个代码段,如果想要其它空间来存放程序,就需要其它空间来存,如果自己来分配的话,可能导致空间不安全,让操作系统来分配就不会出现这样的问题。
- 程序取得空间的两种方式
- 在加载程序的时候为程序分配
- 程序在执行过程中向系统申请
原先已经说过,程序头的段地址在cs中,那么其它的内容(不是程序中的内容就应该在其它的段中),这样就有了多个段。
在代码段中使用数据
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
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
这里说明一下,start和end start是改变ip地址的,cs指向的是程序的头,但是在这段代码中cs:0与cs:1上存放的是2301,dw所定义的数据总共就占了10h(16)个字节。所以mov bx, 0
的位置应该是cs:10。ip就应该是从10开始执行,写程序时可以人为设定start后面开始代码的地址就是ip。
end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。用end指令指明了程序入口在start处。
程序的框架:
assume cs:code
code segment
*
数据
*
start:
*
代码
*
code ends
end start
在代码段中使用栈
assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: mov ax, cs
mov ss, ax ;将程序的段入口处设为栈段,也就是上面数据开始,0123h
mov sp, 30h ;将栈指针sp偏移30h
mov bx, 0
mov cx, 8 ;一次传一个字节,总共就要传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.1
将数据、代码、栈放入不同的段
如果将数据、代码都放入同一个段,那么就会显得混乱。
可以用一个段来存放一种数据。
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 ;ax在栈段
mov ss, ax ;给栈段
mov sp, 20h ;16个字型数据,32个字节
mov ax, data
mov ds, ax ;把数据段给ds
mov bx, 0
mov cx, 8
s: push [bx] ;将data中的数据入栈
add bx, 2
loop s
mov bx, 0
mov cx, 8
s0: pop [bx] ;将栈入数据送入到data中
add bx, 2
loop s0
mov ax, 4c00h
int 21h
code ends
end start
以上代码:数据存放在了一个段中,而栈也存放在了一个段中
-
定义多个段的方法
与前面定义代码段没有区别,只是自己要想好名字
-
对段地址的引用
和原先访问内存地址一样,段问这些段地址中的内容,也要给出段地址和偏移地址。
-
“代码段”、“数据段”、“栈段“完全就是人为的安排
assume cs:code, ds:data, ss:stack
用这条伪指令,CPU是否就将cs指向code,ds指向data,ss指向stack呢?
事实并不是这样。
原先学过,DS是PSP的段地址,这后面256个字节就是PSP中的。也就是段地址加10H后就是cs的段地址。而程序的开头并不是由cs一个人决定的,还有ip也起到作用。
可以通过改变ip来改变程序的起始位置。