汇编器注解

Copyright (c) 2017-2019 Jiri Slaby

本文档描述了用于注解汇编中的数据和代码的新宏。特别是,它包含有关 SYM_FUNC_STARTSYM_FUNC_ENDSYM_CODE_START 和类似宏的信息。

原理

某些代码(如入口、跳转或引导代码)需要在汇编中编写。与 C 语言一样,此类代码被分组到函数中并附带有数据。标准汇编器不会强制用户精确地将这些片段标记为代码、数据,甚至指定其长度。然而,汇编器为开发人员提供了此类注释,以帮助调试器完成汇编。最重要的是,开发人员还希望将某些函数标记为*全局*,以便在其转换单元之外可见。

随着时间的推移,Linux 内核采用了来自各种项目(如 binutils)的宏,以方便此类注释。因此,出于历史原因,开发人员一直在汇编中使用 ENTRYENDENDPROC 和其他注释。由于缺乏相关文档,这些宏在某些位置的上下文中使用不当。显然,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 解除堆栈器(ORC 解除堆栈器)的注释。这两者对于支持可靠的堆栈跟踪尤为重要,而堆栈跟踪对于内核热补丁(热补丁)是必不可少的。

警告和讨论

正如人们可能意识到的那样,以前只有三个宏。这确实不足以涵盖所有情况的组合

  • 标准/非标准函数

  • 代码/数据

  • 全局/本地符号

有一个 讨论,并且决定不扩展当前的 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_ 前缀开头,可以分为三个主要组

  1. SYM_FUNC_* -- 用于注解类似 C 的函数。这意味着具有标准 C 调用约定的函数。例如,在 x86 上,这意味着堆栈在预定义的位置包含返回地址,并且可以以标准方式从函数返回。当启用帧指针时,还应在函数的开始/结束时分别进行帧指针的保存/恢复。

    诸如 objtool 之类的检查工具应确保如此标记的函数符合这些规则。这些工具还可以轻松地使用调试信息(如*ORC 数据*)自动注释这些函数。

  2. SYM_CODE_* -- 以特殊堆栈调用的特殊函数。无论是具有特殊堆栈内容的 中断处理程序、跳转还是启动函数。

    检查工具大多忽略对这些函数的检查。但是仍然可以自动生成一些调试信息。为了获得正确的调试数据,此代码需要开发人员提供的提示,如 UNWIND_HINT_REGS

  3. SYM_DATA* -- 显然属于 .data 部分而不是 .text 的数据。数据不包含指令,因此工具必须特殊处理它们:它们不应将字节视为指令,也不应为其分配任何调试信息。

指令宏

本节介绍上面列出的 SYM_FUNC_*SYM_CODE_*

objtool 要求所有代码都必须包含在 ELF 符号中。具有 .L 前缀的符号名称不会发出符号表条目。.L 前缀的符号可以在代码区域内使用,但应避免使用它们通过 SYM_*_START/END 注释来表示代码范围。

  • SYM_FUNC_STARTSYM_FUNC_START_LOCAL 应该是最常用的标记。它们用于具有标准调用约定的函数——全局和本地。与 C 语言一样,它们都将函数对齐到特定于架构的 __ALIGN 字节。对于开发人员不希望进行这种隐式对齐的特殊情况,还有 _NOALIGN 变体。

    SYM_FUNC_START_WEAKSYM_FUNC_START_WEAK_NOALIGN 标记也作为 C 中已知的 *weak* 属性的汇编对应物提供。

    所有这些都SYM_FUNC_END 结合使用。首先,它将指令序列标记为一个函数,并计算其大小以生成目标文件。其次,它还简化了对此类目标文件的检查和处理,因为工具可以轻松找到确切的函数边界。

    因此,在大多数情况下,开发人员应该像以下示例中那样编写代码,当然,在宏之间会有一些汇编指令

    SYM_FUNC_START(memset)
        ... asm insns ...
    SYM_FUNC_END(memset)
    

    实际上,这种注释对应于现在已弃用的 ENTRYENDPROC 宏。

  • SYM_FUNC_ALIASSYM_FUNC_ALIAS_LOCALSYM_FUNC_ALIAS_WEAK 可用于为一个函数定义多个名称。典型的用法是

    SYM_FUNC_START(__memset)
        ... asm insns ...
    SYN_FUNC_END(__memset)
    SYM_FUNC_ALIAS(memset, __memset)
    

    在此示例中,可以调用 __memsetmemset,结果相同,只是指令的调试信息只生成到目标文件一次——对于非 ALIAS 的情况。

  • 只有在特殊情况下——如果您知道自己在做什么,才应使用 SYM_CODE_STARTSYM_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 配对。

    在某种程度上,此类别对应于已弃用的 ENTRYEND。只是 END 也有其他几种含义。

  • SYM_INNER_LABEL* 用于表示某些 SYM_{CODE,FUNC}_STARTSYM_{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_STARTSYM_DATA_START_LOCAL 标记某些数据的开始,应与 SYM_DATA_ENDSYM_DATA_END_LABEL 结合使用。后者还在末尾添加一个标签,以便人们可以使用以下示例中的 lstack 和(本地)lstack_end

    SYM_DATA_START_LOCAL(lstack)
        .skip 4096
    SYM_DATA_END_LABEL(lstack, SYM_L_LOCAL, lstack_end)
    
  • SYM_DATASYM_DATA_LOCAL 是用于简单、大多为单行数据的变体

    SYM_DATA(HEAP,     .long rm_heap)
    SYM_DATA(heap_end, .long rm_stack)
    

    最后,它们会在内部扩展为带有 SYM_DATA_ENDSYM_DATA_START

支持宏

以上所有操作最终都会归结为对 SYM_STARTSYM_ENDSYM_ENTRY 的某种调用。通常,开发者应避免直接使用这些宏。

此外,在上述示例中,可以看到 SYM_L_LOCAL。还有 SYM_L_GLOBALSYM_L_WEAK。它们都旨在表示由它们标记的符号的链接属性。它们被用于之前宏的 _LABEL 变体中,或者用于 SYM_START 中。

覆盖宏

架构也可以在它们自己的 asm/linkage.h 中覆盖任何宏,包括指定符号类型的宏(SYM_T_FUNCSYM_T_OBJECTSYM_T_NONE)。由于此文件中描述的每个宏都用 #ifdef + #endif 包围,因此只需在上述架构相关的头文件中以不同的方式定义这些宏即可。