汇编基础入门知识

学习汇编前你应该知道的知识

 

一、汇编须要什么工具和程序,到哪里下载?

    目前阶段,汇编程序仅须要两个程序就够了。masm.exe,link.exe。  前者是编译程序,后者是连接程序。另外,为了验证和调试程序,还须要一个程序debug.exe,该程序由windows自己就提供。
    将两者下载后,放到某一个目录中(任意目录均可以),考虑到不少命令须要经过键盘敲入,因此建议你不要把文件放入到长文件名目录、中文目录或很深的目录中。好比你能够建一个“D:\Masm”目录,并建议此后的程序都放这个目录,此后称这个目录为汇编目录。
 
二、学习汇编须要有哪些编程方面的知识?
 
    没有任何编程方面的知识,学习此语言等于缘木求鱼,因此请放弃学习的想法。通常来讲至少要知道以下几点:
    *)程序的运行逻辑结构有顺序(按语句依次执行)、分支结构(IF...THEN...ELSE...),循环结构(FOR...NEXT)三种结构。
    *)知道什么是子程序,什么是调用。
    *)汇编程序员的视角。不一样编程视角编程要求是不同的。好比删除文件:
        >>用户的视角是找到“删除”按钮或菜单,而后单击一下便可。
        >>高级程序员的视角是知道删除的文件,并发出删除命令。这些经过API实现。
        >>汇编程员的视角是获得要删除的文件名,找到该文件所在位置,经过调用删除“中断命令”进行删除。
        >>操做系统开发人员的视角则是接到删除命令后,先找到系统根目录区,由根目录区的连接依次找到子目录区,直到找到要删除的文件,而后按照操做系统删除文件的规则对该文件名进行修改。好比DOS,只把第一个字符改为"?"。
 
    按程序语句等价的角度看,一行VB的打印语句,用汇编实现大约须要一百二十多行。知道汇编语言的视角后就要知道,前面的道路是坎坷的,没有耐心是不行的。想经过几分钟几行程序就完成很复杂的操做不是件容易的事。
 
三、学汇编有什么用?
 
    汇编产生于DOS时代或更早,而如今是Windows时代,因此可能 遗憾地说:尽管还有批牛人在用汇编开发核心级程序,但咱们几乎没什么用,除了必要时间能拿来分析一两个程序的部分代码以外,别的也就没干什么用了。而且并非全部的汇编命令都能在windows下使用。而泛泛地追求“时髦”而学本语言,最后的结果是损了夫人又折兵。因此学以前你要考虑好。我劝那些为了当“黑客”而学汇编的人就此止步。
 
 

第零讲 预备知识

 
 
一、一个汇编程序的编译过程是怎么样的?

    1)首先你须要找一个编辑器,编辑器用任何“纯文本”编辑器均可以。好比记事本。编好之后保存到汇编目录中。扩展名为asm,好比myfirst.asm。但这里建议你找一个能显示出当前行的编译器。这样出错后排错很容易。
    2)而后在DOS下进入D:\Masm目录中,输入“masm myfirst.asm",若是有错系统会提示出错的行位置和出错缘由。
     3)而后再输入“link myfirst.obj”,便可看到当前目录下有一个myfirst.exe程序。
 
二、宏汇编和汇编有什么区别吗? 
 
    两者的区别在于前者提供宏,后者不提供。后者已找不到了,因此你能够认为两者没有区别。
 
三、机器语言、汇编语言、高级语言的关系

    最先的计算机采用机器语言,这种语言直接用二进制数表示,经过直接输入二进制数,插拔电路板等实现,这种“编程”很容易出错,每一个命令都是经过查命令表实现,既然是经过“查表”实现的,那固然也可让计算机来代替人查表实现了。因而就产生了汇编语言,因此无论别人怎么定义机、汇语言,我就认为,两者是等价。
    后来人们发现,用汇编语言编某一功能的时候,连续一段代码都是相同或类似,因而就考虑用一句语言来代替这一段汇编语言,因而就产生了高级语言。所以,全部高级语言都能转化成汇编语言,而全部汇编语言又可转化成机器语言。反之,全部机器语言能够转成汇编语言(由于两者等价)。但并非因此汇编语言都能转成高级语言。
 
四、计算机的组成

    一般都把计算机定义成五部分:运算器、控制器、存储器、输入系统、输出系统。
    为了简单起见,咱们如此理解:运算器+控制器=CPU。存储器=内存(暂不包括外存,也不包括CACHE)。输入系统=键盘(不包括鼠标),输出系统=显示器(不包括打印机,绘图仪)。
 
五、寄存器和内存的区别

    寄存器在CPU中。内存在内存条中。前者的速度比后者快100倍左右。后面的程序要求每条指定要么没有内存数据,要么在有一个寄存器的参与下有一个内存数据。(也就是说,不存在只访问内存的指令)。
 
六、汇编语言的计数

    与生活中的计数不同,汇编中的计数是从0开始的。好比16个计数,则是从0~15,而不是生活中的1~16。这一点看起来简单,真运算起来就不是件容易的事了,不信等着瞧。
 
七、进制问题
 
    又与生活中不同的地方是进制。切记下面的常识:
    *)计算机内部存储都用二进制。
    *)咱们的汇编源程序默认都用十进制。(除非你指明类型)
    *)咱们用的调试程序debug默认的都是十六进制。(没法指明其余类型)
    其中十六进制的十六个个位数依次是:0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。
 
八、进制转换

    一个比较简单的方法是查表法。
    十进制 十六进制 二进制
      0      0      0000
      1      1      0001
      2      2      0010
      3      3      0011
      4      4      0100
      5      5      0101
      6      6      0110
     7      7      0111
      8      8      1000
      9      9      1001
      10     A      1010
      11     B      1011
      12     C      1100
      13     D      1101
      14     E      1110
      15     F      1111
  
    好了,结合6,7,8三条。你们来算一个“题”。某一组数据显示时,每一个数据占了四个位置,每行共十六个。问:十六进制的13位置在哪里(第几行,第几列)。
    格式以下:m m m m n n n n o o o o p p p p '注:之因此没用ABC是怕与上面十六进制弄混。
            r r r r s s s s t t t t u u u u
 
 

第一讲 基础知识

 

一、访问内存

    程序在内存中,访问内存是几乎每一程序都要进行的操做,计算机对内存编址是线性的,也就是说是一维的,好比256M的内存,地址就应该是从0~(256M-1),这个地址称为物理地址或绝对地址。

  1.1 地址表示 
 
    但从汇编程序员的角度看,内存倒是二维的,要说明一个地址,须要给出两个值,就象你在平面上指定一点须要说出(X,Y)坐标同样,汇编程序员的内存视角也须要两个“坐标”,前一个称为段地址(Segment),后一个称为偏移地址(Offset),该地址称为逻辑地址。
    好比“1234:3DF5”就是一个地址。“1F3F:”不是一个地址,由于他只有段地址,没有偏移地址。注意此后的地址都用十六进制表示。
 
  1.2 地址计算 
 
    前面提到,计算机编址是一维的,汇编程序员是二维的,那么两者怎么换算呢?
     由后者到前者的换算方法是,“段地址串”后面加个“0”,而后再加上偏移地址。
    好比“1234:3DF5”(十六进制的加减运算参见相关资料)
    12340  --串后加了一个0
     3DF5
    -----
    16135  --注意此串仍然是十六进制。
    因此,汇编程序员眼中的地址“1234:3DF5”就是物理地址(计算机编址):16135。
    知道了由后者向前者的转换,那么由前者向后者的转换呢?
    “不知道”,为何不知道,继续往下看。
 
  1.3 到底哪一个地址对
 
    知道了1.2的地址算法后,我又发现一个问题:
    “1000:6135”的物理地址是多少呢? 10000+6135=16135。
    “1001:6125”的物理地址呢? 10010+6125=16135。
    ......
    那么到底哪一个对呢?问题的回答是这样的:假设我如今让你按一下“L”键,我能够告诉你以下几种方法中的一种或几种。1 请按一下“L”键; 2请按一下键盘上第四行第十个键;3 请按一下第十列中的第四个键;4 请按一下“K”右边的键;5 按标准指法单击一下右手无名指。
 
     举上面的例子也就是说,同一个地址有不少种表示方式,具体用哪种,要看实际使用时的状况。但不管用哪一种方式,只要能达到目的便可。(实际中该问题通常不会受此问题困扰,但初学时忽然想不通)。
 
  1.4 有多少内存能够访问 
 
    不管是段地址仍是偏移地址都是四位十六进制(若是不够四位,前面补0)。也就是说:总共能够访问的地址说是:0000:0000~FFFF:FFFF。 总共FFFF0+FFFF+1=10FFF0个地址。也就是不到1M的空间。
    记住以下结论:
    *无论你实际内存有多少,目前咱们只能访问不到1M的空间。
    *而实际上连这1M也用不完。其中上端的384K的地址只能读不能写,只能读,通常称为ROM。
    *低端的640K能够读写。但这640K的低端100多K也不能随便写,由于DOS系统使用该区。
    *原来1024M的内存,汇编程序只能使用其中400多K。这段内存的容量至关于一个普通文档的大小。不过这就足够了。

二、DEBUG的使用 
 
    先记住如下两个命令:D命令和Q命令。前者是显示内存内容,后者是退出DEBUG命令。

------------------------如下为抄别的人内容----------------------
    DEBUG.EXE程序是专门为分析、研制和开发汇编语言程序而设计的一种调试工具,具备跟踪程序执行、观察中间运行结果、显示和修改寄存器或存储单元内容等多种功能。它能使程序设计人员或用户触及到机器内部,所以能够说它是80X86CPU的心灵窗口,也是咱们学习汇编语言必须掌握的调试工具。
 
    1DEBUG程序使用
 
    在DOS提示符下键入命令:
    C>DEBUG [盘符:][路径][文件名.EXE][参数1][参数2]
 
    这时屏幕上出现DEBUG的提示符“-”,表示系统在DEBUG管理之下,此时能够用DEBUG进行程序调试。若全部选项省略,仅把DEBUG装入内存,可对当前内存中的内容进行调试,或者再用N和L命令,从指定盘上装入要调试的程序;若命令行中有文件名,则DOS把DEBUG程序调入内存后,再由DEBUG将指定的文件名装入内存。
 
    2DEBUG的经常使用命令 
 
         (1)退出命令 Q
        格式:Q
        功能:退出DEBUG,返回到操做系统。
        (2)显示存储单元命令 D
        格式1:D[起始地址]
        格式2:D[起始地址][结束地址|字节数]
        功能:格式1从起始地址开始按十六进制显示80H个单元的内容,每行16个单元,共8行,每行右边显示16个单元的ASCII码,不可显示的ASCII码则显示“.”。格式2显示指定范围内存储单元的内容,其余显示方式与格式1同样。若是缺省起始地址或地址范围,则从当前的地址开始按格式1显示。
        例如:-D 200       --表示从DS:0200H开始显示128个单元内容
             -D 100 120   --表示显示DS:0100-DS:0120单元的内容
        说明:在DEBUG中,地址表示方式有以下形式:
        段寄存器名:相对地址,如:DS:100
        段基值:偏移地址(相对地址),如:23A0:1500
--------------------------小抄结束--------------------------------
 
三、验证第一节里的内容 
 
    运行“开始/程序/附件/MS-DOS命令提示符”(这是win2000,win98下本身找吧)
    在“-”下输入D,显示
-d
1398:0100  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0110  00 00 00 00 00 00 00 00-00 00 00 00 34 00 87 13  ............4...
1398:0120  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0150  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0160  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1398:0170  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
-
    咱们记下:1398:011C的值是个34。1389:011C的物理地址应该是:13A9C。
    那么1000:3A9C的物理地址也应该是13A9C,他的内存也应该是34,(由于原本就是一个地址吗,就象第三行第十列和第十列第三行固然应该是同一个位置)。
-d 1000:3A9C
1000:3A90                                      34 00 87 13              4...
1000:3AA0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AB0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AC0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AD0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AE0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3AF0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3B00  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
1000:3B10  00 00 00 00 00 00 00 00-00 00 00 00              ............
-
    果真如此,一样你能够验证:13A9:000C也确定是指这一个地址,不信试试。
 
四、DEBUG命令

------------------------继续小抄----------------------------
    前面已学过:显示存储单元命令 D
    再学一个命令
    (1)修改存储单元命令 E
 
    格式1:E[起始地址] [内容表]
    格式2:E[地址]
    功能:格式1按内容表的内容修改从起始地址开始的多个存储单元内容,即用内容表指定的内容来代替存储单元当前内容。
 
    例如:-E DS:0100 'VAR' 12 34
    表示从DS:0100 为起始单元的连续五个字节单元内容依次被修改成
    'V'、'A'、'R'、12H、34H。
    格式2是逐个修改指定地址单元的当前内容。
    如:-E DS:0010
    156F:0010 41.5F
    其中156F:0010单元原来的值是41H,5FH为输入的修改值。若只修改一个单元的内容,这时按回车键便可;若还想继续修改下一个单元内容,此时应按空格键,就显示下一个单元的内容,需修改就键入新的内容,不修改再按空格跳过,如此重复直到修改完毕,按回车键返回DEBUG“-”提示符。若是在修改过程当中,将空格键换成按“-”键,则表示能够修改前一个单元的内容。
----------------------小抄结束------------------------------
 
五、使用DOS时,汇编用户能够从DOS操做系统中获得什么?

    如今编程,一般不少功能都是经过调用系统API。不少高级语言都直接把这些API包装起来,以系统接口或函数的方式提供给用户,那么汇编函数都能获得什么呢?
    首先,汇编用户有不少东西能够调用。他们主要是:
 
    5.1 BIOS提供的接口。如今硬件与软件的区分已愈来愈不明显,不少硬件不只仅是电路,而还要提供一些固化写入硬件的一部分“程序”,这些程序以ROM的方式出现,汇编用户最大的好处就是能够直接使用这些“程序”,这些使用不只功能强大,并且效率很是高。
 
    5.2 DOS功能调用,做为操做系统也象BIOS同样向用户提供了相应的“程序”。这些程序在很大程序上扩充了BIOS。与BIOS不一样的是,这部分程序放在内存中,它能够被修改。而BIOS中不能再修改。
    ==========================================================
    以上两种接口都经过一种相同的格式调用,这些程序统称为“中断”,如今先不要理解中断的本意,你如今能够认为是系统提供给你的函数。
    ============================================================
 
    5.3 系统共享数据区。编过程序的人都知道全局变量的好处,全局变量方便以外在于任何函数、过程均可以调用、读取、修改。全局变量不足之处是危险性,有一个过程改了这个变量值,其它的也得跟着改变了。DOS操做系统一样也提供了这样的共享数据区,该区是整个系统的共享区,任何程序均可以查找、修改。固然,修改某处必然会对其它程序形成影响。
 
六、再谈中断

    前面5.2已提到中断了,如今问题是不一样硬件不同,即便相同硬件的ROM,不一样版本,各个BIOS中断程序所处的位置也不同。DOS中断也同样,不一样版本、不一样配置,在内存位置也不同。那么你使用某一个中断,系统怎么知道你使用的那个中断程序在哪呢?
    为了解决这一问题,DOS会在启动的时候,把全部这些(BIOS和DOS)中断的首地址保存到一个地址。这个地址很容易记,这段地址是内存的绝对零地址(0000:0000)。前面已讲过,每一个地址在汇编程序员角度来看是二维的,也就是分为段地址和偏移地址。每一个地址各占两个字节,因此要表示这个二维地址须要4个字节。因此每一个中断首地址由4个字节表示。一共256个中断,占用了1024个字节的位置。
    另外须要注意的是,这4个表示地址的字节,数据是由低向高的。好比12 34 56 78所表示的地址是:7856:3412。
 
    通常用INT M表示中断M,若是M是十六进制,则在后面加上一个H。好比19号中断,十六进制应该是13H。因此该中断就是INT 13H。

七、再谈系统共享数据区

    该共享数据区在绝对地址:0040:0000开始。
 
八、验证我上面说的内容 
 
    8.1 找中断 
 
    运行DEBUG后。输入D 0000:0000。显示绝对零地址的内容。
C:\>debug
-d 0:0
0000:0000  68 10 A7 00 8B 01 70 00-16 00 9B 03 8B 01 70 00  h.....p.......p.
0000:0010  8B 01 70 00 B9 06 0E 02-40 07 0E 02 FF 03 0E 02  ..p.....@.......
0000:0020  46 07 0E 02 0A 04 0E 02-3A 00 9B 03 54 00 9B 03  F.......:...T...
0000:0030  6E 00 9B 03 88 00 9B 03-A2 00 9B 03 FF 03 0E 02  n...............
0000:0040  A9 08 0E 02 99 09 0E 02-9F 09 0E 02 5D 04 0E 02  ............]...
0000:0050  A5 09 0E 02 0D 02 DC 02-B8 09 0E 02 8B 05 0E 02  ................
0000:0060  02 0C 0E 02 08 0C 0E 02-13 0C 0E 02 AD 06 0E 02  ................
0000:0070  AD 06 0E 02 A4 F0 00 F0-37 05 0E 02 71 84 00 C0  ........7...q...
-u 0070:018B
0070:018B 1E            PUSH    DS
0070:018C 50            PUSH    AX
0070:018D B84000        MOV    AX,0040
0070:0190 8ED8          MOV    DS,AX
0070:0192 F70614030024  TEST    WORD PTR [0314],2400
0070:0198 754F          JNZ    01E9
0070:019A 55            PUSH    BP
0070:019B 8BEC          MOV    BP,SP
0070:019D 8B460A        MOV    AX,[BP+0A]
0070:01A0 5D            POP    BP
0070:01A1 A90001        TEST    AX,0100
0070:01A4 7543          JNZ    01E9
0070:01A6 A90002        TEST    AX,0200
0070:01A9 7422          JZ      01CD
    首先,D命令把中断首地址显示出来。每4个表示一个地址。其中INT 0的中断首地址为:00A7:1068,INT 1的中断地址为:0070:018B.......0070:018B是中断3的首地址。后面那个U命令就表示显示该地址的“中断程序”的内存。
 
    大家能够试着找找INT 13H的位置在哪。
 
    8.2 验证系统共享数据区 
 
    系统共享数据区内容极为丰富,我实在记不住哪么多了。我曾记在一个本上,惋惜那个本早在N年前(3<N<6)就丢了。兄弟们谁找到这个地址的内容,必定要贴上来,这里有东西可让你们眼界大开。
    前几年,我用的286计算机是黑白显示器(555555~~~~~~~~~,别嫌我老、旧、慢呀),可当时有个游戏非要彩显,不是彩显不让运行。我就是改了这个区的某一个位,让哪游戏“觉得”我用的是彩显,因而游戏能用了。虽然很差看,但总能用。
    在DOS下,你每按一个键,系统都会记下来,下面咱们一块儿找找这个键盘缓冲区的地址。知道这个地址,你就能够做一个“虚拟”键盘,经过发命令来模拟某我的在按键。这个地址位于:0040:001E。 其中每一个键有两个字节,一个字节是ASCII码,一个是扫描码。共16个。
 
C:\>debug
-d 40:0
0040:0000  F8 03 F8 02 E8 03 E8 02-BC 03 78 03 78 02 80 9F  ..........x.x...
0040:0010  22 C8 00 80 02 28 00 00-00 00 2A 00 2A 00 20 39  "....(....*.*. 9
0040:0020  34 05 30 0B 3A 27 30 0B-0D 1C 64 20 20 39 34 05  4.0.:'0...d  94.
0040:0030  30 0B 3A 27 30 0B 0D 1C-71 10 0D 1C 64 20 00 00  0.:'0...q...d ..
0040:0040  A2 00 C3 00 A2 AF 09 E1-C8 03 50 00 00 10 00 00  ..........P.....
0040:0050  00 18 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0040:0060  0F 0C 00 D4 03 29 30 7F-03 00 C0 00 A1 B7 11 00  .....)0.........
0040:0070  00 00 00 00 00 00 00 00-14 14 14 00 01 01 01 01  ................
-d 0040:0000
0040:0000  F8 03 F8 02 E8 03 E8 02-BC 03 78 03 78 02 80 9F  ..........x.x...
0040:0010  22 C8 00 80 02 28 00 00-00 00 2A 00 2A 00 3A 27  "....(....*.*.:'
0040:0020  30 0B 30 0B 30 0B 30 0B-0D 1C 64 20 20 39 30 0B  0.0.0.0...d  90.
0040:0030  30 0B 30 0B 30 0B 08 0E-08 0E 34 05 30 0B 00 00  0.0.0.....4.0...
0040:0040  1F 00 C3 00 A2 AF 09 E1-C8 03 50 00 00 10 00 00  ..........P.....
0040:0050  00 18 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0040:0060  0F 0C 00 D4 03 29 30 7F-03 00 C0 00 24 B8 11 00  .....)0.....$...
0040:0070  00 00 00 00 00 00 00 00-14 14 14 00 01 01 01 01  ................
-
    既然是键盘缓冲区,每一个输入的键都会显示在该区中,第一次我只输入了“d 40:0”,因此你能够在此后显示数据右边字符中找到这些字符,注意是间隔开的。
    第二次我输入“d 0040:0000”,则右边显示的是“d 0040:0000”的内容。你能够找找。 
 
 

第二讲 内存映象

 

    之因此把这个内存单独放一章,是为了说明它的重要性,后面的几乎不少程序都须要你对这一章的理解。这里的内存映象就是指当你把一个可执行文件(EXE或COM文件)放到内存后,整个内存“看”起来是什么样子的。
 
    前面讲过,这里汇编程序只能访问1M的内存空间,因此下面就以1M内存为例。而且以DOS操做系统做为讲解对象,因此所编出来的程序也仅是DOS程序。事实上,经过winasm能够访问远远超过1M的空间,而且能够编出FOR windows的程序。但那是另外的话题。咱们暂且不说那些。

2.1内存映象

    首先,这1M内存若是咱们再也不以二维的方式看,而是一维的,线性地看(二维和一维的转化方式参见前面章节)。但描述仍是以二维的方式描述,从最底端到最高端依次是:
    1中断向量区:该区由0000:0000~0000:03FF。这里存着系统的全部中断的中断向量表对于中断向量表,你如今先理解为一些程序的首地址。由这个地址你就能找到该程序。
    2系统数据区:该区由0040:0000~0040:XXXX (很差意思,忘了),这里存着整个系统中,DOS操做系统要用的数据,因为这个区的数据对用户是开放的,因此用户固然也能够从这里读出来用。
    3) DOS操做系统区:操做系统常驻内存,你向计算机发的每一个命令其实都是操做系统执行的。这个区的大小主要是由操做系统的版本和用户的配置大小决定,若是是驱动程序配置,就放到根目录下的config.sys里,若是是程序,就放到autoexec.bat里。这里设置在如今的windows 95/98/nt/me/2000/xp/2003中仍然有,因此我就很少说了。
    4) 用户程序:这个固然就是你执行的程序了,这种程序分两种,一种是扩展名为com文件,一种是exe文件从程序内部看,前者程序的四个段重合(后面要讲这四个段),因此最大长度只等于一个段,用前面段地址的理解就是com文件最大只能是64K,因此com文件只适合小的程序。而exe,四个段可任何分配,并可扩充段,并且每一个段的段地址能够任何改动,所以exe的访问内存能力大多了。这种格式访问能力只受地址结构的限制了。
    用户程序所占的内存大小彻底由程序自己决定,但最大,只能到640K。这一点,怪不得别人,只能怪当前计算机软硬件设置高手高手高高手们(包括比尔盖茨)们的失误了,60年代的超级计算机只有36K的内存,因此他们就在80年代获得一个结论:640K的内存足够了。
    若是用户程序大于由操做系统所占内存的顶底到640K之间的内存量,就会显示:内存不够,于是程序不能执行。这种现象对于一开始就用windows的人来讲,几乎没见过,但对于一开始用DOS并打汉字的人来讲,再正常不过。若是小于这段内存,多余部分就空着。
    5) 从640K到1M-64K,这段内存就很难说清了。这段内存中有一部分被硬件占有,有一部分是显示缓冲区点有,还有一部分是系统ROM占有。
    6) 从1M-64K到1M之间的这段64K的内存叫做HMA。这段内存是小孩没娘,说来话长,咱们先不说他。
    
2.2 验证上面的理论
 
    2.2.1 中断向量表
 
    中断向量表就是全部中断向量首地址表,这里保存着每一个中断程序的首地址,几乎全部的汇编书都把中断放到后面的章节中,而且对中断的解释也仅从字面意思解释,因此致使大学对中断的不重要和误解。没耐心的没到这个章节就不学汇编了,有耐心的到这里才豁然开朗。我如今不讲中断的原意。我直接告诉你,你把中断当成API也许更合适。也就是说,别人把不少已做好的功能放到了内存中。而且把调用这一功能的号告诉了你,你只要调用这些功能号,系统就自动从这个中断向量表中找到对应的中断,而后执行你的功能。
 
     首先让你感觉一下中断的魅力一下吧。好比中断21H的2A功能调用是读取系统的日期,这个调用的规则是,调用前AH寄存器置为2A。调用后年在CX中,月在DH中,DL在日中,星期在AL中。
-a
139D:0100 mov ah,2a
139D:0102 int 21
139D:0104 int 3
139D:0105
-g=100
 

AX=2A05  BX=0000  CX=07D4  DX=0C18  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=139D  ES=139D  SS=139D  CS=139D  IP=0104  NV UP EI PL NZ NA PO NC
139D:0104 CC            INT    3
-

    可能上面的程序你目前还看不懂。不过不要紧,“mov ah,2a”表示调用功能号是2a的API。“int 21”表示调用十六进制21号中断,“int 3”表示3号中断,表示程序运行到这一句时停一下。“g=100”表示从“139D:0100 ”开始执行。
    AX=2A05  BX=0000  CX=07D4  DX=0C18  SP=FFEE  BP=0000  SI=0000  DI=0000
    DS=139D  ES=139D  SS=139D  CS=139D  IP=0104  NV UP EI PL NZ NA PO NC
    表示执行的结果。其中CX是年,这个年是由CX中存。07D4十进制就是2004年。DH+DL=DX,因此DH=0C,DL=18。两者转化为十进制就是DH=12,DL=24,也就是今天了。AX=AH+AL=2A05,因此AL=05。那就是今天是星期五。
    上面可能大家如今还看不懂,不过经过解说你应该能够知道,仅仅两行命令,就读到了如今的值。如今须要做的就是把这些值提取出来用做他用了。
 
    从中断的做来与中断向量表又有什么关系呢?原来你在汇编里运行int 21时,系统就在上面的中断向量表中找到int 21的中断地址,该中断的地址应该位于: 0000:0084~0000:0087,具体算法前面已说明了。
-d 0000:0084 0087
0000:0080              7C 10 A7 00                              |...
-

    找到内容是:00A7:107C。而后系统就转到这个地址执行int 21。
 
    2.2.2 系统数据区
 
    前面都已说明过。再也不多说。系统区,不少DOS中断程序实现部分就在这个区。程序运行区依不一样的程序而不用。
 
    2.2.3 640K~1M之间
 
    这期间有些地方是ROM,有些地方是硬件的BIOS区。我仅以两个例子说明这一区。
 
    ROM区 :ROM区就是只读内存,也就是说这个区的数据只能读不能写。好比F000:0000开始的内存是ROM。咱们来写一下,而后再看看效果。
 
-d f000:0000 0005  --显示由F000:0000到F000:0005的六个字节值
F000:0000  04 E8 A2 FF F9 C3                                ......
-e f000:0000    --修改命令
F000:0000  04.00  E8.00  A2.00  FF.00  F9.00  C3.00   --注意,.后面的是我改的,把这几个值都改为0了。
 
-d f000:0000 0005   --再次显示这个区的数据。
F000:0000  04 E8 A2 FF F9 C3                                ......
-
    经过上面测试,发现该区数据仍然未改变。但你要是试别的RAM区的,确定会变。若是想试你本身试试吧。
 
     显示缓冲区 :在文本方式下,B800:0000开始的地址保存着屏幕上每一个字符位置的值。在文本方式下,屏幕被分为80×25。每一个位置有两个值,一个值是ASCII字符,一个值是该ASCII的属性值(主要是颜色)。因此一个屏幕共有80×25×2=400个字符。
    咱们来改:
-d b800:0000 0010   --显示屏幕缓冲区的内容,注意此时本行最左边的“-”是屏幕左上角。
B800:0000  2D 07 64 07 20 07 62 07-38 07 30 07 30 07 3A 07  -.d. .b.8.0.0.:.
B800:0010  30                                                0
-
    看上面的命令,屏幕最上边一行是“-d b800:0000 0010”,因此他的内容就是“2D 07 64 07 20 07 62 07-38 07 30 07 30 07 3A 07”其中,2D是“-”的ASCII值,07是“-”的属性值。64是“d”的ASCII值,07是“d”的属性值。。。。。
    如今修改这些值。我把左上角的字改为黄颜色的“-”,那固然是改b800:0001的属性值了。
-e b800:0001  0e
    是否是左上角的颜色变成黄色了吗?
    好了,把第二个字符变成绿色的“-”吧?
-e b800:0002 2d 0b
    变了吗?
 
2.3 可执行文件内存映象 
 
    DOS下可执行文件有两种(BAT是批处理文件,他只是简单调用DOS内部命令或其它程序,因此此处不认为它是可执行文件):一种是COM文件,一种是EXE文件,前面提到,COM文件通常小于64K。EXE文件则能够任意大。为何呢?
    说到这里,还要提到段。每一个段64K。段的做用就是数据组织单位。段的类型有三种:代码段(Code Segment,简称CS)、数据段(Data Segment,简称DS)、栈段(Stack Segment,简称SS),另外还有一个附加数据段(Extra Segment,简称ES),它的用与数据段DS能够认为彻底同样,当数据段的64K不够用,或你就须要把数据放到两个段中以便移动、复制、比较时,才用到附加数据段ES。(固然,移动、复制、比较操做在一个段中也能够完成)
 
    1.段的做用 
 
    1.1 代码段(CS):程序装入内存中,DOS怎么知道是从哪里执行呢?答案就是系统自动从代码段指定位置开始执行,而且始终在代码段中执行。所以代码段CS的做用就是保存全部的指令。这里所说的代码也就是汇编指令了。因此编写汇编程序也就主要是编写代码段中的代码。
 
    1.2 数据段(DS)、附加段(ES):顾名思义,数据段中存的就是数据,这些数据供代码段的程序调用。附加段就是附加数据段。做用与数据段相同。
 
    1.3 栈段(SS):这个段很是重要,但实际上,你在使用中,彷佛用不着这个段,但实际上,这是黑客编程中最重要的一部分,并且系统会不停地“偷偷地”使用这个段,正是这个偷偷地用,使得系统的不少动做被记录到这个段中。还有两点,你必须记住:一是若是你使用了这个栈,好比你把数据存到这个栈中,则必须有相应的出栈命令,而且入几个数据,就得出几个数据,多一个或少一个,你的程序就可能致使死机或异常;二是你要把握操做时机,好比你不能在系统使用栈的先后使用栈,好比你在调用子程序以前入栈,而在子程序中出栈,而在系统调用子程序时,系统也要使用栈,这种也将致使出错。
     栈就是一种先入后出(也有称为后入先出)的结构,有地址由小到大的增长栈,有地址由大到小的逆向减栈。
 
    2.段重叠 
 
    从上面,咱们能够看到,CS,DS,SS三者做用各不相同,内存就是象录音磁带,录新歌,则旧歌被删,带子上存的始终是最后录的那段音乐。所以,若是重叠则必然相互冲突。那还能重叠吗?
    这里所说的重叠不是指内容重叠,而是指概念上的重叠,即数据相互放到一个段中,但相互能够区分开。好比某一段既有数据也有代码,则代码在每要执行到数据以前加一个跳转指令跳过这段代码。这个跳转指令要求用户在编程的时候加上。
    而栈段呢?栈段有本身的特殊性,特殊就在于系统也会自动地使用,而用户则又在不知道系统在使用的状况下使用。避免这种冲突的方法就是采用逆向的栈段。
 
     3 .COM文件内存映象

    COM文件被读到内存中后,该文件的前100H个字节被操做系统使用,操做系统使用这256个字节保存一些系统要使用的数据,汇编语言编程者不能在这里存本身的数据,但在知道这此数据的做用后可使用其中的数据。从100H开始,就是程序的开始了。COM文件之因此最大只能有64K,其缘由是COM文件的四个段是相互重叠的。也就是说,CS、DS、SS、ES四个段的地址都指向这个COM文件的100H处。程序代码、数据、栈都在由100H到64K的区域内。如何把三者分开呢?栈段采用逆向栈,这个栈由64K开始,随着数据入栈,则地址就减少。这样做的好处是,栈段由高端向低端进展,能够详细与数据、代码分开;坏处也不言而喻,假如一个COM程序大量用到栈(好比是个递归程序)所以栈就不停地下降,而程序代码自己也不少,甚至不停地申请新空间,这样数据和栈就会在中间碰头,致使程序被破坏。
    区分开数据代码段与栈段后,下面讨论把数据段和代码段也分开。这个简单的多,只要逻辑上分开就能够。不过通常的方法就是:在100H处放一个跳转指令,随后放数据,而后再放置其它的代码。而100H处的跳转指令就跳到这里。
    所以,COM文件内存映象就是:
    CS:0000    (因为COM的CS,DS,SS,ES三段重叠,所以此行前CS,写成DS,SS,ES都同样)  
    CS:0100    一个跳转到YYYY地址的跳转指令。
    CS:0101    本程序所须要用到的数据
    CS:XXXX    数据结束处。
    CS:YYYY    程序代码保存处。
    CS:ZZZZ    程序代码结束处。
    CS:FFFF    栈段开始处(注意栈是地址愈来愈小,因此这里是开始而不是结束处),也是程序的结束处。另外,此处FFFF与前面XXXX,YYYY,ZZZZ不同,这里是十六进制的64K。
 
    4.EXE文件

    比起COM文件,EXE文件要复杂一些,他的复杂就在于COM文件前面规定了100H个字节用于系统使用,而EXE文件则有个文件头,文件头的大小看具体内容多少。文件头的内容使得EXE看起来复杂了,但也更灵活了。更重要的是,对于病毒设计者,这个文件头使他们如鱼得水。由于文件头处
    EXE文件的内存映象为:
    XXXX:0000 文件头
    XXXX:YYYY 文件头结束处
 
    CS:0000 代码段开始处
    CS:ZZZZ 代码段结束处
 
    DS:0000 数据码段开始处
    DS:WWWW 数据码段结束处
 
     SS:0000 栈段开始处
    SS:UUUU 栈段结束处
 
    ES:0000 附加段开始处
    ES:VVVV 附加段结束处
 
    说明:
    1) 上述ES能够没有,要看实际须要
    2) CS,DS,ES,SS的顺序也是看编程者是怎么安排的,好在用户没必要关心他的具体位置。
    3) 由上可见,CS,DS,ES,SS的段地址确定保存到了文件头中。
    4) 由上可见,实际执行的只是CS,所以DS,ES,SS的首地址,CS确定要想办法知道。:) 
 
 

第三讲 汇编指令

 

3.1 什么是机器语言 
 
    前面提到“最先的计算机采用机器语言,这种语言直接用二进制数表示,经过直接输入二进制数,插拔电路板等实现,这种‘编程’很容易出错,每一个命令都是经过查命令表实现”。
    好比要执行21号中断,须要查表,获得21号中断的指令就是CD 21。这样无论你经过什么方式,在内存指令位置,写入两个字节,一个是CD(这可不是音乐光盘,而是二进制数,转成十进制就是205),另外一个是21(一样是十六进制,十进制是33)
    上面就是机器语言。
 
3.2 什么是汇编语言

    前面也提到“既然是经过‘查表’实现的,那固然也可让计算机来代替人查表实现了。因而就产生了汇编语言”,汇编语言产生的重要目的就是用容易记的符号来代替容易出错的二进制数(或十六进制数)
    好比前面的21号中断,机器语言是CD 21。而汇编语言就规定中断用int表示(interrupt的前三个字母),21号中断就成了int 21h。其中21后面的h表示是表示这个21是十六进制。因为大小写不敏感,因此int 21h写成下列方式都等价:
    int 33
    Int 21h
    INT 21H
 
3.3 汇编指令集

1、数据传输指令 
-----------------------------------------------------------------
它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据. 
1. 通用数据传送指令. 
MOV     传送字或字节. 
MOVSX   先符号扩展,再传送. 
MOVZX   先零扩展,再传送. 
PUSH    把字压入堆栈. 
POP     把字弹出堆栈. 
PUSHA   把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈. 
POPA    把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈. 
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈. 
POPAD   把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈. 
BSWAP   交换32位寄存器里字节的顺序 
XCHG    交换字或字节.( 至少有一个操做数为寄存器,段寄存器不可做为操做数) 
CMPXCHG 比较并交换操做数.( 第二个操做数必须为累加器AL/AX/EAX ) 
XADD    先交换再累加.( 结果在第一个操做数里 ) 
XLAT    字节查表转换. 
BX      指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL ) 
2. 输入输出端口传送指令. 
IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} ) 
OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 ) 
输入输出端口由当即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时, 
其范围是 0-65535. 
3. 目的地址传送指令. 
LEA 装入有效地址. 
例: LEA DX,string ;把偏移地址存到DX. 
LDS 传送目标指针,把指针内容装入DS. 
例: LDS SI,string ;把段地址:偏移地址存到DS:SI. 
LES 传送目标指针,把指针内容装入ES. 
例: LES DI,string ;把段地址:偏移地址存到ES:DI. 
LFS 传送目标指针,把指针内容装入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI. 
LGS 传送目标指针,把指针内容装入GS. 
例: LGS DI,string ;把段地址:偏移地址存到GS:DI. 
LSS 传送目标指针,把指针内容装入SS. 
例: LSS DI,string ;把段地址:偏移地址存到SS:DI. 
4. 标志传送指令. 
LAHF 标志寄存器传送,把标志装入AH. 
SAHF 标志寄存器传送,把AH内容装入标志寄存器. 
PUSHF 标志入栈. 
POPF 标志出栈. 
PUSHD 32位标志入栈. 
POPD 32位标志出栈.

2、算术运算指令 
-----------------------------------------------------------------
ADD 加法. 
ADC 带进位加法. 
INC 加 1. 
AAA 加法的ASCII码调整. 
DAA 加法的十进制调整. 
SUB 减法. 
SBB 带借位减法. 
DEC 减 1. 
NEC 求反(以 0 减之). 
CMP 比较.(两操做数做减法,仅修改标志位,不回送结果). 
AAS 减法的ASCII码调整. 
DAS 减法的十进制调整. 
MUL 无符号乘法. 
IMUL 整数乘法. 
     --以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
 
AAM 乘法的ASCII码调整. 
DIV 无符号除法. 
IDIV 整数除法. 
     --以上两条,结果回送: 商回送AL,余数回送AH, (字节运算);
                     或 商回送AX,余数回送DX, (字运算). 
AAD 除法的ASCII码调整. 
CBW 字节转换为字. (把AL中字节的符号扩展到AH中去) 
CWD 字转换为双字. (把AX中的字的符号扩展到DX中去) 
CWDE 字转换为双字. (把AX中的字符号扩展到EAX中去) 
CDQ 双字扩展. (把EAX中的字的符号扩展到EDX中去)
 
3、逻辑运算指令 
-----------------------------------------------------------------
AND 与运算. 
OR   或运算. 
XOR 异或运算. 
NOT 取反. 
TEST 测试.(两操做数做与运算,仅修改标志位,不回送结果). 
SHL 逻辑左移. 
SAL 算术左移.(=SHL) 
SHR 逻辑右移. 
SAR 算术右移.(=SHR) 
ROL 循环左移. 
ROR 循环右移. 
RCL 经过进位的循环左移. 
RCR 经过进位的循环右移. 
     --以上八种移位指令,其移位次数可达255次. 
移位一次时, 可直接用操做码. 如 SHL AX,1. 
移位>1次时, 则由寄存器CL给出移位次数. 
如 MOV CL,04 
SHL AX,CL
 
4、串指令 
-----------------------------------------------------------------
DS:SI 源串段寄存器 :源串变址. 
ES:DI 目标串段寄存器:目标串变址. 
CX    重复次数计数器. 
AL/AX 扫描值. 
D标志 0表示重复操做中SI和DI应自动增量; 1表示应自动减量. 
Z标志 用来控制扫描或比较操做的结束. 
MOVS 串传送. 
( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. ) 
CMPS 串比较. 
( CMPSB 比较字符. CMPSW 比较字. ) 
SCAS 串扫描. 
把AL或AX的内容与目标串做比较,比较结果反映在标志位. 
LODS 装入串. 
把源串中的元素(字或字节)逐一装入AL或AX中. )
( LODSB 传送字符. LODSW 传送字. LODSD 传送双字. ) 
STOS 保存串. 
是LODS的逆过程. 
REP         当CX/ECX<>0时重复. 
REPE/REPZ   当ZF=1或比较结果相等,且CX/ECX<>0时重复. 
REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX<>0时重复. 
REPC        当CF=1且CX/ECX<>0时重复. 
REPNC       当CF=0且CX/ECX<>0时重复.
 
5、程序转移指令 
-----------------------------------------------------------------
1>无条件转移指令 (长转移) 
JMP 无条件转移指令 
CALL 过程调用 
RET/RETF过程返回.

2>条件转移指令 (短转移,-128到+127的距离内) 
( 当且仅当(SF XOR OF)=1时,OP1<OP2 ) 
JA/JNBE 不小于或不等于时转移. 
JAE/JNB 大于或等于转移. 
JB/JNAE 小于转移. 
JBE/JNA 小于或等于转移. 
以上四条,测试无符号整数运算的结果(标志C和Z). 
JG/JNLE 大于转移. 
JGE/JNL 大于或等于转移. 
JL/JNGE 小于转移. 
JLE/JNG 小于或等于转移. 
以上四条,测试带符号整数运算的结果(标志S,O和Z). 
JE/JZ 等于转移. 
JNE/JNZ 不等于时转移. 
JC 有进位时转移. 
JNC 无进位时转移. 
JNO 不溢出时转移. 
JNP/JPO 奇偶性为奇数时转移. 
JNS 符号位为 "0" 时转移. 
JO 溢出转移. 
JP/JPE 奇偶性为偶数时转移. 
JS 符号位为 "1" 时转移.

3>循环控制指令(短转移) 
LOOP CX不为零时循环. 
LOOPE/LOOPZ CX不为零且标志Z=1时循环. 
LOOPNE/LOOPNZ CX不为零且标志Z=0时循环. 
JCXZ CX为零时转移. 
JECXZ ECX为零时转移.

4>中断指令 
INT 中断指令 
INTO 溢出中断 
IRET 中断返回

5>处理器控制指令 
HLT 处理器暂停, 直到出现中断或复位信号才继续. 
WAIT 当芯片引线TEST为高电平时使CPU进入等待状态. 
ESC 转换到外处理器. 
LOCK 封锁总线. 
NOP 空操做. 
STC 置进位标志位. 
CLC 清进位标志位. 
CMC 进位标志取反. 
STD 置方向标志位. 
CLD 清方向标志位. 
STI 置中断容许位. 
CLI 清中断容许位.

6、伪指令 
-----------------------------------------------------------------
DW      定义字(2字节). 
PROC    定义过程. 
ENDP    过程结束. 
SEGMENT 定义段. 
ASSUME 创建段寄存器寻址. 
ENDS    段结束. 
END     程序结束.
 

3.4 再谈寄存器和内存的区别 
 
    第零讲说到“寄存器在CPU中。内存在内存条中。前者的速度比后者快100倍左右。后面的程序要求每条指定要么没有内存数据,要么在有一个寄存器的参与下有一个内存数据。(也就是说,不存在只访问内存的指令)
    寄存器是在CPU中的存储器,而内存是在内存条中的存储器。CPU访问寄存器,只须要经过微指令直接就能够访问,而访问内存则要先通过总线,再由总线到达内存控制器,读到某单元的内存数据后放上总线,再传到CPU中,CPU才能使用。
    8086系列计算机的寄存器,共有14个,每一个都是十六位的。
    AX,BX,CX,DX,SP,BP,SI,DI,CS,DS,SS,ES,IP,FLAGS。
    其中前四位,每一个能够单位再分红两个,AX=AH+AL,BX=BH+BL,CX=CH+CL,DX=DH+DL。这些分开的每一个都是8位的。
    这个分开不要理解成平时语言中的分开,你能够理解为AX是由AH和AL组合成的,你给AL付值,就意味着同时给AX的低半部付值。你给AX付值,就意味着同时改变AH和AL。这样做的好处是你能够更灵活地控制这个寄存器。
 
3.5 指令说明

    看了3.3的指令集和3.4的寄存器,是否是已经晕了,或者了迷糊?不要急,上面的东西虽然多,我也没让你一下学会,(其实有些永远也不会彷佛也不是什么大不了的事)。为了应付看的懂我后面所说的,我把其中的指令挑几个重点的,你必需要记住,其它的慢慢学吧。
 
1.数据传输指令 
 
    mov A,B 
    注意不是move,这个指令是把B中的数据复制给A,(B中仍保存原状)。这里的A和B能够是寄存器,能够是内存。但能够同时是寄存器,不能同时是内存。好比
 
    mov ax,100这是对的,注意100在这里叫当即数,但这个数在编译系统编译成exe的时候保存在内存中。若是学过别的高级语言,你就能够理解为这就是赋值语句 Let ax=100/ax:=100;/ax=100。

2.伪指令

    伪指令就是否是真的指令,但他同时又是指令。之因此说这样矛盾的话,是由于伪指令不是机器语言的一部分,而是汇编语言的一部分,是你告诉汇编的编译系统如何去做。
    string DB '这是个人第一个汇编语言程序$'
    上面一行指令中,DB就是伪指令,他的做用就是告诉编译程序,把后面一些数据或字符串放到内存中。固然对于exe来讲,已在内存中了,就不用“告诉”了。(这就是为何叫伪指令)。string是你给这段内存起的名字,若是你不须要这段内存,不起名字也能够,但若是后面要用,固然要加上这个名字。'这是个人第一个汇编语言程序$'这个就是要处理的数据,固然你也能够换成别的内容,但须要注意的是,要以'$'结尾,这是汇编的约写,即:只是到了$,就认为字符串结束,不然就一直向下找,直到找到一个$为止。因此这就要求你的字符串中不能有'$',若是必须有,再换别的处理方式,后面再说。
 
3.地址传送指令 
 
    Lea A,string
    前面已经定义了string,后面要把地址找到,就要用到lea指令。lea是把字符串的地址给A这个寄存器中,A固然能够上前面提到的任意寄存器。注意地址和内容的区别。若是是内容就是把string的字符串给A了。(固然这也不成立,一个字符串有不少字节,而一个寄存器只有两个字节)
    那么从上面也看到了,string表明一个地址,lea把这个地址给了A,那这个地址到底在哪里呢?事实上这不重要,就象你要把某书店买书,这个书店在哪并非最重要的,有没有你要的书才是最重要的。因此你前面标出string,后面引用就好了,至于这个地址到底在哪是编译程序的事,不是你的事。

4.运算指令

    ADD A,N
    这个很容易理解吧,寄存器A加上N,把和仍存在A中。相似于高级语言中的let a=a+n/a:=a+n/a+=n。
 
5.串操做指令

    记住串操做指令表面很复杂,其实很简单。
    由于他就象一个复杂的数学公式同样简单,你所要记住的就是公式的格式,使用时具体套用便可。
    从一个地址到另外一个地址的复制须要注意的是:
    *)把源串段地址给DS。
    *)把源串编址给SI。
    *)把目的串段址给ES。
    *)把目的串偏址给DI。
    *)把要复制的个数给CX,这里可不考虑$了。
    *)把FLAG中的方向标志标志你要的方向,一个是顺向,另外一个是逆向。
    *)发送loop movs,scans等命令。

6.转移指令

    记住:无条件转移指令 jmp。等于转 jz,不等于时转jnz
 
7.中断指令 
 
    int 中断号,注意进制,默认是十进制,因此十六进制就加h。
 
    好了,上面的指令变成七八个了,这你不能嫌多了吧,若是再嫌多就不要继续向下看了。
 
 

第四讲 汇编程序

 
 
4.1 汇编程序框架
 
    data SEGMENT '数据段,编程者能够把数据都放到这个段里
    ....数据部分....  '数据格式是: 标识符 db/dw 数据。
    data ENDS  '数据段结束处。
 
    edata SEGMENT '附加数据段,编程者能够把数据都放到这个段里
    ....附加数据部分....
    edata ENDS  '附加数据段结束处。
 
    code SEGMENT  '代码段,实际的程序都是放这个段里。
        ASSUME CS:code,DS:data,ES:edata '告诉编译程序,data段是数据段DS,code段是代码段CS
 
    start:MOV AX,data '前面的start表示一个标识位,后面用到该位,若是用不到,就能够不加
        MOV DS,AX '这一句与上一行共同组成把data赋值给DS。段寄存器.
        MOV AX,edata 
        MOV ES,AX '与前一句共同组成edata->ES
        ....程序部分....
        MOV AX,4C00h  '程序退出,该句内存由下一行决定。退出时,要求ah必须是4c。
        INT 21h
    code ENDS  '代码段结束。
    END start  '整个程序结束,而且程序执行时由start那个位置开始执行。
 
    上面就是一个程序的框架结构。在这个结构中,有三个段,DS、ES、CS。这三个段分别存数据、附加数据、代码段。
 
4.2 编写咱们的Hello,world思路

    开始编写咱们的第一个程序。
    程序要求:显示一个“Hello,Mr.286.”怎么样?
    思路:
    1 要显示一个字符串,根据前面我让大家记的七八个指令够吗?答案是:不只够,并且还用不完。
    首先定义一下总能够吧。
 
    hellostr db 'Hello,Mr.286.$'
    最后的$不要忘了。
 
    2 首先要考虑的问题就是找中断,找到合适的中断,该中断就能帮咱们完成这个显示任务。我找到(在哪找到的,怎么找到的,别问我,到网上或书上都能找到)
    -------------------------------------------
    中断INT 21H功能09H
 
    功能描述: 输出一个字符串到标准输出设备上。若是输出操做被重定向,那么,将没法判断磁盘已满 
    入口参数: AH=09H
    DS:DX=待输出字符的地址
    说明:待显示的字符串以'$'做为其结束标志 
    出口参数: 无 
    ------------------------------------------- 
    由上面看到,咱们所须要做的就是把DS指向数据段,DX指向字符串的地址,AH等于9H,调用21h中断。
    mov ds,数据段地址
    lea dx,hellostr 'hellostr已在前面1中定义了。
    mov ah,9h
    int 21h
    因为只要在调用int 21h以前把准备的东西准备齐就好了,因此int 21h前面三行的顺序并不重要。
 
    3 退出程序,运行完总要退出呀。再查中断手册
    --------------------------------------------
    中断INT 21H功能4CH
 
    功能描述: 终止程序的执行,并可返回一个代码 
    入口参数: AH=4CH
    AL=返回的代码 
    出口参数: 无
    --------------------------------------------
    mov ah,4Ch
    mov al,0
    int 21h
    
    mov ax,4c00h
    int 21h
    这里须要说明的是返回代码有什么用,返回给谁?返回给操做系统,由于是操做系统DOS调用的这个程序,这个返回值能够经过批处理中的errorlevel获得,这里很少说明,实际上操做系统不多处理这一值,所以al你随便写什么值影响都不大。
 
4.3 程序实现

    data SEGMENT
    msg DB 'Hello, Mr.286.$'
    data ENDS
 
    code SEGMENT
        ASSUME CS:code,DS:data
    start:MOV AX,data
          MOV DS,AX
          lea dx,msg 
          mov ah,9h
          int 21h
          MOV AX,4C00h
          INT 21h
 
    code ENDS
    END start

4.4 编译运行

    把上面程序保存成hello286.asm后,就能够编译运行了。进入DOS,进入汇编目录,若是还没下载,到前面找下载地址。
 
    =================================================
    E:\Download\Masm>masm hello286.asm
    Microsoft (R) Macro Assembler Version 5.00
    Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.
 
    Object filename [hello286.OBJ]:
    Source listing  [NUL.LST]:
    Cross-reference [NUL.CRF]:
 
      50408 + 415320 Bytes symbol space free
 
          0 Warning Errors
          0 Severe  Errors
    说明:上面连续三个回车,表示我要的都是默认值。下面是零个警告,零个严重错误,(固然了,个人程序还敢错吗?)
 
    E:\Download\Masm>link hello286
 
    Microsoft (R) Overlay Linker  Version 3.60
    Copyright (C) Microsoft Corp 1983-1987.  All rights reserved.
 
    Run File [HELLO286.EXE]:
    List File [NUL.MAP]:
    Libraries [.LIB]:
    LINK : warning L4021: no stack segment
 
    说明:三个回车仍要默认,后面有个警告,没有栈段,这个不要紧,没有的话系统会自动给一个。
 
    E:\Download\Masm>hello286
    Hello, Mr.286.
    说明:运行成功。
    E:\Download\Masm>
4.4 深度思考 
    4.4.1 是否是数据必须放数据段,代码必段放代码段呢?
    答,代码必段放代码段,不然你怎么执行呀?但数据也能够放到代码段,只是程序要做修改。
    code SEGMENT
         ASSUME CS:code,DS:data
         msg DB 'Hello, Mr.286.$'
    start:MOV AX,data
          MOV DS,AX
          lea dx,msg 
          mov ah,9h
          int 21h
          MOV AX,4C00h
          INT 21h
    code ENDS
    END start
    编译后仍然能够。
    4.4.2 我编的程序在内存中是什么样子的呢?
    ------------------------------------------------------------------------
    E:\Download\Masm>debug hello286.exe
    -u
    1420:0000 B81F14        MOV    AX,141F
    1420:0003 8ED8          MOV    DS,AX
    1420:0005 8D160000      LEA    DX,[0000]
    1420:0009 B409          MOV    AH,09
    1420:000B CD21          INT    21
    1420:000D B8004C        MOV    AX,4C00
    1420:0010 CD21          INT    21
    1420:0012 FF362421      PUSH    [2124]
 
    1420:0016 E87763        CALL    6390
    1420:0019 83C406        ADD    SP,+06
    1420:001C FF362421      PUSH    [2124]
    -d 141f:0000 L20
    141F:0000  48 65 6C 6C 6F 2C 20 4D-72 2E 32 38 36 2E 24 00  Hello, Mr.286.$.
    141F:0010  B8 1F 14 8E D8 8D 16 00-00 B4 09 CD 21 B8 00 4C  ............!..L
    -q
 
    E:\Download\Masm>
    ------------------------------------------------------------------------------
    上面是什么呀?还记得前面说的吗?
    1420:0000 B81F14        MOV    AX,141F
      |  |      |            |       |
    段址:偏址 机器语言      mov指令 把段地址的地址(141f)赋值给AX寄存器。
 
    1420:0012后面的是垃圾数据,不用管它,把上面程序与源程序做一个比较,看有什么不用,差异在于把标号语言转成实际地址了。
    程序前两行一执行,数据段地址就变成了141f,而那个字符串偏移地址在0000,由(LEA    DX,[0000]看出),因此我用-d 141f:0000 L20(后面L20表示只显示20个字节),就能把段地址显示出来了。
    因此刚才的程序在内存中就变成了:
    141f:0000 Hello, Mr.286.$  ----->这是段地址里的内存
    1420:0000 B81F14        MOV    AX,141F  ------>这是代码段里的内存。data变成了实际地址
    1420:0003 8ED8          MOV    DS,AX
    1420:0005 8D160000      LEA    DX,[0000] ------>偏址变成了0000,由于实际上msg也就是从头开始的。固然是0了。
    1420:0009 B409          MOV    AH,09    ------->注意Debug里,默认的是十六进制
    1420:000B CD21          INT    21
    1420:000D B8004C        MOV    AX,4C00
    1420:0010 CD21          INT    21



-The End-html


http://www.blogjava.net/wxqxs/archive/2009/09/17/277328.html
java