原文:https://www.kernel.org/doc/html/latest/core-api/asm-annotations.html
汇编注释
版权所有(c)2017-2019 Jiri Slaby
本文档描述了汇编中用于注释数据和代码的新宏。特别是,它包含了关于SYM_FUNC_START、SYM_FUNC_END、SYM_CODE_START等的信息。
缘由
一些代码,比如入口点、跳板或引导代码,需要用汇编语言编写。与C语言一样,这些代码被分组为函数,并伴随着数据。标准汇编器不会强制用户精确地标记这些部分为代码、数据,甚至指定它们的长度。尽管如此,汇编器提供了这些注释,以帮助调试器进行汇编。此外,开发人员还希望将一些函数标记为全局的,以便在它们的翻译单元之外可见。
随着时间的推移,Linux内核采用了来自各种项目(如binutils)的宏来促进这些注释。因此,出于历史原因,开发人员一直在汇编中使用ENTRY、END、ENDPROC和其他注释。由于缺乏它们的文档,这些宏在某些地方被错误地使用。显然,ENTRY旨在表示全局符号的开始(无论是数据还是代码)。END用于标记数据的结束或具有非标准调用约定的特殊函数的结束。相反,ENDPROC应该仅注释标准函数的结束。
当这些宏被正确使用时,它们有助于汇编器生成一个具有正确设置大小和类型的良好对象。例如,arch/x86/lib/putuser.S
的结果:
Num | Value | Size | Type | Bind | Vis | Ndx | Name |
---|---|---|---|---|---|---|---|
25 | 0000000000000000 | 33 | FUNC | GLOBAL | DEFAULT | 1 | __put_user_1 |
29 | 0000000000000030 | 37 | FUNC | GLOBAL | DEFAULT | 1 | __put_user_2 |
32 | 0000000000000060 | 36 | FUNC | GLOBAL | DEFAULT | 1 | __put_user_4 |
35 | 0000000000000090 | 37 | FUNC | GLOBAL | DEFAULT | 1 | __put_user_8 |
这不仅对调试目的重要。当有了像这样正确注释的对象时,可以对其运行工具以生成更有用的信息。特别是,在正确注释的对象上,可以运行objtool来检查和修复对象(如果需要)。目前,objtool可以报告函数中缺少的帧指针设置/销毁。它还可以自动生成ORC unwinder(ORC解绑器)的注释。这两者对于支持可靠的堆栈跟踪尤为重要,而堆栈跟踪又对于内核实时修补(Livepatch)是必要的。
注意事项和讨论
正如人们可能意识到的那样,以前只有三个宏。这确实不足以涵盖所有情况的组合:
- 标准/非标准函数
- 代码/数据
- 全局/局部符号
进行了讨论,决定不是扩展当前的ENTRY/END*宏,而是引入全新的宏:
那么,使用能够真正显示目的的宏名称,而不是从binutils和旧内核中导入所有那些糟糕、历史悠久、本质上是随意选择的调试符号宏名称,怎么样?
宏描述
新的宏以SYM_前缀开头,可以分为三个主要组:
-
SYM_FUNC_*
-- 用于注释类似C的函数。这意味着具有标准C调用约定的函数。例如,在x86上,这意味着栈包含预定义位置的返回地址,并且函数的返回可以以标准方式发生。当启用帧指针时,函数的开始/结束处也应该发生帧指针的保存/恢复。类似objtool的检查工具应该确保这些标记的函数符合这些规则。这些工具还可以自动为这些函数注释调试信息(如ORC数据)。
-
SYM_CODE_*
-- 使用特殊堆栈调用的特殊函数。无论是具有特殊堆栈内容的中断处理程序,跳板还是启动函数。大多数检查工具忽略对这些函数的检查。但仍然可以自动生成一些调试信息。为了正确的调试数据,这些代码需要开发人员提供像UNWIND_HINT_REGS这样的提示。
-
SYM_DATA*
-- 显然是属于.data节而不是.text的数据。数据不包含指令,因此工具必须对其进行特殊处理:它们不应将字节视为指令,也不应为其分配任何调试信息。
指令宏
这一部分涵盖了上面列举的SYM_FUNC_和SYM_CODE_。
objtool要求所有代码必须包含在ELF符号中。具有.L前缀的符号名称不会发出符号表条目。.L前缀的符号可以在代码区域内使用,但应该避免用于通过SYM_*_START/END注释来表示代码范围。
-
SYM_FUNC_START
和SYM_FUNC_START_LOCAL
应该是最常见的标记。它们用于具有标准调用约定的函数 -- 全局和局部。与C语言一样,它们都将函数对齐到特定架构的__ALIGN字节。还有一些特殊情况的_NOALIGN变体,开发人员不希望进行这种隐式对齐时可以使用。 -
SYM_FUNC_START_WEAK
和SYM_FUNC_START_WEAK_NOALIGN
也作为与C语言中已知的弱属性相对应的汇编器对应提供。所有这些都应该与SYM_FUNC_END配对使用。首先,它将一系列指令标记为函数,并计算其大小到生成的对象文件。其次,它还简化了对这样的对象文件的检查和处理,因为工具可以轻松地找到确切的函数边界。
因此,在大多数情况下,开发人员应该像下面的示例一样编写一些汇编指令,当然,中间会有一些汇编指令:
SYM_FUNC_START(memset) ... 汇编指令 ... SYM_FUNC_END(memset)
实际上,这种类型的注释对应于现在已弃用的ENTRY和ENDPROC宏。
-
SYM_FUNC_ALIAS
、SYM_FUNC_ALIAS_LOCAL
和SYM_FUNC_ALIAS_WEAK
可用于为函数定义多个名称。典型用法是:SYM_FUNC_START(__memset) ... 汇编指令 ... SYN_FUNC_END(__memset) SYM_FUNC_ALIAS(memset, __memset)
在这个例子中,可以使用__memset或memset来调用相同的结果,除了指令的调试信息只会生成一次 -- 对于非别名情况。
-
SYM_CODE_START
和SYM_CODE_START_LOCAL
应该只在特殊情况下使用 -- 如果你知道你在做什么。这仅用于中断处理程序和类似情况,其中调用约定不是C语言的。_NOALIGN变体也存在。用法与上面的FUNC类别相同:SYM_CODE_START_LOCAL(bad_put_user) ... 汇编指令 ... SYM_CODE_END(bad_put_user)
同样,每个SYM_CODE_START*都应该与SYM_CODE_END配对使用。
在某种程度上,这个类别与已弃用的ENTRY和END相对应。除了END还有其他几个含义。
-
SYM_INNER_LABEL*
用于表示在某些SYM_{CODE,FUNC}_START
和SYM_{CODE,FUNC}_END
内部的标签。它们与C标签非常相似,只是它们可以被设置为全局的。使用示例:SYM_CODE_START(ftrace_caller) /* save_mcount_regs填充了前两个参数 */ ... SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL) /* 将ftrace_ops加载到第三个参数中 */ ... SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL) call ftrace_stub ... retq SYM_CODE_END(ftrace_caller)
数据宏
与指令类似,汇编中有一些宏来描述数据。
-
SYM_DATA_START
和SYM_DATA_START_LOCAL
标记了一些数据的开始,并应该与SYM_DATA_END
或SYM_DATA_END_LABEL
一起使用。后者还会在末尾添加一个标签,以便人们可以在下面的示例中使用lstack和(局部)lstack_end:SYM_DATA_START_LOCAL(lstack) .skip 4096 SYM_DATA_END_LABEL(lstack, SYM_L_LOCAL, lstack_end)
-
SYM_DATA
和SYM_DATA_LOCAL
是用于简单的、大多数为单行的数据的变体:SYM_DATA(HEAP, .long rm_heap) SYM_DATA(heap_end, .long rm_stack)
最终,它们在内部扩展为
SYM_DATA_START
和SYM_DATA_END
。
支持宏
上述所有内容最终都归结为对SYM_START
、SYM_END
或最后对SYM_ENTRY
的某种调用。通常,开发人员应该避免使用这些。
此外,在上面的示例中,可以看到SYM_L_LOCAL
。还有SYM_L_GLOBAL
和SYM_L_WEAK
。它们都用于表示由它们标记的符号的链接。它们既可以在前面提到的宏的_LABEL
变体中使用,也可以在SYM_START
中使用。
覆盖宏
架构也可以在它们自己的asm/linkage.h中覆盖任何宏,包括指定符号类型的宏(SYM_T_FUNC
、SYM_T_OBJECT
和SYM_T_NONE
)。由于本文件中描述的每个宏都被#ifdef + #endif
包围,因此在前述的架构相关头文件中以不同方式定义这些宏就足够了。