如何在GCC中禁用内嵌汇编?

5

我正在为编程比赛开发一个在线评测系统。由于某些编程比赛不允许使用C/C++内联汇编,因此我希望在我的系统中添加相同的限制。

我想让GCC在编译包含内联汇编的C/C++程序时产生错误,以便拒绝任何包含内联汇编的程序。有没有办法实现这一点?

注意:禁用内联汇编只是遵守规则,而不是出于安全考虑。


3
#define __asm "不要使用内联汇编" - NathanOliver
这只是3个关键字之一:asm__asm____asm。如果这是为了阻止有动机的人使用汇编语言,而不是意外预防,那么有人可以使用CPP宏将__asm__粘贴在一起,以使__asm__出现在预处理器输出中。我认为你不能用CPP来防御特定的标记。 - Peter Cordes
当然,#undef __asm__是打败#define-D选项的最简单方法。正如klutt的答案所示,未来的读者不应该依赖此方法来保证安全性,只能防止无意识的绕过以保持诚实用户的诚实。如果您允许人们编译并运行C代码,则应假定它可以执行任意机器代码;在系统调用级别上实现安全性是您应该努力的方向。 - Peter Cordes
1个回答

12

有没有一种方法可以在GCC中禁用内联汇编?

是的,有几种方法;但这些方法并不适用于安全性,只是防护栏杆,可以故意绕过,但会阻止人们在不知道不应该使用asm的地方意外使用。

关闭编译器中的asm关键字(仅限C语言)

要在编译阶段执行此操作,请使用参数-fno-asm。但请注意,这只会影响C语言中的asm,而不是C ++。也不会影响任何一种语言的__asm____asm

文档:

-fno-asm

不将"asm"、"inline"或"typeof"识别为关键字,以便代码可以使用这些单词作为标识符。您可以使用关键字"__asm__"、"__inline__"和"__typeof__"代替。 -ansi意味着-fno-asm

在C ++中,此开关仅影响"typeof"关键字,因为"asm"和"inline"是标准关键字。您可能想使用-fno-gnu-keywords标志,其具有相同的效果。在C99模式下(-std=c99-std=gnu99),此开关仅影响"asm"和"typeof"关键字,因为"inline"是ISO C99中的标准关键字。

将关键字定义为宏

您可以使用参数-Dasm=error -D__asm__=error -D__asm=error

请注意,这个结构是通用的。它的作用类似于#define。文档说:

-D name=definition

定义的内容将被分词并处理,就好像它们出现在# define指令的翻译阶段三中一样。特别是,定义将被嵌入的换行符截断。

...

所以它的作用就是简单地将asm__asm__asm__的出现改为error。这是在预处理阶段完成的。您不必使用error,只需选择任何无法编译的内容即可。

使用编译期间触发的宏

一种在编译阶段解决它的方法是使用一个宏,正如zwol在评论中建议的那样,您可以使用-D'asm(...)=_Static_assert(0,"inline assembly not allowed")'。如果存在名为error的标识符,这也将解决该问题。 注意:此方法需要-std=c11或更高版本。

在使用gcc之前使用grep

另一种可能是解决您问题的方法是在编译之前只需在源树的根目录执行grep:
grep -nr "asm"

这也会捕获__asm__,但可能会产生误报,例如如果您有包含子字符串"asm"的字符串文字、标识符或注释。但在您的情况下,您可以通过禁止源代码中任何位置出现该字符串来解决此问题。只需更改规则。

可能出现的意外问题

请注意,禁用汇编可能会导致其他问题。例如,我无法使用此选项的stdio.h。通常,系统头文件包含内联汇编代码。

欺骗上述方法的一种方式

除了微不足道的#undef __asm__之外,还可以将字符串作为机器代码执行。请参见以下答案中的示例:https://dev59.com/3WMl5IYBdhLWcg3wV10F#18477070

链接中的代码片段:

/* our machine code */
char code[] = {0x55,0x48,0x89,0xe5,0x89,0x7d,0xfc,0x48,
0x89,0x75,0xf0,0xb8,0x2a,0x00,0x00,0x00,0xc9,0xc3,0x00};

/* copy code to executable buffer */    
void *buf = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
            MAP_PRIVATE|MAP_ANON,-1,0);
memcpy (buf, code, sizeof(code));

/* run code */
int i = ((int (*) (void))buf)();

上面的代码只是为了快速展示如何欺骗OP所陈述的规则,不打算成为实际执行的好例子。此外,这段代码不是我写的,只是我提供的链接中的一小段代码引用。如果您有改进的想法,请在4pie0的原始帖子中发表评论。{{}}

你不需要使用 mmap / memcpy,只需使用 const char code[] = { ... }; 静态存储类和 const,它可以放在 .rodata 中,作为可执行文件的 Text 段的一部分链接,因此是可执行的。 - Peter Cordes
@PeterCordes 也许是这样,但这只是作为一个例子来展示如何欺骗这些方法,而不是教授执行二进制数据的最佳(如果存在的话)方式。 - klutt
叹气...你用字符串中的机器码作为例子让我想起了旧日子,当时我会在REM语句后的第一行BASIC代码中放置机器码 - 是的,在许多编程语言中你可以嵌入机器码。这是邪恶的。;-) - reichhart
@Broman:值得指出的是,你不需要使用mmapmprotect来创建可执行页面。你仍然可以通过一个包装器宏来阻止mmap中的PROT_EXEC标志。但是只读数据已经在可执行页面中,这打开了大门,使得如果人们将其隐藏在void*变量或static inline函数之后,就没有可靠的方法来检测将数据转换为函数指针。 - Peter Cordes
@PeterCordes 我认为OP会很感激我提供一个概念证明,而不是教程。 :) - klutt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接