汇编乘法循环返回错误的高数
Assembly multiplication loop returning wrong high number
我正在尝试编写一个 for 循环,它通过将一个数字 (var a) 乘以另一个数字 (var b) 次来进行乘法运算。
.globl times
times:
movl [=10=], %ecx # i = 0
cmpl %ecx, %esi #if b-i
jge end # if >= 0, jump to end
loop:
addl (%edi, %eax), %eax #sum += a
incl %ecx # i++
cmpl %esi, %ecx # compare (i-b)
jl loop # < 0? loop b times total
end:
ret
我哪里错了?我已经 运行 通过逻辑,我无法弄清楚问题是什么。
这是伪代码,请牢记。
mov X,ebx <- put into EBX your counter, your B
mov Y,edx <- put into EDX your value, your A
mov 0,eax <- Result
loop:
add eax,edx
dec ebx
jnz loop <- While EBX is not zero
上面的实现应该会导致你的值进入 EAX。您的代码似乎缺少 eax 初始化。
TL:DR:您没有将 EAX 归零,并且您的 ADD 指令正在使用内存操作数。
你应该用过调试器。您很容易看出 EAX 一开始就不是零。有关使用 gdb 调试 asm 的提示,请参阅 x86 标记 wiki 的底部。
我猜你正在使用 x86-64 系统 V ABI,所以你的参数(a 和 b)在 %edi 和 %esi 中。
在一个函数的开头,除了保存你的参数的寄存器之外,其他的寄存器应该被假定为包含垃圾。即使是 是 保存参数的寄存器的高位部分也可能包含垃圾。 (此规则的例外情况:)
两个 arg 都不是指针,因此您不应该取消对它们的引用。 add (%edi, %eax), %eax
计算一个 32 位地址为 EDI+EAX,然后从那里加载 32 位。它将那个双字添加到 EAX(目标操作数)。
令我震惊的是您的程序没有出现段错误,因为您将整数 arg 用作指针。
对于许多 x86 指令(如 ADD),目标操作数不是只写的。 add %edi, %eax
确实 EAX += EDI
。我认为您混淆了 3 操作数 RISC 语法,其中您可能有像 add %src1, %src2, %dst
.
这样的指令
x86 有一些这样的指令,作为最近的扩展添加,比如 BMI2 bzhi
, but the usual instructions are all 2-operand with destructive destinations. (except for LEA, where instead of loading from the address, it stores the address in the destination. So lea (%edi, %eax), %eax
would work. You could even put the result in a different register. LEA is great for saving MOV instructions by doing shift+add and a mov all in one instruction, using the addressing mode syntax and machine-code encoding.
您有一条评论 ie eax = sum + (a x 4bits)
。不知道你在说什么。 a 是 4 个字节(不是位),并且你没有将 a (%edi) 乘以任何东西。
只是为了好玩,下面是我编写函数的方式(如果我不得不避免 imul %edi, %esi
/ mov %esi, %eax
)。为了简单起见,我假设两个参数都是非负的。如果您的参数是有符号整数,并且如果 b
为负则您必须循环 -b
次,那么您需要一些额外的代码。
# args: int a(%edi), int b(%esi) # comments are important for documenting inputs/outputs to blocks of code
# return value: product in %eax
# assumptions: b is non-negative.
times:
xor %eax, %eax # zero eax
test %esi, %esi # set flags from b
jz loop_end # early-out if it's zero
loop: # do{
add %edi, %eax # sum += a,
dec %esi # b-- (setting flags based on the result, except for CF so don't use ja or jb after it)
jge loop # }while(b>=0)
loop_end:
ret
注意缩进样式,这样很容易找到分支目标。有些人喜欢为循环内的指令额外缩进。
你的方法工作正常(如果你做对了),但我的方法说明在 asm 中倒计时更容易(不需要额外的寄存器或立即数来保持上限)。此外,避免冗余比较。但是在您编写出至少可以正常工作的代码之前,不要担心优化。
我正在尝试编写一个 for 循环,它通过将一个数字 (var a) 乘以另一个数字 (var b) 次来进行乘法运算。
.globl times
times:
movl [=10=], %ecx # i = 0
cmpl %ecx, %esi #if b-i
jge end # if >= 0, jump to end
loop:
addl (%edi, %eax), %eax #sum += a
incl %ecx # i++
cmpl %esi, %ecx # compare (i-b)
jl loop # < 0? loop b times total
end:
ret
我哪里错了?我已经 运行 通过逻辑,我无法弄清楚问题是什么。
这是伪代码,请牢记。
mov X,ebx <- put into EBX your counter, your B
mov Y,edx <- put into EDX your value, your A
mov 0,eax <- Result
loop:
add eax,edx
dec ebx
jnz loop <- While EBX is not zero
上面的实现应该会导致你的值进入 EAX。您的代码似乎缺少 eax 初始化。
TL:DR:您没有将 EAX 归零,并且您的 ADD 指令正在使用内存操作数。
你应该用过调试器。您很容易看出 EAX 一开始就不是零。有关使用 gdb 调试 asm 的提示,请参阅 x86 标记 wiki 的底部。
我猜你正在使用 x86-64 系统 V ABI,所以你的参数(a 和 b)在 %edi 和 %esi 中。
在一个函数的开头,除了保存你的参数的寄存器之外,其他的寄存器应该被假定为包含垃圾。即使是 是 保存参数的寄存器的高位部分也可能包含垃圾。 (此规则的例外情况:
两个 arg 都不是指针,因此您不应该取消对它们的引用。 add (%edi, %eax), %eax
计算一个 32 位地址为 EDI+EAX,然后从那里加载 32 位。它将那个双字添加到 EAX(目标操作数)。
令我震惊的是您的程序没有出现段错误,因为您将整数 arg 用作指针。
对于许多 x86 指令(如 ADD),目标操作数不是只写的。 add %edi, %eax
确实 EAX += EDI
。我认为您混淆了 3 操作数 RISC 语法,其中您可能有像 add %src1, %src2, %dst
.
x86 有一些这样的指令,作为最近的扩展添加,比如 BMI2 bzhi
, but the usual instructions are all 2-operand with destructive destinations. (except for LEA, where instead of loading from the address, it stores the address in the destination. So lea (%edi, %eax), %eax
would work. You could even put the result in a different register. LEA is great for saving MOV instructions by doing shift+add and a mov all in one instruction, using the addressing mode syntax and machine-code encoding.
您有一条评论 ie eax = sum + (a x 4bits)
。不知道你在说什么。 a 是 4 个字节(不是位),并且你没有将 a (%edi) 乘以任何东西。
只是为了好玩,下面是我编写函数的方式(如果我不得不避免 imul %edi, %esi
/ mov %esi, %eax
)。为了简单起见,我假设两个参数都是非负的。如果您的参数是有符号整数,并且如果 b
为负则您必须循环 -b
次,那么您需要一些额外的代码。
# args: int a(%edi), int b(%esi) # comments are important for documenting inputs/outputs to blocks of code
# return value: product in %eax
# assumptions: b is non-negative.
times:
xor %eax, %eax # zero eax
test %esi, %esi # set flags from b
jz loop_end # early-out if it's zero
loop: # do{
add %edi, %eax # sum += a,
dec %esi # b-- (setting flags based on the result, except for CF so don't use ja or jb after it)
jge loop # }while(b>=0)
loop_end:
ret
注意缩进样式,这样很容易找到分支目标。有些人喜欢为循环内的指令额外缩进。
你的方法工作正常(如果你做对了),但我的方法说明在 asm 中倒计时更容易(不需要额外的寄存器或立即数来保持上限)。此外,避免冗余比较。但是在您编写出至少可以正常工作的代码之前,不要担心优化。