zoukankan      html  css  js  c++  java
  • x86汇编语言复习笔记

    0 写在前面

      为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序

      在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到x86体系结构在目前的广泛应用,我通过两个月左右的时间对x86的相关内容进行了学习。

      在《x86汇编语言实践》系列中(包括本篇、x86汇编语言实践(1)x86汇编语言实践(2)x86汇编语言实践(3)以及x86汇编语言实践(4)),我通过几个具体案例对x86汇编语言进行实践操作,并记录了自己再编写汇编代码中遇到的困难和心得体会,与各位学习x86汇编的朋友共同分享。

      我将我编写的一些汇编代码放到了github上,感兴趣的朋友可以点击屏幕左上角的小猫咪进入我的github,或请点击这里下载源代码。

      这是《x86汇编语言实践》系列最后一篇文章,明天就要迎来x86汇编的期末考试了,希望所有朋友们以及先先能够考试顺利!

    1 基础知识

    1.Intel 8086/8088PC机的CPU字长为16位,16位的信息称为1个字,内存的基本单元为1个字节,但任何相邻两个单元都可以组成1个字。Intel 8086/8088PC机共有20根地址线,其寻址范围为00000H~FFFFFH

    2.用于间接寻址的寄存器有BX,SP,BP,SI,DI,其中,BX一般用于存放基址;在采用基址变址寻址时,采用SI或BX或DI寄存器,基址寻址默认的段是DS段(DS:[SI]);采用BP或SP寄存器,基址寻址默认的段是SS段(堆栈的位置和大小是由SP和SS共同决定的)。

    3.串操作指令如MOVSB,STOSB,LODSB,SCASB,CMPSB,MOVSW,LODSW,STOSW,SCASW,CMPSW等,源操作数对应的地址是DS:[SI]目的操作数对应的地址是ES:[DI]

    4.Intel8086/8088CPU共有9个1为的标志寄存器(标志位),为了便于CPU的加工,他们被组合在一起形成一个16位的程序状态字寄存器PSW中。几个比较重要的标志位有:

    • ZF:当运算结果为0时,ZF=1,否则ZF=0
    • SF:运算结果为负时,SF=1,否则SF=0
    • CF:算术运算最高位产生进位,CF=1.否则CF=0;还用于移位指令保存最高位左移或最低位右移移出的代码。
    • DF:DF=1时每次串操作SI和DI减1,DF=0时每次串操作SI和DI加1。使用CLD可以将DF清零,即规定为正向操作字符串。
    • TF:TF=1时执行完一条产生单步中断,中断处理程序将TF置0。TF标志用于调试。
    • PF,AF,IF,OF这里我斗胆预测一啵,不考(因为真的没有使用过)。

    5.STD是将DF置1的指令,与CLD将DF清零的效果相反。使用STD,串指令对应的DI,SI寄存器每次操作后根据是SB还是SW操作自动减少1或2

    6.逻辑地址向物理地址的转化(书上P24)。地址转化的动机:20位物理地址无法直接在16位字长的机器中直接运算,因此可以采用Intel的分段方法将其划分为16位段地址和16位段内地址(也称为偏移地址)逻辑地址的基本形式为0000H:0000H,该逻辑地址表示物理地址的00000H。

     那么逻辑地址向物理地址的转换方式可以表述为以下公式:段地址x10H + 偏移地址 = 物理地址

     举例说明:逻辑地址1234H:5678H转换为物理地址为:1234Hx10H + 5678H = 12340H + 5678H = 179B8H。再如:1234H:2001H = 12340H + 2001H = 14341H

    7.几个重要的数据传送指令PUSH,POP,PUSHF,POPF

    • PUSH SRC:先SP = SP - 2 再 SS:[SP] <- SRC 
    • PUSHF    :先SP = SP - 2 再 SS:[SP] <- PSW
    • POP   SRC:先SRC <- SS:[SP] 再 SP = SP + 2
    • POPF         :先PSW <- SS:[SP] 再SP = SP + 2 

     总结而言,PUSH和POP是将操作数压(弹)栈,POPF和PUSHF是将PSW标志寄存器压(弹)栈

    2 寻址方式

    2-1 六种与数据有关的寻址方式

    2-1-1 立即寻址

      直接将立即数写到指令中的寻址方式。注意不得超出寄存器的字节范围:AL8位,AX16位。

      【例】

    • AND AX,0FFFEH
    • MOV AL,100H
    • MOV AL,00000101B
    • MOV AX,512

      【不能使用】

    • MOV AL,100H
    • MOV AX,10000H (超出了字节范围)

    2-1-2 寄存器寻址

      使用寄存器的寻址方式。可以显示使用,也可以隐式使用。也可以使用段寄存器CS,DS,SS,ES。

      【例】

    • MOV DS,AX 
    • PUSH DS
    • PUSHF (隐式操作PSW)
    • STD (隐式操作PSW)
    • CMC  (对CF取反操作,隐式操作PSW)

    2-1-3 直接寻址

      直接使用操作数的偏移地址进行寻址的方式,偏移地址用[立即数]的形式表示,或者直接用数据段中定义的变量名表示,或用数据段中定义的变量名+立即数的形式表示。

      【例】

    • AND AX,[0FFFEH]
    • MOV AX,X    ;其中X为数据段中定义好的数据
    • MOV AX,STR+1   ;其中STR为数据段中定义好的数据,STR+1直接寻址到STR下一个字节单元的内容

    2-1-4 寄存器间接寻址

      使用寄存器中存储的偏移地址进行寻址。注意只能使用寻址寄存器BX,BP,SI,DI进行寻址,而不能用DX等进行寻址。此外,寻址的地址必须为16位,即不能使用BL等进行寻址。

      【例】

    • MOV AX.[BX]
    • MOV BH,[BP]
    • MOV CX,[SI]
    • MOV DL,[DI]

      以上四条指令等价于

    • MOV AX.DS:[BX]
    • MOV BH,SS:[BP]
    • MOV CX,DS:[SI]
    • MOV DL,ES:[DI]

      但是在每条指令前加上一个段超越的段名,既麻烦又没必要,因此通常都默认缺省为上述隐含段规则。

      【不能使用】

    • MOV AX,[DX]  (不能用DX)
    • MOV DL,[BL]  (必须为16位寻址)

    2-1-5 寄存器相对寻址

      在寄存器间接寻址的基础上,再增加一个常偏移量。形式多变,大致有如下几种

      【例】

    • MOV AX,[BX+100]
    • MOV AX,[SI+10H]   <==>  MOV AX,10H[SI]
    • MOV AX,ARRAY[SI]
    • MOV TABLE[DI],AL
    • MOV TABLE[DI+1],AL  3~5展示了立即数也可以是数据段中定义好的变量名

      最终在debug下所有的寻址有效地址会被计算成[DI+XXXX]的形式,XXXX是一个十六进制数。

    2-1-6 基址变址寻址

      即基址加变址寻址方式,基址采用BX,BP寻址,变址采用DI,SI寻址,寻址规则相对固定。

      【例】

    • MOV AX,[BX][SI]
    • MOV AX,[BX+SI]
    • MOV ES:[BX+SI],AL
    • MOV [BP+DI],AX   
    • MOV AX,[BX+SI+200]
    • MOV ARRAY[BP+SI],AX

      其中,段取决于基址寄存器,如BX的段就默认为DS;BP缺省为SS。当然,有指定段的情况除外。也可以在两个寄存器加和的基础上再增加一个立即数。

      【不能使用】

    • MOV [BX+CX],AX  (CX不能做变址寄存器)
    • MOV [BX+BP],AX  (BP不能做变址寄存器)
    • MOV [BX+DI],ARRAY  (两个全在内存中的操作数,不符合语法)

    2-1 五种与转移地址有关的寻址方式

    2-2-1 标号与过程名

      与转移地址相关的指令主要是JMP和CALL指令,而要让代码能够跳跃执行到指定的IP处则需要通过标号指示某行代码,或是通过过程名定义进行CALL调用。

    2-2-2 段内直接寻址

      即直接使用标号与过程名进行跳转。根据位移量的不同,可以加SHORT(8BITS)和NEAR PTR(16BITS)操作符。其中,条件跳转只能是8位因此省略SHORT,而JMP则缺省为16位位移量。因此,在跳转位移已知的前提下,使用JMP SHORT可以提高程序的执行效率。

      【例】

    • JMP L1
    • CALL P1
    • JMP SHORT L1
    • JMP NEAR PTR L1  (L!与当前IP位移量为16位的数值)

    2-2-3 段内间接寻址

      即将转移目的地址放入寄存器中进行存储,调用的也是寄存器中的相应数值。

      【例】

    • MOV AX,OFFSET P1     CALL AX
    • JMP BX

      这里要特别注意段内间接寻址与数据寻址中寄存器间接寻址的区别,后者有[]进行寻址。

    • MOV AX,OFFSET P1    MOV ADD1,AX    CALL ADD1
    • MOV BX,OFFSET ADD1   CALL [BX]

      以上两种也是段内间接寻址,注意这里ADD1不是过程名,而是数据段中的一个数据的地址,存放了子程序P1的位移量。BX则存放了ADD1的地址,因此调用CALL [BX]也属于段内间接寻址。

    2-2-4 段间直接寻址

      具备FAR属性的寻址。例如P2为一个有FAR属性定义的过程:

    • CALL FAR P2

    2-2-5 段间间接寻址

       形式如下:

    • JMP DWORD PTR [BX+INTERS]

      只要DWORD PTR后面是除了立即寻址寄存器寻址之外的任何一种数据寻址方式即可。

    3 语法知识

    【判断指令正误】

    • MOV [CX],AL     不正确。CX不能作为寄存器间接寻址的寄存器
    • MOV BH,320     不正确。320超出了8位范围(255)
    • MOV DS,2000H     不正确。不存在从立即数到段寄存器的数据通路。此外,段寄存器作目的操作数时,不允许使用CS作为目的操作数。
    • ADD SI,FDDH     不能确定。如果在数据段定义过一个名为FDDH的数据变量,且该数据在字节范围内,则此指令正确。否则会认为FDDH是一个未定义的变量,改成0FFDH后正确。
    • SHL AX,2     不正确。移位指令格式中,移位的数量count只能是1或CL。移动位数大于1(0和1也可以)必须放入CL寄存器中操作。
    • CMP BYTE PTR [SI],X     不正确。源操作数和目的操作数不能同时为内存中的数。
    • LEA BX,[SI]       正确。
    • LDS BX,[DX]     不正确。DX不能用作寄存器间接寻址的寄存器
    • JMP BYTE PTR AX     不正确。转移只有NEAR/FAR PTR + 标号或SHORT+标号或只有标号/寄存器的形式。没有JMP BYTE PTR的形式
    • JMP AX     正确。
    • JMP [AX]     不正确。AX不能用作间接寻址的寄存器。
    • RET 5     不正确。后面的立即数必须为偶数。这是为了带参数调用的子程序在返回时要弹出几个参数的位置,进而维持堆栈的平衡。
    • MOV [BX+SI+10],100     不正确。注意只有在寄存器相对寻址取数(作为源操作数)时直接寻址即可,若作为目的操作数则必须指定size。修改为MOV BYTE PTR[BX+SI+10],100即可。
    • DIV AL    正确。执行结果为AX=0001即除以本身,商1余0。

    4 简答问题

    4-1 解读指令执行过程

     1. RET EXP

      IP ← [SP]

      SP ← SP + 2

      SP ← SP + EXP

    2.RETF

      注意如果是FAR属性的过程,返回时是段间返回(即在汇编器中会被汇编成RETF指令),会执行以下过程

      IP ← [SP]

      SP ← SP + 2

      CS ← [SP]

      SP ← SP + 2

    3.PUSH SRC

      SP ← SP - 2

      SS:[SP] ← SRC

    4.PUSHF

      SP ← SP - 2

      SS:[SP] ← PSW

    5.POP DST

      DST ← SS:[SP]

      SP ← SP + 2

    6.POPF

      PSW ← SS:[SP]

      SP ← SP + 2

    7.LEA REG,SRC

      将SRC的偏移地址送入REG  

    8.LDS/LES REG,SRC

      将SRC中的双字内容分别送REG和DS/ES中。

      这里的SRC中的双字通常保存的是某个程序或变量的逻辑地址(SEG:OFFSET),前面的低字送入REG,后面的高字送入DS/ES。

    9.CALL FAR PTR P1 

      SP ← SP - 2

      SS:[SP] ← 返回地址段值

      SP ← SP - 2

      SS:[SP] ← 返回地址偏移值

      IP  ← 目的偏移地址

      CS ← 目的段地址  

    10.CALL AX(假设为段内间接调用)

      SP ← SP - 2

      SS:[SP] ← 返回地址偏移值

      IP ← AX中有效地址

    11.JMP [BX]

      从内存中根据BX间接寻址,取得的标号值送IP进行跳转。

    12.JMP DX

      将DX中有效地址偏移值送入IP进行跳转。

    13.CALL DWORD PTR [BX]

      段间间接调用,过程地址CS:IP(这是一个双字,因此用DWORD PTR)位于数据段中通过BX间接寻址得到。

    14.LOOP LP1

      CX = CX - 1

      若CX ≠ 0,则跳转至LP1,否则顺序执行之后代码。

    15.CMP BX,X1

      分别通过寄存器寻址和直接寻址取得BX与X1的值,计算BX-X1并影响标志位。(如可用ZF判断两数是否相等、CF=1或SF=1则BX<X1等等,再配合JC,JS等指令即可进行条件跳转

    16.INT 21H / IRET

      Intel8086/8088指令系统中用于支持中断调用的指令为INT n,返回中断的指令时IRET。此外,CLI用于清除中断标志,STI用于设置中断标志。

                                                   ----《书》P173

      INT 21H的执行过程:

    • SP ← SP - 2
    • SS:[SP] ← PSW
    • SP ← SP - 2
    • SS:[SP] ← INT N 下一条指令的CS
    • SP ← SP - 2
    • SS:[SP] ← INT N 下一条指令的IP
    • IP ← [0000 : N*4]
    • CS ← [0000 : N*4+2]

      IRET的执行过程:

    • IP ← SS:[SP]
    • SP ← SP + 2
    • CS ← SS:[SP]
    • SP ← SP + 2
    • PSW ← SS:[SP]
    • SP ← SP + 2

    4-2 图解移位指令

    4-3 指出目的寄存器中的内容

      已知:DS = 2100H,BX = 0100H,SI = 0002H;内存中:[21100H] = 12H,[21101H] = 34H,[21102H] = 56H,[21103H] = 78H。

    • MOV AX,[101H]  ;直接寻址,默认段DS。AX的结果为3456H ?在DOS下,汇编后变成了MOV AX,101这种立即寻址形式,使得AX最终的结果为0101H
    • MOV AX,WORD PTR [BX+2] ;寄存器相对寻址。AX结果为7856H
    • MOV AL,BYTE PTR [BX][SI+1] ;基址变址寻址。AL结果为78H
    • MOV AX,100H [SI] ;寄存器相对寻址。AX结果为7856H(注意取出的为一个字!而且是小端存储!低字节在高位!

    4-4 指出CS与IP的值

       已知:DS = 2100H,BX = 0101H,CS = 1900H;内存中:[21101H] = 0C7H,[21102H] = 0FFH,[21103H] = 00H,[21104H] = 0F0H。

    • JMP BX  ;CS = 1900H,IP=0101H
    • JMP [BX]        ;CS = 1900H,IP=0FFC7H           
    • JMP WORD PTR [BX+1]          ;CS = 1900H,IP=00FFH   (注意小端存储,低字节在低地址)
    • JMP DWORD PTR [BX]            ;CS = 0F000H,IP=0FFC7H  (取双字分别取得的是段地址与偏移值)

    4-5 根据要求画内存示意图

      1.定义MYSEG数据段,其中有S1,内容'ABCD'以00H结尾;S2是能用AH=9,INT 21H显示的字符串;S3为10x10的二维字数组。L1为S1+S2+S3的长度。

      则可以画出该数据段内存示意图如下:

      

      其中一个英文字母占据1字节,即一个内存单元;显示字符串必须以'$'结尾。常量定义形式应为 L1 EQU $-S1。注意,字数组一个字占两个字节。故L1的值为210

      2.书P96第2题的数据段可以定义如下:

    1 DATA SEGMENT PARA
    2     X1 DB 'Display string',0DH,0AH,'$'
    3     X2 DB 32
    4     X3 DW 40H
    5     X4 DD A000H,0120H
    6     X5 DW 10 DUP(8 DUP(0))
    7     X6 EQU $-X1
    8 DATA ENDS

    4-6 综合练习题

      【题签】有数据段定义如下:

    1 DATA1 SEGMENT PARA
    2     X1 DB 20H,?,'A'
    3     X2 DW 2 DUP(1,2DUP(1,?))
    4     X3 DD 12345678H
    5     LEN EQU $-X2
    6 DATA1 ENDS

      (1)画出内存图

      (2)执行MOV AX,X3+1后,AX为?

      (3)执行MOV CX,LEN后,CX为?

      【解】

      (1)内存图如下:

      

      说明:对于字和双字的定义,低字节在低位,因此如对于DW 1234H来说,在内存中由低地址到高地址依次为34H、12H;对于DD 12345678H而言,在内存中由低地址到高地址依次为78H、56H、34H、12H。而对于数组的定义而言,如DUP,则是按照其定义先后顺序在内存中由低到高排列的。必须注意的是:由于前面定义的是字DW,所以DUP中的每一个数值都占据两个内存单元,即1个字的空间,这在画内存图时必须要注意!

      (2)AX = 3456H这道题这里有点小bug,编译后会报告1个warning,更好的改进是使用MOV AX,WORD PTR X3+1

        这道题可以改进成一个更有意思的考法MOV AX,WORD PTR X3+2

        这样以来就要联系(1)中画的内存图了。内存中高地址存放的是数据中的高字节。因此结果应该是AX=1234H

      (3)CX = 18H(可以表示为16进制,一定注意数组定义DW DUP的问题!这会对LEN的计算产生影响)

    5 编程题

    5-1 加法

      计算Z=X+Y。其中X,Y为16位数,Z为32位数。

    1 XOR     DX,DX
    2 MOV     AX,X
    3 ADD     AX,Y
    4 ADC     DX,0
    5 MOV     WORD PTR Z+2,DX
    6 MOV     WORD PTR Z,AX

      这里引入一个技巧:为了操作32位数,我们需要借用DX:AX进行操作,我们一个一个地计算这两个寄存器中的数值,低位产生的进位补到DX中去,使用ADC指令。最后为内存中的Z使用WORD PTR进行赋值即可。

    5-2 右移

      将32位X右移4位。

     1 MOV     AX,WORD PTR X
     2 MOV     DX,WORD PTR X+2
     3 SHR     DX,1
     4 RCR     AX,1
     5 SHR     DX,1
     6 RCR     AX,1
     7 SHR     DX,1
     8 RCR     AX,1
     9 SHR     DX,1
    10 RCR     AX,1

      必须要注意的是这里必须使用SHR与RCR指令配合4次,每次移动1位进行使用,这是由于,CF只能存放1位数字

    5-3 乘法

      用移位及加法指令,将32位数X计算X = X * 10。

     1 MOV     AX,WORD PTR X
     2 MOV     DX,WORD PTR X+2
     3 SHL     AX,1
     4 RCL     DX,1
     5 MOV     BX,AX
     6 MOV     CX,DX
     7 SHL     AX,1
     8 RCL     DX,1
     9 SHL     AX,1
    10 RCL     DX,1
    11 ADD     AX,BX
    12 ADC     DX,CX
    13 MOV     WORD PTR X,AX
    14 MOV     WORD PTR X+2,DX

      这里用到的技巧是将X*10分解成X*2 + X*8来计算,也就是将X左移1位保存下来再左移2位加上刚才保存的值即可。

    5-4 打印

      将内存中16位X显示为十六进制ASCII码。

     1     MOV     BX,X
     2     MOV     CX,4
     3 LP:
     4     PUSH     CX
     5     MOV     CL,4
     6     ROL     BX,CL
     7     MOV     AL,BL
     8     AND     AL,0FH
     9     ADD     AL,30H
    10     CMP     AL,39H
    11     JBE     DISP
    12     ADD     AL,7
    13 
    14 DISP:
    15     MOV     DL,AL
    16     MOV     AH,2
    17     INT     21H
    18     POP     CX
    19     LOOP     LP

      注意以下几点技巧:

    • 16位数字X需要输出4位数字,因此设置CX的值为4作为外层循环次数
    • 输出每次对X的值进行循环左移4位(不带CF)的ROL指令,这样每次BL的低4位即为当前要输出的值
    • 由于又用到了CL,因此外层循环的CX需要在第4行处压栈处理
    • 由于每次输出只有4位,而最少取出AL为8位,因此需要使用第8行AND AL,0FH来屏蔽AL的高4位
    • 需要注意的是,在16进制中超过9的数字变成了A,由于ASCII码中‘9’与‘A’之间相差8,因此需要判断是否需要给AL增加相应值,使用的是ADD AL,7实现
    • 输出单个字符的中断调用为2号中断调用

    6 写在最后

      熊老师的《x86汇编语言》这门课程是本学期选的最成功的一门课程,熊老师对学生也十分认真负责。这也再次印证了那个真理,那就是只有实践,才能真正把理论中的内容理解、消化。

      从最开始的连课都听不懂、程序写不出、编程毫无头绪,到后来经历了几次作业的历练后,思路渐渐清晰,我不得不十分感谢熊老师的严格要求。

      汇编是一种十分贴近计算机底层的语言,它深刻的揭示了程序运行的过程以及内存的使用和分配机制,在本科阶段,有汇编编程的锻炼经历,我认为是十分有必要的。

      最后,在编写这篇笔记的过程中,还要特别感谢小马哥给我提出的宝贵的修改意见!

      明天就要期末考试了。真心的希望先先能够发挥高水平,取得好成绩。与各位共勉!

  • 相关阅读:
    html 一号店静态页面
    多线程
    TCP通信
    MySQL连接查询
    Mysql数据库 DDL 数据定义语言
    MySQL数据库 DML 数据操作语言
    java字符流
    java File类
    java变量
    JDK、JRE、JVM的关系
  • 原文地址:https://www.cnblogs.com/chrischen98/p/10836078.html
Copyright © 2011-2022 走看看