【问题标题】:How to ignore output in inline assembly?如何忽略内联汇编中的输出?
【发布时间】:2021-05-21 14:56:10
【问题描述】:

在内联汇编中,第一个 : 指的是输出,第二个是输入,如果我不想使用输出怎么办?我可以像这样把它留空吗:

asm ("add $0, %rcx"
:
:"m"(Example) /* intput */
);

另外,如果我只想使用输出,我可以删除另一个:吗?

【问题讨论】:

  • 可以留空,但不能删除另一个冒号
  • 其他情况我删不掉?
  • 如果你只想使用输出,你可以删除多余的冒号。如果只想使用输入,则不能

标签: c gcc inline-assembly


【解决方案1】:

如果冒号后面的所有部分都为空,则可以将输出部分留空并省略冒号。

引用Extended Asm (Using the GNU Compiler Collection (GCC)):

asm asm-qualifiers ( AssemblerTemplate 
                 : OutputOperands 
                 [ : InputOperands
                 [ : Clobbers ] ])

如果没有输出操作数但有输入操作数,则在输出操作数所在的位置放置两个连续的冒号:

__asm__ ("some instructions"
   : /* No outputs. */
   : "r" (Offset / 8));

请注意,您通常需要告诉编译器您修改的寄存器,无论是输出操作数还是clobber。您可以在同一行放置多个冒号,例如
asm("" ::: "memory") 是编写编译器屏障的常用方法。

【讨论】:

  • 这是一个危险的答案;您仍然必须告诉编译器您踩到的所有寄存器。例如,问题中的代码可以使用"rcx" clobber 而不是虚拟的"=c" 输出,但其中一个是必需的,否则你会踩到编译器的脚趾(UB)。
  • 我也许应该写一个单独的答案;在我编辑您的答案以提及这一点时,我注意到问题中的代码存在更多问题,并且最终写的不仅仅是 RCX 破坏者。我很好地为您的回答做出了贡献,但是如果您不希望它在那里,请告诉我;我可以将其复制到单独的答案中。
  • @PeterCordes 这对我来说似乎是另一个答案,我认为你的答案应该是你的。请分开你的答案。
【解决方案2】:

GCC 确定哪些操作数是输出还是来自冒号的输入,而不是从缺少 "=""+" 推断出来的。是的,这是多余的,但不,您对此无能为力。

您可以将多个冒号放在同一行,例如
asm("" ::: "memory") 是编写编译器屏障的常用方法。因此,包含必要的冒号并不痛苦。你可以asm("..." :: "m"(input) );,如果这实际上是安全的,没有任何输出或破坏。

但是你通常需要告诉编译器你修改了一个寄存器,无论是输出操作数还是clobber,所以你经常需要所有三个冒号。或者,如果您不编写任何寄存器,通常是因为您正在包装一些“系统”指令,这些指令具有某种效果,您通常不希望编译器重新排序内存访问。喜欢invlpg。或者例如:

asm("clflush %0" ::"m"(*ptr) : "memory");

应该有一个内存破坏器来在任何较早的存储(可能是同一行)之后命令缓存行刷新。


您的代码有一些语法错误,以及正确性/UB,至少如果您打算添加内存操作数,而不是常量0

在 GNU C Extended Asm 中,模板字符串非常类似于 printf 格式字符串,供编译器替换您使用 %something 的操作数。 (是的,创建文本以提供给汇编器 as 只是一个愚蠢的文本替换。GCC 不“理解”你的 asm,这就是为什么你必须使用 input/output/clobber 向编译器准确地描述它约束)。

如果您需要文字 %,例如 AT&T 语法中的寄存器名称,例如 %rcx,您必须实际编写 %%rcx

(通常最好避免硬编码寄存器;使用虚拟输出操作数让编译器选择要使用的寄存器。您甚至可以命名它们,例如%[input]

我假设您打算将内存源操作数添加到 RCX。那将是 %0
$0 是立即数 0,即 RCX += 0,因此该指令实际上只修改 FLAGS。

假设您的意思是 add %0, %%rcx,您的代码会写入一个寄存器 (RCX)。 必须告诉编译器您修改的寄存器/内存。否则它可能在 RCX 中有一个 C 变量,并期望在 asm 语句之后读取它的值。所以无论如何你都需要一个(虚拟)输出或一个clobber。

(如果您确实实际上是指"add $0, %%rcx",那么唯一的架构效果就是设置 FLAGS。i386 / amd64 的内联 asm 已经隐含了一个 "cc" clobber,所以我们没有告诉编译器该副作用。)

您对"add %0, %%rcx" 的安全选择包括:

使用破坏者:

asm ("add %0, %%rcx"    // %0 expands the first operand, $0 was an immediate
    :
    : "Irm"(Example) /* input, also allow reg or 32-bit immediate */
    : "rcx"
);

使用虚拟输出操作数(并将其设为volatile,就像没有输出操作数时隐含的那样)。请注意,我们可以省略 asm 语句的 : clobbers 部分。

uint64_t dummy;
asm volatile ("add %0, %%rcx"
    : "=c"(dummy);       // "c" forces picking cl/cx/ecx/rcx based on size
    : "Irm"(Example)
);
// without volatile, the asm statement can be optimized away if you don't read dummy later

请注意,添加周围的push %%rcx ; pop %%rcx 并不安全:它会修改 RSP,这可能会影响编译器为 "m"(Example) 选择的寻址模式,并且它会踩到 RSP 下方的红色区域。

请参阅https://stackoverflow.com/tags/inline-assembly/info 了解更多信息。

【讨论】:

    猜你喜欢
    • 2018-01-16
    • 2012-08-03
    • 2013-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多