【问题标题】:reference variables without constraint inline assembly无约束内联汇编的引用变量
【发布时间】:2021-01-10 18:09:43
【问题描述】:

我想在 C 程序中使用内联汇编,而不使用约束,因为我不想在我足够复杂的学校论文中解释这一点。我试图用一个键异或一些字符。代码如下:

#include <stdio.h>
char text[11] = {'H','e','l','l','o'};
int key = 42;
int e;
char z;
int main()
{
    for (e=0; e<12; e++){
    z = text[e];
    asm volatile(
        "movl $z, %ax;" \
        "movl $key, %bx;" \
        "xor $ax, %bx;" \
        "movl $ax, %[z];");
    printf("%c\n", z);
    }
return 0;
}

但我不断得到: main.c:11:13: error: undefined named operand ‘z’ "movl $key, %ax;" \ 我尝试在变量前面添加static,因为它可能是编译器/链接器优化的东西。我也尝试添加__attribute__((used)),但没有任何帮助。我正在使用 GNU GCC v7.1.1 任何帮助表示赞赏,谢谢。

【问题讨论】:

  • 要解决您遇到的直接问题,请使用 zkey 而不是 %[z]$z$key 来访问变量。请注意,代码仍然不正确,因为您在未通知编译器的情况下破坏了寄存器。
  • 重要的是所需的寻址模式不同(对于 64 位模式,您需要 z(%rip) 而不是 z)。编译器假定在asm 语句之后,所有寄存器都保留其先前的值。您通过覆盖axbx 违反了此假设,导致行为未定义。它现在可能看起来有效,但以后可能会以神秘的方式失败。如果您愿意,我可以写一个概述如何正确执行此操作的答案。
  • 如果您修改任何寄存器或其余代码读取或写入的任何内存变量,就不可能在函数内安全地使用 GNU C Basic asm(无约束)。对于 x86-64,您也不能使用堆栈来推送/弹出,因为这会破坏红色区域。您可能会想出可能会正常工作的代码(在调试版本中),但要使其真正安全是不可能的。编写大量错误的代码而不解释它似乎是一个非常糟糕的主意。
  • (虽然在某些时候 GCC 决定让非空的基本 asm 语句具有隐含的 "memory" clobber,但这没有记录,基本上只是为了使旧代码和/或错误代码发生 -更频繁地工作。不要在新代码中依赖它。gcc.gnu.org/wiki/ConvertBasicAsmToExtended)
  • 这是我学校论文的例子 - 不要写一个任何人都不应该做的事情的例子。 GNU C 内联 asm 已经够难了,人们不会被不安全的例子误导。 永远不要鼓励在任何地方使用 GNU C Basic asm,除非是在全局范围内,或者作为 __attribute__((naked)) 函数的整个主体。两者都与在单独的 .S 文件(您可以从普通 C 中调用)中用 asm 编写整个函数没有根本不同,如果您想避免处理约束,您实际上应该这样做。

标签: c assembly gcc compiler-errors inline-assembly


【解决方案1】:

如果可能,请完全避免使用内联汇编。

如果必须使用内联汇编,请尽可能使用扩展内联汇编,并声明正确的操作数和破坏列表。在您的特定情况下,这样的事情会起作用:

#include <stdio.h>
char text[11] = {'H','e','l','l','o'};
int key = 42;
int e;
char z;
int main()
{
    for (e=0; e<12; e++){
        z = text[e];
        asm (
            "movzbl %0, %%eax;" \
            "movl %1, %%ebx;" \
            "xor %%ebx, %%eax;" \
            "movb %%al, %0;"
            : "+m"(z) : "m"(key) : "eax", "ebx");
        printf("%c\n", z);
    }

    return 0;
}

这里,"+m"(z) 是一个输出操作数,而"m"(key) 是一个输入操作数。 "eax", "ebx"clobber 列表,指示您的代码覆盖哪些寄存器。由于asm 语句的所有副作用都通过其输出操作数和clobbers 显式显示,因此可以省略volatile 关键字,以便为编译器提供更大的灵活性。

请注意,除非要求 keyze 是全局变量,否则最好将它们变成自动变量并使用附加的 ri 约束输入和输出操作数,让编译器在选择保存变量的位置时更加灵活。

还要注意更正的操作数大小。 key 是 32 位变量,z 是 8 位变量。最后,我已经修复了你的xor 指令。你不小心切换了操作数。

请注意,汇编代码可以做得更好。一种更高效的代码渲染方式如下:

#include <stdio.h>
char text[11] = {'H','e','l','l','o'};
int key = 42;
int e;
char z;
int main()
{
    for (e=0; e<12; e++){
        z = text[e];
        asm (
            "xorb %b1, %0"
            : "+m"(z) : "r"(key));
        printf("%c\n", z);
    }

    return 0;
}

这会将key 从内存中加载到 C 编译器,使其能够就变量的保存位置做出更明智的选择。将keyz 设为自动变量可能也是一个好主意,但我不确定在您可能遇到的一些未命名的约束下是否允许这样做。

【讨论】:

  • key 的约束可以是 "ir" 以允许立即。此外,asm 语句不必一定是volatile,因为您想要的唯一效果是修改显式操作数之一"+m"(z)。并且变量都可以是本地的;我认为 OP 只是让它们成为全局的,所以它们会有 asm 符号名称。
  • 另外,我会从您使用扩展 Asm 的建议中删除“如果可能”。认真地不要将 Basic Asm 作为一个有效的选项,因为它确实不是没有跳过特别的箍(比如 add $-128, %rsp 跳过红色区域,然后保存你想要使用的所有 regs,破坏效率使其内联而不是单独的函数)。
  • 更根本的是,读取或修改全局变量(您可以做的唯一事情)是不安全的,除非您使用的是 GCC 版本(不是 clang AFAIK)这恰好有几年前的补丁,可以将非空基本asm("...") 语句视为有"memory" clobber。在较旧的 GCC(以及一般的 GCC)上,实际上没有安全的方法可以从函数内部的基本 asm 中接触任何 C 状态。
  • @PeterCordes 我不明白“如果必须使用内联汇编,请尽可能使用扩展内联汇编并声明正确的操作数和破坏列表。”可以阅读以支持基本的内联汇编作为可行的替代方案。剩下的部分我会做一些文案编辑。
  • 这让某人有可能说服自己扩展 asm 出于某种原因对他们来说“不可能”,然后继续使用基本 asm。您的措辞暗示有一个有效的后备选项。例如就像Inline assembly statements in C code and extended ASM for ARM Cortex architectures 一样,有人有一个现有的无约束语句代码库,比如asm("dmb"),可能适用于 ARMcc,并希望让它们在 GNU C 中工作。我的回答指出,这只适用于依赖于内隐记忆破坏者
猜你喜欢
  • 2011-04-23
  • 2015-12-23
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多