Assembler Annotations (翻译 by chatgpt)

发布时间 2023-12-07 23:20:14作者: 摩斯电码

原文: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_STARTSYM_FUNC_START_LOCAL应该是最常见的标记。它们用于具有标准调用约定的函数 -- 全局和局部。与C语言一样,它们都将函数对齐到特定架构的__ALIGN字节。还有一些特殊情况的_NOALIGN变体,开发人员不希望进行这种隐式对齐时可以使用。

  • SYM_FUNC_START_WEAKSYM_FUNC_START_WEAK_NOALIGN也作为与C语言中已知的弱属性相对应的汇编器对应提供。

    所有这些都应该与SYM_FUNC_END配对使用。首先,它将一系列指令标记为函数,并计算其大小到生成的对象文件。其次,它还简化了对这样的对象文件的检查和处理,因为工具可以轻松地找到确切的函数边界。

    因此,在大多数情况下,开发人员应该像下面的示例一样编写一些汇编指令,当然,中间会有一些汇编指令:

    SYM_FUNC_START(memset)
        ... 汇编指令 ...
    SYM_FUNC_END(memset)
    

    实际上,这种类型的注释对应于现在已弃用的ENTRY和ENDPROC宏。

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

    SYM_FUNC_START(__memset)
        ... 汇编指令 ...
    SYN_FUNC_END(__memset)
    SYM_FUNC_ALIAS(memset, __memset)
    

    在这个例子中,可以使用__memset或memset来调用相同的结果,除了指令的调试信息只会生成一次 -- 对于非别名情况。

  • SYM_CODE_STARTSYM_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}_STARTSYM_{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_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_STARTSYM_DATA_END

支持宏

上述所有内容最终都归结为对SYM_STARTSYM_END或最后对SYM_ENTRY的某种调用。通常,开发人员应该避免使用这些。

此外,在上面的示例中,可以看到SYM_L_LOCAL。还有SYM_L_GLOBALSYM_L_WEAK。它们都用于表示由它们标记的符号的链接。它们既可以在前面提到的宏的_LABEL变体中使用,也可以在SYM_START中使用。

覆盖宏

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