5.4 汇编语言:算数运算指令集

lyshark
全栈领域优质创作者
博客专家认证
2023-11-17 09:45:11

算术运算指令集是计算机中的一组基本操作,用于对数字执行常见的算术运算操作。这些指令都是计算机中非常基础的运算指令,可以用于实现所有常见的算术运算操作,并可以通过组合使用实现更加复杂的数学运算。在实际编程中,程序员可以根据具体需求选择合适的运算指令,实现程序中的算术运算操作。

4.1 MOV/INC/DEC/XCHG

MOV/INC/DEC 指令是汇编语言中的三种基本指令,用于在寄存器和内存中进行数据传输和操作。通过这些基本的指令,我们可以完成许多基础的汇编操作,并通过组合使用这些指令,实现更加复杂的功能。

  • MOV指令:MOV指令用于将数据从一个位置复制到另一个位置
  • INC指令:INC指令用于将一个寄存器或内存单元中的值加1
  • DEC指令:DEC指令用于将一个寄存器或内存单元中的值减1
  • XCHG指令:XCHG指令用于将一个寄存器与另一个寄存器之间进行数据交换

MOV指令: 该指令从源操作数向目标操作数之间复制数据,两个操作数必须尺寸一致,目的操作数不能是CS/EIP/IP等.

00A41000 | B8 24100000        | mov eax,1024                        | EAX = 1024
00A41005 | 8BD8               | mov ebx,eax                         | EBX = 1024
00A41007 | 66:B9 0010         | mov cx,1000                         | CX = 1000

MOVZX指令: 零扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值零扩展(zero-extend)至16位或者32位,该指令适用于无符号整数,其基本格式如下:

01301000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
01301004 | 0FB7C3             | movzx eax,bx                        | EAX = 0000A69B
01301007 | 0FB6D3             | movzx edx,bl                        | EDX = 0000009B
0130100A | 66:0FB6CB          | movzx cx,bl                         | CX = 009B

MOVSX指令: 符号扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值符号扩展(sign-extend)至16位或者是32位,该指令只能用于有符号整数,其基本格式如下:

00FD1000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
00FD1004 | 0FBFC3             | movsx eax,bx                        | EAX = FFFFA69B
00FD1007 | 0FBED3             | movsx edx,bl                        | EDX = FFFFFF0B
00FD100A | 66:0FBECB          | movsx cx,bl                         | CX = FF9B

XCHG指令: 数据交换指令,该指令用于交换两个操作数中的内容,但该指令不接受立即数操作数.

00D71000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00D71005 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
00D7100A | 93                 | xchg ebx,eax                        | EAX = 2000h;EBX = 1000h

INC/DEC指令: 数据递增与递减,INC指令用于对寄存器或内存数据的递增,DEC指令用于对寄存器或内存数据递减.

00881000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00881005 | 40                 | inc eax                             | EAX = 1001h
00881006 | 40                 | inc eax                             | EAX = 1002h
00881007 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
0088100C | 4B                 | dec ebx                             | EBX = 1FFFF
0088100D | 4B                 | dec ebx                             | EBX = 1FFFE
0088100E | 4B                 | dec ebx                             | EBX = 1FFFD

4.2 ADD/SUB

ADD和SUB是计算机汇编语言中的算术运算指令,分别用于实现加法运算和减法运算。ADD指令用于将两个操作数相加,并将结果存放到目的操作数中;SUB指令用于将两个操作数相减,并将结果存放到目的操作数中。目的操作数可以是寄存器或存储器单元,而源操作数可以是立即数、寄存器或存储器单元。

使用ADD和SUB指令,我们可以在寄存器和存储器中进行简单的加减法运算,实现各种基础的运算操作。这些指令是汇编语言编程中非常基础的操作,程序员可以通过组合使用这些指令,实现更加复杂的算术运算操作。

将将同尺寸的源操作数和目的操作数相加,且不改变原操作数,相加后的结果存入目的操作数中.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  MyList DWORD 10h,20h,30h,40h

.code
  main PROC
    ; 将eax于ebx 两数相加: 将相加后的结果放入eax
    mov eax,1024
    mov ebx,2048
    add eax,ebx
    
    ; 同样两数相减,将结果放到eax中
    mov eax,1024
    sub eax,512
    
    ; 针对数组的相加同样可以
    mov esi,offset MyList    ; 获取到首地址
    mov eax,0
    mov ebx,0
    
    mov eax,dword ptr ds:[esi]            ; 找到第一个元素
    mov ebx,dword ptr ds:[esi + 1 * 4]    ; 找到第二个元素
    add eax,ebx                           ; 相加操作
    
    invoke ExitProcess,0
  main ENDP
END main

4.3 NEG/NOT

NEG和NOT指令是计算机汇编语言中的逻辑运算指令,分别用于对操作数进行取反和按位取反操作。NEG指令可以将一个操作数的值取反(即变为相反数),NOT指令可以将一个操作数的二进位取反(0变为1,1变为0)。

这些指令能够增强程序的处理能力和灵活性,可以完成各种逻辑运算,包括对数值和位运算的计算。在一些特定的场景中,NEG和NOT指令非常有用。

该指令通过将数字转换为对应的补码而求出其值的相反数,结合上面的加法与减法案例,我们来模拟编译器处理特定语句的写法Rval = -Xvar + (Yvar - Zvar),而使用NOT指令则是对二进制位取反.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval SDWORD ?
  Xval SDWORD 26
  Yval SDWORD 30
  Zval SDWORD 40
.code
  main PROC
    ; 写出: Rval = -Xvar + (Yvar - Zvar) 汇编格式
    ; 首先将Xval的值通过neg取反
    mov eax,dword ptr ds:[Xval]
    neg eax
    
    ; 然后将Yval与Zval相减后复制到Yval
    mov ebx,dword ptr ds:[Yval]
    sub ebx,dword ptr ds:[Zval]
    
    ;最后将两个子项相加后放入到Rval中
    add eax,ebx
    mov dword ptr ds:[Rval],eax
    
    ; 2.写出: Rval = (Xval+Yval) - (Yval+Zval)
    mov eax,dword ptr ds:[Xval]
    add eax,dword ptr ds:[Yval]
    mov dword ptr ds:[Rval],eax
    
    mov ebx,dword ptr ds:[Yval]
    add ebx,dword ptr ds:[Zval]
    sub dword ptr ds:[Rval],ebx

    ; not 二进制取反
    xor eax,eax
    mov al,11110000b
    not al              ; al = 00001111b
    
    invoke ExitProcess,0
  main ENDP
END main

4.4 AND/OR/XOR

AND、OR、和XOR指令是计算机汇编语言中的布尔运算指令,用于对二进制数进行逻辑运算。AND指令可以将两个操作数的二进制数分别按位进行“与”(and)操作,OR指令可以将两个操作数的二进制数分别按位进行“或”(or)操作,XOR指令可以将两个操作数的二进制数分别按位进行“异或”(xor)操作。使用这些指令,我们可以在汇编语言程序中进行各种布尔运算,实现各种逻辑控制和计算。

这些指令可以操作的目的操作数包括寄存器和存储器单元,而源操作数可以是立即数、寄存器或存储器单元。这些指令也是汇编语言编程中非常基础和常用的操作。

AND指令是对数据进行与运算,OR指令则是对数据进行或运算,XOR则是异或,这三个指令都是将操作值保存在目的操作数中,需要注意的是这些运算符都是针对二进制数进行操作的.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  String BYTE "hello LYSHARK",0dh,0h
  
.code
  ToString proc
    mov ecx,lengthof String
    mov esi,offset String
  s:  ;and byte ptr [esi],11011111b  ; 清除第五位,小写变大写
    or byte ptr [esi],00100000b    ; 设置第五位,大写变小写
    inc esi
    loop s
    ret
  ToString endp

  main PROC
    ; and 逻辑与运算
    xor eax,eax
    mov al,00111011b
    and al,00001111b  ; 运算后去除al中的0
    
    mov eax,00401024h
    and eax,0ffh         ; eax = 00000024h
    
    mov eax,00401024h
    and eax,0ffffh       ; eax = 00001024h
    
    mov eax,00401024h
    and eax,0ffff0000h   ; eax = 00400000h
    
    ; or 逻辑或运算
    xor edx,edx
    mov dl,5              ; 二进制值
    or dl,30h             ; 转为有ASCII码
    
    ; xor 异或运算
    mov eax,0ffh
    xor eax,0ffh          ; eax = 0
    mov eax,4
    xor eax,5             ; eax = 1

    mov eax,0401000h
    xor eax,0400000h      ; eax = 00001000h
    mov eax,0401000h
    xor eax,01000h        ; eax = 00400000h

    ; 异或可用于检查标志位
    xor eax,eax
    mov eax,00001111h
    xor eax,0          ; 检查基偶标志
    
    mov eax,00100101h
    xor eax,0          ; 影响PF标志
    
    call ToString

    invoke ExitProcess,0
  main ENDP
END main

4.5 SHL/SHR

SHL/SHR指令是计算机汇编语言中的逻辑移位指令,用于对二进制数字节或字进行逻辑位移操作。其中,SHL(shift left)指令用于将操作数左移指定的位数,而SHR(shift right)指令则用于将操作数右移指定的位数。这些指令可以用于实现各种算法和数据处理操作。

SHL常用于对目标操作数执行逻辑左移(无符号数)操作,其左移后最低位以0填充,而移动出去的最高位则会送入CF(进位标志)中,而SHR则相反,对目标操作数执行逻辑右移(无符号数)操作,移出的数据位用0代替,最低位被复制到CF(进位标志)中,原来的进位标志位丢失.

Intel处理器中定义,执行移位的源操作数的范围必须在0-255之间,在任何处理器上都可以使用CL寄存器存放移位位数,例如在下面的指令中,AL寄存器被左移一位,最高位被复制到了进位标志中,最低位被清零:

01251006 | B3 8F                | mov al,10001111b                            | AL = 10001111b
01251008 | D0E3                 | shl al,1                                    | CF = 1,AL = 00011110b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 02              | shl al,2                                    | CF = 0,AL = 00000000b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 01              | shl al,1                                    | CF = 1,AL = 00000000b

01251006 | B0 01                | mov al,10100000b                            | AL = 10100000b
01251008 | C0E0 03              | shl al,2                                    | CF = 0,AL = 10000000b

另外使用SHL指令还可以进行2的次幂的高速乘法运算,任何操作数左移动N位,就相当于该操作数乘以2的N次方,如下例子:

01311002 | B0 05                | mov al,5                                    | AL 左移动101311004 | D0E0                 | shl al,1                                    | al * 2 = 10

01311007 | B0 05                | mov al,5                                    | AL左移201311009 | C0E0 02              | shl al,2                                    | al * 4 = 20

01311007 | B0 05                | mov al,5                                    | AL左移301311009 | C0E0 03              | shl al,3                                    | al * 8 = 40

下面是一个左移计算的案例,我们通过汇编来计算Rval = ((Xval + Yval) - (Yval + Zval)) * 8的结果.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval SDWORD ?
  Xval SDWORD 50
  Yval SDWORD 100
  Zval SDWORD 10
.code
  main PROC
    ; Rval = ((Xval + Yval) - (Yval + Zval)) * 8
    mov eax,dword ptr ds:[Xval]
    add eax,dword ptr ds:[Yval]
    
    mov ebx,dword ptr ds:[Yval]
    add ebx,dword ptr ds:[Zval]
    
    sub eax,ebx
    
    ; 乘以8也就是左移3位 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
    shl eax,3
    mov dword ptr ds:[Rval],eax
    
    invoke ExitProcess,0
  main ENDP
END main

对目标操作数执行SHR逻辑右移(无符号数)操作,移出的数据位用0代替,最低位被复制到CF进位标志中,原来的进位标志位丢失.

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,1                                    | CF = 1,AL = 01000111b

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,2                                    | CF = 1,AL = 00100011b

另外任何无符号操作数逻辑右移N位,就相当于该操作数除以2的N次方,如下例子:

01311012 | B2 20                | mov dl,20                                   | DL 右移101311014 | D0EA                 | shr dl,1                                    | dl/2 = 10

01311012 | B2 20                | mov dl,20                                   | DL 右移201311014 | D0EA                 | shr dl,2                                    | dl/4 = 5

使用乘法运算的效率是非常低的,通过右移操作可以显著提高程序的计算乘法的效率,在乘数是2的次幂的情况下才可以使用,如果不是则需要拆分后继续计算,我们先来通过汇编来计算Rval = (Xval / 8) + (Yval * 16) - (Zval * 4)的结果.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval SDWORD ?
  Xval SDWORD 200
  Yval SDWORD 50
  Zval SDWORD 10
.code
  main PROC
    ; Rval = (Xval / 8) + (Yval * 16) - (Zval * 4)
    ; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
    ; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384
    
    ; 先通过右移3位,计算除法
    mov eax,dword ptr ds:[Xval]   ; Xval / 8
    shr eax,3
    
    ; 再通过左移4位和2位分别计算乘法
    mov ebx,dword ptr ds:[Yval]   ; Yval * 16
    shl ebx,4
    
    mov ecx,dword ptr ds:[Zval]   ; Zval * 4
    shl ecx,2
    
    add eax,ebx
    sub eax,ecx
    mov dword ptr ds:[Rval],eax
    
    invoke ExitProcess,0
  main ENDP
END main

上面的这种计算方式属于乘数刚好是2的次幂,如果不是2的次幂则需要拆分后计算,如下案例,为了计算无符号乘以36,可以把36分解成2的5次方和2的2次方,然后利用移位命令高效计算.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval SDWORD ?
.code
  main PROC
    ; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
    ; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384
    
    ; 计算 123 * 36
    ; 等式拆分 EAX * 36 => EAX * (32 + 4) => (EAX * 32) + (EAX * 4)
    mov eax,123
    mov ebx,eax         ; 拷贝出一份
    shl eax,5           ; 计算 (EAX * 32)
    shl ebx,2           ; 计算 (EAX * 4)
    add eax,ebx         ; 最后相加
    mov dword ptr ds:[Rval],eax
    
    ; 计算 123 * 24
    ; 等式拆分: EAX * 24 => EAX * (16 + 8) => (EAX * 16) + (EAX * 8)
    mov eax,123
    mov ebx,eax
    shl eax,4           ; 计算 (EAX * 16)
    shl ebx,3           ; 计算 (EAX * 8)
    add eax,ebx
    mov dword ptr ds:[Rval],eax
    
    ; 计算 123 * 21
    ; 等式拆分: EAX * 21 => EAX * (16 + 4 + 1) => (EAX * 16) + (EAX * 4) + (EAX * 1)
    mov eax,123
    mov ebx,eax
    mov ecx,eax         ; 得到 (EAX * 1)
    shl eax,4           ; 计算 (EAX * 16)
    shl ebx,2           ; 计算 (EAX * 4)
    add eax,ebx
    add eax,ecx
    mov dword ptr ds:[Rval],eax
    
    invoke ExitProcess,0
  main ENDP
END main

下面是我通过寻找一些规律,能够在不查表的情况下逆推出其左移或者是右移中乘数或除数的具体值,如果比较复杂的话还是直接查表来的容易一些,此处只是一种思考方式.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
.code
  main PROC
  ; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
  ; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384
    
    ; 乘法逆推: 28/7=4
    mov eax,7                      ; eax=7
    shl eax,2                      ; 求0x2是乘以几,乘以4
    mov dword ptr ds:[Rval],eax    ;  eax = 28 计算出: 0x2 => 28/7=4
    
    ; 乘法逆推: 96/6 = 16 => 4*4=16
    mov eax,6                      ; eax = 6
    shl eax,2                      ; 4
    shl eax,2                      ; 4
    mov dword ptr ds:[Rval],eax    ; eax = 96
    
    ; 乘法逆推: 4*4*8
    mov eax,4                      ; eax = 4
    shl eax,2                      ; 运行到此处 eax=16 通过16/4 = 4 故乘以4
    shl eax,3                      ; 运行到此处 eax  =128  通过 128/4=32 , 32/4=8 故乘以8
    mov dword ptr ds:[Rval],eax
    
    ; 除法逆推: 7/1.75 = 4
    mov eax,7                      ; eax = 7
    shr eax,2                      ; 此处乘以4
    mov dword ptr ds:[Rval],eax    ; eax = 1.75 => 7/1.75=4
    
    invoke ExitProcess,0
  main ENDP
END main

4.6 SAL/SAR

SAL和SAR指令是计算机汇编语言中的算数移位指令,它们和逻辑移位指令相似,都是对二进制数字节或字进行位移操作。但是SAL和SAR指令可以用于进行带符号的算术操作,实现各种算法和数据处理操作。通过使用SAL和SAR指令,我们可以在汇编语言程序中对带符号的二进制数字节或字进行算术位移操作,实现各种算法和数据处理操作。需要注意的是,在算术位移操作中,符号位需要保持不变。这些指令也是汇编语言编程中非常基础和常用的操作。

SAL指令与SHL指令等价,SAR指令可以对有符号数进行快速除以2的次幂操作,也可以将一个AX寄存器中的值进行扩展,扩展成EAX.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  main PROC
    ; SAR => 算数右移
    mov al,0f0h         ; AL = 11110000b (-16)
    sar al,1            ; AL = 11111000b (-8)
    
    ; SAR => 有符号除法 计算-128的,2的3次方
    ; 2次方 => -32 3次方 => -16 4次方 => -8 5次方 => -4
    mov eax,-128        ; AL = 10000000b
    sar eax,3           ; AL = 11110000b EAX = -16
    
    ; SAR => 符号扩展AX扩展到EAX
    ; 先左移EAX 16位,然后算术右移EAX 16位
    mov ax,-128         ; EAX = ????FF80h
    shl eax,16          ; EAX = FF800000h
    sar eax,16          ; EAX = FFFFFF80h
    
    invoke ExitProcess,0
  main ENDP
END main

4.7 ROL/ROR

ROL/ROR指令是计算机汇编语言中的循环移位指令,它们可以将某个二进制数字进行指定位数的移位,并将移出的位重新放置到高位或低位。ROL指令向左循环移位,ROR指令向右循环移位。通过使用ROL和ROR指令,我们可以在汇编语言程序中进行位运算操作,实现各种算法和数据处理操作。这些指令也是汇编语言编程中非常常用的指令。

ROL位循环左移,其左移1位后会把最高位同时复制到进位标志位和最低位中,而ROR则是循环右移,其右移1位后,把最低位同时复制到进位标志位和最高位中.

循环移位和普通移位不同之处在于前者并不会丢失任何数据位,从一端走的数据位会从另一端出现,如循环左移会将高位复制到低位中,循环右移则将低位复制到高位中,但需要注意不论是左移/右移,都是对二进制格式进行操作的.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  main PROC
    ; ROL => 循环左移
    mov al,40h        ; AL = 01000000b
    rol al,1          ; AL = 10000000b , CF = 0
    rol al,1          ; AL = 00000001b , CF = 1
    rol al,1          ; AL = 00000010b , CF = 0
    
    mov al,00100000b
    rol al,3          ; AL = 00000001b , CF = 1
    
    ; ROR => 循环右移
    mov al,01h        ; AL = 00000001b
    ror al,1          ; AL = 10000000b , CF = 1
    ror al,1          ; AL = 01000000b , CF = 0
    
    mov al,00000100b
    ror al,3          ; AL = 10000000b , CF = 1
    
    ; ROL => 循环左移,交换位组
    ; 交换 高半部分(位4-7) 低半部分(位0-3)
    mov al,26h
    rol al,4          ; rol 与 ror 结果一致
    ror al,4

    invoke ExitProcess,0
  main ENDP
END main

4.8 RCL/RCR

RCL/RCR指令是计算机汇编语言中的标志位移位指令,它们可以将某个二进制数字进行指定位数的移位,同时会将操作数的最高位和进位标志位呈现为下一轮移位的输入。RCL指令向左移位,RCR指令向右移位。通过使用RCL和RCR指令,我们可以在汇编语言程序中进行标志位运算操作,实现各种算法和数据处理操作。这些指令也是汇编语言编程中非常常用的指令。

RCL指令在每位左移1位后,把CF进位标志复制到最低有效位中,最高有效位复制到进位标志中,RCR则相反,右移后把CF进位标志复制到最高有效位中,并把最低有效位复制到进位标志中.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  main PROC
    ; RCL 左移
    clc            ; 将CF进位标志置0
    mov bl,88h     ; CF = 0 , BL = 10001000b
    rcl bl,1       ; CF = 1 , BL = 00010000b
    rcl bl,1       ; CF = 0 , BL = 00100001b
    
    ; RCR 右移
    stc            ; 将CF进位标志置1
    mov ah,10h     ; CF = 1 , ah = 00010000h
    rcr ah,1       ; CF = 1 , ah = 10001000h
    
    invoke ExitProcess,0
  main ENDP
END main

有时候我们需要让一个数组中的元素整体进行左移或者右移操作,当然可以循环,此处我们通过直接寻址来实现.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval SDWORD ?
  ArraySize = 3
  Array DWORD ArraySize DUP(99999999h)
  ByteArray BYTE 81h,20h,33h
  WordArray WORD 810dh,0c064h,93abh
.code
  main PROC
    ; 多双字同时右移
    mov esi,0
    shr Array[esi + 8],1     ; 高双字
    rcr Array[esi + 4],1     ; 中间双字
    rcr Array[esi],1         ; 低双字
    
    ; 多双字同时左移
    mov esi,0
    shl Array[esi+8],1
    rcl Array[esi+4],1
    rcl Array[esi],1
    
    ; 让数组整体右移 (从高字节到低字节)
    shr [ByteArray + 2],1
    rcr [ByteArray + 1],1
    rcr [ByteArray],1
    
    ; 让数组整体左移 (从低字节到高字节)
    shl [ByteArray],1
    rcl [ByteArray + 2],1
    rcl [ByteArray + 4],1

    invoke ExitProcess,0
  main ENDP
END main

4.9 MUL/IMUL

MUL指令和IMUL指令是计算机汇编语言中用于进行乘法运算的指令,它们可以将两个操作数相乘,获取最终的计算结果。通过使用MUL和IMUL指令,我们可以在汇编语言程序中进行乘法运算操作,实现各种算法和数据处理操作。需要注意的是,MUL指令和IMUL指令都会改变标志寄存器中的CF和OF标志位,因此在使用这些指令时要注意处理这些标志位。

MUL/IMUL分别可进行有符号与无符号的乘法运算,通常该指令都接受寄存器操作数,也接受内存操作数,但是不接受立即数,且乘数与被乘数大小必须相同,乘基尺寸是乘数/被乘数的两倍.

MUL乘法指令有三种格式:

  • 第一种将8位操作数与AL相乘,结果放入AX中
  • 第二种将16位操作数与AX相乘,结果的高16位放入DX低16位放入AX
  • 第三种将32位操作数与EAX相乘,结果高32位放入EDX第32位放入EAX中.
  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
  VarWordA WORD 2000h
  VarWordB WORD 0100h
.code
  main PROC
    ; 执行8位乘法运算 al = al*bl
    mov al,5h
    mov bl,10h
    mul bl
    mov byte ptr ds:[Rval],al
    
    ; 执行16位乘法运算
    xor eax,eax
    xor edx,edx
    mov ax,word ptr ds:[VarWordA]
    mul word ptr ds:[VarWordB]
    mov word ptr ds:[Rval],ax     ; 低半部分
    mov word ptr ds:[Rval],dx     ; 高半部分  DX:AX = 00000020h
    
    ; 执行32位乘法运算
    xor eax,eax
    xor edx,edx
    mov eax,12345h
    mov ebx,1000h
    mul ebx
    mov dword ptr ds:[Rval],eax   ; 低半部分
    mov dword ptr ds:[Rval],edx   ; 高半部分 EDX:EAX = 0000000012345000h
    
    invoke ExitProcess,0
  main ENDP
END main

IMUL指令主要用于执行有符号整数的乘法运算,并保留乘积的符号位,且在32位汇编中有三种格式:但操作数格式,双操作数格式,三操作数格式,首先是单操作数模式,该模式把乘积存储在累加器AX中,或者将符号位放入EDX将结果放入EAX中.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  EDX_Rval DWORD ?
  EAX_Rval DWORD ?
.code
  main PROC
    
    ; 执行8位乘法运算: 48*8 得到的积+192 溢出
    xor eax,eax
    mov al,48
    mov bl,4
    imul bl          ; CF 进位 = 1 OF 溢出 = 1
    
    xor eax,eax
    mov al,-4
    mov bl,4
    imul bl           ; AX=FFF0h OF=0
    
    ; 执行16位乘法运算: 48*4 得到的积 +192
    xor eax,eax
    mov ax,48
    mov bx,4
    imul bx
    mov word ptr ds:[EDX_Rval],dx
    mov word ptr ds:[EAX_Rval],ax  ; DX:AX = 000000C0h OF=0
    
    ; 执行32位乘法运算: +4823424 *(-423)
    xor eax,eax
    xor ebx,ebx
    mov eax,+4823424
    mov ebx,-423
    imul ebx
    mov dword ptr ds:[EDX_Rval],edx   ; EDX为符号位
    mov dword ptr ds:[EAX_Rval],eax   ; EDX:EAX = FFFFFFFF86635D80h OF=0
    
    invoke ExitProcess,0
  main ENDP
END main

接着就是乘法语句的双操作数与三操作数模式了,在双操作数中第一个操作数必须是寄存器,第二个操作数可以是寄存器或内存等,在三操作数模式中,把乘积存储在第一个操作数中,其他与双操作数类似.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  wordp   SWORD 4
  dwordp  SDWORD 4
  Rval DWORD ?
.code
  main PROC
  
    ; 双操作数乘法运算
    xor eax,eax
    xor ebx,ebx
    
    mov ax,-16                   ; ax = -16
    mov bx,2                     ; bx = 2
    imul bx,ax                   ; bx = bx * ax
    imul bx,2                    ; bx = bx * 2
    imul bx,word ptr ds:[wordp]  ; bx = bx * wordp
    mov word ptr ds:[Rval],bx    ; 放入变量中保存
    
    ; 三操作数乘法运算
    xor eax,eax
    xor ebx,ebx
    
    imul bx,wordp,-16           ; bx = wordp * -16
    imul ebx,dwordp,-16         ; ebx = dwordp * -16
    imul ebx,dwordp,-20         ; ebx = dwordp * -20
    mov dword ptr ds:[Rval],ebx ; 放入变量中
    
    invoke ExitProcess,0
  main ENDP
END main

到此为止我们学会了通过移位的方式实现快速乘法运算,也学过使用MUL指令进行乘法计算,接下来我们可以编写两个案例分别通过移位和MUL计算EAX与36相乘的结果,看看哪一个效率更高一些.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  shl_proc proc
    mov ecx,10
  s:
    push eax
    mov ebx,eax
    shl eax,5
    shl eax,2
    add eax,ebx
    pop eax
    loop s
    ret
  shl_proc endp
  
  mul_proc proc
    mov ecx,10
  s:
    push eax
    mov ebx,36
    mul ebx
    pop eax
    loop s
    ret
  mul_proc endp

  main PROC
    mov eax,10
    call shl_proc
    mov eax,10
    call mul_proc
  
    invoke ExitProcess,0
  main ENDP
END main

4.10 DIV/IDIV

DIV指令和IDIV指令是计算机汇编语言中用于进行除法运算的指令,DIV用于无符号整数的除法运算,IDIV用于带符号整数的除法运算。这两条指令均使用32位被除数,得到32位的商和余数。如果被除数是16位的,则由DX:AX提供了32位的被除数。或者如果被除数是8位的,则AL提供了8位的被除数。通过使用DIV和IDIV指令,我们可以在汇编语言程序中进行除法运算操作,实现各种算法和数据处理操作。

DIV是无符号除法指令,该指令支持8/16/32位无符号整数的除法运算,指令中唯的寄存器或内存操作数是除数,IDIV则是有符号除法指令,该指令与无符号除法几乎一致,唯一的不同在于有符号除法在进行相除操作时需要符号扩展.

首先我们先来学习一下DIV无符号除法运算的使用技巧,分别演示8/16/32位无符号除法的使用方式.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
  DivIdend QWORD 0000000800300020h
  DivIsor DWORD 00000100h
  Div_Eax DWORD ?
  Div_Edx DWORD ?
.code
  main PROC
    
    ; 执行8位除法: 83/2 商al是41h 余数ah是1
    xor eax,eax
    mov ax,0083h                  ; 被除数
    mov bl,2                      ; 除数
    div bl                        ; ax = ax / bl
    mov byte ptr ds:[Rval],ah     ; ah = 01h
    mov byte ptr ds:[Rval],al     ; al = 41h
    
    ; 执行16位除法: 8003h/100h 商是80h 余数是3
    xor edx,edx                   ; 清除edx寄存器
    mov ax,8003h                  ; 被除数
    mov cx,100h                   ; 除数
    div cx                        ; ax = ax / cx
    mov word ptr ds:[Rval],ax     ; ax = 0080h
    mov word ptr ds:[Rval],dx     ; dx = 0003h
     
    ; 执行32位除法
    mov edx,dword ptr DivIdend + 4  ; 高双字
    mov eax,dword ptr DivIdend      ; 低双字
    div DivIsor                     ; 与被除数相除
    
    mov dword ptr ds:[Div_Eax],eax  ; eax = 08003000h
    mov dword ptr ds:[Div_Edx],edx  ; edx = 00000020h

    invoke ExitProcess,0
  main ENDP
END main

针对IDIV有符号数的除法运算,需要对被除数进行除法操作之前,对其进行符号扩展,汇编中有三条扩展命令.

  • CBW指令:将字节符号扩展至字,扩展AL的符号位至AH中,保留了数字的符号.
  • CWD指令:将字符号扩展至双字,指令扩展AX的符号位至DX中.
  • CDQ指令:双字符号扩展至八字节,指令扩展EAX的符号位至EDX中.

当使用符号扩展指令扩展后,寄存器就可以被用来计算有符号除法了,代码如下所示:

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  ByteVal SBYTE -48
  WordVal SWORD -5000
  DworeVal SDWORD +50000
  Rval DWORD ?
.code
  main PROC
    ; 字节扩展至字 -48/5
    xor eax,eax
    mov al,byte ptr ds:[ByteVal]  ; al = D0 取出 -48
    cbw                           ; ax = FFD0 将al扩展至ax
    mov bl,5                      ; bl = 05
    idiv bl                       ; ax=ax/bl
    mov word ptr ds:[Rval],ax     ; 结果: ax = FDF7
    
    mov byte ptr ds:[Rval],al     ; AL保存商 -9
    mov byte ptr ds:[Rval],ah     ; AH保存余数 -3
    
    ; 字扩展至双字
    xor eax,eax
    mov ax,word ptr ds:[WordVal]  ; 除数
    cwd                           ; 扩展至双字(扩展AX至DX)
    mov bx,+256                   ; 被除数
    idiv bx                       ; ax = ax/bx
    mov word ptr ds:[Rval],ax     ; 商AX=-19
    mov word ptr ds:[Rval],dx     ; 余数DX=-136
    
    ; 双字符号扩展至八字节
    mov eax,dword ptr ds:[DworeVal]
    cdq                             ; 扩展EAX到EDX
    mov ebx,-256
    idiv ebx
    mov dword ptr ds:[Rval],eax     ; 商 EAX = -195
    mov dword ptr ds:[Rval],edx     ; 余数 EDX = +80

    invoke ExitProcess,0
  main ENDP
END main

学习了前面的这几种计算方式以后,我们就可以将其总结起来实现计算复杂的表达式了,先来三个练手的.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
  var1 DWORD 10
  var2 DWORD 15
  var3 DWORD 20
  var4 DWORD 25
.code
  main PROC
    ; 实现计算: var4 = (var1 * 5) / (var2 - 3)
    mov eax,dword ptr ds:[var1] ; 先计算左边 (var1 * 5)
    mov ebx,5
    mul dword ptr ds:[ebx]      ; EDX:EAX 乘积
    
    mov ebx,dword ptr ds:[var2] ; 计算右边 (var2 - 3)
    sub ebx,3
    
    div dword ptr ds:[ebx]       ; 计算两者的除法
    mov dword ptr ds:[var4],eax  ; 最后赋值操作
    
    ; 实现计算: var4 = (var1+var2) * var3
    mov eax,dword ptr ds:[var1]
    add eax,dword ptr ds:[var2]   ; 计算前半部分
    
    mov ebx,dword ptr ds:[var3]   ; 计算后半部分
    mul ebx
    mov dword ptr ds:[var4],eax   ; 最后赋值操作
    
    ; 实现计算: var1 = (var2/var3) * (var1+var2)
    mov eax,var2
    cdq                           ; 扩展为EDX:EAX
    idiv dword ptr ds:[var3]      ; 计算除法,存入eax
    
    mov ebx,dword ptr ds:[var1]   ; 计算加法
    add ebx,dword ptr ds:[var2]
    
    imul dword ptr ds:[ebx]       ; 最后计算乘法
    mov dword ptr ds:[var1],eax   ; 在eax中取值

    invoke ExitProcess,0
  main ENDP
END main

最后我们来实现一个相对复杂的案例,总体的复习一下,该案例计算var4 = (var1 * -5) / (-var2 % var3)返回值,我们可以从右边开始计算,并把右边的值存储到EBX中,然后把被除数符号扩展到EDX,最后使用IDIV计算除法.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
  var1 DWORD 54
  var2 DWORD 56
  var3 DWORD 5
  var4 DWORD 52
.code
  main PROC
    ; 实现计算: var4 = (var1 * -5) / (-var2 % var3)
    mov eax,dword ptr ds:[var2]
    neg eax                      ; 将var2反转
    
    cdq                          ; 将被除数符号扩展
    idiv var3                    ; 除以var3 则 EDX=余数
    mov ebx,edx                  ; 将余数给EBX
    
    mov eax,-5
    imul var1                    ; 计算 var1 * -5 结果给EAX
    
    idiv ebx                     ; eax = eax/ebx
    mov dword ptr ds:[var4],eax  ; 最后将结果给var4
    
    invoke ExitProcess,0
  main ENDP
END main

4.11 ADC/SBB

ADC和SBB是计算机汇编语言中的指令,用于完成带进位的扩展加法和扩展减法。通过使用ADC和SBB指令,我们可以在汇编语言程序中进行带进位的扩展加法和扩展减法的操作,实现各种算法和数据处理操作。需要注意的是,在使用这些指令时需要妥善地处理进位标志位和借位标志位的值,以确保计算结果的正确性。

扩展加减法是指任意尺寸大小数字的加减法,其中ADC指令主要用户实现带进位加法,SBB指令则实现带进位减法,起作用都是将源操作数与目的操作数以及进位等相加减.

以扩展加法为例,计算两个8位整数相加(FFh+FFh)产生的16位结果将被存放在DL:AL (01feh)中,如果是计算两个32位整数相加(FFFFFFFFh+FFFFFFFFh),则会在EDX和EAX中分别存放00000001h:FFFFFFFEh这两个值,扩展SBB同理.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.data
  Rval DWORD ?
.code
  main PROC
    ; 计算8位加法操作
    xor edx,edx
    xor eax,eax
    mov al,0ffh                   ; 设置加数
    add al,0ffh                   ; al = al + 0ffh
    adc dl,0                      ; 进位加法
    mov byte ptr ds:[Rval],dl     ; 存放高位
    mov byte ptr ds:[Rval],al     ; 存放低位
    
    ; 计算32位加法操作
    xor edx,edx
    xor eax,eax
    mov eax,0ffffffffh
    add eax,0ffffffffh
    adc edx,0                      ; 带进位加法
    mov dword ptr ds:[Rval],edx    ; 存放高位
    mov dword ptr ds:[Rval],eax    ; 存放低位
    
    ; 计算32位减法操作
    xor edx,edx
    xor eax,eax
    ;mov edx,1                     ; 设置高半部分
    mov eax,0                      ; 设置低半部分
    sub eax,1                      ; eax减去1
    sbb edx,0                      ; 减去1则高半部分为0
    mov dword ptr ds:[Rval],edx    ; 存放高位
    mov dword ptr ds:[Rval],eax    ; 存放低位

    ; 计算32位减法操作 执行sbb相当于edx中的值5减去原始值1
    mov edx,5h            ; 设置 edx = 5
    mov eax,80000000h     ; eax = 80000000h
    sub eax,90000000h     ; eax - 90000000h = F0000000
    sbb edx,0             ; edx = edx - 1 = 4
    
    invoke ExitProcess,0
  main ENDP
END main

LEA 指令实现四则运算: Lea指令的使用初衷是取出某个内存的地址,但在汇编手册中可以发现其不止可以取地址同样可以实现算数运算,但这个运算与移位运算符一样只能计算2的次幂,当需要计算一个非次幂数字,则需对其进行分析与拆分,来实现对算数的计算.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
  x DWORD ?
  y DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    ; 针对加法的lea指令优化
    mov dword ptr ds:[x],5
    mov dword ptr ds:[y],3
    mov eax,dword ptr ds:[x]
    mov ebx,dword ptr ds:[y]
    
    lea eax,dword ptr ds:[eax + 3]         ; eax = edx + 3
    invoke crt_printf,addr szFmt,eax
    
    lea eax,dword ptr ds:[eax + ebx + 2]   ; eax = eax + ebx + 2
    invoke crt_printf,addr szFmt,eax
    
    ; 针对减法的lea指令优化
    mov dword ptr ds:[x],6
    mov eax,dword ptr ds:[x]
    
    lea eax,dword ptr ds:[eax - 2]         ; eax = eax - 2
    invoke crt_printf,addr szFmt,eax
    
    ; 针对乘法的lea指令优化
    mov dword ptr ds:[x],5
    mov dword ptr ds:[y],3
    
    mov eax,dword ptr ds:[x]
    xor ebx,ebx
    lea ebx,dword ptr ds:[eax * 8 + 2]     ; ebx = eax * 8 + 2
    invoke crt_printf,addr szFmt,ebx
    
    ; 如果使用lea计算乘法,则乘数必须是2/4/8
    mov eax,dword ptr ds:[y]               ; eax = 3 => 计算 15 * eax
    lea edx,dword ptr ds:[eax * 4 + eax]   ; edx = 4eax + eax => 5eax
    lea edx,dword ptr ds:[edx * 2 + edx]   ; edx = 5eax * 2 + 5eax => 15eax
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 15 = 45
    
    ; 如果计算乘法时乘数非2的次幂,则此时需要减
    mov eax,dword ptr ds:[y]               ; eax = 3 => 计算 eax * 7 + 10
    lea edx,dword ptr ds:[eax * 8]         ; edx = eax * 8
    sub edx,eax                            ; edx = edx - 1eax
    add edx,10                             ; edx = edx + 10
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 7 + 10
    
    mov eax,dword ptr ds:[y]               ; eax = 3 => 计算 eax * 3 - 7
    lea edx,dword ptr ds:[eax * 2]         ; edx = eax * 2
    add edx,eax                            ; edx = edx + eax
    sub edx,7                              ; edx = edx - 7
    invoke crt_printf,addr szFmt,edx       ; edx = eax * 3 - 7

    invoke ExitProcess,0
  main ENDP
END main

除法转换为乘法: 相比较于乘法运算,除法运算则显得略微复杂些,当计算中被除数为正数时,则可以直接使用sar(算数右移)快速计算除法,如果被除数为负数,则需要使用cdq符号扩展后,然后and edx,xxx之后才能进行相除运算.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
  x DWORD ?
  y DWORD ?
  z DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],5
    mov dword ptr ds:[y],-3
    mov dword ptr ds:[z],-10

    ; 除数为2的优化方式
    ; 被除数为正数(无需扩展): eax => 5 / 2 = 2
    mov eax,dword ptr ds:[x]   ; 被除数
    sar eax,1                  ; 算数右移
    invoke crt_printf,addr szFmt,eax
    
    ; 被除数为负数(需要扩展): eax => -3 / 2 = -1
    mov eax,dword ptr ds:[y]   ; 被除数
    cdq                        ; 符号扩展
    sub eax,edx                ; 被除数减去edx符号扩展
    sar eax,1                  ; 算数右移(向下取整完成除法)
    invoke crt_printf,addr szFmt,eax
    
    
    ; 除数为4的优化方式
    ; 被除数为正数(无需扩展): eax => 5 / 4 = 1
    mov eax,dword ptr ds:[x]
    sar eax,2
    invoke crt_printf,addr szFmt,eax
    
    ; 被除数为负数(需要扩展): eax => -10 / 4 = -2
    ; 抽取出公式来: (x + y - 1) / y
    mov eax,dword ptr ds:[z]          ; eax = x
    cdq
    and edx,3                         ; edx = y-1
    add eax,edx                       ; eax = eax + edx => x + (y - 1)
    sar eax,2                         ; 2 => y
    invoke crt_printf,addr szFmt,eax
    
    ; 除数为8的优化方式
    ; 被除数为正数(无需扩展): eax => 5 / 8 = 1
    mov eax,dword ptr ds:[x]
    sar eax,3
    invoke crt_printf,addr szFmt,eax
    
    ; 被除数为负数(需要扩展): eax => -10 / 8 = -1
    mov eax,dword ptr ds:[z]
    cdq                               ; 符号扩展edx要么为1要么为0
    and edx,7                         ; 被除数 2^n - 1
    add eax,edx                       ; 被除数与符号扩展相加
    sar eax,3                         ; 向下取整完成除法计算
    invoke crt_printf,addr szFmt,eax

    invoke ExitProcess,0
  main ENDP
END main

除法中的除数与被除数都可以分为有符号与无符号,两种计算方式均有一定差异,其差异如下所示.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
  x DWORD ?
  y DWORD ?
  z DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],5
    mov dword ptr ds:[y],10
    mov dword ptr ds:[z],-10

    ; 除数为(无符号)正2的次幂的计算过程
    mov eax,dword ptr ds:[x]
    shr eax,1                  ; eax = 5 / 2
    
    mov eax,dword ptr ds:[x]
    shr eax,2                  ; eax = 5 / 4
    
    mov eax,dword ptr ds:[x]
    shr eax,3                  ; eax = 5 / 8
    
    
    ; 被除数为(有符号)的计算过程
    mov eax,dword ptr ds:[z]
    cdq
    sub eax,edx
    sar eax,1                  ; eax = -10 / 2
    ;neg eax                    ; 将eax取反
    invoke crt_printf,addr szFmt,eax
    
    mov eax,dword ptr ds:[z]
    cdq
    and edx,3
    add eax,edx
    sar eax,2                  ; eax = -10 / 4
    ;neg eax
    invoke crt_printf,addr szFmt,eax
    
    mov eax,dword ptr ds:[z]
    cdq
    and edx,7
    add eax,edx
    sar eax,3                   ; eax = -10 / 8
    ;neg eax
    invoke crt_printf,addr szFmt,eax
    
    
    ; 除数为(有符号)负2的次幂的计算过程
    mov eax,dword ptr ds:[y]    ; y = 10
    cdq
    sub eax,edx
    sar eax,1                   ; eax = 10 / -2
    neg eax                     ; 将正数 eax翻转为负数 = -5
    invoke crt_printf,addr szFmt,eax
    
    mov eax,dword ptr ds:[y]    ; y = 10
    cdq
    and edx,3
    add eax,edx
    sar eax,2                   ; eax = 10 / -4
    neg eax                     ; eax = -2
    invoke crt_printf,addr szFmt,eax
    
    mov eax,dword ptr ds:[z]    ; z = -10 
    cdq
    and edx,7
    add eax,edx
    sar eax,3                   ; eax = -10 / -8 
    neg eax                     ; eax = 1 (负负得正)
    invoke crt_printf,addr szFmt,eax
    
    invoke ExitProcess,0
  main ENDP
END main

上方的除法运算被除数均为2的次幂,除数的范围也被限定在了2/4/8这样的范围之内,如果是计算非2的次幂该怎么写呢,如下是计算非2的次幂的计算方式,通常情况下编译器会将除法运算转换为乘法,如果需要知道除数是多少则可以使用公式2^(32+n) / M计算后得出.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
  x DWORD ?
  y DWORD ?
  z DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],5
    mov dword ptr ds:[y],10
    mov dword ptr ds:[z],-10
    
    ; 除法(有符号)非2的幂转换为乘法
    
    mov ecx,dword ptr ds:[y]      ; 被除数 ecx = 10 / 3 = 3
    mov eax,055555556h            ; eax = M值 1431655766
    imul ecx
    mov eax,edx                   ; edx = n 计算: 2^(32+n) / M
    shr eax,01fh                  ; 计算出除数为 2.9999 => 3
    add edx,eax
    invoke crt_printf,addr szFmt,edx
    
    
    mov ecx,dword ptr ds:[y]       ; ecx = 10 / 5 = 2
    mov eax,066666667h             ; 此处的M模值是编译器计算后得到的
    imul ecx
    sar edx,1                      ; 想要知道除数是多少,只需要
    mov eax,edx                    ; 2^(32 + edx) / M = 2^33 / 66666667 = 5
    shr eax,01fh
    add edx,eax
    invoke crt_printf,addr szFmt,edx
    
    
    mov ecx,dword ptr ds:[y]       ; ecx = 10 / 6 = 1
    mov eax,02AAAAAABh             ; eax = 715827883
    imul ecx
    mov eax,edx                    ; 2^(32 + edx) / M = 2^32 / 2AAAAAAB = 6
    shr eax,01fh
    add edx,eax
    invoke crt_printf,addr szFmt,edx
    
    mov ecx,dword ptr ds:[z]       ; ecx = -10 / 9 = -1
    mov eax,038E38E39h             ; eax = 954437177 
    imul ecx
    sar edx,1                      ; 2^(32 + edx) / M = 2^33 / 38E38E39 = 9
    mov ecx,edx
    shr ecx,01fh
    add edx,ecx
    invoke crt_printf,addr szFmt,edx
    
    invoke ExitProcess,0
  main ENDP
END main

上方代码中的除法计算是针对有符号数进行的,如果是针对无符号数则需要以下方式计算.

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
  x DWORD ?
  y DWORD ?
  z DWORD ?
  szFmt BYTE '计算结果: %d',0dh,0ah,0
.code
  main PROC
    mov dword ptr ds:[x],-5
    mov dword ptr ds:[y],10
    mov dword ptr ds:[z],20
    
    ; 除法(无符号)非2的次幂(正数)转换为乘法
    
    xor edx,edx
    mov ecx,dword ptr ds:[y]    ; ecx = 10
    mov eax,0AAAAAAABh          ; ecx / 3 = 3
    mul ecx
    shr edx,1
    invoke crt_printf,addr szFmt,edx
    
    ; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0CCCCCCCDh = 5
    xor edx,edx
    mov ecx,dword ptr ds:[y]    ; ecx = 10 => 计算: 10/5
    mov eax,0CCCCCCCDh          ; eax = M
    mul ecx
    shr edx,2                   ; edx= n
    invoke crt_printf,addr szFmt,edx
    
    ; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0AAAAAAABh = 6
    xor edx,edx
    mov ecx,dword ptr ds:[y]     ; ecx = 10 => 计算:10/6
    mov eax,0AAAAAAABh           ; eax = M
    mul ecx
    shr edx,2                    ; edx = n
    invoke crt_printf,addr szFmt,edx
    
    
    ;还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 038E38E39h = 9
    xor edx,edx
    mov ecx,dword ptr ds:[z]     ; ecx = 20  => 计算: 20/9
    mov eax,038E38E39h           ; eax = M
    mul ecx
    shr edx,1                    ; edx = n
    invoke crt_printf,addr szFmt,edx
    
    
    ; 除法(无符号)非2的次幂(负数)转换为乘法
    ; 还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 0AAAAAAABh = nge(3) => -3
    xor edx,edx
    mov ecx,dword ptr ds:[z]      ; ecx = 20  => 计算: 20/-3
    mov eax,0AAAAAAABh            ; eax = M
    mul ecx
    shr edx,1                     ; edx = n 
    neg edx                       ; edx=6 结果neg取反
    invoke crt_printf,addr szFmt,edx
    
    ; 还原除数: 2 ^(32 + n) / M => 2 ^ 62 / 040000001h = 4294967292
    ; 输入的时候以QWORD输入.然后再点击一下变成DWORD则可以看到表达为-4
    ; 此时直接对其NOT取反则可以得到原始的被除数的绝对值,前面加上负数即可
    xor edx,edx
    mov ecx,dword ptr ds:[y]       ; ecx = 10 => 计算: 10 / -3
    mov eax,040000001h             ; eax = M
    mul ecx
    shr edx,01eh                   ; edx = n
    invoke crt_printf,addr szFmt,edx
    
    invoke ExitProcess,0
  main ENDP
END main
...全文
21 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
本书分为三部分。第一部分是基础部分,以8086/8088为背景,以DOS和PC兼容机为软硬件平台,以MASM和TASM为汇编器,介绍汇编语言的有关概念,讲解汇编语言程序设计技术。第二部分是提高部分,以80386为背景,以新一代微处理器Pentium为目标,细致和通俗地介绍了保护方式下的有关概念,系统和详细地讲解了保护方式下的编程技术,真实和生动地展示了保护方式下的编程细节。第三部分是上机实验指导。 本书的第一部分适合初学者,可作为学习汇编语言程序设计的教材。本书的第二部分适合已基本掌握8086/8088汇编语言的程序员,可作为学习保护方式编程技术的教材或参考书,也可作为其他人员了解高档微处理器和保护方式编程技术的参考书,还可作为程序员透彻地了解Windows程序设计技术的参考书。 第一部分 基础部分 第1章 绪论 1.1 汇编语言概述 1.1.1 汇编语言 1.1.2 汇编语言的特点 1.1.3 恰当地使用汇编语言 1.2 数据的表示和类型 1.2.1 数值数据的表示 1.2.2 非数值数据的表示 1.2.3 基本数据类型 1.3 Intel系列CPU简介 1.3.1 8位微处理器 1.3.2 16位微处理器 1.3.3 32位微处理器 1.3.4 Pentium和Pentium Pro 1.4 习题 第2章 8086/8088寻址方式和指令系统 2.1 8086/8088寄存器组 2.1.1 8086/8088 CPU寄存器组 2.1.2 标志寄存器 2.2 存储器分段和地址的形成 2.2.1 存储单元的地址和内容 2.2.2 存储器的分段 2.2.3 物理地址的形成 2.2.4 段寄存器的引用 2.3 8086/8088的寻址方式 2.3.1 立即寻址方式 2.3.2 寄存器寻址方式 2.3.3 直接寻址方式 2.3.4 寄存器间接寻址方式 2.3.5 寄存器相对寻址方式 2.3.6 基址加变址寻址方式 2.3.7 相对基址加变址寻址方式 2.4 8086/8088指令系统 2.4.1 指令集说明 2.4.2 数据传送指令 2.4.3 堆栈操作指令 2.4.4 标志操作指令 2.4.5 加减运算指令 2.4.6 乘除运算指令 2.4.7 逻辑运算和移位指令 2.4.8 转移指令 2.5 习题 第3章 汇编语言及其程序设计初步 3.1 汇编语言的语句 3.1.1 语句的种类和格式 3.1.2 数值表达式 3.1.3 地址表达式 3.2 变量和标号 3.2.1 数据定义语句 3.2.2 变量和标号 3.3 常用伪指令语句和源程序组织 3.3.1 符号定义语句 3.3.2 段定义语句 3.3.3 汇编语言源程序的组织 3.4 顺序程序设计 3.4.1 顺序程序举例 3.4.2 简单查表法代码转换 3.4.3 查表法求函数值 3.5 分支程序设计 3.5.1 分支程序举例 3.5.2 利用地址表实现多向分支 3.6 循环程序设计 3.6.1 循环程序举例 3.6.2 多重循环程序举例 3.7 习题 第4章 子程序设计和DOS功能调用 4.1 子程序设计 4.1.1 过程调用和返回指令 4.1.2 过程定义语句 4.1.3 子程序举例 4.1.4 子程序说明信息 4.1.5 寄存器的保护与恢复 4.2 主程序与子程序间的参数传递 4.2.1 利用寄存器传递参数 4.2.2 利用约定存储单元传递参数 4.2.3 利用堆栈传递参数 4.2.4 利用CALL后续区传递参数 4.3 DOS功能调用及应用 4.3.1 DOS功能调用概述 4.3.2 基本I/O功能调用 4.3.3 应用举例 4.4 磁盘文件管理及应用 4.4.1 DOS磁盘文件管理功能调用 4.4.2 应用举例 4.5 子程序的递归和重入 4.5.1 递归子程序 4.5.2 可重入子程序 4.6 习题 第5章 输入输出与中断 5.1输 入和输出的基本概念 5.1.1 I/O端口地址和I/O指令 5.1.2 数据传送方式 5.1.3 存取RT/CMOS RAM 5.2 查询方式传送数据 5.2.1 查询传送方式 5.2.2 读实时钟 5.2.3 查询方式打印输出 5.3 中断 5.3.1 中断和中断传送方式 5.3.2 中断向量表 5.3.3 中断响应过程 5.3.4 外部中断 5.3.5 内部中断 5.3.6 中断优先级和中断嵌套 5.3.7 中断处理程序的设计 5.4 基本输入输出系统BIOS 5.4.1 基本输入输出系统BIOS概述 5.4.2 键盘输入 5.4.3 显示输出 5.4.4 打印输出 5.5 软中断处理程序举例 5.5.1 打印I/O程序 5.5.2 时钟显示程序 5.6 习题 第6章 简单应用程序的设计 6.1 字符串处理 6.1.1 字符串操作指令 6.1.2 重复前缀 6.1.3 字符串操作举例 6.2 十进制数算术运算调整指令及应用 6.2.1 组合BCD码的算术运算调整指令 6.2.2 未组合BCD码的算术运算调整指令 6.2.3 应用举例 6.3 DOS程序段前缀和特殊情况处理程序 6.3.1 DOS程序段前缀PSP 6.3.2 对Ctrl+C键和Ctrl+Break键的处理 6.4 TSR程序设计举例 6.4.1 驻留的时钟显示程序 6.4.2 热键激活的TSR程序 6.5 习题 第7章 高级汇编语言技术 7.1 结构和记录 7.1.1 结构 7.1.2 记录 7.2 宏 7.2.1 宏指令的定义和使用 7.2.2 宏指令的用途 7.2.3 宏指令中参数的使用 7.2.4 特殊的宏运算符 7.2.5 宏与子程序的区别 7.2.6 与宏有关的伪指令 7.2.7 宏定义的嵌套 7.3 重复汇编 7.3.1 伪指令REPT 7.3.2 伪指令IRP 7.3.3 伪指令IRPC 7.4 条件汇编 7.4.1 条件汇编伪指令 7.4.2 条件汇编与宏结合 7.5 源程序的结合 7.5.1 源程序的结合 7.5.2 宏库的使用 7.6 习题 第8章 模块化程序设计技术 8.1 段的完整定义 8.1.1 完整的段定义 8.1.2 关于堆栈段的说明 8.1.3 段组的说明和使用 8.2 段的简化定义 8.2.1 存储模型说明伪指令 8.2.2 简化的段定义伪指令 8.2.3 存储模型说明伪指令的隐含动作 8.3 模块间的通信 8.3.1 伪指令PUBLIC和伪指令EXTRN 8.3.2 模块间的转移 8.3.3 模块间的信息传递 8.4 子程序库 8.4.1 子程序库 8.4.2 建立子程序库 8.4.3 使用举例 8.5 编写供Turbo C调用的函数 8.5.1 汇编格式的编译结果 8.5.2 汇编模块应该遵守的约定 8.5.3 参数传递和寄存器保护 8.5.4 举例 8.6 习题 第二部分 提高部分 第9章 80386程序设计基础 9.1 80386寄存器 9.1.1 通用寄存器 9.1.2 段寄存器 9.1.3 指令指针和标志寄存器 9.2 80386存储器寻址 9.2.1 存储器寻址基本概念 9.2.2 灵活的存储器寻址方式 9.2.3 支持各种数据结构 9.3 80386指令集 9.3.1 数据传送指令 9.3.2 算术运算指令 9.3.3 逻辑运算和移位指令 9.3.4 控制转移指令 9.3.5 串操作指令 9.3.6 高级语言支持指令 9.3.7 条件字节设置指令 9.3.8 位操作指令 9.3.9 处理器控制指令 9.4 实方式下的程序设计 9.4.1 说明 9.4.2 实例 9.5 习题 第10章 保护方式下的80386及其编程 10.1 保护方式简述 10.1.1 存储管理机制 10.1.2 保护机制 10.2 分段管理机制 10.2.1 段定义和虚拟地址到线性地址转换 10.2.2 存储段描述符 10 2.3 全局和局部描述符表 10.2.4 段选择子 10.2.5 段描述符高速缓冲寄存器 10.3 80386控制寄存器和系统地址寄存器 10.3.1 控制寄存器 10 3.2 系统地址寄存器 10.4 实方式与保护方式切换实例 10.4.1 演示实方式和保护方式切换的实例(实例一) 10.4.2 演示32位代码段和16位代码段切换的实例(实例二) 10.5 任务状态段和控制门 10.5.1 系统段描述符 10.5.2 门描述符 10.5.3 任务状态段 10.6 控制转移 10.6.1 任务内无特权级变换的转移 10.6.2 演示任务内无特权级变换转移的实例(实例三) 10.6.3 任务内不同特权级的变换 10.6.4 演示任务内特权级变换的实例(实例四) 10.6.5 任务切换 10.6.6 演示任务切换的实例(实例五) 10.7 80386的中断和异常 10.7.1 80386的中断和异常 10.7.2 异常类型 10.7.3 中断和异常的转移方法 10.7.4 演示中断处理的实例(实例六) 10.7.5 演示异常处理的实例(实例七) 10.7.6 各种转移途径小结 10.8 操作系统类指令 10.8.1 实方式和任何特权级下可执行的指令 10.8.2 实方式及特权级0下可执行的指令 10 8.3 只能在保护方式下执行的指令 10.8.4 显示关键寄存器内容的实例(实例八) 10.8.5 特权指令 10.9 输入/输出保护 10.9.1 输入/输出保护 10.9.2 重要标志保护 10.9.3 演示输入/输出保护的实例(实例九) 10.10 分页管理机制 10.10.1 存储器分页管理机制 10.10.2 线性地址到物理地址的转换 10.10.3 页级保护和虚拟存储器支持 10.10.4 页异常 10.10.5 演示分页机制的实例(实例十) 10.11 虚拟8086方式 10.11.1 V86方式 10.11.2 进入和离开V86方式 10.11.3 演示进入和离开V86方式的实例(实例十一) 10.11.4 V86方式下的敏感指令 10.12 习题 第11章 80486及Pentium程序设计基础 11.1 80486程序设计基础 11.1.1 寄存器 11.1.2 指令系统 11.1.3 片上超高速缓存 11.2 80486对调试的支持 11 2.1 调试寄存器 11.2.2 演示调试故障/陷阶的实例 11.3 Pentium程序设计基础 11.3.1 寄存器 11.3.2 指令系统 11.3.3 处理器的识别 11.3.4 片上超高速缓存 11.4 基于Pentium的程序优化技术 11.4.1 流水线优化技术 11.4.2 分支优化技术 11.4.3 超高速缓存代化技术 11.5 习题 第三部分 上机实验指导 第12章 实验指导 12.1 实验的一般步骤 12.2 汇编器和连接器的使用 12.2.1 MASM的使用 12.2.2 LINK的使用 12.2.3 TASM的使用 12.2.4 TLINK的使用 12.3 调试器DEBUG的使用 12.3.1 启动和退出DEBUG 12.3.2 命令一览 12.3.3 利用DEBUG调试程序 12.4 Turbo Debugger的使用 12.4.1 启动和退出TD 12.4.2 利用TD调试汇编程序 参考文献 附录 Pentium指令与标志参考表
中文名: 80x86汇编语言程序设计教程 版本: [PDF] 发行时间: 1998年 地区: 大陆 简介: 本书分为三部分。第一部分是基础部分,以8086/8088为背景,以DOS和PC兼容机为软硬件平台,以MASM和TASM为汇编器,介绍汇编语言的有关概念,讲解汇编语言程序设计技术。第二部分是提高部分,以80386为背景,以新一代微处理器Pentium为目标,细致和通俗地介绍了保护方式下的有关概念,系统和详细地讲解了保护方式下的编程技术,真实和生动地展示了保护方式下的编程细节。第三部分是上机实验指导。 本书的第一部分适合初学者,可作为学习汇编语言程序设计的教材。本书的第二部分适合已基本掌握8086/8088汇编语言的程序员,可作为学习保护方式编程技术的教材或参考书,也可作为其他人员了解高档微处理器和保护方式编程技术的参考书,还可作为程序员透彻地了解Windows程序设计技术的参考书。 第一部分 基础部分 第1章 绪论 1.1 汇编语言概述 1.1.1 汇编语言 1.1.2 汇编语言的特点 1.1.3 恰当地使用汇编语言 1.2 数据的表示和类型 1.2.1 数值数据的表示 1.2.2 非数值数据的表示 1.2.3 基本数据类型 1.3 Intel系列CPU简介 1.3.1 8位微处理器 1.3.2 16位微处理器 1.3.3 32位微处理器 1.3.4 Pentium和Pentium Pro 1.4 习题 第2章 8086/8088寻址方式和指令系统 2.1 8086/8088寄存器组 2.1.1 8086/8088 CPU寄存器组 2.1.2 标志寄存器 2.2 存储器分段和地址的形成 2.2.1 存储单元的地址和内容 2.2.2 存储器的分段 2.2.3 物理地址的形成 2.2.4 段寄存器的引用 2.3 8086/8088的寻址方式 2.3.1 立即寻址方式 2.3.2 寄存器寻址方式 2.3.3 直接寻址方式 2.3.4 寄存器间接寻址方式 2.3.5 寄存器相对寻址方式 2.3.6 基址加变址寻址方式 2.3.7 相对基址加变址寻址方式 2.4 8086/8088指令系统 2.4.1 指令集说明 2.4.2 数据传送指令 2.4.3 堆栈操作指令 2.4.4 标志操作指令 2.4.5 加减运算指令 2.4.6 乘除运算指令 2.4.7 逻辑运算和移位指令 2.4.8 转移指令 2.5 习题 第3章 汇编语言及其程序设计初步 3.1 汇编语言的语句 3.1.1 语句的种类和格式 3.1.2 数值表达式 3.1.3 地址表达式 3.2 变量和标号 3.2.1 数据定义语句 3.2.2 变量和标号 3.3 常用伪指令语句和源程序组织 3.3.1 符号定义语句 3.3.2 段定义语句 3.3.3 汇编语言源程序的组织 3.4 顺序程序设计 3.4.1 顺序程序举例 3.4.2 简单查表法代码转换 3.4.3 查表法求函数值 3.5 分支程序设计 3.5.1 分支程序举例 3.5.2 利用地址表实现多向分支 3.6 循环程序设计 3.6.1 循环程序举例 3.6.2 多重循环程序举例 3.7 习题 第4章 子程序设计和DOS功能调用 4.1 子程序设计 4.1.1 过程调用和返回指令 4.1.2 过程定义语句 4.1.3 子程序举例 4.1.4 子程序说明信息 4.1.5 寄存器的保护与恢复 4.2 主程序与子程序间的参数传递 4.2.1 利用寄存器传递参数 4.2.2 利用约定存储单元传递参数 4.2.3 利用堆栈传递参数 4.2.4 利用CALL后续区传递参数 4.3 DOS功能调用及应用 4.3.1 DOS功能调用概述 4.3.2 基本I/O功能调用 4.3.3 应用举例 4.4 磁盘文件管理及应用 4.4.1 DOS磁盘文件管理功能调用 4.4.2 应用举例 4.5 子程序的递归和重入 4.5.1 递归子程序 4.5.2 可重入子程序 4.6 习题 第5章 输入输出与中断 5.1输 入和输出的基本概念 5.1.1 I/O端口地址和I/O指令 5.1.2 数据传送方式 5.1.3 存取RT/CMOS RAM 5.2 查询方式传送数据 5.2.1 查询传送方式 5.2.2 读实时钟 5.2.3 查询方式打印输出 5.3 中断 5.3.1 中断和中断传送方式 5.3.2 中断向量表 5.3.3 中断响应过程 5.3.4 外部中断 5.3.5 内部中断 5.3.6 中断优先级和中断嵌套 5.3.7 中断处理程序的设计 5.4 基本输入输出系统BIOS 5.4.1 基本输入输出系统BIOS概述 5.4.2 键盘输入 5.4.3 显示输出 5.4.4 打印输出 5.5 软中断处理程序举例 5.5.1 打印I/O程序 5.5.2 时钟显示程序 5.6 习题 第6章 简单应用程序的设计 6.1 字符串处理 6.1.1 字符串操作指令 6.1.2 重复前缀 6.1.3 字符串操作举例 6.2 十进制数算术运算调整指令及应用 6.2.1 组合BCD码的算术运算调整指令 6.2.2 未组合BCD码的算术运算调整指令 6.2.3 应用举例 6.3 DOS程序段前缀和特殊情况处理程序 6.3.1 DOS程序段前缀PSP 6.3.2 对Ctrl+C键和Ctrl+Break键的处理 6.4 TSR程序设计举例 6.4.1 驻留的时钟显示程序 6.4.2 热键激活的TSR程序 6.5 习题 第7章 高级汇编语言技术 7.1 结构和记录 7.1.1 结构 7.1.2 记录 7.2 宏 7.2.1 宏指令的定义和使用 7.2.2 宏指令的用途 7.2.3 宏指令中参数的使用 7.2.4 特殊的宏运算符 7.2.5 宏与子程序的区别 7.2.6 与宏有关的伪指令 7.2.7 宏定义的嵌套 7.3 重复汇编 7.3.1 伪指令REPT 7.3.2 伪指令IRP 7.3.3 伪指令IRPC 7.4 条件汇编 7.4.1 条件汇编伪指令 7.4.2 条件汇编与宏结合 7.5 源程序的结合 7.5.1 源程序的结合 7.5.2 宏库的使用 7.6 习题 第8章 模块化程序设计技术 8.1 段的完整定义 8.1.1 完整的段定义 8.1.2 关于堆栈段的说明 8.1.3 段组的说明和使用 8.2 段的简化定义 8.2.1 存储模型说明伪指令 8.2.2 简化的段定义伪指令 8.2.3 存储模型说明伪指令的隐含动作 8.3 模块间的通信 8.3.1 伪指令PUBLIC和伪指令EXTRN 8.3.2 模块间的转移 8.3.3 模块间的信息传递 8.4 子程序库 8.4.1 子程序库 8.4.2 建立子程序库 8.4.3 使用举例 8.5 编写供Turbo C调用的函数 8.5.1 汇编格式的编译结果 8.5.2 汇编模块应该遵守的约定 8.5.3 参数传递和寄存器保护 8.5.4 举例 8.6 习题 第二部分 提高部分 第9章 80386程序设计基础 9.1 80386寄存器 9.1.1 通用寄存器 9.1.2 段寄存器 9.1.3 指令指针和标志寄存器 9.2 80386存储器寻址 9.2.1 存储器寻址基本概念 9.2.2 灵活的存储器寻址方式 9.2.3 支持各种数据结构 9.3 80386指令集 9.3.1 数据传送指令 9.3.2 算术运算指令 9.3.3 逻辑运算和移位指令 9.3.4 控制转移指令 9.3.5 串操作指令 9.3.6 高级语言支持指令 9.3.7 条件字节设置指令 9.3.8 位操作指令 9.3.9 处理器控制指令 9.4 实方式下的程序设计 9.4.1 说明 9.4.2 实例 9.5 习题 第10章 保护方式下的80386及其编程 10.1 保护方式简述 10.1.1 存储管理机制 10.1.2 保护机制 10.2 分段管理机制 10.2.1 段定义和虚拟地址到线性地址转换 10.2.2 存储段描述符 10 2.3 全局和局部描述符表 10.2.4 段选择子 10.2.5 段描述符高速缓冲寄存器 10.3 80386控制寄存器和系统地址寄存器 10.3.1 控制寄存器 10 3.2 系统地址寄存器 10.4 实方式与保护方式切换实例 10.4.1 演示实方式和保护方式切换的实例(实例一) 10.4.2 演示32位代码段和16位代码段切换的实例(实例二) 10.5 任务状态段和控制门 10.5.1 系统段描述符 10.5.2 门描述符 10.5.3 任务状态段 10.6 控制转移 10.6.1 任务内无特权级变换的转移 10.6.2 演示任务内无特权级变换转移的实例(实例三) 10.6.3 任务内不同特权级的变换 10.6.4 演示任务内特权级变换的实例(实例四) 10.6.5 任务切换 10.6.6 演示任务切换的实例(实例五) 10.7 80386的中断和异常 10.7.1 80386的中断和异常 10.7.2 异常类型 10.7.3 中断和异常的转移方法 10.7.4 演示中断处理的实例(实例六) 10.7.5 演示异常处理的实例(实例七) 10.7.6 各种转移途径小结 10.8 操作系统类指令 10.8.1 实方式和任何特权级下可执行的指令 10.8.2 实方式及特权级0下可执行的指令 10 8.3 只能在保护方式下执行的指令 10.8.4 显示关键寄存器内容的实例(实例八) 10.8.5 特权指令 10.9 输入/输出保护 10.9.1 输入/输出保护 10.9.2 重要标志保护 10.9.3 演示输入/输出保护的实例(实例九) 10.10 分页管理机制 10.10.1 存储器分页管理机制 10.10.2 线性地址到物理地址的转换 10.10.3 页级保护和虚拟存储器支持 10.10.4 页异常 10.10.5 演示分页机制的实例(实例十) 10.11 虚拟8086方式 10.11.1 V86方式 10.11.2 进入和离开V86方式 10.11.3 演示进入和离开V86方式的实例(实例十一) 10.11.4 V86方式下的敏感指令 10.12 习题 第11章 80486及Pentium程序设计基础 11.1 80486程序设计基础 11.1.1 寄存器 11.1.2 指令系统 11.1.3 片上超高速缓存 11.2 80486对调试的支持 11 2.1 调试寄存器 11.2.2 演示调试故障/陷阶的实例 11.3 Pentium程序设计基础 11.3.1 寄存器 11.3.2 指令系统 11.3.3 处理器的识别 11.3.4 片上超高速缓存 11.4 基于Pentium的程序优化技术 11.4.1 流水线优化技术 11.4.2 分支优化技术 11.4.3 超高速缓存代化技术 11.5 习题 第三部分 上机实验指导 第12章 实验指导 12.1 实验的一般步骤 12.2 汇编器和连接器的使用 12.2.1 MASM的使用 12.2.2 LINK的使用 12.2.3 TASM的使用 12.2.4 TLINK的使用 12.3 调试器DEBUG的使用 12.3.1 启动和退出DEBUG 12.3.2 命令一览 12.3.3 利用DEBUG调试程序 12.4 Turbo Debugger的使用 12.4.1 启动和退出TD 12.4.2 利用TD调试汇编程序
书分为三部分。第一部分是基础部分,以8086/8088为背景,以DOS和PC兼容机为软硬件平台,以MASM和TASM为汇编器,介绍汇编语言的有关概念,讲解汇编语言程序设计技术。第二部分是提高部分,以80386为背景,以新一代微处理器Pentium为目标,细致和通俗地介绍了保护方式下的有关概念,系统和详细地讲解了保护方式下的编程技术,真实和生动地展示了保护方式下的编程细节。第三部分是上机实验指导。 本书的第一部分适合初学者,可作为学习汇编语言程序设计的教材。本书的第二部分适合已基本掌握8086/8088汇编语言的程序员,可作为学习保护方式编程技术的教材或参考书,也可作为其他人员了解高档微处理器和保护方式编程技术的参考书,还可作为程序员透彻地了解Windows程序设计技术的参考书。 第一部分 基础部分 第1章 绪论 1.1 汇编语言概述 1.1.1 汇编语言 1.1.2 汇编语言的特点 1.1.3 恰当地使用汇编语言 1.2 数据的表示和类型 1.2.1 数值数据的表示 1.2.2 非数值数据的表示 1.2.3 基本数据类型 1.3 Intel系列CPU简介 1.3.1 8位微处理器 1.3.2 16位微处理器 1.3.3 32位微处理器 1.3.4 Pentium和Pentium Pro 1.4 习题 第2章 8086/8088寻址方式和指令系统 2.1 8086/8088寄存器组 2.1.1 8086/8088 CPU寄存器组 2.1.2 标志寄存器 2.2 存储器分段和地址的形成 2.2.1 存储单元的地址和内容 2.2.2 存储器的分段 2.2.3 物理地址的形成 2.2.4 段寄存器的引用 2.3 8086/8088的寻址方式 2.3.1 立即寻址方式 2.3.2 寄存器寻址方式 2.3.3 直接寻址方式 2.3.4 寄存器间接寻址方式 2.3.5 寄存器相对寻址方式 2.3.6 基址加变址寻址方式 2.3.7 相对基址加变址寻址方式 2.4 8086/8088指令系统 2.4.1 指令集说明 2.4.2 数据传送指令 2.4.3 堆栈操作指令 2.4.4 标志操作指令 2.4.5 加减运算指令 2.4.6 乘除运算指令 2.4.7 逻辑运算和移位指令 2.4.8 转移指令 2.5 习题 第3章 汇编语言及其程序设计初步 3.1 汇编语言的语句 3.1.1 语句的种类和格式 3.1.2 数值表达式 3.1.3 地址表达式 3.2 变量和标号 3.2.1 数据定义语句 3.2.2 变量和标号 3.3 常用伪指令语句和源程序组织 3.3.1 符号定义语句 3.3.2 段定义语句 3.3.3 汇编语言源程序的组织 3.4 顺序程序设计 3.4.1 顺序程序举例 3.4.2 简单查表法代码转换 3.4.3 查表法求函数值 3.5 分支程序设计 3.5.1 分支程序举例 3.5.2 利用地址表实现多向分支 3.6 循环程序设计 3.6.1 循环程序举例 3.6.2 多重循环程序举例 3.7 习题 第4章 子程序设计和DOS功能调用 4.1 子程序设计 4.1.1 过程调用和返回指令 4.1.2 过程定义语句 4.1.3 子程序举例 4.1.4 子程序说明信息 4.1.5 寄存器的保护与恢复 4.2 主程序与子程序间的参数传递 4.2.1 利用寄存器传递参数 4.2.2 利用约定存储单元传递参数 4.2.3 利用堆栈传递参数 4.2.4 利用CALL后续区传递参数 4.3 DOS功能调用及应用 4.3.1 DOS功能调用概述 4.3.2 基本I/O功能调用 4.3.3 应用举例 4.4 磁盘文件管理及应用 4.4.1 DOS磁盘文件管理功能调用 4.4.2 应用举例 4.5 子程序的递归和重入 4.5.1 递归子程序 4.5.2 可重入子程序 4.6 习题 第5章 输入输出与中断 5.1输 入和输出的基本概念 5.1.1 I/O端口地址和I/O指令 5.1.2 数据传送方式 5.1.3 存取RT/CMOS RAM 5.2 查询方式传送数据 5.2.1 查询传送方式 5.2.2 读实时钟 5.2.3 查询方式打印输出 5.3 中断 5.3.1 中断和中断传送方式 5.3.2 中断向量表 5.3.3 中断响应过程 5.3.4 外部中断 5.3.5 内部中断 5.3.6 中断优先级和中断嵌套 5.3.7 中断处理程序的设计 5.4 基本输入输出系统BIOS 5.4.1 基本输入输出系统BIOS概述 5.4.2 键盘输入 5.4.3 显示输出 5.4.4 打印输出 5.5 软中断处理程序举例 5.5.1 打印I/O程序 5.5.2 时钟显示程序 5.6 习题 第6章 简单应用程序的设计 6.1 字符串处理 6.1.1 字符串操作指令 6.1.2 重复前缀 6.1.3 字符串操作举例 6.2 十进制数算术运算调整指令及应用 6.2.1 组合BCD码的算术运算调整指令 6.2.2 未组合BCD码的算术运算调整指令 6.2.3 应用举例 6.3 DOS程序段前缀和特殊情况处理程序 6.3.1 DOS程序段前缀PSP 6.3.2 对Ctrl+C键和Ctrl+Break键的处理 6.4 TSR程序设计举例 6.4.1 驻留的时钟显示程序 6.4.2 热键激活的TSR程序 6.5 习题 第7章 高级汇编语言技术 7.1 结构和记录 7.1.1 结构 7.1.2 记录 7.2 宏 7.2.1 宏指令的定义和使用 7.2.2 宏指令的用途 7.2.3 宏指令中参数的使用 7.2.4 特殊的宏运算符 7.2.5 宏与子程序的区别 7.2.6 与宏有关的伪指令 7.2.7 宏定义的嵌套 7.3 重复汇编 7.3.1 伪指令REPT 7.3.2 伪指令IRP 7.3.3 伪指令IRPC 7.4 条件汇编 7.4.1 条件汇编伪指令 7.4.2 条件汇编与宏结合 7.5 源程序的结合 7.5.1 源程序的结合 7.5.2 宏库的使用 7.6 习题 第8章 模块化程序设计技术 8.1 段的完整定义 8.1.1 完整的段定义 8.1.2 关于堆栈段的说明 8.1.3 段组的说明和使用 8.2 段的简化定义 8.2.1 存储模型说明伪指令 8.2.2 简化的段定义伪指令 8.2.3 存储模型说明伪指令的隐含动作 8.3 模块间的通信 8.3.1 伪指令PUBLIC和伪指令EXTRN 8.3.2 模块间的转移 8.3.3 模块间的信息传递 8.4 子程序库 8.4.1 子程序库 8.4.2 建立子程序库 8.4.3 使用举例 8.5 编写供Turbo C调用的函数 8.5.1 汇编格式的编译结果 8.5.2 汇编模块应该遵守的约定 8.5.3 参数传递和寄存器保护 8.5.4 举例 8.6 习题 第二部分 提高部分
英文版 以下是我从网上所的相关介绍 内容简介 本书以X86系列微机为背景,从简单的Hello程序开始,系统而详细地阐述了X86微机汇编语言编程的行种基础知识和编程技巧,内容涉及到数据表示、存储器管理、各种数据类型、过程、与汇编语言相关的体系结构、控制结构、文件、宏指令、位处理指令、字符串指令、MMX指令、类和对象,以及混合语言编程等,尤其是在高级汇编语言(HLA)方面,该书给予了细致深入的讲解。对于有意学习X86汇编语言编程的程序员来说,这是一本难得的好书。 本书的作者Randall Hyde拥有十多年的汇编语言教学经验,并且开发了多个商用软件,具有实际的汇编语言开发经验。该书的英文网络版受到全球成千上万的网站和高级程序员的高度评价,被大家公推为高级汇编语言编程的经典之作。该书的英文正版推出不久,即有很多人在“亚马逊”网站上为其作评,而且几乎所有的人都给予5星的高分,可见其内容之好 目录 第1章 进入汇编语言的世界 1.1 本章概述 1.2 HLA程序的结构 1.3 运行第一个HLA程序 1.4 基本的HLA数据声明 1.5 布尔值 1.6 字符值 1.7 Intel80x86处理器简介 1.8 基本的机器指令 1.9 基本的HLA控制结构 1.10 HLA标准库入门 1.11 关于TRY..ENDTRY的其他细节 1.12 高级汇编语言与底级汇编语言比较 1.13 更多信息 第2章 数据表示 2.1 本章概述 2.2 数字系统 2.3 十六进制数字系统 2.4 数据结构 2.5 二进制数与十六进制数的算术运算 2.6 关于数字及其表示法 2.7 位逻辑运算 2.8 二进制数和位串的逻辑运算 2.9 有符号数和无符号数 2.10 符号扩展、零扩展、压缩和饱和 2.11 侈位和缩环移位 2.12 位域和压缩数据 2.13 浮点运算简介 2.14 BCD数据表示 2.15 字符 2.16 Unicode字符集 2.17 更多信息 第3章 存储器的访问与结构 3.1 本章概述 3.2 80x86的寻址方式 3.3 运行时存储器的结构 3.4 HLA如何为变量分配内存 3.5 HLA对数据对齐的支持 3.6 地址表达式 3.7 类型强制转换 3.8 寄存器类型强制转换 3.9 栈段与PUSH及POP指令 3.10 动态内存分配和堆段 3.11 INC和DEC指令 3.12 获取存储器对象的地址 3.13 更多信息 第4章 常量、变量与数据类型 4.1 本章概述 4.2 一些额外的指令:INTMUL、BOUND、INTO 4.3 TBYTE数据类型 4.4 HLA常量和数值声明 4.5 HLA和TYPE段 4.6 ENUM和HLA枚举数据类型 4.7 指针数据类型 4.8 HLA标准库CHARS.HHF模型 4.9 复合数据类型 4.10 字符串 4.11 HLA字符串 4.12 访问字符中的某个字符 4.13 HLA字符串模块和其他与字符串机关的例程 4.14 存储器内转换 4.15 字符集 4.16 在HLA中实现字符集 4.17 HLA字符集常量和字符集表达工 4.18 HLA HLL布尔表达式中的IN操作符 4.19 HLA标准库对字符集的支持 4.20 在HLA程序中使用字符集 4.21 数组 4.22 在HLA程序中声明数组 4.23 HLA数组常量 4.24 访问一维数组的元素 4.25 多维数组 4.26 多维数组的存储空间分配 4.27 汇编语言中多维数组元素的访问 4.28 大数组和MASM(只适用于Windows程序员) 4.29 记录 4.30 记录常量 4.31 记录数组 4.32 数组/记录作为记录字段 4.33 控制记录中的字面偏移量 4.34 对齐记录中的字段 4.35 记录指针 4.36 联合 4.37 匿名联合 4.38 变量类型 4.39 联合常量 4.40 命名空间 4.41 汇编语言中的动态数组 4.42 HLA标准库数组支持 4.43 更多信息 第5章 过程与单元 5.1 本章概述 5.2 过程 5.3 机器状态的保存 5.4 过程的提前返回 5.5 局部变量 5.6 其他局部和全局符号类型 5.7 参数 5.8 函数和函数的结果 5.9 递归 5.10 过程的向前引用 5.11 过程的底层实现与CALL指令 5.12 过程与堆栈 5.13 活动记录 5.14 标准入口序列 5.15 标准出口序列 5.16 自动(局部)变量的底层实现 5.17 参数的度层实现 5.18 过程指针 5.19 过程参数 5.20 无类型的引用参数 5.21 管理大型程序 5.22 #INCLUDE伪指令 5.23 忽略重复的#INCLUDE操作 5.24 单元与EXTERNAL伪指令 5.25 命名空间的污染 5.26 更多信息 第6章 算术运算 6.1 本章概述 6.2 80x86的整数运算指令 6.3 算术表达式 6.4 逻辑(布尔)表达式 6.5 机器特征与运算技巧 6.6 浮点运算 6.7 浮点表达式到汇编语言的转换 6.8 HLA标准库对浮点算术运算的支持 6.9 算术运算小结 第7章 低级控制结构 7.1 本章概述 7.2 低级控制结构 7.3 语句标号 7.4 无条件控制转移(JMP) 7.5 条件跳转指令 7.6 “中级”控制结构:JT和JF 7.7 使用汇编语言实现通用控制结构 7.8 选择 7.9 状态机和间接跳转 7.10 “面条式”代码 7.11 循环 7.12 性能提高 7.13 HLA中的混合控制结构 7.14 更多信息 第8章 文件 8.1 本章概述 8.2 文件组织 8.3 顺序文件 8.4 随机访问文件 8.5 ISAM文件 8.6 截断文件 8.7 更多信息 第9章 高级算术运算 9.1 本章概述 9.2 多精度操作 9.3 对不同长度的操作数进行操作 9.4 十进制算术运算 9.5 表 9.6 更多信息 第10章 宏与HLA编译时语言 10.1 本章概述 10.2 编译时语言 10.3 #PRINT和#ERROR语句 10.4 编译时常量和变量 10.5 编译时表达式和操作符 10.6 编译时函数 10.7 条件编译(编译时决定) 10.8 重复编译(编译时循环) 10.9 宏(编译时过程) 10.10 编写编译时“程序” 10.11 在不同的源文件中使用宏 10.12 更多信息 第11章 位操作 11.1 本章概述 11.2 位数据 11.3 位操作指令 11.4 作为位累加器的进位标志位 11.5 位串的压缩与解压缩 11.6 接合位组与分布位串 11.7 压缩的位串数组 11.8 搜索位 11.9 位的计数 11.10 倒置位串 11.11 合并位串 11.12 提取位串 11.13 搜索位模式 11.14 HLA标准库的位模块 11.15 更多信息 第12章 字符串指令 12.1 本章概述 12.2 80x86字符串指令 12.3 80x86字符串指令的性能 12.4 更多信息 第13章 MMX指令集 13.1 本章概述 13.2 判断CPU是否支持MMX指令集 13.3 MMX编程环境 13.4 设计MMX指令集的目的 13.5 饱和算未能和回转模式 13.6 MMX指令操作数 13.7 MMX技术指令 第14章 类与对象 14.1 本章概述 14.2 通用原则 14.3 HLA中的类 14.4 对象 14.5 继承 14.6 重载 14.7 虚拟方法与静态过程 14.8 编写类方法和过程 14.9 对象实现 14.10 构造函数和对象初始化 14.11 析构函数 14.12 HLA的“_initialize_”和“_finalize_”字符串 14.13 抽像方法 14.14 运行时类型信息(RTTI) 14.15 调用基类的方法 14.16 更多信息 第15章 混合语言编程 15.1 本章概述 15.2 在同一程序中混合使用HLA和MASM/Gas代码 15.3 使用Delphi/Kylix和HLA编程 15.4 使用C/C++和HLA编程 15.5 更多信息 附录A ASCII字符集 附录B 80x86指令集

121

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 个人社区
社区管理员
  • 微软技术分享
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

试试用AI创作助手写篇文章吧