汇编开发(五):算法

1. Shift 和 Rotate 指令

位移意味着在操作数内部按位左/右移动,其影响着OF和CF标志位。

Shift and Rotate Instructions.png
1). 逻辑移动和算术移动
  • 逻辑移动
    将新创建的字节位填充0,
Logical shift.png
  • 算术移动
    新创建的字节位填充原位置上的数据。
Arithmetic shift.png
2). SHL 指令

SHL指令表示在目标操作数上逻辑左移,低位填充0。

SHL.png
  • 格式
; 第一个是目标操作数,第二个是移动的位数
SHL destination,count

SHL reg,imm8
SHL mem,imm8
SHL reg,CL
SHL mem,CL

x86 处理器支持imm8的范围在整型0—255之间,另外,CL寄存器也可以包含移动的位数。此格式同样应用在SHR, SAL, SAR, ROR, ROL,
RCR 和RCL 指令。

示例:

mov bl,8Fh      ; BL = 10001111b
shl bl,1        ; CF = 1, BL = 00011110b

mov al,10000000b
shl al,2        ; CF = 0, AL = 00000000b
  • 按位乘
    左移n位相当于操作数乘以2^n。
mov dl,5
shl dl,1
Bitwise Multiplication.png
3). SHR 指令

SHR 指令表示在目标操作数上逻辑右移,高位用0替换。

SHR.png

示例:

mov al,0D0h     ; AL = 11010000b
shr al,1        ; AL = 01101000b, CF = 0

mov al,00000010b
shr al,2        ; AL = 00000000b, CF = 1
  • 按位除
    右移一个无符号整数n位相当于操作数除以2^n。
mov dl,32
shr dl,1
Bitwise Division.png
4). SAL 和 SAR 指令
  • SAL
    SAL 指令与SHL指令相同。SAL指令的每一位向高位移动,低位补0.
SAL.png
  • SAR
    SAR 指令移动后新的位置上填充原位置上的数字。
SAR.png

示例:

mov al,0F0h     ; AL = 11110000b (-16)
sar al,1        ; AL = 11111000b (-8), CF = 0
  • 符号除
    有符号的操作数除以2,可以使用SAR指令。
mov dl,-128     ; DL = 10000000b
sar dl,3        ; DL = 11110000b
  • AX符号扩展到EAX
    AX包含一个有符号的整型,并将它扩展到EAX寄存器中。
mov ax,-128     ; EAX = ????FF80h
shl eax,16      ; EAX = FF800000h
sar eax,16      ; EAX = FFFFFF80h
5). ROL 指令

ROL 指令指循环移动,即移出的一位补充到先增加的那个位置上。

SHL.png

示例:

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
  • 多次旋转
    当循环位数大于1的时候,进位标志与最后一位相同
mov al,00100000b
rol al,3            ; CF = 1, AL = 00000001b
  • 交换比特组
    可以使用ROL交换高位和低位的字节。
mov al,26h
rol al,4        ; AL = 62h
6). ROR 指令

ROR 指令 每一位右移,并复制最低位到CF标志位和最高位。

ROR.png

示例:

mov al,01h      ; AL = 00000001b
ror al,1        ; AL = 10000000b, CF = 1
ror al,1        ; AL = 01000000b, CF = 0
  • 多次旋转
    当使用旋转位数大于1时,CF标志位与最后一次移动的位数值相同。
mov al,00000100b
ror al,3            ; AL = 10000000b, CF = 1
7). RCL 和 RCR 指令

RCL 指令向左移动每一位,复制到CF标志位和最低位。

RCL.png

示例:

clc         ; CF = 0 清空CF标志位
mov bl,88h  ; CF,BL = 0 10001000b
rcl bl,1    ; CF,BL = 1 00010000b
rcl bl,1    ; CF,BL = 0 00100001b
  • 从进位标志中恢复
    RCL 可以恢复移动之前的CF标志位。
.data
    testval BYTE 01101010b
.code
    shr testval,1   ; shift LSB into Carry flag
    jc exit         ; exit if Carry flag set
    rcl testval,1   ; else restore the number
  • RCR 指令
    RCR 指令向右移动每一位,复制CF标志位到最高位,将最低位复制到CF标志位。
RCR.png

示例:

stc         ; CF = 1 设置CF标志位为1
mov ah,10h  ; AH, CF = 00010000 1
rcr ah,1    ; AH, CF = 10001000 0
8). 符号溢出

移动或者旋转一个有符号的整型时,符号位溢出后,OF标志位被设置。

mov al,+127 ; AL = 01111111b
rol al,1    ; OF = 1, AL = 11111110b
9). SHLD / SHRD 指令

SHLD/SHRD 指令向左/右移动一个目标操作数指定的数字位数,新添加的位数由源操作数中的高/低位填充。格式如下:

SHLD/SHRD dest, source, count

SHLD/SHRD reg16,reg16,CL/imm8
SHLD/SHRD mem16,reg16,CL/imm8
SHLD/SHRD reg32,reg32,CL/imm8
SHLD/SHRD mem32,reg32,CL/imm8
SHLD.png
SHRD.png
  • 示例1:
.data
    wval WORD 9BA6h
.code
    mov ax,0AC36h
    shld wval,ax,4      ; wval = BA6Ah
  • 示例2:
mov ax,234Bh
mov dx,7654h
shrd ax,dx,4        ; ax = 4234h

2. 移动和旋转应用

1). 转换多个DWORD
; 数组元素移位

INCLUDE Irvine32.inc

.data
    ArraySize = 3
    array BYTE ArraySize DUP(99h)

.code
main PROC
    mov esi, 0
    shr array[esi + 2], 1       ; 最高字节
    rcr array[esi + 1], 1       ; 中间字节
    rcr array[esi], 1           ; 低字节
    
    call Crlf
    call WaitMsg
    exit
main ENDP

END
2). 二进制乘法

将某个数分解之后成为2的n次方,并将乘数依次与之相乘,最后求和。例如:36可以分为25+22, 则10036 = 10025+100*22。

mov eax,100
mov ebx,eax
shl eax,5   ; multiply by 25
shl ebx,2   ; multiply by 22
add eax,ebx ; add the products
3). 显示二进制位

共同的代码程序将二进制整数转换为ASCII码字符串,并将ASCII码显示。

;---------------------------------------------------------
;
; Converts 32-bit binary integer to ASCII binary.
; Receives: EAX = binary integer, ESI points to buffer
; Returns: buffer filled with ASCII binary digits
;---------------------------------------------------------
BinToAsc PROC USES ecx, esi
    mov ecx,32              ; number of bits in EAX
L1: 
    shl eax,1               ; shift high bit into Carry flag
    mov BYTE PTR [esi],'0'  ; choose 0 as default digit
    jnc L2                  ; if no Carry, jump to L2
    mov BYTE PTR [esi],'1'  ; else move 1 to buffer
L2: 
    inc esi                 ; next buffer position
    loop L1                 ; shift another bit to left
    RET
BinToAsc ENDP
4). 提取文件日期

应用经常需要提取位串用于获取时间。在MS-DOS中日期存储在DX中,其中0—4位代表天,5—8位代表月,9—15代表年。

Extracting File Date Fields.png

示例:

; 获取天
mov al,dl           ; make a copy of DL
and al,00011111b    ; clear bits 5-7
mov day,al          ; save in day

; 获取月
mov ax,dx           ; make a copy of DX
shr ax,5            ; shift right 5 bits
and al,00001111b    ; clear bits 4-7
mov month,al        ; save in month

; 获取年
mov al,dh           ; make a copy of DH
shr al,1            ; shift right one position
mov ah,0            ; clear AH to zeros
add ax,1980         ; year is relative to 1980
mov year,ax         ; save in year

3. MUL 和 DIV 指令

在32位模式中,MUL和DIV 可以作用于32位,16位,8位操作数。
在64为模式中,MUL和DIV指令作用于无符号整型中,IMUL和IDIV指令作用于有符号整型中。

1). MUL 指令
  • 格式
MUL reg/mem8
MUL reg/mem16
MUL reg/mem32
MUL Operands.png
; 示例1
mov al,5h
mov bl,10h
mul bl      ; AX = 0050h, CF = 0

; 示例2
.data
val1 WORD 2000h
val2 WORD 0100h
.code
mov ax,val1     ; AX = 2000h
mul val2        ; DX:AX = 00200000h, CF = 1

; 示例3
mov eax,12345h
mov ebx,1000h
mul ebx         ; EDX:EAX = 0000000012345000h, CF = 0
  • 64位模式中的MUL
.data
    multiplier QWORD 10h
.code
    mov rax,0AABBBBCCCCDDDDh
    mul multiplier          ; RDX:RAX = 00000000000000000AABBBBCCCCDDDD0h
2). IMUL 指令

IMUL 指令表示有符号整数的乘法。

  • 单操作数格式
IMUL reg/mem8   ; AX = AL * reg/mem8
IMUL reg/mem16  ; DX:AX = AX * reg/mem16
IMUL reg/mem32  ; EDX:EAX = EAX * reg/mem32
  • 双操作数格式
IMUL reg16,reg/mem16
IMUL reg16,imm8
IMUL reg16,imm16
IMUL reg32,reg/mem32
IMUL reg32,imm8
IMUL reg32,imm32
  • 三操作数格式
IMUL reg16,reg/mem16,imm8
IMUL reg16,reg/mem16,imm16
IMUL reg32,reg/mem32,imm8
IMUL reg32,reg/mem32,imm32

如果符号位丢失,则需要设置OF和CF标志位。

  • 64位模式下的IMUL
    64位模式下,可以使用64位模式下MUL指令。在双操作数格式下,一个64位寄存器或者内存数乘以RDX,产生一个128位有符号扩展的RDX:RAX。64位模式下三操作数格式也是可用的。
; 双操作数
mov rax,-4
mov rbx,4
imul rb     ; RDX = 0FFFFFFFFFFFFFFFFh, RAX = -16

; 三操作数
.data
    multiplicand QWORD -16
.code
    imul rax, multiplicand, 4   ; RAX = FFFFFFFFFFFFFFC0 (-64)

示例:

; 单操作数
mov al,48
mov bl,4
imul bl     ; AX = 00C0h, OF = 1

mov al,-4
mov bl,4
imul bl     ; AX = FFF0h, OF = 0

; 双操作数
.data
    word1 SWORD 4
    dword1 SDWORD 4
.code
    mov ax,-16      ; AX = -16
    mov bx,2        ; BX = 2
    imul bx,ax      ; BX = -32
    imul bx,2       ; BX = -64
    imul bx,word1   ; BX = -256
    mov eax,-16     ; EAX = -16
    mov ebx,2       ; EBX = 2
    imul ebx,eax    ; EBX = -32
    imul ebx,2      ; EBX = -64
    imul ebx,dword1 ; EBX = -256

    mov ax,-32000
    imul ax,2 ; OF = 1  
    
; 三操作数
.data
    word1 SWORD 4
    dword1 SDWORD 4
.code
    imul bx,word1,-16   ; BX = word1 * -16
    imul ebx,dword1,-16 ; EBX = dword1 * -16
    imul ebx,dword1,-2000000000 ; signed overflow!
3). 比较MUL/IMUL和移位的运行时间
; 比较MUL/IMUL和移位的运行时间

INCLUDE Irvine32.inc

.data
    LOOP_COUNT = 0FFFFFFFFh
    intval DWORD 5
    startTime DWORD ?
    shiftTime DWORD ?
    mulTime DWORD ?

.code
main PROC
    call GetMseconds    ; get start time
    mov startTime,eax
    mov eax,intval      ; multiply now
    call mult_by_shifting
    call GetMseconds    ; get stop time
    sub eax,startTime
    call WriteDec       ; display elapsed time

    call Crlf

    call GetMseconds    ; get start time
    mov startTime,eax
    mov eax,intval      ; multiply now
    call mult_by_MUL
    call GetMseconds    ; get stop time
    sub eax,startTime
    call WriteDec       ; display elapsed time

    call Crlf
    call WaitMsg
    exit
main ENDP

;----------------------------------
; 使用SHL移位计算EAX*36
;----------------------------------
mult_by_shifting PROC USES eax
    mov ecx, LOOP_COUNT
L1:
    mov ebx, eax
    shl eax, 5
    shl eax, 2
    add eax, ebx
    LOOP L1
    RET
mult_by_shifting ENDP

;----------------------------------
; 使用乘法计算EAX*36
;----------------------------------
mult_by_MUL PROC USES eax
    mov ecx, LOOP_COUNT
L1:
    mov ebx, 36
    mul ebx
    LOOP L1
    RET
mult_by_MUL ENDP

END
效果.png
4). DIV 指令
  • 格式
DIV reg/mem8
DIV reg/mem16
DIV reg/mem32
被除数、除数、商和余数关系.png
  • 示例
; 示例1
mov ax,0083h    ; dividend
mov bl,2        ; divisor
div bl          ; AL = 41h, AH = 01h

; 示例2
mov dx,0        ; clear dividend, high
mov ax,8003h    ; dividend, low
mov cx,100h     ; divisor
div cx          ; AX = 0080h, DX = 0003h
5). 带符号整数除法

有符号整数除法与无符号除法几乎相同,但有一个重要区别:除法必须在除法发生之前进行符号扩展。

.data
    wordVal SWORD -101  ; 009Bh
.code
    mov eax,0           ; EAX = 00000000h
    mov ax,wordVal      ; EAX = 0000009Bh (+155)
    cwd                 ; EAX = FFFFFF9Bh (-101)
    mov bx,2            ; EBX is the divisor
    idiv bx             ; divide EAX by BX
  • 符号扩展指令 (CBW, CWD, CDQ)
    CBW 指令(BYTE -> WORD)扩展符号位从AL到AH。CWD(WORD -> DWORD)扩展符号位AX到DX。CDQ(DWORD -> QWORD)口占EAX到EDX。
; CBW 使用
.data
    byteVal SBYTE -101  ; 9Bh
.code
    mov al,byteVal      ; AL = 9Bh
    cbw                 ; AX = FF9Bh

; CWD 使用
.data
    wordVal SWORD -101  ; FF9Bh
.code
    mov ax,wordVal      ; AX = FF9Bh
    cwd

; CDQ 使用
.data
    dwordVal SDWORD -101    ; FFFFFF9Bh
.code
    mov eax,dwordVal
    cdq
  • IDIV 指令
    IDIV 指令表示有符号除法,使用和DIV相同。
.data
    byteVal SBYTE -48   ; D0 hexadecimal
.code
    mov al,byteVal      ; lower half of dividend
    cbw                 ; extend AL into AH
    mov bl,+5           ; divisor
    idiv bl             ; AL = -9, AH = -3
  • 除法溢出
mov ax,1000h
mov bl,10h
div bl      ; AL cannot hold 100h

建议:使用32位除数和64位被除数来降低除法溢出条件的概率。

6). 实现除法/乘法的算术表达式
; 示例表达式1: var4 = (var1 + var2) * var3
    mov eax,var1
    add eax,var2
    mul var3        ; EAX = EAX * var3
    jc tooBig       ; unsigned overflow?
    mov var4,eax
    jmp next
tooBig:         ; display error message

; 示例表达式2: var4 = (var1 * 5) / (var2 - 3)
    mov eax,var1    ; left side
    mov ebx,5
    mul ebx         ; EDX:EAX = product
    mov ebx,var2    ; right side
    sub ebx,3
    div ebx         ; final division
    mov var4,eax

4. 扩展加和减

扩展加和减是添加和减去几乎无限大小的数字的技术。

1). ADC 指令

ADC指令将源操作数和CF标志的内容添加到目标操作数。

  • 格式
ADC reg,reg
ADC mem,reg
ADC reg,mem
ADC mem,imm
ADC reg,imm

示例:

mov dl,0
mov al,0FFh
add al,0FFh     ; AL = FEh
adc dl,0        ; DL/AL = 01FEh
效果.png
2). 扩展加示例

将数组中的数据一一对应相加。

; 扩展加法示例:数组元素对应相加

INCLUDE Irvine32.inc

.data
    op1 BYTE 34h,12h,98h,74h,06h,0A4h,0B2h,0A2h
    op2 BYTE 02h,45h,23h,00h,00h,87h,10h,80h
    sum BYTE 9 dup(0)

.code
main PROC
    mov esi, OFFSET op1
    mov edi, OFFSET op2
    mov ebx, OFFSET sum
    mov ecx, LENGTHOF op1

    call Extended_Add

    ; 显示和
    mov esi, OFFSET sum
    mov ecx, LENGTHOF sum
    call Display_Sum
    
    call Crlf
    call WaitMsg
    exit
main ENDP

;-------------------------------------------------
; 计算两个字节数组之和
; Receives: ESI和EDI指向两个整型,EBX指向一个存储和的
;           变量,ECX循环计数
; Returns: 无
;-------------------------------------------------
Extended_Add PROC
    pushad
    clc

L1: 
    mov al, [esi]       ; 获取第一个整数
    adc al, [edi]       ; 添加第二个整数
    pushfd              ; 保存CF标记位
    mov [ebx], al       ; 存储和
    add esi, 1          ; 指针指向下一个数
    add edi, 1
    add ebx, 1
    popfd               ; 恢复CF
    LOOP L1             ; 循环

    mov byte ptr [ebx], 0; 清除sum的高位
    adc byte ptr [ebx], 0; 添加CF标记位
    popad
    RET
Extended_Add ENDP

Display_Sum PROC
    pushad
    ; point to the last array element
    add esi,ecx
    sub esi,TYPE BYTE
    mov ebx,TYPE BYTE
L1: 
    mov al,[esi]        ; get an array byte
    call WriteHexB      ; display it
    sub esi,TYPE BYTE   ; point to previous byte
    call Crlf
    loop L1
    popad
    ret
Display_Sum ENDP

END

结果:0122C32B0674BB5736, 数组对应元素的和,在结果中从低位到高位显示,最高位为符号位。

3). SBB 指令

SBB指令表示从目的操作数中减去源操作数和CF标志位。

mov edx,7 ; upper half
mov eax,1 ; lower half
sub eax,2 ; subtract 2
sbb edx,0 ; subtract upper half
Subtracting from a 64-bit integer using SBB.png

5. ASCII 和未压缩的十进制算法

  • 四个处理指令
deal with ASCII addition, subtraction, multiplication, and division.png
  • ASCII 十进制 和 未解压十进制
All values are in hexadecimal.png
1). AAA 指令
mov ah,0
mov al,'8'      ; AX = 0038h
add al,'2'      ; AX = 006Ah
aaa             ; AX = 0100h (ASCII adjust result)
or ax,3030h     ; AX = 3130h = '10' (convert to ASCII)
  • 使用AAA进行多字节相加
; ASCII 加法
; 字符串中ASCII算法
INCLUDE Irvine32.inc

DECIMAL_OFFSET = 5          ; 从右数字符串偏移
.data
    decimal_one BYTE "100123456789765"  ; 1001234567.89765
    decimal_two BYTE "900402076502015"  ; 9004020765.02015
    sum BYTE (SIZEOF decimal_one + 1) DUP(0), 0

.code
main PROC
    ; 开始从最后一个数字的位置
    mov esi, SIZEOF decimal_one - 1
    mov edi, SIZEOF decimal_one
    mov ecx, SIZEOF decimal_one
    mov bh, 0                       ; 设置CF为0
L1:
    mov ah, 0                       ; 加之前清空AH
    mov al, decimal_one[esi]        ; 获取当前位置的数字
    add al, bh                      ; 添加之前的进位
    aaa                             ; 调整总和
    mov bh, ah                      ; 存储CF标志位
    or bh, 30h                      ; 转换ASCII
    add al, decimal_two[esi]        ; 添加第二个数
    aaa                             ; 调整总和
    or bh, ah                       ; 或CF标志位
    or bh, 30h                      ; 转换为ASCII码
    or al, 30h                      ; 转换al为ASCII
    mov sum[edi], al                ; 存储和
    dec esi                         ; 移动到下一位
    dec edi
    LOOP L1
    mov sum[edi], bh                ; 存储进位
    
    ; 显示总和字符串
    mov edx, OFFSET sum
    call WriteString

    call Crlf
    call WaitMsg
    exit
main ENDP

END
效果.png
2). AAS 指令
.data
    val1 BYTE '8'
    val2 BYTE '9'
.code
    mov ah,0
    mov al,val1     ; AX = 0038h
    sub al,val2     ; AX = 00FFh
    aas             ; AX = FF09h
    pushf           ; save the Carry flag
    or al,30h       ; AX = FF39h
    popf            ; restore the Carry flag

此处8 - 9 之后,AH减1变为FFh, AL变为39h, 可理解为18 - 9,即个位为9,向前借了一位。

3). AAM 指令
.data
    AscVal BYTE 05h,06h
.code
    mov bl,ascVal       ; first operand
    mov al,[ascVal+1]   ; second operand
    mul bl              ; AX = 001Eh
    aam                 ; AX = 0300h , or 3030h
4). AAD 指令
.data
    quotient BYTE ?
    remainder BYTE ?
.code
    mov ax,0307h    ; dividend
    aad             ; AX = 0025h, or 3030h
    mov bl,5        ; divisor
    div bl          ; AX = 0207h
    mov quotient,al
    mov remainder,ah

6. 压缩十进制算术

涉及到两个指令:DAA (decimal adjust after addition) 与 DAS (decimal adjust after subtraction)。压缩十进制算术中无乘法和相关指令。

1). DAA
; 压缩加法
; 压缩十进制加法
INCLUDE Irvine32.inc

.data
    packed_1 WORD 4536h
    packed_2 WORD 7207h
    sum DWORD ?

.code
main PROC
    ; 初始化总和和序号
    mov sum, 0
    mov esi, 0

    ; 添加低字节
    mov al, BYTE PTR packed_1[esi]
    add al, BYTE PTR packed_2[esi]
    daa
    mov BYTE PTR sum[esi], al

    ; 添加高字节,包含CF标志位
    inc esi
    mov al, BYTE PTR packed_1[esi]
    adc al, BYTE PTR packed_2[esi]
    daa
    mov BYTE PTR sum[esi], al
    ; 添加CF标志位
    inc esi
    mov al, 0
    adc al, 0
    mov BYTE PTR sum[esi], al

    ; 十六进制格式显示
    mov eax, sum
    call WriteHex

    call Crlf
    call WaitMsg
    exit
main ENDP

END
AddPacked.png
2). DAS
mov bl,48h
mov al,85h
sub al,bl   ; AL = 3Dh
das         ; AL = 37h (adjusted result)

你可能感兴趣的:(汇编开发(五):算法)