C 和 asm 中的 imulq 和 unsigned long long 溢出检测 [英] imulq and unsigned long long overflow detection in C and asm

查看:66
本文介绍了C 和 asm 中的 imulq 和 unsigned long long 溢出检测的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为组装新手,我使用 gcc 进行逆向工程.但是现在我遇到了一个有趣的问题:我尝试将 x86-64 的两个 64 位整数相乘.C - 代码如下所示:

As somebody new to assembly, I use gcc for reverse engineering. But now I ran in a somehow funny problem: I try to multiply two 64bit-integer for x86-64. The C - code looks as follows:

unsigned long long 
val(unsigned long long a, unsigned long long b){
    return a*b;
}

并用gcc编译:

val:
    movq    %rdi, %rax
    imulq   %rsi, %rax
    ret

对无符号整数使用有符号乘法可能违反直觉,但它适用于 C.

It might be counterintuitive to use signed multiplication for unsigned integers, but it works for C.

但是,我想检查乘法是否溢出.现在,如果结果大于 2^63-1(我猜是因为它毕竟是有符号乘法),则设置溢出标志.但是对于无符号 64 位,只要结果不大于 2^64-1,这仍然可以.

However, I would like to check the multiplication for overflows. Now, the overflow flag is set if the result is greater than 2^63-1 (I guess because it is signed multiplication after all). But for unsigned 64bit this would be still OK as long as the result is not greater than 2^64-1.

在这种情况下进行乘法(在汇编中)的正确方法是什么?

What is the right way to do the multiplication (in assembly) in this case?

推荐答案

当两个值相乘时,结果的最低有效位完全相同,无论是无符号乘法还是有符号乘法.因此,如果将两个 32 位值相乘,将得到 64 位结果,无论乘法是有符号还是无符号,它们的低 32 位都是相同的.64 位乘法也是一样,它产生 128 位结果,两者的低 64 位是相同的.

When multiplying two values, the least significant bits of the result are exactly the same, whether you do unsigned or signed multiplication. So, if you multiply two 32-bit values, you get a 64-bit result, the low 32-bits of which are the same, whether the multiplication is signed or unsigned. Same thing for a 64-bit multiplication, which produces a 128-bit result, the lower 64-bits of which are identical in both cases.

因此,编译器通常对两种乘法都使用 IMUL 指令(其助记符建议有符号乘法),因为它比 MUL 更灵活,而且通常更快.而 MUL 只有一种形式(允许任意通用寄存器或内存位置乘以隐含的目标寄存器 AL/AX/EAX/RAX),IMUL有多种形式,包括单操作数形式(与 MUL 相同)、双操作数形式(寄存器或内存 × 寄存器或内存或立即数)和三操作数形式(寄存器或内存)memory ×immediate,将结果存储在第三个目标寄存器中).英特尔的文档中提供了更多详细信息(请参阅 标记 wiki 链接),或快速参考 MULIMUL.

As such, compilers often use the IMUL instruction (whose mnemonic suggests signed multiplication) for both types of multiplication because it is more flexible than MUL, and generally faster. Whereas MUL comes in only one form (allowing an arbitrary general-purpose register or memory location to be multiplied by the implied destination register AL/AX/EAX/RAX), IMUL has many forms, including a one-operand form (same as MUL), a two-operand form (register or memory × register or memory or immediate), and a three-operand form (register or memory × immediate, storing the result in a third destination register). More details are available in Intel's documentation (see the x86 tag wiki for links), or quick reference for MUL and IMUL.

编译器可以一直使用 IMUL 的原因是因为您丢弃了结果的高位.当你做一个 32 位的 ×32 位乘法并将结果存储在 32 位变量中,整个 64 位结果的高 32 位被丢弃.同样,对于 64 位 ×64位乘法,丢弃128位结果的高64位,只留下低64位,无论是有符号乘法还是无符号乘法都是一样的.

The reason the compiler can get away with using IMUL all the time is because you throw away the high-order bits of the result. When you do a 32-bit × 32-bit multiplication and store the result in a 32-bit variable, the upper 32-bits of the entire 64-bit result were discarded. Again, same for a 64-bit × 64-bit multiplication, which discards the upper 64-bits of the 128-bit result, leaving only the lower 64-bits, which are the same whether it is a signed or unsigned multiply.

引自英特尔手册:

[IMUL] 的二操作数和三操作数形式也可用于无符号操作数,因为无论操作数是有符号还是无符号,乘积的下半部分都是相同的.但是,CF 和 OF 标志不能用于确定结果的上半部分是否为非零.

The two- and three-operand forms [of IMUL] may also be used with unsigned operands because the lower half of the product is the same regardless if the operands are signed or unsigned. The CF and OF flags, however, cannot be used to determine if the upper half of the result is non-zero.

Peter Cordes 在他的关于补码算术运算的一个非常普遍的问题的更大答案的一部分中也很好地解释了这一点.

Peter Cordes has also explained this very well in a section of his larger answer to a very general question on two's-complement arithmetic operations.

无论如何,在自己编写汇编代码时,您必须决定是否要执行编译器所做的相同操作并丢弃产品的高位,还是要保留它们.如果您不关心高位并假设操作不会溢出,请编写与编译器相同的代码.

Anyway, when writing the assembly code yourself, you have to decide whether you want to do the same thing the compiler does and throw away the upper bits of the products, or whether you want to keep them. If you don't care about the upper bits and assume that the operation will not overflow, write the same code as the compiler does.

如果您确实关心高位,只需使用 MUL 指令,如果乘积大于其操作数的类型,该指令将设置 CF 和 OF 标志.

If you do care about the upper bits, just use the MUL instruction, which sets the CF and OF flags if the product of the multiplication is larger than can fit into the type of its operands.

mov  rax, QWORD PTR [a]   ; put 64-bit operand 'a' into RAX
mov  rbx, QWORD PTR [b]   ; put 64-bit operand 'b' into RBX
mul  rbx                  ; multiply 'a' * 'b'
; 128-bit result is returned in RDX:RAX (upper-bits:lower-bits)

jo  ProductOverflowed

在这里使用 MUL 几乎肯定比尝试找到一种使用 IMUL 的方法并在之后测试高 64 位以查看它们是否为非零更有效(这将表明溢出).与使用 IMUL 节省的 1 或 2 μops 相比,简单地拥有一个不可预测的分支会使您的性能落后.

Using MUL here is almost certainly more efficient than trying to find a way to use IMUL and testing the high 64-bits afterwards to see if they are non-zero (which would indicate an overflow). Simply having a non-predictable branch would put you way behind in performance, compared to the 1 or 2 μops you would stand to save with IMUL.

这篇关于C 和 asm 中的 imulq 和 unsigned long long 溢出检测的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆