如何在没有“ADD”指令的情况下将两个寄存器组合在一起

时间:2013-04-10 08:55:35

标签: assembly x86 add

首先,不要打扰“你使用AT& T或Intel汇编吗?”或类似的东西。

我想知道是否可以将两个寄存器(例如AX和BX)的内容一起添加到而不使用ADDADC 指令。我想只使用这套指令来做到这一点:

MOV
CMP
JMP
JE
JNE

我想看一个简单的例子,即使它只是算法而没有任何实际代码,或者是一个很好的教程。

如果你想知道为什么我这么问,那是因为我正在创建一个非常基本的计算机,暂时只提供那些指令,我想知道它是否已经能够将两个寄存器组合在一起。

3 个答案:

答案 0 :(得分:3)

理论上,是的。可以使用您的指令集表达以下非常棒的程序,并执行添加:

if ax = 0 and bx = 0:
  result = 0
else if ax = 0 and bx = 1:
  result = 1
...
else if ax = 42 and bx = 24:
  result = 66
...

在任何实际意义上,你的问题的答案都是“不”。

答案 1 :(得分:3)

修改:可以使用一个258字节的查找表进行任何加法或减法,并且仅使用movcmpjne。对于巨型查找表完全没有必要。使用相同的查找表分别更新低8位和高8位。

以下代码仅使用一个258字节的查找表,仅使用axbxmov来汇总cmpjne: / p>

[bits 64]

; valid instuctions: mov, cmp, jmp, je, jne
; used instuctions: mov, cmp, jne

section .text
global _start

; this code sums ax & bx

_start:

; define the values to be summed (in ax & bx).

        mov     ax,12853        ; example summand 1.
        mov     bx,33276        ; example summand 2.

; summing is easy: just decrement each summand until it becomes zero,
; and for each decrement, increment the sum (in ax).

        cmp     bx,0
        jne     start_summing   ; normally this would be je ready and
                                ; next 2 instructions would be deleted.

                cmp     bx,1    ; this shows that jne is sufficient.
                jne     ready   ; this conditional jump branches always.

start_summing:
        mov     ecx,0

summing_loop:
        mov     cl,bl
        mov     bl,[rcx+(number_line-1)]        ; decrement bl.
        cmp     bl,255
        jne     bl_not_carry

                mov     cl,bh
                mov     bh,[rcx+(number_line-1)]        ; decrement bh.

bl_not_carry:
        mov     cl,al
        mov     al,[rcx+(number_line+1)]        ; increment al.
        cmp     al,0
        jne     al_not_carry

                mov     cl,ah
                mov     ah,[rcx+(number_line+1)]        ; increment ah.

al_not_carry:
        cmp     bx,0
        jne     summing_loop

ready:

; sum is now in eax.

section .data

max_value equ 255
max_value_plus_1 equ (max_value + 1)

db max_value ; 0 - 1 = 255

number_line:

%assign myValue 0

%rep max_value_plus_1
        db myValue
        %assign myValue (myValue + 1)
%endrep

db 0

编辑:其余答案涉及需要更多内存的其他解决方案。

编辑:对于16位操作数的任何加法或减法,一维128 KiB查找表就足够了。无需巨型查找表。 编辑:修正了导致通常设置进位标志的添加产生错误结果的错误。

这是x86-64汇编中的代码,与YASM汇编,也可能与NASM汇编。实现add ax,bx,仅使用movcmp& jne

[bits 64]

; valid commands: mov, cmp, jmp, je, jne
; used commands: mov, cmp, jne

section .text
global _start

; this code sums ax & bx

_start:

; define the values to be summed (in ax & bx).

        mov     ax,12853  ; example summand 1.
        mov     bx,33276  ; example summand 2.

; summing is easy: just decrement each summand until it becomes zero,
; and for each decrement, increment the sum (in ax).

        mov     edx,0
        mov     dx,ax
        mov     eax,edx   ; eax = ax

        mov     ecx,0
        mov     cx,bx     ; ecx = bx

summing_loop:
        mov     cx,[2*rcx+(number_line-2)]      ; decrement ecx.
        mov     ax,[2*rax+(number_line+2)]      ; increment eax.
        cmp     ecx,0
        jne     summing_loop

; sum is now in eax.

section .data

max_value equ 65535

dw max_value ; 0 - 1 = 65535

number_line:

%assign myValue 0

%rep max_value
        dw myValue
        %assign myValue (myValue + 1)
%endrep

dw 0

编辑:其余的答案涉及我最初提出的更有限的解决方案。

可以使用二维查找表来完成。

对于8位寄存器,例如al& bl,这很简单。对于16位寄存器,它可以完成,但查找表将是巨大的(几乎1 tebibyte),请参阅下面的原因。查找表的每个单元格包含相应X&的总和。 Y坐标(X和Y坐标是加数)。

对于 8位和,查找表(256 * 256矩阵)是这样的:

db   0,   1,   2, ... , 253, 254, 255
db   1,   2,   3, ... , 254, 255,   0
db   2,   3,   4, ... , 255,   0,   1
     .    .    .  .       .    .    .
     .    .    .   .      .    .    .
     .    .    .    .     .    .    .
db 253, 254, 255, ... , 250, 251, 252
db 254, 255,   0, ... , 251, 252, 253
db 255,   0,   1, ... , 252, 253, 254

在x86和x86-64中, mov可用于乘以256 ^ n ,即:256,65536,16777216,......

使用mov乘以256非常简单,可以计算ax = 256 * bl

mov ax,0
mov ah,bl

添加例如。 al& bl,我们需要获得正确的偏移量,它是256 * al + bl256 * bl + al(因为查找表是对称矩阵,并且它是对称的,因为加法是可交换的操作)。

在x86 / x86-64中仅使用mov乘以65536和更大的数字需要使用内存,因为无法直接寻址32位通用寄存器的高16位(例如eax)或64位通用寄存器的高32位(例如rax)。

仅使用eax = 65536 * bx计算mov

mov [temp], dword 0
mov [temp+2], bx
mov eax, [temp]

...

temp dd 0

但是16位值的真正问题是在x86 / x86-64内存中使用字节偏移来寻址,而不是使用字/双字/ qword偏移,我们只能乘以 256 ^ n 。但是,如果我们在乘法和字节偏移量寻址方面没有这个问题,那么我们首先看一下查找表的外观。查找表可以是这样的:

dw     0,     1,     2, ... , 65533, 65534, 65535
dw     1,     2,     3, ... , 65534, 65535,     0
dw     2,     3,     4, ... , 65535,     0,     1
       .      .      .  .         .      .      .
       .      .      .   .        .      .      .
       .      .      .    .       .      .      .
dw 65533, 65534, 65535, ... , 65530, 65531, 65532
dw 65534, 65535,     0, ... , 65531, 65532, 65533
dw 65535,     0,     1, ... , 65532, 65533, 65534

这里,每行有65536个单元,每个单元都是dword,因此每行需要2 * 65536个字节= 131072个字节。有65536行,所以它是 65536 * 65536矩阵

字大小的单元格对于X(水平索引,任何一个加法)都不是问题,因为x86程序集允许比例因子为1,2,4和8

编辑:修正了数组大小的文字,实际上比1 TiB小一点。

这里的问题是,仅使用mov不能将Y(垂直索引,另一个加数)乘以131072。因此,查找表的每一行必须重复32768次,或者更准确地说,在任何实际数据行之间必须有32767个未使用的填充行。为什么32767? 因为mov只能用于乘以256,65536,16777216 ...所以我们需要将Y(垂直索引,另一个加数)乘以16777216。每行需要131072字节,为了使每个16777216字节的新数据行开始,每个数据行之后必须有32767个未使用的填充行(每个占用131072个字节)。在最后一个数据行之后不需要填充行,因此总的来说数组大小为:

65535 * 16777216 + 131072 = 10.99 * 10 ^ 12字节=几乎1 tebibyte(1 TiB)

不幸的是,我的计算机中没有那么多内存,但是在x86-64中它是可能的。

以下是仅使用mov 256 * 256查找表的8位加法代码(使用YASM进行测试,也应与NASM一起组装):

[bits 64]

; valid instructions: mov, cmp, jmp, je, jne
; used instructions: mov

section .text
global _start

; al & bl must be preserved

; this code sums al & bl

_start:

; define the values to be summed (in al & bl).

        mov     al,47  ; example first summand
        mov     bl,55  ; example second summand

; the summing code starts here.

        mov     ecx,0
        mov     cl,al  ; ecx = al
        mov     ch,bl  ; ecx = 256 * bl + al
        mov     al,[rcx+sum_look_up_table] ; fetch the sum from look-up table.
                                           ; for 32-bit code, rcx -> ecx
; the sum is now in al.

section .data

y_times equ 256
x_times equ 256

sum_look_up_table:

%assign myY 0

%rep y_times
        %assign myX 0

        %rep x_times
                %assign myValue (myX + myY)
                %rep y_times
                        %if myValue >= 256
                                %assign myValue (myValue - 256)
                        %endif
                %endrep
                db myValue
                %assign myX (myX + 1)
        %endrep

        %assign myY (myY + 1)
%endrep

答案 2 :(得分:2)

  

因为我正在创建一台非常基本的计算机,暂时只提供这些指令,我想知道它是否已经能够将两个寄存器组合在一起。

在这种情况下,您应该阅读What is the minimum instruction set required for any Assembly language to be considered useful?

简而言之,最小可能的架构有only one instruction。但是要更强大有用 非常基本的计算机应该至少有一位操作指令。 NAND只需NORMOV就足够了all logic calculations。有了这个,你也可以做算术,但不如单独的ADD / SUB有效。此外,你需要另一个条件跳转。这总计为3条指令。但是,如果您可以有5个这样的指令,那么您可以在另一个问题上阅读更多更好的指令选择

也就是说,可以在x86和8051之类的许多其他架构中只执行static void alu_add8(char* s, char* x, char* y, char* c, int off) { /* requires dword carry initialized */ print("# alu_add8\n"); debug_id(); print("movl $0, %%eax\n"); print("movl $0, %%ebx\n"); print("movl $0, %%ecx\n"); print("movl $0, %%edx\n"); print("movb (%s+%d), %%al\n", x, off); print("movb (%s+%d), %%cl\n", y, off); print("movl (%s), %%ebx\n", c); print("movb alu_add8l(%%eax,%%ecx), %%dl\n"); print("movb alu_add8h(%%eax,%%ecx), %%dh\n"); print("movb alu_add8l(%%edx,%%ebx), %%al\n"); print("movb %%al, (%s+%d)\n", s, off); print("movb alu_add8h(%%edx,%%ebx), %%al\n"); print("movb %%al, (%s)\n", c); print("# end alu_add8\n"); } ,因为它是proved to be Turing-complete。甚至还有一个编译器可以将有效的C代码编译成只有MOV的程序(或者只有XOR,SUB,ADD,XADD,ADC,SBB,AND / OR,PUSH / POP,1位移位或CMPXCHG / XCHG)名为movfuscator。有关编译器如何工作的详细信息,请参阅Break Me00 The MoVfuscator Turning mov into a soul crushing RE nightmare Christopher Domas,如果没有时间,则只需read the slide。基本上它使用查找表来实现大多数目的。以下是有关如何实现8-bit addition的示例

{{1}}

进一步阅读