3

首先,不要为“您使用 AT&T 还是 Intel 组件?”而烦恼。或类似的事情。

我想知道是否可以在不使用ADDorADC指令的情况下将两个寄存器的内容相加,比如 AX 和 BX。我想只使用这组指令来做到这一点:

MOV
CMP
JMP
JE
JNE

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

如果您想知道我为什么要问这个问题,那是因为我正在创建一台非常基本的计算机,目前仅提供这些指令,并且我想知道它是否已经能够将两个寄存器添加在一起。

4

3 回答 3

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
...

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

于 2013-04-10T09:19:19.407 回答
3

编辑:任何加法或减法都可以使用258 字节查找表并仅使用mov,cmpjne. 完全不需要巨大的查找表。低 8 位和高 8 位使用相同的查找表分别更新。

这是总结axbx仅使用一个 258 字节查找表并且仅mov,cmp和的代码jne

[位 64]

; 有效指令:mov、cmp、jmp、je、jne
; 使用指令:mov、cmp、jne

部分 .text
全局_start

; 这段代码总结了 ax & bx

_开始:

; 定义要求和的值(在 ax 和 bx 中)。

        移动斧头,12853;示例总和 1。
        mov bx,33276 ; 示例总和 2。

; 求和很容易:只需递减每个求和,直到它变为零,
; 并且对于每次递减,增加总和(以 ax 为单位)。

        cmp bx,0
        jne start_summing ; 通常这将是 je 准备好并且
                                ; 接下来的 2 条指令将被删除。

                cmp bx,1 ; 这表明 jne 就足够了。
                准备好了;这个条件跳转总是分支。

开始求和:
        移动 ecx,0

求和循环:
        mov cl,bl
        mov bl,[rcx+(number_line-1)] ; 减少bl。
        CMP BL,255
        jne bl_not_carry

                mov cl,bh
                mov bh,[rcx+(number_line-1)] ; 减少 bh。

bl_not_carry:
        mov cl,al
        移动,[rcx+(number_line+1)]; 增加的。
        cmp al,0
        jne al_not_carry

                mov cl,ah
                mov 啊,[rcx+(number_line+1)] ; 增量啊。

al_not_carry:
        cmp bx,0
        jne summing_loop

准备好:

; sum 现在在 eax 中。

节 .data

最大值等于 255
max_value_plus_1 equ (max_value + 1)

数据库最大值;0 - 1 = 255

number_line:

%分配我的价值 0

%rep max_value_plus_1
        数据库 myValue
        %assign myValue (myValue + 1)
%endrep

分贝 0

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

编辑:一维 128 KiB 查找表足以用于 16 位操作数的任何加法或减法。不需要巨大的查找表。 编辑:修复了导致通常设置进位标志的添加产生错误结果的错误。

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

[位 64]

; 有效命令:mov、cmp、jmp、je、jne
; 使用的命令:mov、cmp、jne

部分 .text
全局_start

; 这段代码总结了 ax & bx

_开始:

; 定义要求和的值(在 ax 和 bx 中)。

        移动斧头,12853;示例总和 1。
        mov bx,33276 ; 示例总和 2。

; 求和很容易:只需递减每个求和,直到它变为零,
; 并且对于每次递减,增加总和(以 ax 为单位)。

        移动 edx,0
        mov dx,ax
        移动 eax,edx ; eax = 斧头

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

求和循环:
        mov cx,[2*rcx+(number_line-2)] ; 递减 ecx。
        mov ax,[2*rax+(number_line+2)] ; 增加eax。
        cmp ecx,0
        jne summing_loop

; sum 现在在 eax 中。

节 .data

最大值等于 65535

dw 最大值;0 - 1 = 65535

number_line:

%分配我的价值 0

%rep 最大值
        dw 我的价值
        %assign myValue (myValue + 1)
%endrep

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-64mov中可用于乘以 256^n,即:256, 65536, 16777216, ...

乘以 256mov很容易计算ax = 256 * bl

mov ax,0
mov ah,bl

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

仅在 x86/x86-64 中使用乘以 65536 和更大的数字mov需要使用内存,因为无法直接寻址 32 位通用寄存器(例如 eax)的高 16 位或 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 中,内存是使用字节偏移量来寻址的,而不是使用 word/dword/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 TB (1 TiB)

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

这是仅使用 8 位加法mov256 * 256 查找表的代码(使用 YASM 测试,也应使用 NASM 组装):

[位 64]

; 有效指令:mov、cmp、jmp、je、jne
; 使用说明:mov

部分 .text
全局_start

; al & bl 必须保留

; 这段代码总结了 al 和 bl

_开始:

; 定义要求和的值(在 al 和 bl 中)。

        移动,47; 示例第一个加法
        mov bl,55 ; 第二个加法示例

; 求和代码从这里开始。

        移动 ecx,0
        移动 cl,al ; ecx = 人
        mov ch,bl ; ecx = 256 * bl + al
        移动,[rcx+sum_look_up_table] ; 从查找表中获取总和。
                                           ; 对于 32 位代码,rcx -> ecx
; 总和现在是 al。

节 .data

y_time 等于 256
x_times equ 256

sum_look_up_table:

% 分配我的 0

%rep y_times
        %分配我的X 0

        %rep x_times
                %assign myValue (myX + myY)
                %rep y_times
                        %if myValue >= 256
                                %assign myValue (myValue - 256)
                        %万一
                %endrep
                数据库 myValue
                %assign myX (myX + 1)
        %endrep

        %assign myY (myY + 1)
%endrep
于 2013-04-11T22:47:52.377 回答
2

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

在这种情况下,您应该阅读什么是任何汇编语言被认为有用所需的最低指令集?

简而言之,最小可能的架构只有一条指令。然而,为了更有,一台非常基本的计算机应该至少有一个位操作指令。只需一个NANDorNOR就足以完成所有逻辑计算。有了它,您也可以进行算术运算,但不如单独的 ADD/SUB 高效。此外,您还需要另一个条件跳转。总共有 3 条指令。但是,如果您可以有 5 条这样的指令,那么您可以在另一个问题上阅读许多更好的指令选择

也就是说,仅MOV在 x86 和许多其他架构(如8051 )中就可以做任何事情,因为它已被证明是图灵完备的。甚至还有一个编译器可以将有效的 C 代码编译成只有 MOV(或只有 XOR、SUB、ADD、XADD、ADC、SBB、AND/OR、PUSH/POP、1 位移位或 CMPXCHG/XCHG 之一)的程序命名为movfuscatorBreak Me00 The MoVfuscator Turning mov into a soul crushing RE nightmare Christopher Domas中解释了编译器如何工作的详细信息,或者如果您没有时间,请阅读幻灯片。基本上它为大多数目的使用查找表。这是一个关于如何实现8位加法的示例

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");
}

进一步阅读

于 2013-08-03T01:01:25.943 回答