GCC内联汇编乘法

Cha*_*l72 5 c x86 assembly integer-overflow inline-assembly

我正在尝试在Linux(x86)上学习GCC内联汇编,我的第一个实验是尝试实现乘法的整数溢出检测.这似乎很容易,但它有副作用,我不明白.

所以,在这里我想要将两个无符号8位整数相乘,并查看结果是否溢出.基本上我只是将第一个操作数加载到AL寄存器中,将另一个操作数加载到BL寄存器中,然后使用该mul指令.结果存储为AX寄存器中的16位值.所以我然后将AX寄存器中的值复制到我的C变量b,除非它溢出.如果它溢出我设置c为1.

 uint8_t a = 10;
 uint8_t b = 25;
 uint8_t c = 0; // carry flag

 __asm__
 (
  "clc;"                  // Clear carry flag
  "movb %3, %%al;"        // Load b into %al
  "movb %2, %%bl;"        // Load a into %bl 
  "mul %%bl;"             // Multiply a * b (result is stored in %ax)
  "movw %%ax, %0;"        // Load result into b
  "jnc out;"              // Jump to 'out' if the carry flag is not set
  "movb $1, %1;"          // Set 'c' to 1 to indicate an overflow
  "out:"
  :"=m"(b), "=m"(c)       // Output list
  :"ir"(a), "m"(b)        // Input list
  :"%al", "%bl"           // Clobbered registers (not sure about this)
 );
Run Code Online (Sandbox Code Playgroud)

这似乎工作正常.如果我printf'b'的值我得到250,这是正确的.此外,如果我将'b'的起始值更改为26,则在乘法c设置为1之后,表示当然是溢出(10*26> ~uint8_t(0)).我看到的问题是C变量a在乘法后设置为0(或溢出时为1).我不明白为什么a我会在这里做任何改变.它甚至不在输出变量列表中,那么为什么我的汇编例程会影响值a?

此外,我不确定被破坏的寄存器列表.该列表应该告知GCC有关在汇编例程中使用的任何寄存器,以便GCC不会尝试错误地使用它们.我想我需要告知GCC我使用了AL和BL寄存器,但AX寄存器怎么样?它隐式用于存储两个8位整数的乘积,所以我是否需要将它包含在被破坏的寄存器列表中?

Mat*_*ery 7

我看到的问题是C变量a在乘法后设置为0(或溢出时为1).我不明白为什么a我会在这里做任何改变.它甚至不在输出变量列表中,那么为什么我的汇编例程会影响值a?

mul %%bl 将AL(8位)乘以BL(8位),将结果置于AX(16位).

注意,AL和AX不是单独的寄存器:AL只是AX的底部8位.

movw %%ax, %0存储AX(16个比特)到的地址b......这是一个uint8_t.因此,该指令也会用结果的前8位覆盖存储器中的下一个字节.在这种情况下,该字节恰好a是存储值的位置(这解释了为什么a在不溢出时被0覆盖,而在它溢出时为1).

您需要将其替换为movb %%al, %0,仅对结果的最后8位执行字节存储.

我想我需要告知GCC我使用了AL和BL寄存器,但AX寄存器怎么样?它隐式用于存储两个8位整数的乘积,所以我是否需要将它包含在被破坏的寄存器列表中?

是的 - 您应该告诉GCC有关您更改其值的任何寄存器(并且,正如nategoose在另一个答案中指出的那样,您应该告诉它您也在更改标志).所以这里的clobber列表应该是"%ax", "%bl", "cc"(AX包括AL,所以你不需要明确提到AL).


nat*_*ose 2

您应该使用该选项编译代码-S并查看 *.s 文件。所有的程序集都在同一行上,并用分号分隔,我相信分号在 gnu 汇编器中开始注释。您需要将“\n”(或者更好的是“\n\t”)添加到所有汇编指令的末尾。

您可能还想将“cc”添加到破坏列表中。

此外,GCC 有一种方法可以指定输入既是您可能感兴趣的程序集的输入又是输出。

另外,最好让 GCC 决定输入的位置,而不是强制它们位于内存中。我似乎记得GCC的内联汇编对x86有一个限制,这意味着“要么在寄存器中,要么在内存中”,可以用于无关紧要的情况,但你可能不应该有很多“mov”指令内联汇编的开始和结束,因为 GCC 最大的工作之一是找出放置在实际计算指令之间的最佳移动指令集。例如,在您的代码中,GCC 要做的最好的事情就是将常量 10 和 25 存储在您开始使用的寄存器中。