奇趣技术网 收藏本站
设为主页
商务合作
首页 新闻中心 行业动态 软件新闻 安全资讯 病毒预警 漏洞发布 操作系统 Dos Win9x Win2000 WinXP Win2003 WinVista Linux Unix
数据库 DB2 Access MSSQL MySQL Oracle Sybase 编程技术 ASP PHP JSP CGI/Perl XML .Net C/C++/C# VB VC Delphi Java 汇编
安全技术 安全教学 工具介绍 漏洞利用 病毒防范 入侵检测 防火墙 安全防范 汉化破解 攻击实例 加密解密 技术论坛
中华网络安全联盟 >> 程序开发 >> 汇编 >> 汇编语言的艺术(组合语言的艺术)--基本认识(3)
程序开发
Asp
PHP
JSP
CGI/Perl
XML
.Net
C/C++/C#
Visual Basic
Visual C++
Delphi
Java
汇编语言
  • JDBC专题介绍(1)

  • 全面解析JDBC(9)

  • 全面解析JDBC(6)

  • 全面解析JDBC(5)

  • 全面解析JDBC(4)

  • 全面解析JDBC(1)

  • MySQL中修改密码及访问

  • SQL语法参考

  • 汇编语言的艺术(组合语言的艺术)--基本认识(3)
    字体:

    中华网络安全联盟    作者:佚名    来源:网络转载    时间:2006-3-21

    汇编语言的艺术(组合语言的艺术)--基本认识(3):二、程式要条理通顺

      1,在比较判断的过程中,邻近值不必连比。
            CMP     AL,0
            JE      ABCD0
            CMP     AL,1
            JE      ABCD1
            CMP     AL,2
            JE      ABCD2
            ..
        应为:
            CMP     AL,1
            JNE     ABCD0
        ABCD1: 
            ..
        在标题为ABCD0 中,再作:
            JA      ABCD2
        这种做法端视时间效益而定,似此 ABCD1之速度最快。

      2,未经慎思的流程:
            ADD     AX,4
        ABCD:
            STOSW
            ADD     AX,4
            ADD     DI,2
            LOOP    ABCD
            ..
        稍稍动点脑筋,就好得多了:
        ABCD:
            ADD     AX,4
            STOSW
            INC     DI
            INC     DI
            LOOP    ABCD
            ..

      3,错误的处理方式:
            MOV     BX,SI
        ABCD:
            MOV     BX,[BX]
            OR      BX,BX
            JZ      ABCD1
            MOV     SI,BX
            JMP     ABCD
        ABCD1:
            LODSW
            ..
        上例应该写成:
            MOV     BX,SI
        ABCD:
            LODSW
            OR      AX,AX
            JZ      ABCD1
            MOV     SI,BX
            JMP     ABCD
        ABCD1:
            ..

      4,错误的流程:
            TEST    AL,20H
            JNZ     ABCD
            CALL    CDEF[BX]
            JMP     SHORT ABCD1
        ABCD:
            CALL    CDEF[BX+2]
        ABCD1:
            ..
     应该写成:  
            TEST    AL,20H
            JZ      ABCD
            INC     BX
            INC     BX
        ABCD:
            CALL    CDEF[BX]
        ABCD1:
            ..

      5,下面是时间的损失:
            PUSH    DI
            MOV     CX,BX
            REP     STOSB
            POP     DI
            PUSH,POP 很费时间,应为:
            MOV     CX,BX
            REP     STOSB
            SUB     DI,BX
            同理,很多时候稍稍想一下,就可省下一些指令:
            PUSH    CX
            REP     MOVSB
            POP     CX
            SUB     DX,CX
        为什么不干脆些?
            SUB     DX,CX
            REP     MOVSB

      6,有段程式,很有规律,但却极无效率:
        X1:
            TEST    AH,1
            JZ      X2
            MOV     BUF1,BL
        X2:
            TEST    AH,2
            JZ      X3
            MOV     BUF2,DX     ; 凡双数用DX,单数用BL
        X3:
            TEST    AH,4
            JZ      X4
            MOV     BUF3,BL
        X4:
            ..                  ; 以下各段与上述程式相似
        X8:
            ..
            这种金玉其表的程式,最没有实用价值,改的方法应由缓冲器着手,先安排成序列,由小而大如:
            BUF1    DB  ?
            BUF2    DW  ?
            BUF3    DB  ?
            BUF4    DW  ?
            ..
        然后,程式改为:
            MOV     DI,OFFSET BUF1      ; 第一个缓冲器
            MOV     AL,BL
            MOV     CX,4        
        X1:
            SHR     AH,1
            JZ      X2
            STOSB
        X2:
            SHR     AH,1
            JZ      X3
            MOV     [DI],DX
            INC     DI
            INC     DI
        X3:
            LOOP    X1

      7,回路最怕千回百转,不畅不顺,如:
            SUB     AH,AH
        ABCD:
            CMP     AL,BL
            JB      ABCD1
            SUB     AL,BL
            INC     AH
            JMP     ABCD
        ABCD1:
            ..
          以上 ABCD1这个入口是多余的,下面就好得多:
            MOV     AH,-1
        ABCD:
            INC     AH
            SUB     AL,BL
            JA      ABCD
            ADD     AL,BL       ; 还原
            ..

      8,当处理字码时,需要字母的序数,有这样的写法:
            CMP     AL,60H  
            JA      ABCD1 
            SUB     AL,40H      ; 大写字母
        ABCD:
            ..
        ABCD1:
            SUB     AL,60H      ; 小写字母
            JMP     ABCD
            要知道字母码的特色在于大写为 40H 至4AH,小写为60H 至6AH ,以上程式,其实只要一个指令就可以了:
            AND     AL,1FH
        简单明瞭!

      9,大多数的程式在程式师自己测试下很少发生错误,而一旦换一另个人执,就会发现错误百出。
            其原因在于写程式者已经假定了正确的情况,当然不会以明知为错误的方式操作。可是换了一个人,没有先入为主的成见,很可能输入了「不正确」的资料,结果是问题丛生。
            要知道真正的使用者,绝非设计者本人,在操作过程中,按键错误在所难免。这种错误应该在程式中事先加以检查,凡是输入资料有「正确、错误」之别者,错误性资料一定要事先加以排除。
            这样做看起来似乎程式不够精简,可是正确的重要性远在精简之上。一旦发生了错误,再精简的程式也没有使 用价值。
            此外,在程式中常有加、减的运算,这时也应该作正确性检查,否则会发生上述同样的问题。

    三、指令应用要灵活

        有一段很简单的程式,其写作的方法甚多,但是指令应用的良窳,会使得程式的效率相去天上地下,难以估计。
        这段程式的用途,是要将一段资料中,英文字符大、小写相互转换。当然,转换的选择要由使用者决定,在下面程式且略去使用介面,假设已得知转换的方式。
        设资料在 DS:SI中,资料长度=CX ,大写转小写时BL=0,反之,则BL=1。
        我见过一种写法,简直无法原谅:
        1: LOOP1:
        2:        CALL    CHANGE
        3:        JC    LOOP11
        4:        ADD    AL,20H
        5:        JMP    SHORT LOOP12
        6: LOOP11:
        7:        SUB    AL,20H
        8: LOOP12:
        9:        MOV    [SI-1],AL
       10:        LOOP    LOOP1
       11:        RET
       12: CHANGE:
       13:        LODSB
       14:        OR    BL,BL
       15:        JZ    CHANGS
       16:        CMP    AL,61H
       17:        JB    CHARET
       18:        CMP    AL,7AH
       19:        JA    CHARET
       20:        STC
       21: CHARET:
       22:        RET
       23: CHANGS:
       24:        CMP    AL,41H
       25:        JB    CHARET
       26:        CMP    AL,5AH
       27:        JA    CHARET
       28:        CLC
       29:        RET
        这种程式错在把由12到29的程式写得太长,共 25B,有共用的价值,于是作为子程式调用。
        试想一下,每一笔资料,都要调用一次,浪费四个字元事小,但每次要费 23+20个时钟脉冲,资料多时,不啻为天文数字。更何况这段程式写得极差,在回路中,又多浪费了几十个时钟。关于这一点,下面会继续讨论。
        照上面这段程式,略加改进,写法如下:
        1: CHANGE:
        2:        LODSB
        3:        OR    BL,BL
        4:        JZ    CHANGS
        5:        CMP    AL,61H
        6:        JB    CHARET
        7:        CMP    AL,7AH
        8:        JA    CHARET
        9:        SUB    AL,20H
       10: CHANG0:
       11:        MOV    [SI-1],AL
       12: CHANG1:
       13:        LOOP    CHANGE
       14:     RET
       15: CHANGS:
       16:        CMP    AL,41H
       17:        JB    CHANG1
       18:        CMP    AL,5AH
       19:        JA    CHANG1
       20:        ADD    AL,20H
       21:        JMP    CHANG1
        这样的写法还是不佳,因为在回路中,用常数与暂存器比较,速度较暂存器相比为慢。应该先将需要比较的值,放在暂存器DH,DL 中,改进如次:
        1:        MOV    AH,20H
        2:        MOV    DX,7A61H
        3:        OR    BL,BL
        4:        JZ    CHANGE
        5:        MOV    DX,5A41H
        6: CHANGE:
        7:        LODSB
        8:        CMP    AL,DL
        9:        JB    CHANG1
       10:        CMP    AL,DH
       11:        JA    CHANG1
       12:        XOR    AL,AH
       13:        MOV    [SI-1],AL
       14: CHANG1:
       15:        LOOP    CHANGE
       16:     RET
        以上这段程式,空间小,速度快,每笔资料,平均仅需不到40个时钟值,以10 MHZ计,十万笔资料,约需半秒钟!
     请注意程式中所用的技巧,由2至6的分支法,就比下面这种写法为佳:
        1:        OR    BL,BL
        2:        JZ    CHAN1 
        3:        MOV    DX,5A41H
        4:      JMP    SHORT CHANGE
        5: CHAN1:
        6:        MOV    DX,7A61H
        7: CHANGE:
        这种分支也可以由另一种技巧所取代,即预设法。事先将所需用的参数放在固定的缓冲区中,此时取用即可:
               MOV  DX,BWCOM   ; 比较之预设值 
        这样程式又简单些了:
        1:       MOV    AH,20H
        2:        MOV    DX,BWCOM
        3: CHANGE:
        4:        LODSB
        5:        CMP    AL,DL
        6:        JB    CHANG1
        7:        CMP    AL,DH
        8:        JA    CHANG1
        9:        XOR    AL,AH
       10:        MOV    [SI-1],AL
       11: CHANG1:
       12:        LOOP    CHANGE
       13:     RET

        以上介绍为变数法技巧,即将所要比较的值,放在暂存器中。由于暂存器快速、节省空间,因此程式效率高。更重要的一点,是程式本身的弹性大,只要应用方式统一,事先把参数设妥,即可共用。

    四、回路中的指令

        回路最重要的是速度,因为本段程式,将在计数器的范围之内,连续执行下去。如果不小心浪费了几个时钟值,在回路的累积下,很可能使程式成为牛步。
        要想把回路写好,一定要记清楚每个指令的执行时钟,以便选择效率最高者。同时,要知道哪些指令可以获得相同的处理效果,才能有更多的选择。
        其次,在回路中,最忌讳用缓冲器,不仅占用空间大,处理速度慢,而且不能灵活运用,功能有限。另外也应极力避免常数,尽量设法经由暂存器执行,用得巧妙时,常会将整个程式的效率提高百十倍。
        还有便是少用 PUSH,POP,DIV,MUL和 CALL 等浪费时钟的指令。除此之外,小心、谨慎,深思、熟虑,才是把回路写好的不二法门。
        在前例中,把比较常数的指令换为比较暂存器,便是很好的证明。如果用常数,两段程式决不可能共用,时、空都无谓地浪费了。
        以下再举数例,乍看这似乎有些吹毛求疵,但是仔细计算一下所浪费的时间,可能就笑不出声了。
     兹假定以下回路需处理五万字元的资料,频率为 10MHZ,其情况为:
        1: LOOP1:
        2:          LODSB
        3:        XOR    AL,[DI]
        4:        STOSB
        5:        LOOP    LOOP1
        本程式计数器等于50,000,每次需
        12T+14T+11T+17T=55T 个时钟脉冲
    若以50,000次计,需时 47*50,000/10,000,000 秒,即约四分之一秒。
        只要稍稍将指令调整一下,为:
        1: LOOP1:
        2:             LODSW
        3:        XOR    AX,[DI]
        4:        STOSW
        5:        LOOP    LOOP1
        这样计数器只要25,000次,每次
        16T+18T+15T+17T=66T
        则25,000次需时 66*25,000/10,000,000 秒,约六分之一秒,比前面的程式快了二分之一。
        同理,在回路中加回路,而每个回路需 17T,也是很大的浪费。倘若加调用 CALL 指令,则需 23T+20T=43T,浪费得更多,读者不可不慎。
        当某一段程式用得很频繁时,理应视作子程式,例如下面的 LODAX:
        1: LOOP1:
        2:        CALL    LODAX
        3:        LOOP    LOOP1
        4:        RET
        5: LODAX:
        6:        LODSW
        7:        XOR    AX,[DI]
        8:        STOSW
        9:        RET
        其实这是贪小失大,仅四个字元的程式,竟用三个字元的调用指令去交换,是绝对得不偿失的。
        再如同下面的程式,颇有值得商榷之处。
        1: LOOP1:
        2:        MOV    DX,NUMBER1
        3:        MOV    CX,NUMBER2
        4:    LOOP2:
        5:        PUSH    CX
        6:        MOV    CX,DX
        7: LOOP3:
        8:        LODSW
        9:        XOR    AX,[DI]
       10:        STOSW
       11:        LOOP    LOOP3
       12:        INC     DI
       13:        INC     DI
       14:        POP    CX
       15:        LOOP    LOOP2
       16:        RET
        第二个回路是多余的,这是高阶语言常用的观念,对组合语言完全不适用。
        稍加改动,不损上面程式原有的条件,得到:
        1: LOOP1:
        2:        MOV    DX,NUMBER1
        3: LOOP2:
        4:        MOV    CX,NUMBER2
        5: LOOP3:
        6:        LODSW
        7:        XOR    AX,[DI]
        8:        STOSW
        9:        LOOP    LOOP3
       10:        INC     DI
       11:        INC     DI
       12:        DEC     DX
       13:        JNZ    LOOP2
       14:        RET
    这样回路少了一个,程式中将5,6,14,15 各条中原来为15T+2T+12T+17T=46T的时间,省为12,13,14条的2T+16T+17T=35T。

                第五节  分支处理

        比较资料后,作条件分支 (Conditional Jump ),是程式中不可避免的手续。程式一长,分支距离超过 128个字元,条件分支就无法到达。当然,精简程式有时可以避免这种情形,但却不尽然。
        处理条件分支的技术很多,其效率端视情况而定。最要紧的是事先规划,要比较些什么?在何种情况下?分支到哪里?做些什么工作等等。
        不仅是写程式,人的各种能力,都可以由工作的方式判断出来。智慧高的人,很快就能抓住重点,再分门别类,钜细无遗的理出完整的系统。经过良好训练的专家,则能根据一套法规,逐步地整理归纳,也能推出合情合理的结果来。
        老实说,电脑程式的写作技术还没有到成熟的阶段,当今所有的从业人员,都只能算是「拓荒者」,并没有真正的「专家学者」。充其量,像我个人一样,比别人机会好些,天天得以与电脑为伍,多一点经验而已。
        因此,目前写程式几乎可以说没有可资遵循的法规,海阔天空,爱怎样写,就怎样写,只要能够使用,程式卖得出去,赚了大钱,就会被人视为大师。
        只是这种情况维持不了多久了,初民的壁画,仅具有历史意义。今天的程式师,如果不认清现实,立刻觉醒,多致力于法规的制定,电脑将永远是个不成熟的孩子。一旦这些法规经得住考验,为未来的专家学者奠定基础,那才能真正的被视为大师。
        我不讳言我们正朝着这个方向努力,但是,我却不认为做得到。因为电脑的硬体设计在今后的十年内,必然会有重大的突破,谁都难以预测会有什么结果。软体的制作观念虽然不可能有很大的改变,却难免会受到影响。只有各位年轻朋友,你们成长在电脑时代,肯多一分耕耘,必有收获!
        下面,且介绍一些我对条件分支的处理技巧:

    一、资料的分类

      1,位元分类:
            在本书第四章第五节所举的,由输入码作为输出字形的处理依据之例,就是采用位元分类的例证。
            但凡以资料位元作为共同的分类讯息,而且各类皆有独特的处理方式者,皆应以其位元为顺序,用间接定址或分支技巧,作为程式处理之手段。

      2,字元分类:
            每一个字元具有 256种排列组合,设若有 128种以内的分类项目,应该取双数分类,否则须用连续分类。
            分类之值,立即可以用间接定址执行。但须注意,各分类的入口标题应先行定义。由于定义必须用到双字元,所以,凡采用连续分类者,其值应乘二。

      3,间隔分类:
            在有些情况下,原有资料不容许重新安排,而且其中若干资料已具备分类之特性,这种情况,我们称之为间隔分类。
            在处理此类资料时,应该先将可以作分类处理的资料提取出来,并视为字串,定义在一缓冲区内。当须要类比时,可利用「比对字串」 (SCAS) 的指令以求得其定义位置,再作间接定址。设有
            4700H,4900H,4F00H,5100H,4A2DH,4EABH
        等键盘输入数据。设上述值在AX中,需要作特殊处理,分别进入COD1至COD6等子程式。
        11将资料定义在缓冲器 ABC中,程式则定义在DEF:
          ABC    DW   4700H,4900H,4F00H,5100H,4A2DH,4E2BH
          DEF    DW   COD1,COD2,COD3,COD4,COD5,COD6
        12使DI=ABC,CX=6:
            MOV     DI,OFFSET ABC
            MOV     CX,6
        13由比对字串后,判断是否AX中有上述之值,如有,则用间接定址的方式执行之。
            REPNZ   SCASW               ; 比对六组字串
            JCXZ    NOTHING             ; 没有所比之字串
            SUB     DI,OFFSET ABC+2     ; 得到比对位置值
            CALL    CS:DEF[DI]          ; 或作JMP
              上述之DEF 如果放在DG段中,还可以节省一字元,并可加快速度:
            CALL    DEF[DI]

    二、程式的结构

        若在程式规划之初,未先做好准备工作,临时想用前述的方法,并非绝不可能。但是,东添一点,西补一段,这种程式不仅会导致测试的麻烦,更可能影响未来的维护和调整。
        因此,每当瞭解了工作任务后,需要作间接定址的部份,最好能集中在一个模组内。万一性质不同必须分割,也应该将间接定址的程式,置放在模组的起头处。
        这样做的好处很多,一方面便于扩充功能,每次增加定址因素时,不必在程式中寻来找去,立刻可以安排妥当。其次,这种定址的需求,必然与整体功能有关,而且定义表相当于一个目录,把纲领放在前面,按图索骥,一目瞭然。更重要的,是可以表现出程式结构的层次,层次处理是网状流程中最难以掌握的一环,不可不慎。
        还有,就是各子程式的标题安排,其位置的先后应以功能的集中性为准。这样做的好处是,如果有可以共用的程式段,很容易就可合并为一,节省空间。

    三、次序与条件「真」「假」

        条件分支的「时钟数」有二个可能,条件符合时,执行分支为 16T,不符合则为 4T ,且继续下一指令。两者相差有四倍之多,我们正该利用这一特点,速度重要的条件,都应该设为主流程,否则为分流程。
        尤其是在需要高速的回路中,分支处理得好坏,效率相去甚远。这种分支需要平时多加小心,培养出良好的习惯。
        CDEF: 
            CMP    AL,'?'
            JZ     ABCD       ; 各比较符号中,'?' 者最少
            LOOP   CDEF       ; NZ条件仅需4T速度较快
        ABCD: 
            ..

    四、JMP 与 JMP SHORT

        当程式师专心写作或侦错之时,常无法瞻前顾后。然而侦错完毕程式无误时,最好彻底检查一下所有的JMP 指令,经常会大有斩获!
        因JMP 需要三字元,而JMP SHORT 只要两个,其条件是所跳越的位址不能超过128 字元。
        在程式编译时,若向上JMP 的距离在 128字元以内,编译器会自动译为两字元。往下则不然,如在128 字元内,会再多加一个 NOP指令,不仅浪费一字元且多了两个时钟。
        因此,细心检查一下,凡是向下跳,在128 字元以内,皆应改为JMP SHORT 才是。
    字体:
     
    设为主页 收藏本站 联系我们 友情连接 商务合作 网友留言
    Copyright©2006-2008 中华网络安全联盟 All rights reserved.