gcc 属性 __attribute__((naked)) 使用场景
问题背景
去年一时兴起,实现了一个协程库 cocpp:
在进行协程上下文的切换时,使用了 gcc 内嵌汇编来实现,第一版大概是这样:
这个代码在没有开启编译优化的情况下工作得很好。
但是在开启了-O3 优化的时候就出问题了。
接下来我们看一下两种模式下的汇编语句差别:
不开优化:
开启优化:
可以看到,不开优化的时候,函数开头生成了开辟栈帧的汇编指令:
函数尾部生成了恢复 rbp 的指令和返回指令:
而开启了优化选项,函数开头没有开辟栈帧的指令,结尾也只有返回的指令ret
。
发生这个现象的原因是编译优化选线会增加-fomit-frame-pointer
优化选项,此优化选项会避免生成栈帧。
如下在-O0 优化等级增加此优化选项:
可以看到增加-fomit-frame-pointer
选项之后,不再生成开辟栈帧的指令(但是和-O3 优化不完全一样)。
想要解决不同优化级别生成指令不一致的问题,大致有两种方案:
修改编译参数
修改代码
解决方案 1:修改编译参数
对于方案 1,又分为两种思路,一种是在不开启优化的时候手动增加-fomit-frame-pointer
的优化选项,另一种是在开启优化的时候指定-fno-omit-frame-pointer
,禁止栈指针的优化选项。但是第二种思路有问题,经过实际测试,-O3
优化选项会覆盖-fno-omit-frame-pointer
,导致-fno-omit-frame-pointer
选项不生效。因此只能不指定-O3
,而是根据gcc
的文档,手动开启除了-fomit-frame-pointer
的所有优化选项。
暂且不论此方案的实现繁琐程度,此方案最大的问题在于gcc
优化选项会对所有的函数生效,而对于其他函数,原本不应该受到影响,所以此方案不合适。
解决方案 2:修改代码
对于此方案,又分为两种思路:
将 switch_to 函数使用纯汇编文件实现,而不是通过
gcc
内嵌汇编使用
gcc
相关的属性控制,不生成开栈指令
对于思路 1,这里不做描述,实际测试确实可以解决问题,但是带来的新问题是引入了汇编文件,编译时需要特殊处理一下。这里重点讨论一下思路 2。
在过去的某个时间点,我知道有相关的属性可以禁止 gcc 生成开栈指令的,但是具体是什么指令我给忘了,搜索引擎上也没有查到(应该是搜索姿势不对)。直到后来有一天,在一本书上看到:
为了演示特定的编译结果,这里使用 GCC 的 naked 语法让 test 不产生函数的入口和出口。
这段话出自《深入 Linux 内核架构和底层原理》,作者是刘京洋,书的质量很高,在此安利一波。
主角登场
于是我便在函数定义时添加了__attribute__((naked))属性,查看了一下生成的汇编指令,如下(内嵌汇编修改了一部分):
从上面的汇编结果可以看出,在不开启优化的情况下,也没有生成开栈指令,也没有生成返回指令(返回指令对应的是内嵌汇编中写的)。开启优化的结果是一样的。
根据 gcc 的文档说明:
Use this attribute on the ARM, AVR, C4x and IP2K ports to indicate that the specified function does not need prologue/epilogue sequences generated by the compiler. It is up to the programmer to provide these sequences.
大意就是编译器不生成函数序言/结尾指令,而由程序员提供这些序列。这在手写内嵌汇编是很有用的。
总结
__attribute
__((naked))
属性是 GCC 扩展出来的,它告诉编译器不为该函数生成任何函数序言或结尾。这在程序员需要手动管理堆栈框架和寄存器的特殊情况下很有用。此属性的常见应用程序包括编写汇编语言功能,实现上下文切换和编写中断处理程序。
版权声明: 本文为 InfoQ 作者【SkyFire】的原创文章。
原文链接:【http://xie.infoq.cn/article/01626e36599e891c9bec766ef】。文章转载请联系作者。
评论