源程序到可执行程序
1、编写源程序,开各种编辑器写代码,产生一个文本文件。
2、编译、链接,编译产生目标文件obj,再链接生成可执行文件。
可执行文件包含”程序”和”数据”以及相关的描述信息(程序多大,占用内存空间大小等)
3、操作系统执行可执行文件中的程序。
源程序:
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
说明:
1、伪指令
xxx segment end
xxx ends
assume
2、指令
3、标号
codesg
图解:
程序的基本框架
框架:各种段构成,存放数据、代码、栈空间等等。
eg. 编程运算2^3。
(1)我们要定义一个段,名称为 abc。
abc segment
..
abc ends
(2)在这个段中写入汇编指令,来实现我们的任务。
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
(2)然后,要指出程序在何处结束。
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
(4)abc 被当作代码段来用,所以,应该将 abc 和 cs 联系起来。(当然,对于这个程序,也不是非这样做不可。)
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
(5)还有一个要加:
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
DOSBox
使用暂时省略…
两个问题:
1、之前说过有一个正在运行的程序将1.exe装入内存,这个程序是什么?载入内存后,如何执行?
2、程序结束后,返回到哪里?
操作系统都有内核和外壳,操作员通过shell与系统交互DOS中有一个command.com,叫命令解释器,是DOS的shell,DOS初始化后,会运行command.com,然后你懂的。
答:DOS中执行1.exe的,是command.com,command.com设置CPU的CS:IP指向程序入口,得以执行,结束后返回到command.com。
[]:当bx破门而入
提示:对于[bx]来说,默认段寄存器也是ds。
mov ax,[bx]
mov al,[bx]
潜规则——()
因为某些原因,我们使用”()”来表示一个寄存器或内存单元中的内容,如(ax)表示ax的内容,(2000H)表示内存2000H单元中的内容。
潜规则——idata
idata表示常量,比如mov ax,[0]其中的0就是常量,以后统一写出mov ax,[idata]
而对于mov bx,idata 就相当于mov bx,1 或 mov bx2 等等。
Loop指令
loop:名字听起来,像是循环的样子,没错,他就是循环指令。
loop指令格式:loop 标号
CPU执行loop时,要进行两步操作:
1、(cx)=(cx-1)
2、判断cx中的值,不为0则跳到标号处执行,为0则向下执行。
总而言之,loop做循环指令,cx放循环次数。
eg.编程计算2^n,这里我们取n=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
测试
inc dec
在在这之前要先了解:
inc 加1指令
dec 减1指令
一、加一指令inc
inc a 相当于 add a,1 //i++
优点 速度比sub指令快,占用空间小
这条指令执行结果影响AF、OF、PF、SF、ZF标志位,但不影响CF进位标志位.
二、减一指令dec
dec a 相当于 sub a,1
004012D7 83E8 01 SUB EAX,1
004012DA 836D FC 01 SUB DWORD PTR SS:[EBP-4],1
004012DE 41 INC ECX
004012DE FF41 FC INC DWORD PTR DS:[ECX-4]
优点 速度比sub指令快,占用空间小
这条指令执行结果影响AF、OF、PF、SF、ZF标志位,但不影响CF进位标志位.
内存中的情况 | 地址 |
---|---|
BE | 21000H |
00 | 21001H |
BE | 21002H |
00 | 21003H |
BE | 21004H |
BE | 21005H |
BE | 21006H |
21007H |
巨蠢MASM
大家可以试试在debug中写mov ax,[0] 发现没有问题。再试试用masm编译mov ax,[0],你会神奇的发现竟然被当成mov ax,0 处理!!!
怎么解决呢?我们可以先将0送入bx,再 mov ax,[bx] 这样就可以被识别。或是在[0]的时候加上 ds:[0] 也可以,如 mov ax,ds:[0]。
程序与段
db 定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1。 dw 定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2。 dd 定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4。
一个程序,需要很多空间,用于存放代码,数据和一些复杂的结构。
编程计算以下8个数据的和,结果放在ax中,0123h, 0456h, 0789h, 0abch, 0edfh, 0fedh, 0cbah, 0987h。
assume cs:code
code segment
0123h,0456h,0789h,0abch,0edfh,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
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0edfh,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
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:codesg,ds:datasg,ss:stacksg
datasg segment
dw 0123h,0456h,0789h,0abch,0edfh,0fedh,0cbah,0987h
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stacksg ends
codesg segment
mov ax,stacksg
mov ss,ax
mov sp,20h
mov ax,datasg
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
codesg ends
end
测试
1、下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,完成程序:
注意:这里括号内为我们要填的内容,不是代表值
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0edfh,0fedh,0cbah,0987h
start: mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: mov ax,[bx]
(mov cs:[bx],ax)
add bx,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,0edfh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0 ;10个字单元用作栈空间
start: mov ax,(cs)
mov ss,ax
mov sp,(24h)
mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
(pop ss:[bx])
add bx,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
与或非
and:与指令,按位进行与运算。
mov al,01100011B
and al,00111011B ;al=00100011B
or:或指令,按位进行或运算。
mov al,01100011B
or al,00111011B ;al=01111011
not:非指令,按位进行求反运算。
格式:NOT OPRD (OPRD可为任意通用寄存器或存储器操作数)
卷土重来的字符(串)
字符的定义以及ds和cs在debug下微妙的关系….
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
字母以ASCII的形式存放。
大小写转换程序
将dataseg中第一个字符串改为大写,第二个字符串改为小写。
因为是ASCII形式存放,所以 小写=大写+20H。
assume cs:codesg,ds:datasg
datasg segment
db 'BaSic'
ab '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,
int 21h
codesg ends
end start
codesg ends
end start
[bx+idata]
mov ax,[bx+200]
含义:将一个内存单元的内容送入ax,长度为2个字节,偏移地址bx的值+200,段地址是ds。
潜规则描述法:(ax)=((ds)*16+(bx)+200)
其它皮肤:mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
SI和DI
SI和DI也是寄存器,和bx功能相近。
SI和DI的使用:
codesg segment
start:
mov ax,datasg
mov ds,ax
mov si,0
mov di,16
mov c,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, di 联合使用
mov ax,[bx+si]
mov ax,[bx][si]
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
寻址
所谓寻址方式,就是定位内存地址的方法。
bx si di bp
bp:是基址指针,段地址默认在SS中.可以定位物理地址,比如:”mov ax,[bp+si+6]或mov ax,[bp+di+6]。
寻址方式:
汇编中的数据位置
1、立即数(idata) 对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编中成为:立即数,在汇编指令中直接给出。
2、寄存器 指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。
3、段地址(SA)和偏移地址(EA) 指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。存放段地址的寄存器可用使用默认的。
寻址方式
X ptr
x ptr 操作符指明内存长度,如8086里可以为byte和word:
mov word ptr ds:[0],1
inc byte ptr [bx]
add word ptr [bp],2
push和pop永远都是对word(16位)进行操作。
div
div 是除法指令,使用div做除法时要注意以下问题:
- 除数:有8位和16位两种,在一个reg或内存单元中。
- 被除数:默认放在ax或dx中,dx存放高16位,ax存放低16位。
- 结果:如果除数为8位,则al存储除法操作的商,ah存储余数;如果除数为16位,则ax存储除法操作的商,dx存储除法操作的余数。
例子:
转移指令
能够修改CS和IP的指令都是转移指令。
8086CPU的转移行为有如下几类:
- 段内转移:只修改IP,比如 jmp ax (近转移、短转移)
- 段间转移:同时修改CS:IP,比如 jmp 1000:0
8086CPU转移指令分类如下:
1、无条件跳转
2、条件跳转
3、循环指令
4、过程
5、中断
jmp:简单&不简单
assume cs:codesg
codesg segment
start: mov ax,0
jmp short s
add ax,1
s: inc ax
codesg ends
end start
jmp short s ;跳转到标号s:处
CPU三部曲:
(1)从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
(2)(IP)=(IP)+所读取指令的长度,从而指向下一条指令;
(3)执行指令。转到 1,重复这个过程。
jmp流程:
段内短转移和近转移
jmp short 标号 (IP) = (IP) +8位位移
jmp near ptr 标号 (IP) = (IP) +16位位移
段间转移(远转移)
assume cs:codesg
codesg segment
start: mov ax,0
mov bx,0
jmp far ptr s
db 256 dup (0)
s: add ax,1
inc ax
codesg ends
end start
jcxz指令
条件转移指令之一。鉴于所有的条件转移指令都是短转移,所以机器码中包含的是位移,而不是目的地址。IP修改范围:-128~127
指令格式:jcxz 标号(如果(cx)=0,转移到标号处执行。)
操作:当(cx)=0 时,(IP)=(IP)+8 位位移;
用高级语言(C语言)来描述,就是:
if((cx)==0) jmp short 标号;
检测点
1、(1)程序如下:
assume cs:code
data segment
?
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx, 0
jmp word ptr [bx+1]
code ends
end start
若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应该定义哪些数据?
本题中,执行jmp word ptr [b+1]指令后,会将data段[bx+1]中的数据赋值给IP,题目要求指向程序中的第一条指令,因此IP需等于0,所以只需data[1]中的数据为0即可。所以答案:
db x dup() ;x是重复的次数,()里是要重复的数逗号分隔。它可以按照给定的次数来复制某个操作数,可以避免多次输入同样一个数据。
db 8 dup(0) ;这里定义八个字节的0
(2)补全程序,使jmp指令执行后,CS:IP指向程序的第一条指令:
assume cs:code
data segment
dd 12345678H
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx, 0
mov [bx], __
mov [bx+2], __
jmp dword ptr ds:[0]
code ends
end start
执行完jmp指令后,跳到第一条指令(将IP置为0),jmp dword ptr ds:[0]指令执行完,会从ds:[0]开始,取出两个字的数据,把前面两个字节的数据赋值给IP,后面两个字节的数据赋值给CS,所以要将data[0]存放0,data[2]存放cs。
0
cs
(3)用Debug查看内存,结果如下:
2000:1000 BE 00 06 00 00 00 ……
此时CPU执行指令
mov ax, 2000H
mov es, ax
jmp dword ptr es:[1000H]
后,(CS) = ? (IP) = ?
因 jmp dword ptr es:[1000H] 的功能是将从es:10000H开始的两个字(四个字节),前面的两个字节赋值给IP,后面两个赋值给CS。所以答案为:
CS=0006 IP=00BE
2、补全编程,利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax
mov bx,0
s: (mov cl,ds:[bx])
(mov ch,0)
(jcxz ok)
(inc bx)
jmp short s
ok: mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
程序分析:
标号s前面是将ds:bx指向了段地址为2000H的内存段。s标号到jmp short s是一个循环(死循环,除非有跳出语句)。
这里我们需要jcxz有条件转移语句来实现循环的跳出,jcxz的逻辑表达式只有一个就是(cx)=0,想法将cx的内容赋值为ds:bx,也就是说从ds:[0]开始,逐个字节的将单元内容赋值给cx,然后执行jcxz语句。
由于是逐个字节的比较,bx的偏移量应该是以字节为单元。我们使用的cx寄存器是16位的,我们只需要低8位的cl寄存器就可以了。为了保证ch为0,首先必须置零。它们组合在一起就是cx的整体值(dh+dl)
需填充的 第一行:mov cl,[bx] ;将2000段内存逐个字节赋值给cl
第二行:mov ch, 0 ;保证高8位为0
第三行:jcxz ok ;判断cx值,如果cx=0,跳转到ok标号
第四行:inc bx ;cx!=0,继续执行jcxz后面的语句。递增bx,
3、补全编程,利用loop指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。
assume cs:code
code segment
start:
mov ax,2000h
mov ds,ax
mov bx,0
s:mov cl,[bx]
mov ch,0
________
inc bx
loop s
ok:dec bx ;dec指令功能和inc相反,dec bx操作为:(bx) = (bx)-1
mov dx,bx
mov ax,4c00h
int 21h
code ends
end start
程序分析:
1)保证这个loop循环的动力是:cx!=0,首先搞清这点。
2)如果ds:[0]=0情况下。如果我们使用jcxz指令,只有这种情况满足条件,第一个字节为0.故不能使用jcxz指令。
3)理解loop指令的动作,首先是(cx)=(cx)-1.然后才是跳转到标号执行。
4)添加inc cx指令
如果第一个字节是0情况,inc cx导致(cx)=1,执行到loop指令时,首先(cx)=(cx)-1,导致(cx)=0,loop顺序执行下面的指令(也就是标号ok的指令)。
如果第一个字节非0情况,inc cx导致(cx)>1,执行到loop指令是,首先(cx)=(cx)-1,导致(cx)> 0(不为0),loop跳转到s标号处执行代码。
5)答案:inc cx
ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移。
ret指令本质:
(IP)=((SS)*16+(SP))
(SP)=(SP)+2
retf指令用栈中的数据,修改CS和IP内容,实现远转移。
retf指令本质:
(IP)=((SS)*16+(SP))
(SP)=(SP)+2
(CS)=((SS)*16+(SP))
(SP)=(SP)+2
call
call指令做两件事:
1、将call的下一段指令的IP或CS和IP压入栈中。
2、转移。
显然,call指令不能用于短转移,其他的和jmp一致。
call使用一:
call 标号
(SP)=(SP)-2
((SS)*16+(SP))=(IP)
(IP)=(IP)+16位位移
相当于:
push ip
jmp near ptr 标号
16位位移=标号处地址-call指令后的第一个字节的地址
16位位移范围 -32768~32767,补码表示
16位位移由编译器算出
call使用二:
call far ptr 标号
(SP)=(SP)-2
((SS)*16+(SP))=(CS)
(SP)=(SP)-2
((SS)*16+(SP))=(IP)
(CS)=标号所在段的段地址
(IP)=标号所在段中的偏移地址
相当于:
push cs(call指令的下一条指令的cs地址)
push ip(call指令的下一条指令的ip地址)
jmp far ptr 标号
call使用三:
call 16位reg
(sp)=(sp)-2
((ss)*16+(sp))=(ip)
(ip)=(16位reg)
相当于:
push ip
jmp 16位寄存器
call使用四:
call word ptr 内存单元地址 或 call dword ptr 内存单元地址
相当于:
push ip push cs
jmp word ptr 内存单元地址 push ip
jmp dword ptr 内存单元地址
检测
(1)下面程序执行后,ax中的数值为多少?
assume cs:codesg
stack segment
dw 8 dup (0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax ;设置ss指向stack数据段
mov sp, 16 ;设置sp栈指针指向栈底
mov ds, ax ;ds也指向stack段
mov ax, 0
call word ptr ds:[0EH] ;执行这条语句时, ip已经指向下一条语句, 即“inc ax”. 那么设此时IP = x;
;call 相当于push IP; IP= x; 然后jump 到 ds:[0EH], 又因为ds:[0EH] = x;
;故结果IP依然等于原值, 程序顺序执行
inc ax
inc ax
inc ax
mov ax, 4c00h
int 21h
code ends
end start
ax=3。
(2)下面的程序执行后 , ax 和 bx 的值是什么 ?
assume cs:code,ds:data
data segment
dw 8 dup(0)
data ends
code segment
start:
mov ax, data
mov ss, ax
mov sp, 16
mov word ptr ss:[0], offset second
mov ss:[2], cs
call dword ptr ss:[0]
nop
second:
mov ax, offset second
sub ax, ss:[0CH]
mov bx, cs
sub bx, ss:[0EH]
code ends
end start
这个程序定义了数据段 , 其实也是栈段和数据段使用了同一段内存 首先也是初始化栈 : mov ax, data mov ss, ax mov sp, 16 然后执行 : mov word ptr ss:[0], offset s 将 s 标号处两个字节的地址移动到 ss:[0] 处 mov ss:[2], cs 将代码段地址的值保存在到 ss:[2] 处 , 同样两个字节 call dword ptr ss:[0] CPU首先读取该指令 然后将 ip 增加该指令的长度 然后准备开始执行 (由于是 dword 因此 cs 和 ip 都要压栈) 执行首先要将当前的 cs 压栈 (保存在 ss:[0EH]) 然后 ip (也就是 call 指令下一个指令的地址 , 也就是 nop 的地址) 压栈 (保存在 ss:[0CH]) 处 然后读取 ss:[0] 中的四个字节的数据 , 相当于 : 高地址为段地址 , 低地址为偏移地址 也就说 cs 为 (ss:[2]) , ip 为 (ss:[0]) 这个 cs 和 ip 组成的地址就是 second 的地址 现在开始执行 second mov ax, offset second 读指令 , ip自增 , 然后执行 , 因此执行完后 , ax 为该指令的偏移地址 然后 sub ax, ss:[0CH] 通过之前的分析 : ss:[0CH] 处保存的是 : call dword ptr ss:[0] 这条指令的下一条指令 nop 的相对于代码段偏移地址 ax = (mov ax, offset second 的偏移地址) - ( nop 的偏移地址) 也就是 nop 指令的长度 , 也就是 1 接下来 : mov bx, cs 将代码段的基址赋值给 bx bx = (bx) - (ss:[0EH]) (ss:[0EH]) = (cs) 因此 bx = 0
乘法指令:mul
1、两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在al中,另一个放在8位reg或内存字节单元中;如果是16位,一个在AX中,另一个在16位reg或内存字单元中。
2、结果,如果是8位乘法,结果默认放在ax中;如果是16位乘法,结果高位默认放在dx中存放,低位放在ax中。
格式 :
mul reg
mul 内存单元
内存单元可以用不同的寻址方式给出,比如:
mul byte ptr ds:[0]
eg. 100*
10, 100*
10000 (10的十六进制为:aH,100的十六进制为:64H,10000的十六进制为:2170H)
mov al,64H mov ax,64H
mov bl,0aH mov bx,2710H
mul bl mul bx
eg.编程,计算data段中第一组数据的3次方,结果保存在后面的一组dword单元中。
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
程序:
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
s:
mov bx,[si]
call cube
mov [di],ax
mov [di+2],dx
add si,2
add di,4
loop s
mov ax,4cooh
int 21h
cube:
mov ax,bx
mul bx
mul bx
ret
code ends
end start