【问题标题】:The code after conversion does not execute a given action [duplicate]转换后的代码不执行给定的操作[重复]
【发布时间】:2021-03-28 11:45:35
【问题描述】:

我的任务是根据 AT&T 语法将某个代码从 C++ 转换为 ASM,所以我从简单的示例开始,遇到了第一个问题。

我开始练习的代码

void func() {
  int num = 1;
  std::cout << "before: " << num << std::endl;
  num = num << 3;
  std::cout << "after: " << num << std::endl;
}

给出一个结果:

before: 1
after: 8

我的翻译 变量 num 是第一个局部变量,所以它应该在地址 -4(%ebp)

void func() {
  int num = 1;
  std::cout << "before: " << num << std::endl;
  asm (
    "mov -4(%ebp), %eax         \n"
    "sall $3, %eax              \n"
    "mov %eax, -4(%ebp)         \n"
  );
  std::cout << "after: " << num << std::endl;
}

给出一个结果:

before: 1
after: 1

为什么这段代码对 num var 没有影响?

【问题讨论】:

  • 我已回滚您的编辑。您不应将第二个问题编辑为现有问题,尤其是在第一个问题已经有(正确)答案的情况下。如果您有新问题,请打开一个新问题。

标签: c++ assembly gcc inline-assembly


【解决方案1】:

您正在编写的代码是非常特定于实现的。在您的情况下,代码可能不起作用,因为您使用的是 32 位寻址寄存器 ebp,而您在使用 rbp 的 64 位机器上运行。

但是您的处理方法不正确。要么编写纯汇编,要么使用正确的(扩展的)C 内联汇编,它与局部变量正确接口。否则,正如您自己所经历的那样,一旦您更改某些内容,代码就会中断。

according to this answer 内联汇编应该如下所示:

asm ( "assembly code"
    : output operands                  /* optional */
    : input operands                   /* optional */
    : list of clobbered registers      /* optional */
);

所以你的代码看起来像

asm (
    "mov %0, %%eax;"
    "sal $3, %%eax;"
    "mov %%eax, %1;"
    :"=r" (num)
    :"r" (num)
    :"%eax"
);

但是,您不需要指定和使用eax,因此代码可以简化(和澄清)为

asm (
    "sal $3, %[num];"
    :[num] "+r" (num)
);

【讨论】:

  • 考虑到 OP 的编译器碰巧生成的周围代码,这可能会发生,但它完全不安全。在不告诉编译器的情况下修改寄存器实际上是未定义的行为。切勿在非裸函数内使用 GNU C Basic asm(无 out / in / clobber 约束),仅在全局范围内。见gcc.gnu.org/wiki/ConvertBasicAsmToExtended。这是一个糟糕的答案,因为它完全错误地支持使用内联汇编,甚至没有提到 OP 如何尝试这样做的根本缺陷。 stackoverflow.com/tags/inline-assembly/info
  • 这就是为什么当周围的代码发生变化时它会中断。 (或者即使您在启用优化的情况下进行编译。)它取决于 GCC 代码生成中的大量假设,包括调试模式一切都是易失的,并且寄存器在语句之间是“死的”。以及 GCC 将变量放入堆栈的位置。
  • 更好;您的第一个版本是安全的,但在强制编译器为您提供 reg 中的值(而不是使用“g”选择 reg/mem/immediate )后无缘无故地使用 EAX。您的最终版本无法从 %1 读取,而是盲目地假设编译器将为 %1 选择与 %0 相同的寄存器,即使您使用"r" 选择任何寄存器也是如此。要强制使用相同的 reg,请使用 "0"(其中 0 是要匹配的操作数)。或者更简单地说,只使用一个读写操作数(在幕后发明了一个匹配的输入约束)。 asm("sal $3, %0" : "+r"(num));
  • 编译器可能会选择使用您的 asm 语句作为复制和操作并选择不同的 regs,例如如果它是在 EDI 中具有寄存器 arg 并希望在 EAX 中返回值的函数体。或者如果它仍然想要旧值的副本,例如因为 int foo = num 在 asm 语句之前。因此,一旦您知道要戳哪个弱点,就很容易构建一个中断的案例。
  • Timothy 的编辑让我再次看到了中间的代码块。在这种情况下(如果您要强制执行 2 个 mov 指令而不是像语法设计的 sal $3, %0),您应该使用 2 个单独的输入和输出操作数,让编译器使用 asm 语句作为复制和移位。通常只有在保存指令时才强制就地。但是没有理由在 asm 模板中使用 2 个mov 指令来编写它;如有必要,让编译器发出一个 mov 以保留输入。因此,只需将其展示为从原始到良好的非优化婴儿步骤就可以了。
猜你喜欢
  • 2016-10-28
  • 2017-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多