汇编扫盲帖

来源:互联网 发布:dota2 赛事 知乎 编辑:程序博客网 时间:2024/05/12 05:30
写这个帖子目的只是让没有入门的同学入个门,其实我的汇编技术很差的,很多我也不清楚,我把我知道得写写吧,来这么久了几乎没怎么发过原创的帖子,惭愧! 
开始正题
一,汇编常用的基本的语句

我们不从语句讲起,先看个例子。
code segment
assume cs:code
in db ? 定义变量in
begin:
mov ah 01
int 21h 调用dos中断,此中断的作用:从键盘读入一个数ascii,存入al中
sub al,30 将ascii码-30,存入bl
mov bl,al
mov ah,01 读入第2个字符
int 21h
sub al,30
add al, bl al的内容+bl的内容
mov in, al 把al的内容放到变量in 中
mov ah,4ch
int 21h 这两条语句是调用dos中断,作用: 停机,一般用于程序末尾
code ends
end begin


讲解:

mov : 数据搬移指令,
格式: mov 目标操作数 原操作数。
把原操作数的内容放到立即操作数中。
这个mov很有学问的,下一篇专门讲解这个mov指令,高手见了不要见笑。

sub : 减法指令
格式: sub d,s
d-s并把结果放在d里面,也就是d=d-s

add: 加法指令
格式: add d,s
跟sub差不多,d=d+s


关于汇编语言的格式,(dos下)

code segment 
assume ......
定义变量.......
begin:
.......
code ends
end begin

segment和ends必须成对出现,segment 和ends间为段体,格式:
段名 segment [定位类型][组合类型][‘类别’]
。。。。。。。。。。
段名 ends

assume 伪指令的作用是告诉汇编程序,有关段寄存器将被设定为内存中哪一个段的地址寄存器,而其中段地址值的真正装入还必须通过给段寄存器赋值的执行性指令来完成。

说说定义变量
举几个例子:
num dw 12 定义一个名为num的dw型的变量,值为12
abc db 10 定义一个名为abc的db型的变量,值为10
worker dd 1123 定义一个名为worker的dd型的变量,值为1123

db:字节变量
dw:字变量,(一个字等于2字节)
dd:双字变量
dq:4字
dt:10字


好累了,明天还要上机械设计课和软件基础,能不能去就看天意了,(练健身全身痛)
最后说说中断调用
通常汇编中的输入输出有两条指令联合描绘:
mov ah,01
int 21h
其中第二条叫做系统调用,ah=1说明调用的是1号功能,此中断作用是从键盘读入一个数ascii,存入al中,(注意我没写错,是存入al中)

让我们开始把。上次用一个例子给大家一个模糊的概念,可能你并不是完全明白那个例子的来龙去脉,这没有关系,有个认识就可以了,
我觉得学习汇编最大的障碍是 书上的教法不适合初学者,即使是有基础的人学起来也有点迷糊,既然是编成当然要从实际入手,这次开始我们来一个例子热身,和上次那个差不多
=========================================================================
;目的:将键盘输入的多字符(注意是多字符)保存到由若干个存储单元组成的变量inbuf中,以回车结束。
code segment
assume cs:code 由于没有声明变量,不需要设置数据段
start: mov bx,00h
again: mov ah,01h 调用中断,作用不用说了吧,键入一个字符将ascii到al(注意是一个字符)
int 21h
cmp al,0dh 如果al中的值是0dh,
jz stop ;则转到stop标号处执行
mov [inbuf+bx],al ;把al的数据装入到地址是inbuf+bx的地址
inc bx ;bx=bx+1
jmp again ;跳到again标号处执行,以接受下一个字符,直到用户回车
stop: mov ah,4ch
int 21h
code ends
end start
=============================================================================== 
程序不长,初学者最怕很长一段的程序,我那时也怕,现在也怕,人懒呀。
就这段程序,讲一讲mov,和分支程序。

注意了,背景音乐好了么,建议用神秘园,为什么?因为我在听。

mov指令
格式:mov 目的操作数,原操作数
说明:1、立即数,段寄存器cs,不能作为目的操作数;
     2、原操作数和目的操作数不能同为存储器操作数。下面指令是错误的
        mov 4h,bl
mov cs,ax
mov [1000h],[bx] ;[]表示取地址
    3、原操作数和目的操作数的类型必须一致,即同为单字节数,或同为双字节数,当指令中有一个操作数的类型明确时,另一个操作数被视为同一类型。可以用byte ptr 或word ptr将一个存储器操作数定义为字节或字类型。当存储器操作数为字类型时,寄存器或立即数的高字节对应其高地址,低字节对应低地址。
解释一下,byte ptr是将一个操作数强制定义为byte类型,word ptr类似,类似c语言的()
最后一句话什么意思呢?我们知道,一个字=两个字节,在计算机中,对于字的存储是以字节为单位的,大家都知道堆栈遵循“先进后出”的原则,所以,要高低位对换,举个例子:一个字类型,值为:1234h,在内存中的位置:
| 34h | 34为变量的低位。
|-------|
| 12h | 12为变量的高位。
|------ |
| |
好,我们来看这句话,“当存储器操作数为字类型时,寄存器或立即数的高字节对应其高地址,低字节对应低地址。”举个例子,
mov [2000h],ax
用图表示: 
| 存储器 | 
|----------| 
|存ax的低位al 
|----------|
|存ax的高位ah 
|----------|


分支程序
就是if...then ,goto等等这些,当然在汇编里面,没有这些语句

先看看cmp指令,
格式:cmp 目的数,原操作数
作用:根据两操作数的大小关系产生状态标志值,

jc/jnc指令
格式:jc/jnc 标号
作用:jc有进位则转,即cf=1 jnc相反 (什么是cf? 以后我会专门讲解电脑的寄存器,别急)

jp/jnp
jp:1的个数为偶数则转(pf=1),jnp相反

jz/jnz
jz:为零则转,(zf=1) jnz:相反

jl/jge
jl:小于则转(sf<>of或者zf=0) jge 大于等于则转(sf<=of或者zf=1)
。。。。。。。。还有很多哦,不写了,自己查资料,不要太懒了哦。

还有一个最重要的,jmp无条件跳转指令,不用解释了吧?
上面那个例子再看看,体会一下怎么跳转的。会有收获的哦~~

不过瘾?再来一段程序,
=========================================================================
求x-y的绝对值
dseg segment
x db 40h
y db 73h
z db ?
dseg ends
sseg segment stack 设置堆栈段
db 80h dup(0)
sseg ends
cseg segment
assume ds:dseg,ss:sseg,cs:cseg 是不是觉得和以前的不一样了?呵呵,体会一下
start:
mov ax,dseg
mov ds,ax
mov al,x
cmp al,y ;根据x-y产生状态标志
jl xl 有符号数则跳转
sub al,y 
mov z,al z=x-y
xl: mov bl,y
sub bl,al
mov z,bl z=y-x
ok:
mov ah,4ch
int 21h
cseg ends
end start
=============================================================================


好了,应该没什么问题了吧? 本来还想讲一些寻址方式的,下一章需要专门讲一下基础知识,避免不了的,不过到了现在,你对汇编的认识应该有点深度了,汇编其实并不难的,是么?如果你有这个感觉了,那我的目的也达到了,下一讲我们讲解寄存器,和寻址方式。不讲不行的。
经过了前面2篇教程的学习,我向大家应该对汇编程序有了比较深刻的影响了吧?所以不能教扫盲帖了,可以升级了,不过,在升级前,不免的对一些基础的东西要了解一下。 
一、 CPU的内部寄存器: 
4个16位的通用寄存器: 
_____________________________ 
|__ah________|____al_________| ax  
|__bh________|____bl_________| bx 
|__ch________|____cl_________| cx  
|__dh________|____dl_________| dx  
 
其中:一般来说,ax用作乘除法中的累加器,bx可存放地址,也可作指针寄存器。 
cx可存放循环次数,dx可存放I/O号。 
ah为两个4位,al为两个4位,所以,ax为16位,其余的以此类推 
  
  
4个16位的指针寄存器 
______________________________ 
|bp基址指针 
|_____________________________ 
|sp堆栈指针 用于做堆栈顶端指针 
|_____________________________ 
|si源变址寄存器  
|________________这两个有时用来做变址器,存放数据以及运算结果 
|di目标寄存器 
|_____________________________ 
  
  
4个16位的内部寄存器: 
  
______________________________ 
|cs:代码段寄存器 
|_____________________________ 
|ds:数据段寄存器 
|_____________________________ 
|es:附加段寄存器 
|_____________________________ 
|ss:堆栈段寄存器 
|_____________________________ 
  
标志寄存器: 
  
____________________________ 
ip指令指针 | 
___________________________| 
flagsh | flagsl | 
_____________|_____________|状态标志 
16位的标志寄存器,只用了其中的9个标志位,6各状态标志位,3个控制标志位。 
  
fh: 
________________ 
|of|df|if|tf|  
___|__|__|__|__| 
  
fl: 
___________________________ 
|sf|zf|空 |af|空 |pf|空|cf| 
|__|__|_ |__|_ |__|__|__| 
  
具体的每个标志位的意思我简单的讲一下,如果要编程还要自己看一下书 
cf:进位标志,进位/借位的时候cf=1 
pf:奇偶标志,当指令执行结果的低8位中含有偶数个1时,pf=1,f否则为0 
af:辅助进位标志,加减法的结果的低字节向高4位有进/借位时af=1. 
zf:0标志。运算结果为0时,zf=1 
sf:符号标志,他和运算结果的最高位相同。负数为1,正数为1 
of:溢出标志,补码运算有溢出时为1 
上面是状态标志,下面是控制标志: 
df:方向标志,控制数据串操作指令的步进方向 
if:中断允许标志 
tf:跟踪标志,为调试程序而设置的,喜欢破解的人要特别注意这个 
  
  
  
二、寻址方式: 
这个东西非讲不可,没办法,理解起来有困难,我尽量说清楚把! 
  
1、固定寻址 
这种方式,操作数隐藏在指令中,例如,daa,操作数隐含在al中 
  
2、立即寻址 
用来表示常数,例如: 
mov ax,9 9为立即数 
mov ax 1234h ;1234h为立即数, 
  
3、寄存器寻址 
例如:mov ax,cx 
执行前ax=9602h,cx=2081h 
执行后ax=2081h. 
  
4、存储器寻址 
这种方式下,操作数一般是代码段之外的数据段、堆栈段,附加段中的存储单元,指令给出的存储单元的地址或表达式。一般而言,一条指令的目的操作数和原操作数不能同为存储器操作数。存储器寻址又分为5种: 
  
直接寻址: 
操作数的有效地址由指令直接给出,是带有方括号的常量或变量。 物理地址pa=16*(ds)+偏移地址nn 
例如: 
mov al,[1000h] 将ds段的1000h单元的内容传到al,注意方括号和不加方括号的区别。 
  
mov ax,[1000h] 
将ds段的1000h内容传到ax,注意ah,al的内容。另外,不要忽视物理地址,这是很总要的信息,破解还是编程,都必须考虑到这个东西。 
 
mov al,es:[2000h] 表示将es段的2000h内容传到al,这叫段超越前缀。 
  
  
寄存器间接寻址: 
格式:[bx,bp,si或di].地址由基址寄存器或变址寄存器给出 
如果指令中使用的是bx,si,di则用ds作为段地址 
pa=16*(ds)+(bx/si/di) 
例如:mov al,[bx] 设bx的内容为1000h,则把ds段的1000h的内容传到al 
如果用bp,则用ss做段地址  
pa=16*(ss)+(bp) 
这种寻址方式一般用来对一维数组操作,改变bx等寄存器的值就可以对连续的存储器单元操作。 
  
以后的就有点复杂了,注意看了, 
  
基址寻址: 
格式:偏移量[bx或bp] 
例如:mov al,80h[bp] 设bp内容=2040h,则将堆栈段的20c0单元的内容传到al 
为什么?因为在16进制下2040h+80h=20c0h不信可以到windows计算器中试一试。其实这条语句等同于mov al,[80h+bp] 
操作数的有效地址ea=(bx)/(bp)+偏移量 
  
  
变址寻址 
格式:偏移量[si或di] 
操作数的有效地址ea=(si)/(di)+偏移量 
是不是和上面差不多? 
我们来看一个操作数组的例子: 
mov ax,array1[si] 
mov array2[di],ax 
懂了么? 为什么不能mov array2[di] array1[si]? 因为两个操作数不能同为变量。 
  
别急,这里我没有给出这两种方式的物理地址,算是给大家的一个题目,很容易的,想想看! 
  
  
  
基址变址寻址 
其实就是上面两个结合起来 
格式:偏移量[bx/bp + si/di] 
pa=16*ds+(bx/bp + si/di +偏移量) 
或者16*ss+(bx/bp + si/di +偏移量) 
注意bx和bp选择的不同,段地址会不同,前者用ds,后者用ss 
  
好了,就这么多,看着是有些头痛,有什么不懂就回帖问我吧,本来是要画图的,我偷了懒。 
初学者慢慢看,多看几次,别想一次看懂,有信心就可以了!

不知道上一片你们看得怎么样?看不懂也没关系,慢慢体会,我最开始的时候就看的是一大堆的寄存器,标志位,什么都不懂,如们还花了很多时间的。 
正题。

一、循环结构:
1 循环指令。
格式:loop 指令标号
功能:将寄存器cx的值减去1。然后判断:如果cx<>0,这转移指令到标号处。否则继续执行后续指令。相当于以下两条指令:
dec cx 
jnz 指令标号

所以要在循环前先确定循环次数,置入cx中。
注意:在循环体中不要对cx改写,至于原因我想我不必多说了吧。

来一段程序:
设变量var中有10个数据,12,0,-6,44,-54,0,3,51,98,69 统计其中0的个数。

现在我们学习重在分析了,不像以前重点理解语句。
10个数据,每个占用一个字节就可以了,那我们怎么样实现数据统计呢?应该用循环。有几个问题:
怎样逐个读入数据?从一个变量中。
比较后怎么样统计0的个数?
会不会重复统计?
解决了这几个问题这个程序也就出来了。
第一个问题,对于一个变量里的多个数据的读入,上一章讲过了,这就相当于一个数组,最常用的方法就是利用bx来寻址。
第二个问题,用一个专门的寄存器,统计0的个数
第三个问题,其实很简单,关键是不要把这个程序和冒泡法的算法搞混了,这个程序对于全部的数据只要循环一次就可以了。

ok,写出规划,这个很重要,刚刚学习的最好写一下,尤其是汇编,免得寄存器出错。很难查错的。

al:保存从变量读入的数据
bx:间接寻址用
cx:循环
dl:计数器,统计0的个数。

data segment 
var db 12,0,-6,44,-54,0,3,51,98,69
result db ?
data ends

code segment
assume cs:code,ds :data
start:
mov ax,data
mov ds,ax
mov bx,0
mov dl,0
mov cx,10 上面一堆mov都是为了初始化
again:
mov al,[bx+var]
cmp al,0 
jnz lab 如果不等于0就转lab
inc dl 如果读入的数为0,dl++
lab:
inc bx
loop again
mov result,dl

mov ah,4ch
int 21h
code ends
end start


it is a easy job,isn't?
这里有一个问题,因为前面我说了,10个数据,每个占用一个字节就可以了,那为什么循环计数不用cl或ch?这样不会浪费,别忘了,循环指令的默认操作对象就是cx,不能修改的。

在设计程序的时候,多注意寄存器的使用,初学者往往对怎么用乱而无序。

下面,来一段冒泡法的程序,我不解释,不懂的再问。

data segment 
var db -1,-10,-100,27h,0ah,47h
n equ $-var
data ends

code segment 
assume cs:code,ds:data

b mov ax,data
mov ds,ax
mov cx,n-1
mov dx,1

ag: call subp ;调用子程序
inc dx
loop ag
mov ah,4ch
int 21h
**************************;子程序开始
subp proc 
push cx
mov cx,n
sub cx,dx
mov si,0
recmp: mov al,var[si]
cmp al,var[si+1]
jle noch
xchg al,var[si+1]
xchg al,var[si]
noch: inc si
loop recmp
pop cx ;
ret
subp endp
**************************
code ends
end b


有点难度,不过多看看一定会想通的,巩固一下,以后会比较深了。