在添加#pragma GCC optimize("O0") 后 Linux 内核构建期间,从一个函数到另一个函数的部分不匹配

Section mismatch in reference from a function to another function during Linux kernel build after adding #pragma GCC optimize("O0")

在Linux5.4.21源码中,我把

#pragma GCC push_options
#pragma GCC optimize ("O0")

#pragma GCC pop_options

围绕文件 /drivers/irqchip/irq-gic-v3.c 中的函数 static int __init gic_init_bases

当我构建它时,我收到此警告消息(部分不匹配)。后来我发现它实际上是导致它的 #pragma GCC optimize ("O0") 行。

  CALL    scripts/atomic/check-atomics.sh
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CC      arch/arm64/kernel/irq.o
  CC      arch/arm64/kernel/setup.o
  CC      drivers/irqchip/irq-gic-v3.o
  AS      arch/arm64/kernel/head.o
  AR      arch/arm64/kernel/built-in.a
  AR      arch/arm64/built-in.a
  AR      drivers/irqchip/built-in.a
  AR      drivers/built-in.a
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  AR      init/built-in.a
  LD      vmlinux.o
  MODPOST vmlinux.o
WARNING: vmlinux.o(.text+0x227cc0): Section mismatch in reference from the function gic_smp_init() to the function .init.text:set_smp_cross_call()
The function gic_smp_init() references
the function __init set_smp_cross_call().
This is often because gic_smp_init lacks a __init 
annotation or the annotation of set_smp_cross_call is wrong.

  MODINFO modules.builtin.modinfo
  LD      .tmp_vmlinux1
  KSYM    .tmp_kallsyms1.o
  LD      .tmp_vmlinux2
  KSYM    .tmp_kallsyms2.o
  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map
  OBJCOPY arch/arm64/boot/Image

函数调用链是这样的:gic_init_bases -> gic_smp_init() -> set_smp_cross_call(目前是CONFIG_SMP=y)。该消息似乎说 set_smp_cross_call 是用 __init 注释的(意思是它放在 .init.text 中),但 gic_smp_init 不是。

没有#pragma调试设置,就没有这种警告。我不确定是否可以将 __init 添加到 gic_smp_init()(或从 set_smp_cross_call 中删除 _init)。正确的修复方法是什么?

如果我将 __init 添加到 gic_smp_init,此警告就会消失,但我认为这会使该函数在初始化后被删除(也许可以吗?)。

TL;DR: gic_smp_init() 应该用 __init 注释。


更新:这已在内核 v5.8 中修复,这里是 relevant commit.

在我看来您发现了一个错误:gic_smp_init() 仅由 __init gic_init_bases() 调用,因此没有真正的理由不使用 __init 进行注释。函数 set_smp_cross_call() 被注释为 __init,因此调用者也应被注释为 __init.

如果您使用优化(没有 pragma)进行编译,这种不一致就会消失,因为编译器只是将 gic_smp_init() 的整个主体内联到 gic_init_bases() 中:那么调​​用链就是 gic_init_bases -> set_smp_cross_call一切都很好,因为它们都带有注释 __init。但是,当您禁用优化 (#pragma GCC optimize ("O0")) 时,编译器不再内联对 gic_smp_init() 的调用,它作为一个实际函数保持独立,并且不一致暴露出来。

模块的作者可能错过了 set_smp_cross_call()__init 注释,或者注释是后来添加的,但没有人注意到它也需要在该驱动程序代码中“传播”。

set_smp_cross_call() 的注释之间似乎也存在不一致:在 C 文件 (/arch/{arm,arm64}/kernel/smp.c) 中,它被注释为 __init,而在头文件中 (/arch/{arm,arm64}/include/asm/smp.h) 它不是。后面的实例也应该被注释为 the doc-comment 对于 __init 宏建议:

 * If the function has a prototype somewhere, you can also add
 * __init between closing brace of the prototype and semicolon:
 *
 * extern int initialize_foobar_device(int, int, int) __init;