没有 div 的 ASM 8086 除法

Posted

技术标签:

【中文标题】没有 div 的 ASM 8086 除法【英文标题】:ASM 8086 division without div 【发布时间】:2020-05-30 15:04:16 【问题描述】:

我需要在 asm 8086 中编写一个类似 b=a/6 但没有 DIV 指令的程序。我知道如何使用 SAR,但只有 2、4、8、16...

mov ax,a
sar ax,1 ;//div a by 2
mov b,ax

我的问题是如何将 div 设为 6?

【问题讨论】:

您可以使用减法并计算归零所需的次数,例如。 30/6=5 和 30-6-6-6-6-6=0 所以对于 30,你必须 5 次减去 6 才能达到零 对于一个固定的(编译时常数)除数,有一个使用乘法来进行精确整数除法的定点技巧:Why does GCC use multiplication by a strange number in implementing integer division?。另外,您要签名还是未签名的部门? div 是无符号的,sar 是有符号的(对于负数,舍入方式与 idiv 不同) 【参考方案1】:

给定another answer 的方法是简单的暴力循环,对于较大的a 值可能需要一段时间。这是一个使用较大块的版本(像长除法问题一样工作),专门编码为将有符号数除以 6:

; signed divide by 6
    mov ax,a
    mov cx,1000h  ; initial count of how many divisors into ax to check for
    mov bx,6000h  ; value of "divisor * cx"
    xor dx,dx     ; result
top:
    cmp ax,bx
    jl skip
    ; we can fit "cx" copies of the divisor into ax, so tally them
    add dx,cx
    sub ax,bx
    ; optionally can have a "jz done" here to break out of the loop
skip:
    shr bx,1
    shr cx,1
    jnz top

    ; copy result into ax
    mov ax,dx

如果需要除 6 以外的东西,则需要调整初始的 cxbx 值。 cx 是保留第 14 位设置的除数的 2 的幂(因为第 15 位是符号位;对于无符号除法,您希望设置第 15 位)。 bx 是 2 的幂。如果 a 的初始值有限制,您可以调整初始 cxbx 值,但必须小心,因为如果让它们太小。

【讨论】:

【参考方案2】:

您可以使用减法并计算归零所需的次数,例如。 30/6=5 和 30-6-6-6-6-6=0 所以对于 30,你必须 5 次减去 6 才能达到零

类似的东西:

mov cx,0
mov ax, dividend

divloop:
  cmp ax, 0
  jle done   
  sub ax, divisor
  inc cx
  jmp divloop

done:
  ;result is in cx

【讨论】:

【参考方案3】:

由于您在示例中使用了 sar 指令,我假设您需要 signed 除以 6。

以下代码使用倒数常数的乘法。它将结果向下舍入为 -∞:

mov ax,0x2AAB   ; round(0x10000/6)
imul A          ; DX:AX contains the signed 32-bit result of the multiplication
mov B,dx        ; take only the upper 16 bits

如果您需要将结果四舍五入为 0,则将负结果加一:

      mov  ax,0x2AAB
      imul A
      test dx,dx  ; set FLAGS according to DX
      jns  skip
      inc  dx      ; increase DX if it was negative
skip: mov  B,dx

最后,如果你需要无符号除以6,你需要一个更精确的常数和一个移位:

mov ax,0xAAAB  ; round(0x40000/6)
mul A          ; DX:AX contains the unsigned 32-bit result of the multiplication
shr dx,1
shr dx,1       ; shift DX right by 2 (8086 can't do "shr dx,2")
mov B,dx

【讨论】:

使用test dx,dx根据DX设置FLAGS,it's more efficient than or在某些CPU上,而不是在8086上更糟。在现代CPU上,编译器通常会生成无分支代码,有条件地用@之类的东西加1 987654331@ / sar ax,15 / sub dx, ax 减去 0 或 -1,但在实际的古代 CPU 上会慢得多。 这些魔法常量是 GCC 用于short:godbolt.org/z/4baG7K。有趣的是,对于已签约的部门来说,它只是占据上半场而无需进一步转移;在其他情况下,需要轮班来处理大红利:Divide by 10 using bit shifts? 是该错误的 32 位示例,它仅适用于足够小的红利。还有Why does division by 3 require a rightshift (and other oddities) on x86?. 负数加一,也可以使用进位标志:cmp dh,0x80/sbb dx,-1【参考方案4】:

小学毕业

x/6 = x * 1/6;

看看当你让 C 编译器去做时会发生什么

unsigned short fun ( unsigned short x )

    return(x/6);

32 位 x86

0000000000000000 <fun>:
   0:   0f b7 c7                movzwl %di,%eax
   3:   69 c0 ab aa 00 00       imul   $0xaaab,%eax,%eax
   9:   c1 e8 12                shr    $0x12,%eax
   c:   c3                      retq 

32 位臂

00000000 <fun>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <fun+0x10>
   4:   e0802093    umull   r2, r0, r3, r0
   8:   e1a00120    lsr r0, r0, #2
   c:   e12fff1e    bx  lr
  10:   aaaaaaab

同样的故事。将其转换为 8086。

所以 6 = 3 * 2,所以我们确实需要除以 3。然后调整

unsigned short fun ( unsigned short x )

    return(x/3);


00000000 <fun>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <fun+0x10>
   4:   e0802093    umull   r2, r0, r3, r0
   8:   e1a000a0    lsr r0, r0, #1
   c:   e12fff1e    bx  lr
  10:   aaaaaaab

少一点移位。其中一个转变是提高精度,另一个是因为其中有一个除以 2。

你当然可以做减法循环。否则它是长除法,实际上很容易编码。

【讨论】:

以上是关于没有 div 的 ASM 8086 除法的主要内容,如果未能解决你的问题,请参考以下文章

8086汇编 中断

如何使用库 emu8086.inc 打印除法的其余部分

如何使用库emu8086.inc打印除法的剩余部分

8086汇编0号中断处理程序

实验报告九

逆向课程第四讲逆向中的优化方式,除法原理,以及除法优化上