【问题标题】:GCC Inline Assembly MultiplicationGCC 内联汇编乘法
【发布时间】:2010-10-07 17:04:32
【问题描述】:

我正在尝试在 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 位整数的乘积,所以我需要将它包含在被破坏的寄存器列表中吗?

【问题讨论】:

  • 不要将值加载到 %al%bl 并标记它们已被破坏,您应该设置约束,以便 gcc 将参数放在正确的寄存器中开始。

标签: c assembly x86 inline-assembly integer-overflow


【解决方案1】:

我看到的问题是 C 变量 a 在乘法之后设置为 0(或者在 溢出。)我不明白为什么我在这里所做的任何事情都会改变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)。

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • GNU 汇编器语法根据目标而变化。分号作为 x86 的分隔符可以正常工作,但在其他地方可能不行。
    猜你喜欢
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 2019-05-11
    • 2015-12-23
    • 1970-01-01
    相关资源
    最近更新 更多