内核文档stack-validation.txt中文

翻译内核文档stack-validation.txt,难免有错误

基于4.19.190内核,高版本内核中或许有更新

 

内核 CONFIG_STACK_VALIDATION 选项启用objtool 在编译时运行。 它有一个“检查”子命令,分析每个 .o 文件并确保其堆栈元数据的有效性。
它对 asm 代码和 C 内联汇编代码强制执行一组规则,因此堆栈跟踪是可靠的。对于每个函数,它递归地追踪所有可能的代码路径和在每条指令中验证
正确的帧指针状态。

它还跟踪涉及特殊部分的代码路径,例如.altinstructions、__jump_table 和 __ex_table,可以添加给定指令(或一组指令)的替代执行路径。
同样,它知道如何遵循 switch 语句,因为其中 gcc 有时会使用跳转表。

(Objtool 也有一个 'orc generate' 子命令,生成调试信息用于 ORC 开卷机。 请参阅文档/x86/orc-unwinder.txt内核树以获取更多详细信息。)

Why do we need stack metadata validation?
-----------------------------------------

以下是验证堆栈元数据的一些好处:

a) 为启用帧指针的内核提供更可靠的堆栈跟踪

b) ORC(Oops Rewind Capability)展开表的生成

c) 更高的实时补丁兼容率

Rules
-----

为了实现验证,objtool 强制执行以下规则:

1. 每个可调用函数都必须使用 ELF 函数类型进行注解。 在 asm 代码中,这通常使用
ENTRY/ENDPROC 宏。 如果 objtool 在函数之外找到返回指令,它会标记一个错误,因为这通常表明应相应地注释可调用代码。
这个规则是必要的,以便objtool能够正确地识别每个可调用函数,以便分析其堆栈元数据。

2. 相反,每一段不能被调用的代码都不应该被注释为ELF函数。ENDPROC宏不应该在这种情况下使用。

3.每个调用另一个函数的可调用函数必须具有正确的帧指针逻辑,如果需要CONFIG_FRAME_POINTER或体系结构的回链规则。这可以通过FRAME_BEGIN/FRAME_END宏在asm代码中完成。

此规则确保基于帧指针的堆栈跟踪将按设计工作。 如果函数 A 在调用函数 B 之前没有创建堆栈帧,则函数 A 的_caller_ 将在堆栈追踪时被跳过。

4. 只有在以下情况下才允许动态跳转和跳转到未定义的符号:

a) 跳转是 switch 语句的一部分;
b) 跳转匹配同级(兄弟)调用语义,并且帧指针具有与函数入口相同的值。

需要此规则,以便 objtool 可以可靠地分析所有函数的代码路径。 如果一个函数跳转到另一个文件中的代码,
而且不是同级(兄弟)调用,objtool没办法跟着跳转,因为它一次只分析一个文件。

5. 可调用函数可能不执行内核进入/退出指令。唯一需要此类指令的代码是内核入口代码,无论如何,它不应该在可调用函数中。


Objtool warnings
----------------
对于 asm 文件,如果您遇到没有意义的错误,首先确保受影响的代码遵循上述规则。

对于 C 文件,常见的罪魁祸首是内联 asm 语句和调用“noreturn”功能。 请参阅下面的更多细节。

C 代码出错的另一个可能原因是如果 Makefile 删除了-fno-omit-frame-pointer 或将 -fomit-frame-pointer 添加到 gcc 选项。

以下是 objtool 报告的一些常见警告示例、它们的含义以及如何修复它们的建议。

1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup

在启用 CONFIG_FRAME_POINTER的情况下,func() 函数在执行一个函数调用时没有先保存和/或更新帧指针。

如果错误是针对 asm 文件,并且func()确实是可调用的函数,使用 FRAME_BEGIN和FRAME_END 宏添加适当的帧指针逻辑,
如果它不是可调用函数,除它的 ELF 函数注释通过将 ENDPROC 更改为 END来删,而不是使用 asm/unwind_hints.h 中的手动展开提示宏。

如果是 GCC 编译的 .c 文件,错误可能是因为函数使用具有“调用”指令的内联 asm() 语句。一个带有 call 指令的 asm() 语句必须其输出操作数中声明使用
的堆栈指针。 在 x86_64 上,这意味着添加ASM_CALL_CONSTRAINT 作为输出约束:

asm volatile("call func" : ASM_CALL_CONSTRAINT);

否则堆栈帧可能不会在调用之前创建。


2. file.o: warning: objtool: .text+0x53: unreachable instruction

Objtool 找不到到达指令的代码路径。

如果错误是针对 asm 文件,并且指令在一个(或可从)可调用函数内部(到达),该函数应该被注释,使用 ENTRY/ENDPROC 宏(ENDPROC 是重要的)。
除此以外,代码可能应该使用asm/unwind_hints.h 中的展开提示宏进行注释,为了 objtool 和展开器可以知道与代码相关的堆栈状态。

如果您 100% 确定代码不会影响堆栈跟踪,或者如果您仅仅只是一个bad person,你可以告诉 objtool 忽略它。 见“添加例外”一节。

如果它实际上不在可调用函数中(例如内核入口代码),将 ENDPROC 更改为 END。

 

4.file.o:警告:objtool:func():找不到开始指令
或者
file.o:警告:objtool:func()+0x11dd:无法解码指令

该文件是否在text部分中有数据? 如果是这样,那可能会混淆objtool的指令解码器。 将数据移动到更合适的位置,.data 或 .rodata 之类的部分。

5.file.o:警告:objtool:func()+0x6:可调用函数中不支持的指令
这是一个内核进入/退出指令,如 sysenter 或 iret。这样的指令在可调用函数中不允许使用。并且很可能是内核入口代码的一部分。
它们通常不应具有可调用函数注释 (ENDPROC),并且应始终使用 asm/unwind_hints.h 中的展开提示宏进行注释。

6.file.o: 警告: objtool: func()+0x26: 来自可调用指令的同级调用,修改了堆栈帧
这是动态跳转或跳转到未定义符号。 Objtool 假定它是同级调用并检测到帧指针没有首先恢复到其原始状态。
如果它不是真正的同级调用,您可能需要将目标代码移动到本地文件。

如果指令实际上不在可调用函数中(例如内核入口代码),将 ENDPROC 更改为 END 并手动注释
asm/unwind_hints.h 中的展开提示宏。

7. file: warning: objtool:func()+0x5c:堆栈状态不匹配
指令的帧指针状态不一致,采用哪条执行路径到达指令。确保在启用 CONFIG_FRAME_POINTER 时,函数
设置并入栈帧指针(for x86_64, this means rbp)在函数的开头,并在函数的末尾弹出它。

还要确保函数中没有其他代码接触帧指针。
另一种可能性是代码有一些汇编或内联汇编,它们对堆栈或帧指针做了一些不寻常的事情。在这种情况下,
可能适合使用 asm/unwind_hints.h 中的展开提示宏。

8. file.o: warning: objtool:objtool: funcA() “掉到”下一个函数 funcB()

这意味着 funcA() 不会以返回指令或无条件跳转结束,并且objtool已经确定该函数可以落入下一个函数。
这可能有不同的原因:

1) funcA() 的最后一条指令是调用“noreturn”函数,例如 panic(),在这种情况下,需要将 noreturn 函数添加到
objtool 的hard-coded global_noreturns 数组。指出错误给objtool 维护者,或者您可以提交补丁。
2)funcA() 在一段实际可达的代码中使用了 unreachable() 注释。
3) 如果 funcA() 调用内联函数,则 funcA() 的目标代码可能由于 gcc bug而损坏。 有关更多详细信息,请参阅:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646

Adding exceptions
-----------------

如果你_真的_需要 objtool 来忽略某些东西,并且 100% 确定它不会影响内核堆栈跟踪,您可以告诉 objtool
忽略它:


- 要跳过函数验证,请使用 STACK_FRAME_NON_STANDARD
宏。

- 要跳过文件验证,请添加

OBJECT_FILES_NON_STANDARD_filename.o := n 到 Makefile。

- 要跳过目录验证,请添加

OBJECT_FILES_NON_STANDARD := y 到 Makefile。

posted on 2022-04-22 11:21  lh03061238  阅读(419)  评论(0编辑  收藏  举报

导航