汇编器注解¶
版权所有 (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 unwinder) 的注解。这两点对于支持可靠的堆栈跟踪尤其重要,而堆栈跟踪又是内核实时补丁 (Livepatch) 所必需的。
注意事项和讨论¶
正如人们可能意识到的,之前只有三个宏。这确实不足以涵盖所有情况的组合:
标准/非标准函数
代码/数据
全局/局部符号
曾有过一次讨论,结果决定不扩展当前的 ENTRY/END*
宏,而是引入全新的宏。
So how about using macro names that actually show the purpose, instead
of importing all the crappy, historic, essentially randomly chosen
debug symbol macro names from the binutils and older kernels?
宏描述¶
新宏以 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 语言中已知的weak 属性相对应的形式提供。所有这些都应与
SYM_FUNC_END
结合使用。首先,它将指令序列标记为函数,并计算其大小到生成的目标文件中。其次,它还简化了对此类目标文件的检查和处理,因为工具可以轻松找到精确的函数边界。因此在大多数情况下,开发者应该像下面的例子那样编写,当然,宏之间需要有一些汇编指令:
SYM_FUNC_START(memset) ... asm insns ... SYM_FUNC_END(memset)
事实上,这种注解与现在已弃用的
ENTRY
和ENDPROC
宏相对应。SYM_FUNC_ALIAS
、SYM_FUNC_ALIAS_LOCAL
和SYM_FUNC_ALIAS_WEAK
可用于为一个函数定义多个名称。典型的用法是:SYM_FUNC_START(__memset) ... asm insns ... SYN_FUNC_END(__memset) SYM_FUNC_ALIAS(memset, __memset)
在此示例中,调用
__memset
或memset
会得到相同的结果,不同之处在于指令的调试信息只生成一次到目标文件中——对于非ALIAS
的情况。SYM_CODE_START
和SYM_CODE_START_LOCAL
应该只在特殊情况下使用——如果您知道自己在做什么。这专门用于中断处理程序和类似情况,其中调用约定不是 C 语言的。_NOALIGN
变体也存在。用法与上面的FUNC
类别相同:SYM_CODE_START_LOCAL(bad_put_user) ... asm insns ... 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 fills in first two parameters */ ... SYM_INNER_LABEL(ftrace_caller_op_ptr, SYM_L_GLOBAL) /* Load the ftrace_ops into the 3rd parameter */ ... 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_END
的SYM_DATA_START
。
支持宏¶
所有上述宏最终都归结为对 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
包围,因此只需在前面提到的依赖于架构的头文件中以不同方式定义这些宏即可。