5

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

所以,这里我想将两个无符号的 8 位整数相乘,看看结果是否溢出。基本上我只是将第一个操作数加载到 AL 寄存器中,将另一个操作数加载到 BL 寄存器中,然后使用该mul指令。结果作为 16 位值存储在 AX 寄存器中。所以我然后将 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)
 );

这似乎工作正常。如果我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 位整数的乘积,所以我需要将它包含在被破坏的寄存器列表中吗?

4

2 回答 2

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)。

于 2010-10-07T22:05:27.677 回答
2

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

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

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

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

于 2010-10-07T17:46:31.003 回答