了解预处理器宏中的内联汇编与函数中的内联汇编

时间:2019-05-15 21:50:42

标签: c gcc x86 inline-assembly osdev

GGC的内联汇编可能难以正确实现,并且容易出错 1 。从更高的角度来看,内联汇编在内联汇编语句可能发出的指令之外必须考虑一些规则。

C / C ++ 标准认为asm是一个选项,并定义了实现。实现定义的行为是documented in GCC,其中包括以下内容:

  

即使您使用的是volatile限定符,也不要期望 asm 语句序列在编译后会保持完全连续。如果某些指令需要在输出中保持连续,请将它们放在单个多指令asm语句中。

基本内联汇编或无任何输出约束的扩展内联汇编隐式volatile。该文档说,易失性不能保证连续的语句将按源代码中的顺序进行排序。这段代码的顺序不能保证:

asm ("cli");
asm ("mov $'M', %%al; out %%al, $0xe9" ::: "eax");
asm ("mov $'D', %%al; out %%al, $0xe9" ::: "eax");
asm ("mov $'P', %%al; out %%al, $0xe9" ::: "eax");
asm ("sti");

如果打算使用 CLI STI 关闭(然后重新打开)外部中断,并以MDP的顺序输出一些字母, QEMU调试控制台(端口0xe9),则无法保证。您可以将它们全部放在单个内联汇编语句中,也可以使用扩展的内联汇编模板将虚拟依赖项传递给每个语句以保证顺序。

为了使事情更易于管理,众所周知,OS开发人员会围绕此类代码创建方便的包装。一些开发人员将其用作C预处理器宏。从理论上讲,这看起来很有用:

#define outb(port, value) \
        asm ("out %0, %1" \
             : \
             : "a"((uint8_t)value), "Nd"((uint16_t)port))

#define cli() asm ("cli")

#define sti() asm ("sti")

然后您可以像这样使用它们:

cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();

当然,在 C 编译器开始处理代码本身之前,首先要完成C预处理程序。预处理器将在一行中全部生成这些语句,代码生成器也不能保证以特定顺序发出这些语句:

asm ("cli");
asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9));
asm ("sti");

我的问题

一些开发人员已经开始使用宏,它们将内联汇编语句放置在复合语句中,如下所示:

#define outb(port, value) ({ \
        asm ("out %0, %1" \
             : \
             : "a"((uint8_t)value), "Nd"((uint16_t)port)); \
    })

#define cli() ({ \
        asm ("cli"); \
    })

#define sti() ({ \
        asm ("sti"); \
    })

像以前一样使用这些宏将使 C 预处理器生成以下代码:

({ asm ("cli"); });
({ asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9)); });
({ asm ("sti"); });

问题1 :将asm语句放在复合语句中是否可以保证顺序?我的观点是我不相信,但是我不确定。这是我避免使用预处理器宏生成内联程序集的原因之一,我可以按这样的顺序使用内联程序集。


多年以来,我一直在行内汇编语句的标头中使用static inline函数。函数提供类型检查,但我也相信函数中的内联汇编确实可以确保副作用(包括内联汇编)由下一个序列点(函数调用末尾的;)发出。

如果我要调用实际函数,我的期望是这些函数中的每个函数都会相对于彼此按顺序生成内联汇编语句:

cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();

问题2 :将内联汇编语句放在实际功能(无论是外部链接还是内联)中是否可以保证顺序?我的感觉是,如果不是这种情况,则代码如下:

printf ("hello ");
printf ("world ");

可以输出为hello worldworld hello C as-if rule表明优化不能改变可观察的行为。我相信编译器将无法假定内联程序集实际上是否改变了可观察的行为,因此不允许编译器以其他顺序发出函数的内联程序集。

1 个答案:

答案 0 :(得分:7)

  

即使在使用volatile限定符时,也不要期望汇编后的asm语句序列能够完美地保持连续。如果某些指令需要在输出中保持连续,请将它们放在一个多指令汇编语句。

您实际上是在误读(或过度阅读)。并不是说可变的asm语句可以重新排序;它们不能重新排序或删除-这就是易失性的全部内容。这就是说,其他(非易失性)事物可以相对于asm语句重新排序,尤其是可以在这些asm语句中的任何两个之间移动。因此,在优化程序处理完它们之后,它们可能不会连续,但是它们仍然是有序的。

请注意,这仅适用于volatile asm块(包括所有没有输出的块-它们隐式易失)。如果允许,其他任何非易失性asm块或语句也可以在易失性asm块之间移动。