* [PATCH 01/27] objtool: Remove obsolete noreturns.h entries
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 02/27] kbuild: Add CONFIG_OBJTOOL_DEFERRED Josh Poimboeuf
` (25 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Remove some obsolete noreturns.h entries: bcachefs is no longer in-tree,
kthread_exit() is a macro, and rust_begin_unwind() is only called by its
mangled name.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/noreturns.h | 5 -----
1 file changed, 5 deletions(-)
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index 14f8ab653449c..08d441b8895cb 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -18,9 +18,6 @@ NORETURN(__x64_sys_exit)
NORETURN(__x64_sys_exit_group)
NORETURN(acpi_processor_ffh_play_dead)
NORETURN(arch_cpu_idle_dead)
-NORETURN(bch2_trans_in_restart_error)
-NORETURN(bch2_trans_restart_error)
-NORETURN(bch2_trans_unlocked_or_in_restart_error)
NORETURN(cpu_bringup_and_idle)
NORETURN(cpu_startup_entry)
NORETURN(do_exit)
@@ -30,7 +27,6 @@ NORETURN(ex_handler_msr_mce)
NORETURN(hlt_play_dead)
NORETURN(hv_ghcb_terminate)
NORETURN(kthread_complete_and_exit)
-NORETURN(kthread_exit)
NORETURN(kunit_try_catch_throw)
NORETURN(machine_real_restart)
NORETURN(make_task_dead)
@@ -43,7 +39,6 @@ NORETURN(vpanic)
NORETURN(panic_smp_self_stop)
NORETURN(rest_init)
NORETURN(rewind_stack_and_make_dead)
-NORETURN(rust_begin_unwind)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
NORETURN(start_kernel)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 02/27] kbuild: Add CONFIG_OBJTOOL_DEFERRED
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 01/27] objtool: Remove obsolete noreturns.h entries Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 03/27] kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW Josh Poimboeuf
` (24 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
For LTO, IBT, and KLP builds, the running of objtool is deferred to
vmlinux.o rather than individual translation units. In preparation for
deferring it in more cases, add a new config option.
No functional change.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/boot/startup/Makefile | 2 +-
lib/Kconfig.debug | 7 +++++++
scripts/Makefile.build | 4 ++--
scripts/Makefile.lib | 4 +---
scripts/Makefile.vmlinux_o | 13 +++++++------
scripts/link-vmlinux.sh | 3 +--
6 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index 5e499cfb29b5c..ab6e9970d4f4e 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -36,7 +36,7 @@ $(patsubst %.o,$(obj)/%.o,$(lib-y)): OBJECT_FILES_NON_STANDARD := y
# relocations, even if other objtool actions are being deferred.
#
$(pi-objs): objtool-enabled = 1
-$(pi-objs): objtool-args = $(if $(delay-objtool),--dry-run,$(objtool-args-y)) --noabs
+$(pi-objs): objtool-args = $(if $(CONFIG_OBJTOOL_DEFERRED),--dry-run,$(objtool-args-y)) --noabs
#
# Confine the startup code by prefixing all symbols with __pi_ (for position
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1244dcac2294a..9ca9b2519d604 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -588,6 +588,13 @@ config NOINSTR_VALIDATION
select OBJTOOL
default y
+# For when objtool defers its work to the linked image (vmlinux.o or module.o)
+# rather than running on individual translation units.
+config OBJTOOL_DEFERRED
+ def_bool y
+ depends on OBJTOOL
+ depends on LTO_CLANG || X86_KERNEL_IBT || KLP_BUILD
+
config VMLINUX_MAP
bool "Generate vmlinux.map file when linking"
depends on EXPERT
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 9117457432463..84ef12f784330 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -277,7 +277,7 @@ endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT
is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),$(is-kernel-object))
ifdef CONFIG_OBJTOOL
-$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(delay-objtool),$(is-single-obj-m),y))
+$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(CONFIG_OBJTOOL_DEFERRED),$(is-single-obj-m),y))
endif
ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),)
@@ -502,7 +502,7 @@ define rule_ld_multi_m
$(call cmd,gen_objtooldep)
endef
-$(multi-obj-m): private objtool-enabled := $(delay-objtool)
+$(multi-obj-m): private objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
$(multi-obj-m): private part-of-module := y
$(multi-obj-m): %.o: %.mod FORCE
$(call if_changed_rule,ld_multi_m)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 0a4fdd8bd975d..3622237e41d2e 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -210,11 +210,9 @@ objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable
objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
objtool-args = $(objtool-args-y) \
- $(if $(delay-objtool), --link) \
+ $(if $(CONFIG_OBJTOOL_DEFERRED), --link) \
$(if $(part-of-module), --module)
-delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT),$(CONFIG_KLP_BUILD))
-
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool-args) $@)
cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
index 24a3a4fd271c2..9bac917e8b819 100644
--- a/scripts/Makefile.vmlinux_o
+++ b/scripts/Makefile.vmlinux_o
@@ -30,15 +30,16 @@ endif
# objtool for vmlinux.o
# ---------------------------------------------------------------------------
#
-# For delay-objtool (IBT or LTO), objtool doesn't run on individual translation
-# units. Instead it runs on vmlinux.o.
+# For CONFIG_OBJTOOL_DEFERRED (IBT or LTO), objtool doesn't run on individual
+# translation units. Instead it runs on vmlinux.o.
#
-# For !delay-objtool + CONFIG_NOINSTR_VALIDATION, it runs on both translation
-# units and vmlinux.o, with the latter only used for noinstr/unret validation.
+# For !CONFIG_OBJTOOL_DEFERRED + CONFIG_NOINSTR_VALIDATION, it runs on both
+# translation units and vmlinux.o, with the latter only used for noinstr/unret
+# validation.
-objtool-enabled := $(or $(delay-objtool),$(CONFIG_NOINSTR_VALIDATION))
+objtool-enabled := $(or $(CONFIG_OBJTOOL_DEFERRED),$(CONFIG_NOINSTR_VALIDATION))
-ifeq ($(delay-objtool),y)
+ifeq ($(CONFIG_OBJTOOL_DEFERRED),y)
vmlinux-objtool-args-y += $(objtool-args-y)
else
vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index f99e196abeea4..aa10867a04de2 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -60,8 +60,7 @@ vmlinux_link()
# skip output file argument
shift
- if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT ||
- is_enabled CONFIG_KLP_BUILD; then
+ if is_enabled CONFIG_OBJTOOL_DEFERRED || is_enabled CONFIG_LTO_CLANG; then
# Use vmlinux.o instead of performing the slow LTO link again.
objs=vmlinux.o
libs=
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 03/27] kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 01/27] objtool: Remove obsolete noreturns.h entries Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 02/27] kbuild: Add CONFIG_OBJTOOL_DEFERRED Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 04/27] objtool: Fix dead end detection for sibling calls Josh Poimboeuf
` (23 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Add a config option for when objtool reverse engineers the control flow
graph of every function.
No functional change.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
lib/Kconfig.debug | 10 ++++++++--
lib/Kconfig.ubsan | 2 +-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 9ca9b2519d604..0ea5ad31969ce 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -588,8 +588,14 @@ config NOINSTR_VALIDATION
select OBJTOOL
default y
-# For when objtool defers its work to the linked image (vmlinux.o or module.o)
-# rather than running on individual translation units.
+# Objtool reverse-engineers the control flow graph
+config OBJTOOL_CONTROL_FLOW
+ def_bool y
+ depends on OBJTOOL
+ depends on UNWINDER_ORC || STACK_VALIDATION || HAVE_UACCESS_VALIDATION
+
+# Objtool defers its work to the linked image (vmlinux.o or module.o) rather
+# than running on individual translation units.
config OBJTOOL_DEFERRED
def_bool y
depends on OBJTOOL
diff --git a/lib/Kconfig.ubsan b/lib/Kconfig.ubsan
index 1ecaae7064d2a..61e8c86ef63be 100644
--- a/lib/Kconfig.ubsan
+++ b/lib/Kconfig.ubsan
@@ -110,7 +110,7 @@ config UBSAN_UNREACHABLE
bool "Perform checking for unreachable code"
# objtool already handles unreachable checking and gets angry about
# seeing UBSan instrumentation located in unreachable places.
- depends on !(OBJTOOL && (STACK_VALIDATION || UNWINDER_ORC || HAVE_UACCESS_VALIDATION))
+ depends on !OBJTOOL_CONTROL_FLOW
depends on $(cc-option,-fsanitize=unreachable)
help
This option enables -fsanitize=unreachable which checks for control
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 04/27] objtool: Fix dead end detection for sibling calls
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (2 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 03/27] kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 05/27] objtool: Refactor the noreturn/dead-end detection Josh Poimboeuf
` (22 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
The dead end detection for sibling calls has apparently always been
broken. There are two issues: it checks jump_dest instead of call_dest,
and it only checks the first sibling call instead of all of them. Fix
both issues.
No warnings were seen, this was only found by code inspection.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/check.c | 37 +++++++++++++++++++------------------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index a98d7589d8a48..01c50aa00b6dc 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -272,30 +272,31 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
return false;
/*
- * A function can have a sibling call instead of a return. In that
- * case, the function's dead-end status depends on whether the target
- * of the sibling call returns.
+ * A function can have sibling calls instead of a return. It's only a
+ * dead end if *all* the sibling call targets are dead ends.
*/
func_for_each_insn(file, func, insn) {
- if (is_sibling_call(insn)) {
- struct instruction *dest = insn->jump_dest;
+ struct symbol *dest;
- if (!dest)
- /* sibling call to another file */
- return false;
+ if (!is_sibling_call(insn))
+ continue;
- /* local sibling call */
- if (recursion == 5) {
- /*
- * Infinite recursion: two functions have
- * sibling calls to each other. This is a very
- * rare case. It means they aren't dead ends.
- */
- return false;
- }
+ dest = insn_call_dest(insn);
+ if (!dest)
+ /* call to another file */
+ return false;
- return __dead_end_function(file, insn_func(dest), recursion+1);
+ if (recursion == 5) {
+ /*
+ * Infinite recursion: two functions have sibling
+ * calls to each other. This is a very rare case.
+ * It means they aren't dead ends.
+ */
+ return false;
}
+
+ if (!__dead_end_function(file, dest, recursion+1))
+ return false;
}
return true;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 05/27] objtool: Refactor the noreturn/dead-end detection
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (3 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 04/27] objtool: Fix dead end detection for sibling calls Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 06/27] objtool: Ignore traps after noreturn calls in STT_CODE Josh Poimboeuf
` (21 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
The dead end detection (basically "is this a call to a noreturn?") is
currently derived on demand, at each call site. Also, the answer
isn't saved anywhere, so it's redetermined each time. It also has an
ugly recursion check.
Clean that up by just detecting all the noreturns at the beginning.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/check.c | 159 ++++++++++++++++------------
tools/objtool/include/objtool/elf.h | 1 +
2 files changed, 93 insertions(+), 67 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 01c50aa00b6dc..4528f017277d2 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -217,94 +217,112 @@ static bool is_rust_noreturn(const struct symbol *func)
str_ends_with(func->name, "_fail"));
}
-/*
- * This checks to see if the given function is a "noreturn" function.
- *
- * For global functions which are outside the scope of this object file, we
- * have to keep a manual list of them.
- *
- * For local functions, we have to detect them manually by simply looking for
- * the lack of a return instruction.
- */
-static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
- int recursion)
+static bool is_listed_noreturn(struct symbol *func)
{
- int i;
- struct instruction *insn;
- bool empty = true;
-
#define NORETURN(func) __stringify(func),
static const char * const global_noreturns[] = {
#include "noreturns.h"
};
#undef NORETURN
- if (!func)
+ if (is_local_sym(func))
return false;
- if (!is_local_sym(func)) {
- if (is_rust_noreturn(func))
+ if (is_rust_noreturn(func))
+ return true;
+
+ for (int i = 0; i < ARRAY_SIZE(global_noreturns); i++)
+ if (!strcmp(func->name, global_noreturns[i]))
return true;
- for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
- if (!strcmp(func->name, global_noreturns[i]))
- return true;
- }
+ return false;
+}
+
+/*
+ * Use this rather than reading sym->_noreturn directly: the noreturn status
+ * lives on the primary alias, and ANNOTATE_IGNORE_NORETURN() overrides it.
+ */
+static bool is_noreturn(struct symbol *func)
+{
+ func = func->alias->pfunc;
+
+ if (is_listed_noreturn(func))
+ return true;
+
+ return func->_noreturn;
+}
+
+static bool might_return(struct objtool_file *file, struct symbol *func)
+{
+ struct instruction *insn;
+ struct symbol *dest;
if (is_weak_sym(func))
- return false;
-
- if (!func->len)
- return false;
-
- insn = find_insn(file, func->sec, func->offset);
- if (!insn || !insn_func(insn))
- return false;
+ return true;
func_for_each_insn(file, func, insn) {
- empty = false;
-
if (insn->type == INSN_RETURN)
- return false;
- }
-
- if (empty)
- return false;
-
- /*
- * A function can have sibling calls instead of a return. It's only a
- * dead end if *all* the sibling call targets are dead ends.
- */
- func_for_each_insn(file, func, insn) {
- struct symbol *dest;
+ return true;
if (!is_sibling_call(insn))
continue;
dest = insn_call_dest(insn);
- if (!dest)
- /* call to another file */
- return false;
-
- if (recursion == 5) {
- /*
- * Infinite recursion: two functions have sibling
- * calls to each other. This is a very rare case.
- * It means they aren't dead ends.
- */
- return false;
- }
-
- if (!__dead_end_function(file, dest, recursion+1))
- return false;
+ if (!dest || !is_noreturn(dest))
+ return true;
}
- return true;
+ return false;
}
-static bool dead_end_function(struct objtool_file *file, struct symbol *func)
+static void detect_noreturns(struct objtool_file *file)
{
- return __dead_end_function(file, func, 0);
+ struct symbol *func, *dest;
+ struct instruction *insn;
+ bool changed;
+
+ /* Mark all functions guilty until proven innocent */
+ for_each_sym(file->elf, func) {
+
+ /* Aliases and cold subfunctions inherit the parent's verdict */
+ if (!is_func_sym(func) || is_undef_sym(func) ||
+ is_prefix_func(func) || func->embedded_insn ||
+ func != func->alias->pfunc)
+ continue;
+
+ insn = find_insn(file, func->sec, func->offset);
+ if (!insn || insn_func(insn) != func)
+ continue;
+
+ func->_noreturn = 1;
+ }
+
+ /*
+ * A function's noreturn status depends on those of its sibling call
+ * destinations, which may not be settled yet. Keep clearing the
+ * noreturn bit for known cases until it stops spreading.
+ */
+ do {
+ changed = false;
+
+ for_each_sym(file->elf, func) {
+ if (!func->_noreturn || !might_return(file, func))
+ continue;
+
+ func->_noreturn = 0;
+ changed = true;
+ }
+ } while (changed);
+
+ /* Now mark the dead end call sites */
+ for_each_insn(file, insn) {
+ if (insn->type != INSN_CALL)
+ continue;
+
+ dest = insn_call_dest(insn);
+ if (dest && is_noreturn(dest))
+ insn->dead_end = true;
+ }
}
static void init_cfi_state(struct cfi_state *cfi)
@@ -1420,9 +1438,6 @@ static int annotate_call_site(struct objtool_file *file,
!insn->_call_dest->embedded_insn)
list_add_tail(&insn->call_node, &file->call_list);
- if (!sibling && dead_end_function(file, sym))
- insn->dead_end = true;
-
return 0;
}
@@ -2667,6 +2682,17 @@ int decode_file(struct objtool_file *file)
if (add_jump_table_alts(file))
return -1;
+ /*
+ * Must be after add_jump_table_alts(), which affects sibling call
+ * detection (jump table branches vs indirect sibling calls).
+ *
+ * The dead end marks are read by validate_branch() -- reached from
+ * both validate_functions() and validate_noinstr_sections() -- and by
+ * validate_unret().
+ */
+ if (validate_branch_enabled() || opts.noinstr || opts.unret)
+ detect_noreturns(file);
+
if (read_unwind_hints(file))
return -1;
@@ -2674,8 +2700,7 @@ int decode_file(struct objtool_file *file)
mark_holes(file);
/*
- * Must be after add_call_destinations() such that it can override
- * dead_end_function() marks.
+ * Must be after detect_noreturns() so it can override dead_end marks.
*/
if (read_annotate(file, __annotate_late))
return -1;
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index a82517a76a0f6..46fb2230ca743 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -98,6 +98,7 @@ struct symbol {
u8 klp : 1;
u8 dont_correlate : 1;
u8 fake : 1;
+ u8 _noreturn : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 06/27] objtool: Ignore traps after noreturn calls in STT_CODE
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (4 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 05/27] objtool: Refactor the noreturn/dead-end detection Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 07/27] objtool: Make .discard.stack_frame_non_standard non-allocatable Josh Poimboeuf
` (20 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Objtool doesn't do unreachable warnings for traps after noreturn calls,
but only in functions. Loongarch assembly STT_CODE can have the same
pattern:
bl start_kernel
ASM_BUG()
Ignore those as well.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/check.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 4528f017277d2..7333b0ed86818 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4269,12 +4269,6 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio
!strcmp(insn->sec->name, ".altinstr_aux"))
return true;
- if (!func)
- return false;
-
- if (func->static_call_tramp)
- return true;
-
/*
* CONFIG_UBSAN_TRAP inserts a UD2 when it sees
* __builtin_unreachable(). The BUG() macro has an unreachable() after
@@ -4290,6 +4284,12 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio
insn->jump_dest && insn->jump_dest->type == INSN_BUG)))
return true;
+ if (!func)
+ return false;
+
+ if (func->static_call_tramp)
+ return true;
+
/*
* Check if this (or a subsequent) instruction is related to
* CONFIG_UBSAN or CONFIG_KASAN.
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 07/27] objtool: Make .discard.stack_frame_non_standard non-allocatable
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (5 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 06/27] objtool: Ignore traps after noreturn calls in STT_CODE Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 08/27] efi/libstub: Drop .discard.addressable from the stub objects Josh Poimboeuf
` (19 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Convert STACK_FRAME_NON_STANDARD() to an asm macro to prevent it section
from being allocatable.
This will help it survive being used in libstub, which renames all
allocatable sections.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
include/linux/objtool.h | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/include/linux/objtool.h b/include/linux/objtool.h
index af2e68e496e5d..e9c78e026c7a3 100644
--- a/include/linux/objtool.h
+++ b/include/linux/objtool.h
@@ -4,6 +4,7 @@
#include <linux/objtool_types.h>
#include <linux/annotate.h>
+#include <linux/compiler.h>
#ifdef CONFIG_OBJTOOL
@@ -30,9 +31,11 @@
*
* For more information, see tools/objtool/Documentation/objtool.txt.
*/
-#define STACK_FRAME_NON_STANDARD(func) \
- static void __used __section(".discard.func_stack_frame_non_standard") \
- *__func_stack_frame_non_standard_##func = func
+#define STACK_FRAME_NON_STANDARD(func) \
+ __ADDRESSABLE(func); \
+ asm(".pushsection .discard.func_stack_frame_non_standard\n\t" \
+ ".long " #func " - .\n\t" \
+ ".popsection")
/*
* STACK_FRAME_NON_STANDARD_FP() is a frame-pointer-specific function ignore
@@ -91,8 +94,8 @@
.endm
.macro STACK_FRAME_NON_STANDARD func:req
- .pushsection .discard.func_stack_frame_non_standard, "aw"
- .quad \func
+ .pushsection .discard.func_stack_frame_non_standard, ""
+ .long \func - .
.popsection
.endm
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 08/27] efi/libstub: Drop .discard.addressable from the stub objects
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (6 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 07/27] objtool: Make .discard.stack_frame_non_standard non-allocatable Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 09/27] efi/loongarch: Mark loongarch efi_boot_kernel() non-standard for objtool Josh Poimboeuf
` (18 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
On arm64, riscv and loongarch, the EFI stub objects are objcopy'd with
--prefix-alloc-sections=.init, which renames every allocatable section.
The .discard.addressable section is allocatable, so if
STACK_FRAME_NON_STANDARD() and __ADDRESSABLE() are used, the section
gets renamed and never discarded:
ld: warning: orphan section `.init.discard.addressable' from `vmlinux.o' being placed in section `.init.discard.addressable'
In preparation for using STACK_FRAME_NON_STANDARD() in libstub code,
remove the section during objcopy.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
drivers/firmware/efi/libstub/Makefile | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile
index 77a2b2d74f3f6..1344d08b420a2 100644
--- a/drivers/firmware/efi/libstub/Makefile
+++ b/drivers/firmware/efi/libstub/Makefile
@@ -155,6 +155,10 @@ STUBCOPY_FLAGS-$(CONFIG_LOONGARCH) += --prefix-alloc-sections=.init \
--prefix-symbols=__efistub_
STUBCOPY_RELOC-$(CONFIG_LOONGARCH) := R_LARCH_MARK_LA
+# Drop the section created by __ADDRESSABLE() so it doesn't get prefixed.
+# Its job is already done.
+STUBCOPY_FLAGS-y += --remove-section=.discard.addressable
+
$(obj)/%.stub.o: $(obj)/%.o FORCE
$(call if_changed,stubcopy)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 09/27] efi/loongarch: Mark loongarch efi_boot_kernel() non-standard for objtool
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (7 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 08/27] efi/libstub: Drop .discard.addressable from the stub objects Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 10/27] objtool: Add ANNOTATE_IGNORE_NORETURN() Josh Poimboeuf
` (17 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
efi_boot_kernel() ends with a tail call through a __noreturn function
pointer, so the compiler emits nothing after it. Objtool can't tell that
in indirect call doesn't return, so it sees execution fall off the end
of the function:
vmlinux.o: warning: objtool: __efistub_efi_boot_kernel() falls through to next function __efistub_efi_get_memory_map()
This is boot code for which objtool/ORC coverage doesn't matter, so tell
objtool to ignore it.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
drivers/firmware/efi/libstub/loongarch.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/firmware/efi/libstub/loongarch.c b/drivers/firmware/efi/libstub/loongarch.c
index 2b0c87dc99088..737de9afd6a40 100644
--- a/drivers/firmware/efi/libstub/loongarch.c
+++ b/drivers/firmware/efi/libstub/loongarch.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
+#include <linux/objtool.h>
#include <asm/efi.h>
#include <asm/addrspace.h>
#include "efistub.h"
@@ -103,3 +104,10 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
real_kernel_entry(true, (unsigned long)cmdline_ptr,
(unsigned long)efi_system_table);
}
+
+/*
+ * Objtool has no way of knowing the real_kernel_entry() indirect call is
+ * noreturn. Just tell it to ignore this function: it's boot code anyway, so
+ * ORC coverage isn't needed.
+ */
+STACK_FRAME_NON_STANDARD(efi_boot_kernel);
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 10/27] objtool: Add ANNOTATE_IGNORE_NORETURN()
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (8 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 09/27] efi/loongarch: Mark loongarch efi_boot_kernel() non-standard for objtool Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 11/27] LoongArch: Annotate reboot and kexec paths as returnable Josh Poimboeuf
` (16 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
A generic interface may be noreturn in one arch and returnable in
another, in which case its common declaration can't say __noreturn.
Rust has a similar issue where a few core library calls to returnable
functions are replaced after compilation with calls to a noreturn panic
function.
Add an annotation which tells objtool to ignore the noreturn status of a
given function.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
include/linux/annotate.h | 7 +++++++
include/linux/objtool_types.h | 1 +
tools/include/linux/objtool_types.h | 1 +
tools/objtool/check.c | 19 +++++++++++++++++++
tools/objtool/include/objtool/elf.h | 1 +
5 files changed, 29 insertions(+)
diff --git a/include/linux/annotate.h b/include/linux/annotate.h
index 2f1599c9e5732..a81ca04b4c501 100644
--- a/include/linux/annotate.h
+++ b/include/linux/annotate.h
@@ -3,6 +3,7 @@
#define _LINUX_ANNOTATE_H
#include <linux/objtool_types.h>
+#include <linux/stringify.h>
#ifdef CONFIG_OBJTOOL
@@ -105,6 +106,12 @@
*/
#define ANNOTATE_NOCFI_SYM(sym) asm(ASM_ANNOTATE_LABEL(sym, ANNOTYPE_NOCFI))
+/*
+ * Treat a function as returnable by its callers despite objtool classifying it
+ * as noreturn.
+ */
+#define ANNOTATE_IGNORE_NORETURN(sym) asm(ASM_ANNOTATE_LABEL(sym, ANNOTYPE_IGNORE_NORETURN))
+
/*
* Annotate a special section entry. This emables livepatch module generation
* to find and extract individual special section entries as needed.
diff --git a/include/linux/objtool_types.h b/include/linux/objtool_types.h
index c24e9ea392696..ce08ecc808c67 100644
--- a/include/linux/objtool_types.h
+++ b/include/linux/objtool_types.h
@@ -66,6 +66,7 @@ struct unwind_hint {
#define ANNOTYPE_INTRA_FUNCTION_CALL 7
#define ANNOTYPE_REACHABLE 8
#define ANNOTYPE_NOCFI 9
+#define ANNOTYPE_IGNORE_NORETURN 10
#define ANNOTYPE_DATA_SPECIAL 1
diff --git a/tools/include/linux/objtool_types.h b/tools/include/linux/objtool_types.h
index c24e9ea392696..ce08ecc808c67 100644
--- a/tools/include/linux/objtool_types.h
+++ b/tools/include/linux/objtool_types.h
@@ -66,6 +66,7 @@ struct unwind_hint {
#define ANNOTYPE_INTRA_FUNCTION_CALL 7
#define ANNOTYPE_REACHABLE 8
#define ANNOTYPE_NOCFI 9
+#define ANNOTYPE_IGNORE_NORETURN 10
#define ANNOTYPE_DATA_SPECIAL 1
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 7333b0ed86818..ddb8dbe7f71d9 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -246,6 +246,9 @@ static bool is_noreturn(struct symbol *func)
{
func = func->alias->pfunc;
+ if (func->ignore_noreturn)
+ return false;
+
if (is_listed_noreturn(func))
return true;
@@ -2389,6 +2392,18 @@ static int __annotate_early(struct objtool_file *file, int type, struct instruct
insn->noendbr = 1;
break;
+ /* Must be before detect_noreturns() */
+ case ANNOTYPE_IGNORE_NORETURN: {
+ struct symbol *sym = insn_sym(insn);
+
+ if (!sym) {
+ ERROR_INSN(insn, "dodgy IGNORE_NORETURN annotation");
+ return -1;
+ }
+ sym->ignore_noreturn = 1;
+ break;
+ }
+
default:
break;
}
@@ -2435,6 +2450,10 @@ static int __annotate_late(struct objtool_file *file, int type, struct instructi
/* early */
break;
+ case ANNOTYPE_IGNORE_NORETURN:
+ /* early */
+ break;
+
case ANNOTYPE_RETPOLINE_SAFE:
if (insn->type != INSN_JUMP_DYNAMIC &&
insn->type != INSN_CALL_DYNAMIC &&
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index 46fb2230ca743..0e3593d993ac8 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -99,6 +99,7 @@ struct symbol {
u8 dont_correlate : 1;
u8 fake : 1;
u8 _noreturn : 1;
+ u8 ignore_noreturn : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 11/27] LoongArch: Annotate reboot and kexec paths as returnable
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (9 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 10/27] objtool: Add ANNOTATE_IGNORE_NORETURN() Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features Josh Poimboeuf
` (15 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Running objtool on LoongArch vmlinux.o shows the following warnings:
vmlinux.o: warning: objtool: __do_sys_reboot+0x188: kernel_restart() is missing __noreturn in .c/.h
vmlinux.o: warning: objtool: hw_failure_emergency_action_func+0x74: kernel_restart() is missing __noreturn in .c/.h
vmlinux.o: warning: objtool: hibernate+0x3cc: kernel_halt() is missing __noreturn in .c/.h
vmlinux.o: warning: objtool: kernel_kexec+0xbc: machine_kexec() is missing __noreturn in .c/.h
vmlinux.o: warning: objtool: restart_poweroff_do_poweroff+0x1c: machine_restart() is missing __noreturn in .c/.h
The problem is that the generic declarations of the above functions are
not declared noreturn.
But marking them __noreturn wouldn't be straightforward given the
inconsistent behaviors they have across the arches:
- In many cases they don't return, but the compiler doesn't have
visibility to that for various reasons including inline asm and
indirect calls to noreturn functions (which objtool is currently not
equipped to deal with).
- In some cases they even *do* return, e.g. x86 machine_exec(), arm64
machine_power_off(), and hexagon machine_restart().
In lieu of undertaking such a large cleanup, fix the above warnings with
a few annotations so objtool doesn't consider them noreturn, consistent
with their call sites.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/loongarch/kernel/machine_kexec.c | 2 ++
arch/loongarch/kernel/reset.c | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c
index 1883cae93bc31..e40b6767df24e 100644
--- a/arch/loongarch/kernel/machine_kexec.c
+++ b/arch/loongarch/kernel/machine_kexec.c
@@ -16,6 +16,7 @@
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/sched/task_stack.h>
+#include <linux/annotate.h>
#include <asm/bootinfo.h>
#include <asm/cacheflush.h>
@@ -296,3 +297,4 @@ void machine_kexec(struct kimage *image)
kexec_reboot();
}
+ANNOTATE_IGNORE_NORETURN(machine_kexec);
diff --git a/arch/loongarch/kernel/reset.c b/arch/loongarch/kernel/reset.c
index de8fa5a8a825c..89ee0edcf58d2 100644
--- a/arch/loongarch/kernel/reset.c
+++ b/arch/loongarch/kernel/reset.c
@@ -11,6 +11,7 @@
#include <linux/reboot.h>
#include <linux/delay.h>
#include <linux/console.h>
+#include <linux/annotate.h>
#include <acpi/reboot.h>
#include <asm/idle.h>
@@ -36,6 +37,7 @@ void machine_halt(void)
__asm__ __volatile__("idle 0" : : : "memory");
}
}
+ANNOTATE_IGNORE_NORETURN(machine_halt);
void machine_power_off(void)
{
@@ -56,6 +58,7 @@ void machine_power_off(void)
__asm__ __volatile__("idle 0" : : : "memory");
}
}
+ANNOTATE_IGNORE_NORETURN(machine_power_off);
void machine_restart(char *command)
{
@@ -77,3 +80,4 @@ void machine_restart(char *command)
__asm__ __volatile__("idle 0" : : : "memory");
}
}
+ANNOTATE_IGNORE_NORETURN(machine_restart);
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (10 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 11/27] LoongArch: Annotate reboot and kexec paths as returnable Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 17:57 ` Nathan Chancellor
2026-08-28 4:51 ` [PATCH 13/27] rust: Annotate the intrinsic stubs as returnable Josh Poimboeuf
` (14 subsequent siblings)
26 siblings, 1 reply; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
noreturns.h file is hand-maintained file which hard codes all the known
__noreturn functions in the kernel. It's fragile, often missing
entries, and keeping it up to date is a maintenance burden.
For LTO, IBT, and klp-build configs, the majority of those entries
aren't needed, because objtool runs on vmlinux.o, so it already has
visibility into whether a given function returns (with its dead end
detection).
Remove the need for many of the noreturns.h entries by just always
deferring objtool for all features which rely on having the noreturn
information: namely the ones which rely on the reverse-engineered
control flow graph (CFG).
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
lib/Kconfig.debug | 3 ++-
scripts/Makefile.vmlinux_o | 14 +++-----------
tools/objtool/noreturns.h | 23 -----------------------
3 files changed, 5 insertions(+), 35 deletions(-)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 0ea5ad31969ce..bfb977595631e 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -599,7 +599,8 @@ config OBJTOOL_CONTROL_FLOW
config OBJTOOL_DEFERRED
def_bool y
depends on OBJTOOL
- depends on LTO_CLANG || X86_KERNEL_IBT || KLP_BUILD
+ depends on OBJTOOL_CONTROL_FLOW || NOINSTR_VALIDATION || LTO_CLANG || \
+ X86_KERNEL_IBT || KLP_BUILD
config VMLINUX_MAP
bool "Generate vmlinux.map file when linking"
diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
index 9bac917e8b819..df1e3584883e6 100644
--- a/scripts/Makefile.vmlinux_o
+++ b/scripts/Makefile.vmlinux_o
@@ -30,20 +30,12 @@ endif
# objtool for vmlinux.o
# ---------------------------------------------------------------------------
#
-# For CONFIG_OBJTOOL_DEFERRED (IBT or LTO), objtool doesn't run on individual
-# translation units. Instead it runs on vmlinux.o.
-#
-# For !CONFIG_OBJTOOL_DEFERRED + CONFIG_NOINSTR_VALIDATION, it runs on both
-# translation units and vmlinux.o, with the latter only used for noinstr/unret
-# validation.
+# For CONFIG_OBJTOOL_DEFERRED, objtool doesn't run on individual translation
+# units. Instead it runs on vmlinux.o.
-objtool-enabled := $(or $(CONFIG_OBJTOOL_DEFERRED),$(CONFIG_NOINSTR_VALIDATION))
+objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
-ifeq ($(CONFIG_OBJTOOL_DEFERRED),y)
vmlinux-objtool-args-y += $(objtool-args-y)
-else
-vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
-endif
vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \
$(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret)
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index 08d441b8895cb..cd41f580ccb72 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -7,44 +7,21 @@
* Yes, this is unfortunate. A better solution is in the works.
*/
NORETURN(__fortify_panic)
-NORETURN(__ia32_sys_exit)
-NORETURN(__ia32_sys_exit_group)
NORETURN(__kunit_abort)
NORETURN(__module_put_and_kthread_exit)
NORETURN(__stack_chk_fail)
-NORETURN(__tdx_hypercall_failed)
NORETURN(__ubsan_handle_builtin_unreachable)
-NORETURN(__x64_sys_exit)
-NORETURN(__x64_sys_exit_group)
NORETURN(acpi_processor_ffh_play_dead)
-NORETURN(arch_cpu_idle_dead)
-NORETURN(cpu_bringup_and_idle)
-NORETURN(cpu_startup_entry)
NORETURN(do_exit)
-NORETURN(do_group_exit)
-NORETURN(do_task_dead)
-NORETURN(ex_handler_msr_mce)
-NORETURN(hlt_play_dead)
-NORETURN(hv_ghcb_terminate)
NORETURN(kthread_complete_and_exit)
NORETURN(kunit_try_catch_throw)
-NORETURN(machine_real_restart)
-NORETURN(make_task_dead)
NORETURN(mpt_halt_firmware)
-NORETURN(mwait_play_dead)
-NORETURN(native_play_dead)
NORETURN(nmi_panic_self_stop)
NORETURN(panic)
NORETURN(vpanic)
NORETURN(panic_smp_self_stop)
-NORETURN(rest_init)
NORETURN(rewind_stack_and_make_dead)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
-NORETURN(start_kernel)
-NORETURN(stop_this_cpu)
-NORETURN(usercopy_abort)
-NORETURN(x86_64_start_kernel)
-NORETURN(x86_64_start_reservations)
NORETURN(xen_cpu_bringup_again)
NORETURN(xen_start_kernel)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* Re: [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features
2026-08-28 4:51 ` [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features Josh Poimboeuf
@ 2026-08-28 17:57 ` Nathan Chancellor
2026-08-28 18:19 ` Josh Poimboeuf
0 siblings, 1 reply; 31+ messages in thread
From: Nathan Chancellor @ 2026-08-28 17:57 UTC (permalink / raw)
To: Josh Poimboeuf
Cc: x86, linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nicolas Schier, linux-kbuild,
Huacai Chen
Hi Josh,
On Thu, Aug 27, 2026 at 09:51:41PM -0700, Josh Poimboeuf wrote:
> diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
> index 9bac917e8b819..df1e3584883e6 100644
> --- a/scripts/Makefile.vmlinux_o
> +++ b/scripts/Makefile.vmlinux_o
> @@ -30,20 +30,12 @@ endif
> # objtool for vmlinux.o
> # ---------------------------------------------------------------------------
> #
> -# For CONFIG_OBJTOOL_DEFERRED (IBT or LTO), objtool doesn't run on individual
> -# translation units. Instead it runs on vmlinux.o.
> -#
> -# For !CONFIG_OBJTOOL_DEFERRED + CONFIG_NOINSTR_VALIDATION, it runs on both
> -# translation units and vmlinux.o, with the latter only used for noinstr/unret
> -# validation.
> +# For CONFIG_OBJTOOL_DEFERRED, objtool doesn't run on individual translation
> +# units. Instead it runs on vmlinux.o.
>
> -objtool-enabled := $(or $(CONFIG_OBJTOOL_DEFERRED),$(CONFIG_NOINSTR_VALIDATION))
> +objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
>
> -ifeq ($(CONFIG_OBJTOOL_DEFERRED),y)
> vmlinux-objtool-args-y += $(objtool-args-y)
> -else
> -vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
Did you mean to drop this line or did it accidentally get culled in
cleaning up the conditional statements?
> -endif
>
> vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \
> $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret)
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features
2026-08-28 17:57 ` Nathan Chancellor
@ 2026-08-28 18:19 ` Josh Poimboeuf
2026-08-28 19:27 ` Nathan Chancellor
0 siblings, 1 reply; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 18:19 UTC (permalink / raw)
To: Nathan Chancellor
Cc: x86, linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nicolas Schier, linux-kbuild,
Huacai Chen
On Fri, Aug 28, 2026 at 10:57:08AM -0700, Nathan Chancellor wrote:
> On Thu, Aug 27, 2026 at 09:51:41PM -0700, Josh Poimboeuf wrote:
> > diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
> > index 9bac917e8b819..df1e3584883e6 100644
> > --- a/scripts/Makefile.vmlinux_o
> > +++ b/scripts/Makefile.vmlinux_o
> > @@ -30,20 +30,12 @@ endif
> > # objtool for vmlinux.o
> > # ---------------------------------------------------------------------------
> > #
> > -# For CONFIG_OBJTOOL_DEFERRED (IBT or LTO), objtool doesn't run on individual
> > -# translation units. Instead it runs on vmlinux.o.
> > -#
> > -# For !CONFIG_OBJTOOL_DEFERRED + CONFIG_NOINSTR_VALIDATION, it runs on both
> > -# translation units and vmlinux.o, with the latter only used for noinstr/unret
> > -# validation.
> > +# For CONFIG_OBJTOOL_DEFERRED, objtool doesn't run on individual translation
> > +# units. Instead it runs on vmlinux.o.
> >
> > -objtool-enabled := $(or $(CONFIG_OBJTOOL_DEFERRED),$(CONFIG_NOINSTR_VALIDATION))
> > +objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
> >
> > -ifeq ($(CONFIG_OBJTOOL_DEFERRED),y)
> > vmlinux-objtool-args-y += $(objtool-args-y)
> > -else
> > -vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
>
> Did you mean to drop this line or did it accidentally get culled in
> cleaning up the conditional statements?
That was actually on purpose.
Before this patch, NOINSTR_VALIDATION was a weird special case, where
objtool ran on individual TUs (with all the objtool-args-y), and then
ran again on vmlinux.o (without objtool-args-y except for --werror).
This patch makes NOINSTR_VALIDATION just another OBJTOOL_DEFERRED
feature which runs deferred, and *not* on TUs, so now it gets all the
args like the others.
That was indeed non-obvious, I'll add it to the commit log for v2.
> > -endif
> >
> > vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \
> > $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret)
--
Josh
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features
2026-08-28 18:19 ` Josh Poimboeuf
@ 2026-08-28 19:27 ` Nathan Chancellor
0 siblings, 0 replies; 31+ messages in thread
From: Nathan Chancellor @ 2026-08-28 19:27 UTC (permalink / raw)
To: Josh Poimboeuf
Cc: x86, linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nicolas Schier, linux-kbuild,
Huacai Chen
On Fri, Aug 28, 2026 at 11:19:52AM -0700, Josh Poimboeuf wrote:
> That was actually on purpose.
>
> Before this patch, NOINSTR_VALIDATION was a weird special case, where
> objtool ran on individual TUs (with all the objtool-args-y), and then
> ran again on vmlinux.o (without objtool-args-y except for --werror).
>
> This patch makes NOINSTR_VALIDATION just another OBJTOOL_DEFERRED
> feature which runs deferred, and *not* on TUs, so now it gets all the
> args like the others.
>
> That was indeed non-obvious, I'll add it to the commit log for v2.
Ah okay, that makes sense now that I have zoomed out a bit, got tunnel
vision there. Adding that explanation to the commit message sounds good
to me, thanks!
--
Cheers,
Nathan
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH 13/27] rust: Annotate the intrinsic stubs as returnable
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (11 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 12/27] kbuild: Defer running objtool to link time for all CFG features Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 14/27] panic: Mark abort() __noreturn Josh Poimboeuf
` (13 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
When building the Rust core library, the compiler emits calls to
compiler-rt builtins like __udivti3() for 128-bit arithmetic, which the
kernel doesn't want, so objcopy is used to redirect those calls to stubs
which just panic.
Problem is, their call sites still expect them to return, which confuses
objtool.
Objtool doesn't report any warnings today because the stubs are weak,
which noreturn detection currently ignores. In preparation for removing
that restriction, annotate them as returnable.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
rust/.gitignore | 1 +
rust/Makefile | 17 +++++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/rust/.gitignore b/rust/.gitignore
index d3829ffab80ba..c198d1a52e60e 100644
--- a/rust/.gitignore
+++ b/rust/.gitignore
@@ -6,5 +6,6 @@ doctests_kernel_generated.rs
doctests_kernel_generated_kunit.c
uapi_generated.rs
exports_*_generated.h
+intrinsics_annotate_generated.c
doc/
test/
diff --git a/rust/Makefile b/rust/Makefile
index 627ed79dc6f50..1cbee6f1ca098 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -33,6 +33,9 @@ endif
obj-$(CONFIG_RUST) += exports.o
+always-$(CONFIG_RUST) += intrinsics_annotate_generated.c
+obj-$(CONFIG_RUST) += intrinsics_annotate_generated.o
+
always-$(CONFIG_RUST) += libproc_macro2.rlib libquote.rlib libsyn.rlib
always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs
@@ -684,6 +687,20 @@ ifneq ($(or $(CONFIG_ARM64),$(and $(CONFIG_RISCV),$(CONFIG_64BIT))),)
__ashlti3 __lshrti3
endif
+# For objtool, annotate the redirect-intrinsics as returnable. Their call
+# sites were compiled against the returnable intrinsics, but the redirected
+# versions are noreturn.
+quiet_cmd_intrinsics_annotate = GEN $@
+ cmd_intrinsics_annotate = { \
+ echo '/* Generated by rust/Makefile - do not edit */'; \
+ echo '\#include <linux/annotate.h>'; \
+ for s in $(redirect-intrinsics); do \
+ echo "ANNOTATE_IGNORE_NORETURN(__rust$$s);"; \
+ done; } > $@
+
+$(obj)/intrinsics_annotate_generated.c: $(srctree)/rust/Makefile FORCE
+ $(call if_changed,intrinsics_annotate)
+
ifdef CONFIG_MODVERSIONS
cmd_gendwarfksyms = $(if $(skip_gendwarfksyms),, \
$(call rust_exports,$@,"%s\n") | \
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 14/27] panic: Mark abort() __noreturn
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (12 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 13/27] rust: Annotate the intrinsic stubs as returnable Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 15/27] x86/xen: Ignore noreturn status of weak mem_map_via_hcall() Josh Poimboeuf
` (12 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
abort() is a C compiler builtin interface, so compilers already know it
doesn't return, but mark it as such explicitly for documentation
purposes.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/arc/kernel/traps.c | 4 +++-
arch/arm/kernel/traps.c | 2 +-
include/linux/panic.h | 2 +-
kernel/exit.c | 2 +-
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/arch/arc/kernel/traps.c b/arch/arc/kernel/traps.c
index 8d2ea2cbd98b0..803c131c6aa78 100644
--- a/arch/arc/kernel/traps.c
+++ b/arch/arc/kernel/traps.c
@@ -153,7 +153,9 @@ void do_insterror_or_kprobe(unsigned long address, struct pt_regs *regs)
/*
* abort() call generated by older gcc for __builtin_trap()
*/
-void abort(void)
+void __noreturn abort(void)
{
__asm__ __volatile__("trap_s 5\n");
+
+ unreachable();
}
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c
index afbd2ebe5c39d..a3bd26d41612f 100644
--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -777,7 +777,7 @@ asmlinkage void __div0(void)
}
EXPORT_SYMBOL(__div0);
-void abort(void)
+void __noreturn abort(void)
{
BUG();
diff --git a/include/linux/panic.h b/include/linux/panic.h
index f1dd417e54b29..da8778360dadf 100644
--- a/include/linux/panic.h
+++ b/include/linux/panic.h
@@ -33,7 +33,7 @@ extern int sysctl_panic_on_stackoverflow;
extern bool crash_kexec_post_notifiers;
extern void __stack_chk_fail(void);
-void abort(void);
+void abort(void) __noreturn;
/*
* panic_cpu is used for synchronizing panic() and crash_kexec() execution. It
diff --git a/kernel/exit.c b/kernel/exit.c
index 2c0b1c02920f4..2c55dc0fbbec8 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -2028,7 +2028,7 @@ COMPAT_SYSCALL_DEFINE5(waitid,
*
* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88345#c11
*/
-__weak __function_aligned void abort(void)
+__weak __function_aligned void __noreturn abort(void)
{
BUG();
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 15/27] x86/xen: Ignore noreturn status of weak mem_map_via_hcall()
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (13 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 14/27] panic: Mark abort() __noreturn Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 16/27] objtool: Detect noreturns in weak functions Josh Poimboeuf
` (11 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen, kernel test robot
Unlike the non-weak version, the weak version of mem_map_via_hcall()
doesn't return. Tell objtool to ignore that, as the protoype (and the
non-weak version) aren't noreturn so its callers can't assume it.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202608280214.c28GlIx6-lkp@intel.com/
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/platform/pvh/enlighten.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/x86/platform/pvh/enlighten.c b/arch/x86/platform/pvh/enlighten.c
index f2053cbe9b0ce..9f227f06cbaf5 100644
--- a/arch/x86/platform/pvh/enlighten.c
+++ b/arch/x86/platform/pvh/enlighten.c
@@ -37,6 +37,7 @@ void __init __weak mem_map_via_hcall(struct boot_params *ptr __maybe_unused)
xen_raw_printk("Error: Could not find memory map\n");
BUG();
}
+ANNOTATE_IGNORE_NORETURN(mem_map_via_hcall);
static void __init init_pvh_bootparams(bool xen_guest)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 16/27] objtool: Detect noreturns in weak functions
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (14 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 15/27] x86/xen: Ignore noreturn status of weak mem_map_via_hcall() Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 17/27] x86/entry: Make rewind_stack_and_make_dead() a real function Josh Poimboeuf
` (10 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
The skipping of weak functions in the noreturn detection logic is a bit
weird, as a function's return/noreturn ABI shouldn't change regardless
of which version gets linked.
Fix that, and update the global noreturns list as needed.
Note that abort() now needs to be added to the list: while objtool on
vmlinux.o can detect that its noreturn, modules have no way of knowing.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/check.c | 3 ---
tools/objtool/noreturns.h | 3 +--
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index ddb8dbe7f71d9..c55851ec389cd 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -260,9 +260,6 @@ static bool might_return(struct objtool_file *file, struct symbol *func)
struct instruction *insn;
struct symbol *dest;
- if (is_weak_sym(func))
- return true;
-
func_for_each_insn(file, func, insn) {
if (insn->type == INSN_RETURN)
return true;
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index cd41f580ccb72..26f62f98bc8ef 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -11,15 +11,14 @@ NORETURN(__kunit_abort)
NORETURN(__module_put_and_kthread_exit)
NORETURN(__stack_chk_fail)
NORETURN(__ubsan_handle_builtin_unreachable)
+NORETURN(abort)
NORETURN(acpi_processor_ffh_play_dead)
NORETURN(do_exit)
NORETURN(kthread_complete_and_exit)
NORETURN(kunit_try_catch_throw)
NORETURN(mpt_halt_firmware)
-NORETURN(nmi_panic_self_stop)
NORETURN(panic)
NORETURN(vpanic)
-NORETURN(panic_smp_self_stop)
NORETURN(rewind_stack_and_make_dead)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 17/27] x86/entry: Make rewind_stack_and_make_dead() a real function
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (15 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 16/27] objtool: Detect noreturns in weak functions Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 18/27] x86/xen: Make xen_cpu_bringup_again() " Josh Poimboeuf
` (9 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
rewind_stack_and_make_dead() is SYM_CODE because it switches stacks, but
it's otherwise a callable function from C.
Convert it to SYM_FUNC so objtool can detect that it's noreturn and its
entry can be removed from the hard-coded global noreturns list.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/entry/entry_64.S | 7 +++----
tools/objtool/noreturns.h | 1 -
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index c6d996593f329..1184bf1f9ed7e 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -1480,9 +1480,7 @@ SYM_CODE_START(entry_SYSCALL32_ignore)
SYM_CODE_END(entry_SYSCALL32_ignore)
.pushsection .text, "ax"
- __FUNC_ALIGN
-SYM_CODE_START_NOALIGN(rewind_stack_and_make_dead)
- UNWIND_HINT_FUNC
+SYM_FUNC_START(rewind_stack_and_make_dead)
/* Prevent any naive code from trying to unwind to our caller. */
xorl %ebp, %ebp
@@ -1491,7 +1489,8 @@ SYM_CODE_START_NOALIGN(rewind_stack_and_make_dead)
UNWIND_HINT_REGS
call make_task_dead
-SYM_CODE_END(rewind_stack_and_make_dead)
+SYM_FUNC_END(rewind_stack_and_make_dead)
+STACK_FRAME_NON_STANDARD_FP(rewind_stack_and_make_dead)
.popsection
/*
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index 26f62f98bc8ef..ff93e94ee09a0 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -19,7 +19,6 @@ NORETURN(kunit_try_catch_throw)
NORETURN(mpt_halt_firmware)
NORETURN(panic)
NORETURN(vpanic)
-NORETURN(rewind_stack_and_make_dead)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
NORETURN(xen_cpu_bringup_again)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 18/27] x86/xen: Make xen_cpu_bringup_again() a real function
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (16 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 17/27] x86/entry: Make rewind_stack_and_make_dead() a real function Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 19/27] x86/xen: Make xen_start_kernel() noreturn Josh Poimboeuf
` (8 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
xen_cpu_bringup_again() is SYM_CODE because it switches stacks, but it's
otherwise a callable function from C.
Convert it to STT_FUNC so objtool can detect that it's noreturn and its
entry can be removed from the hard-coded global noreturns list.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/xen/xen-head.S | 5 +++--
tools/objtool/noreturns.h | 1 -
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/x86/xen/xen-head.S b/arch/x86/xen/xen-head.S
index 5dad6c51cdc34..68d769772f309 100644
--- a/arch/x86/xen/xen-head.S
+++ b/arch/x86/xen/xen-head.S
@@ -55,12 +55,13 @@ SYM_CODE_START(asm_cpu_bringup_and_idle)
call cpu_bringup_and_idle
SYM_CODE_END(asm_cpu_bringup_and_idle)
-SYM_CODE_START(xen_cpu_bringup_again)
+SYM_FUNC_START(xen_cpu_bringup_again)
UNWIND_HINT_FUNC
mov %rdi, %rsp
UNWIND_HINT_REGS
call cpu_bringup_and_idle
-SYM_CODE_END(xen_cpu_bringup_again)
+SYM_FUNC_END(xen_cpu_bringup_again)
+STACK_FRAME_NON_STANDARD_FP(xen_cpu_bringup_again)
.popsection
#endif
#endif
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index ff93e94ee09a0..01c94f20d1818 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -21,5 +21,4 @@ NORETURN(panic)
NORETURN(vpanic)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
-NORETURN(xen_cpu_bringup_again)
NORETURN(xen_start_kernel)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 19/27] x86/xen: Make xen_start_kernel() noreturn
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (17 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 18/27] x86/xen: Make xen_cpu_bringup_again() " Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 20/27] x86/boot: Rework how pi startup symbols get exposed to vmlinux Josh Poimboeuf
` (7 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
startup_xen() calls xen_start_kernel() with the assumption that it never
returns, otherwise it would fall off the end of SYM_CODE_END(),
triggering undefined behavior.
But startup_xen() actually can return if 'si' is NULL. That doesn't
seem to be possible, so drop the check and mark xen_start_kernel()
__noreturn.
A NULL 'si' would now fault on the first xen_start_info dereference,
which is at least a more deterministic way to crash.
Now that the function can no longer return, objtool can derive its
noreturn attribute directly and the noreturns.h entry can be removed.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/xen/enlighten_pv.c | 5 +----
arch/x86/xen/xen-ops.h | 2 +-
tools/objtool/noreturns.h | 1 -
3 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/arch/x86/xen/enlighten_pv.c b/arch/x86/xen/enlighten_pv.c
index 2c64b388f6164..8ec78df9d1d5b 100644
--- a/arch/x86/xen/enlighten_pv.c
+++ b/arch/x86/xen/enlighten_pv.c
@@ -1325,15 +1325,12 @@ static void __init xen_domu_set_legacy_features(void)
extern void early_xen_iret_patch(void);
/* First C function to be called on Xen boot */
-asmlinkage __visible void __init xen_start_kernel(struct start_info *si)
+asmlinkage __visible void __init __noreturn xen_start_kernel(struct start_info *si)
{
struct physdev_set_iopl set_iopl;
unsigned long initrd_start = 0;
int rc;
- if (!si)
- return;
-
clear_bss();
xen_start_info = si;
diff --git a/arch/x86/xen/xen-ops.h b/arch/x86/xen/xen-ops.h
index dc265bdda24d7..20e8fb6244426 100644
--- a/arch/x86/xen/xen-ops.h
+++ b/arch/x86/xen/xen-ops.h
@@ -73,7 +73,7 @@ void xen_force_evtchn_callback(void);
void xen_pv_pre_suspend(void);
void xen_pv_post_suspend(int suspend_cancelled);
-void xen_start_kernel(struct start_info *si);
+void __noreturn xen_start_kernel(struct start_info *si);
void set_pte_mfn(unsigned long vaddr, unsigned long pfn, pgprot_t flags);
void xen_init_mmu_ops(void);
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index 01c94f20d1818..c4b93fc6e6299 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -21,4 +21,3 @@ NORETURN(panic)
NORETURN(vpanic)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
-NORETURN(xen_start_kernel)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 20/27] x86/boot: Rework how pi startup symbols get exposed to vmlinux
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (18 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 19/27] x86/xen: Make xen_start_kernel() noreturn Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 21/27] objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE Josh Poimboeuf
` (6 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
objcopy prefixes every symbol in arch/x86/boot/startup/ with __pi_,
which confines the startup code so that it can only reach other startup
code or symbols deliberately exposed to it with SYM_PIC_ALIAS().
Nine of its functions are called by the core kernel at runtime. They
are made accessible to the kernel by exports.h, which is included by
vmlinux.lds.S to create aliases for each one.
That's problematic for objtool running on vmlinux.o, which is built
before the linker script runs, so the call sites to the startup symbols
are undefined, so objtool can't see their actual destinations functions.
Instead of making aliases at link time, rename the needed symbols using
a second objcopy pass so they're visible in vmlinux.o. That also means
objtool can now detect that sev_es_terminate() is noreturn, so it can be
removed from noreturns.h.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
arch/x86/boot/startup/Makefile | 14 ++++++++++----
arch/x86/boot/startup/exports.h | 14 --------------
arch/x86/boot/startup/unprefix.syms | 13 +++++++++++++
arch/x86/kernel/vmlinux.lds.S | 2 --
tools/objtool/noreturns.h | 1 -
5 files changed, 23 insertions(+), 21 deletions(-)
delete mode 100644 arch/x86/boot/startup/exports.h
create mode 100644 arch/x86/boot/startup/unprefix.syms
diff --git a/arch/x86/boot/startup/Makefile b/arch/x86/boot/startup/Makefile
index ab6e9970d4f4e..5255abe2bd77f 100644
--- a/arch/x86/boot/startup/Makefile
+++ b/arch/x86/boot/startup/Makefile
@@ -41,12 +41,18 @@ $(pi-objs): objtool-args = $(if $(CONFIG_OBJTOOL_DEFERRED),--dry-run,$(objtool-a
#
# Confine the startup code by prefixing all symbols with __pi_ (for position
# independent). This ensures that startup code can only call other startup
-# code, or code that has explicitly been made accessible to it via a symbol
-# alias.
+# code.
#
+# The few pi symbols which are allowed to be called by the core kernel are then
+# un-prefixed back to their original name using a second objcopy.
+#
+quiet_cmd_pi_objcopy = $(quiet_cmd_objcopy)
+ cmd_pi_objcopy = $(cmd_objcopy) && \
+ $(OBJCOPY) --redefine-syms=$(src)/unprefix.syms $@
+
$(obj)/%.pi.o: OBJCOPYFLAGS := --prefix-symbols=__pi_
-$(obj)/%.pi.o: $(obj)/%.o FORCE
- $(call if_changed,objcopy)
+$(obj)/%.pi.o: $(obj)/%.o $(src)/unprefix.syms FORCE
+ $(call if_changed,pi_objcopy)
targets += $(obj-y)
obj-y := $(patsubst %.o,%.pi.o,$(obj-y))
diff --git a/arch/x86/boot/startup/exports.h b/arch/x86/boot/startup/exports.h
deleted file mode 100644
index 01d2363dc445f..0000000000000
--- a/arch/x86/boot/startup/exports.h
+++ /dev/null
@@ -1,14 +0,0 @@
-
-/*
- * The symbols below are functions that are implemented by the startup code,
- * but called at runtime by the SEV code residing in the core kernel.
- */
-PROVIDE(early_set_pages_state = __pi_early_set_pages_state);
-PROVIDE(early_snp_set_memory_private = __pi_early_snp_set_memory_private);
-PROVIDE(early_snp_set_memory_shared = __pi_early_snp_set_memory_shared);
-PROVIDE(get_hv_features = __pi_get_hv_features);
-PROVIDE(sev_es_terminate = __pi_sev_es_terminate);
-PROVIDE(snp_cpuid = __pi_snp_cpuid);
-PROVIDE(snp_cpuid_get_table = __pi_snp_cpuid_get_table);
-PROVIDE(svsm_issue_call = __pi_svsm_issue_call);
-PROVIDE(svsm_process_result_codes = __pi_svsm_process_result_codes);
diff --git a/arch/x86/boot/startup/unprefix.syms b/arch/x86/boot/startup/unprefix.syms
new file mode 100644
index 0000000000000..fd80adf558a51
--- /dev/null
+++ b/arch/x86/boot/startup/unprefix.syms
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+# The symbols below are functions that are implemented by the startup code,
+# but called at runtime by the SEV code residing in the core kernel. Undo the
+# __pi_ prefixing so the core kernel can call them.
+__pi_early_set_pages_state early_set_pages_state
+__pi_early_snp_set_memory_private early_snp_set_memory_private
+__pi_early_snp_set_memory_shared early_snp_set_memory_shared
+__pi_get_hv_features get_hv_features
+__pi_sev_es_terminate sev_es_terminate
+__pi_snp_cpuid snp_cpuid
+__pi_snp_cpuid_get_table snp_cpuid_get_table
+__pi_svsm_issue_call svsm_issue_call
+__pi_svsm_process_result_codes svsm_process_result_codes
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 74e336d7f9dd2..3d6f4634475cf 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -533,5 +533,3 @@ xen_elfnote_entry_value =
xen_elfnote_phys32_entry_value =
ABSOLUTE(xen_elfnote_phys32_entry) + ABSOLUTE(pvh_start_xen - LOAD_OFFSET);
#endif
-
-#include "../boot/startup/exports.h"
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index c4b93fc6e6299..2dce56030b980 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -20,4 +20,3 @@ NORETURN(mpt_halt_firmware)
NORETURN(panic)
NORETURN(vpanic)
NORETURN(rust_helper_BUG)
-NORETURN(sev_es_terminate)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 21/27] objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (19 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 20/27] x86/boot: Rework how pi startup symbols get exposed to vmlinux Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 22/27] objtool: Add options to write/read exported noreturns to/from a file Josh Poimboeuf
` (5 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Objtool doesn't consider a jump to be a sibling call if the destination
isn't a function, so srso_alias_untrain_ret()'s jump to
srso_alias_return_thunk() is ignored by objtool and the former is
falsely classified as noreturn.
Change the noreturn detection to consider such cases as effectively
sibling calls, except for one known exception: jumping to .altinstr_aux.
Currently the misclassification is harmless: dead ends aren't marked
when calling a noreturn from an alternative replacement
(CALL_UNTRAIN_RET), they're ignored by rethunk validation, and ORC
generation relies on unwind hints in the SRSO code regardless.
Fix it in the name of robustness and in preparation for a subsequent
patch which generates a list of exported noreturns.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/check.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index c55851ec389cd..d27303220c29d 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -264,8 +264,22 @@ static bool might_return(struct objtool_file *file, struct symbol *func)
if (insn->type == INSN_RETURN)
return true;
- if (!is_sibling_call(insn))
+ if (!is_sibling_call(insn)) {
+ /*
+ * Assume a jump into a non-function eventually returns
+ * to the original caller one way or another, e.g., the
+ * jump in srso_alias_untrain_ret().
+ *
+ * .altinstr_aux is an exception, cpu_feature_enabled()
+ * jumps there and then right back.
+ */
+ if (is_static_jump(insn) && insn->jump_dest &&
+ !insn_func(insn->jump_dest) &&
+ strcmp(insn->jump_dest->sec->name, ".altinstr_aux"))
+ return true;
+
continue;
+ }
dest = insn_call_dest(insn);
if (!dest || !is_noreturn(dest))
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 22/27] objtool: Add options to write/read exported noreturns to/from a file
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (20 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 21/27] objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 23/27] kbuild: Do the per-module objtool pass right before linking Josh Poimboeuf
` (4 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Add --noreturns-write which can be used to write vmlinux's exported
noreturns to a file, and --noreturn-read which can be used to read them
when running objtool on a module.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/builtin-check.c | 12 ++
tools/objtool/check.c | 148 +++++++++++++++++++++++-
tools/objtool/include/objtool/builtin.h | 2 +
tools/objtool/include/objtool/elf.h | 1 +
4 files changed, 162 insertions(+), 1 deletion(-)
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index 75b11dc85010e..c4c4210f84662 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -101,6 +101,8 @@ static const struct option check_options[] = {
OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"),
OPT_BOOLEAN(0, "mnop", &opts.mnop, "nop out mcount call sites"),
OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"),
+ OPT_STRING(0, "noreturns-read", &opts.noreturns_read, "file", "read exported noreturns from a file"),
+ OPT_STRING(0, "noreturns-write", &opts.noreturns_write, "file", "write exported noreturns to a file"),
OPT_STRING('o', "output", &opts.output, "file", "output file name"),
OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"),
OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"),
@@ -180,6 +182,16 @@ static bool opts_valid(void)
return false;
}
+ if (opts.noreturns_write && !opts.link) {
+ ERROR("--noreturns-write requires --link");
+ return false;
+ }
+
+ if (opts.noreturns_write && !opts.stackval && !opts.orc && !opts.uaccess) {
+ ERROR("--noreturns-write requires --stackval, --orc, or --uaccess");
+ return false;
+ }
+
if (opts.disas ||
opts.hack_jump_label ||
opts.hack_noinstr ||
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index d27303220c29d..086e8f9eca867 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -339,6 +339,120 @@ static void detect_noreturns(struct objtool_file *file)
}
}
+static bool noreturns_changed(const char *buf, size_t len)
+{
+ bool changed = true;
+ char *old = NULL;
+ FILE *fp;
+
+ fp = fopen(opts.noreturns_write, "r");
+ if (!fp)
+ return true;
+
+ old = malloc(len + 1);
+ if (!old)
+ goto out;
+
+ if (fread(old, 1, len + 1, fp) != len)
+ goto out;
+
+ if (memcmp(old, buf, len))
+ goto out;
+
+ changed = false;
+
+out:
+ free(old);
+ fclose(fp);
+ return changed;
+}
+
+/*
+ * This is called for vmlinux.o. Write the vmlinux.o noreturn list to a file
+ * so it can be read in module objtool runs by read_noreturns().
+ */
+static int write_noreturns(struct objtool_file *file)
+{
+ struct symbol *func;
+ char *buf, *pos;
+ size_t len = 0;
+ FILE *fp;
+
+ for_each_sym(file->elf, func) {
+ if (is_noreturn(func) && func->exported)
+ len += strlen(func->name) + 1;
+ }
+
+ buf = malloc(len + 1);
+ if (!buf) {
+ ERROR_GLIBC("malloc");
+ return -1;
+ }
+
+ pos = buf;
+ for_each_sym(file->elf, func) {
+ if (is_noreturn(func) && func->exported)
+ pos += sprintf(pos, "%s\n", func->name);
+ }
+
+ /*
+ * Only write the file when the contents have change to avoid relinking
+ * all the modules unnecessarily.
+ */
+ if (!noreturns_changed(buf, len)) {
+ free(buf);
+ return 0;
+ }
+
+ fp = fopen(opts.noreturns_write, "w");
+ if (!fp) {
+ ERROR_GLIBC("fopen");
+ return -1;
+ }
+
+ if (fwrite(buf, 1, len, fp) != len) {
+ ERROR_GLIBC("fwrite");
+ return -1;
+ }
+
+ free(buf);
+
+ if (fclose(fp)) {
+ ERROR_GLIBC("fclose");
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * This is called for modules. Read the noreturn list generated by the
+ * vmlinux.o pass and mark the corresponding undefined symbols noreturn.
+ */
+static int read_noreturns(struct objtool_file *file)
+{
+ char line[SYM_NAME_LEN];
+ struct symbol *func;
+ FILE *fp;
+
+ fp = fopen(opts.noreturns_read, "r");
+ if (!fp) {
+ ERROR("can't open '%s'", opts.noreturns_read);
+ return -1;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ line[strcspn(line, "\n")] = '\0';
+
+ func = find_global_symbol_by_name(file->elf, line);
+ if (func && is_undef_sym(func))
+ func->_noreturn = 1;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
static void init_cfi_state(struct cfi_state *cfi)
{
int i;
@@ -2236,6 +2350,24 @@ static int add_jump_table_alts(struct objtool_file *file)
return 0;
}
+static void read_exports(struct objtool_file *file)
+{
+ struct section *sec;
+ struct symbol *func;
+ struct reloc *reloc;
+
+ sec = find_section_by_name(file->elf, ".export_symbol");
+ if (!sec || !sec->rsec)
+ return;
+
+ for_each_reloc(sec->rsec, reloc) {
+ func = find_func_by_offset(reloc->sym->sec,
+ reloc->sym->offset + reloc_addend(reloc));
+ if (func)
+ func->exported = 1;
+ }
+}
+
static void set_func_state(struct cfi_state *state)
{
state->cfa = initial_func_cfi.cfa;
@@ -2720,8 +2852,10 @@ int decode_file(struct objtool_file *file)
* both validate_functions() and validate_noinstr_sections() -- and by
* validate_unret().
*/
- if (validate_branch_enabled() || opts.noinstr || opts.unret)
+ if (validate_branch_enabled() || opts.noinstr || opts.unret) {
+ read_exports(file);
detect_noreturns(file);
+ }
if (read_unwind_hints(file))
return -1;
@@ -4888,6 +5022,12 @@ int check(struct objtool_file *file)
objtool_disas_ctx = disas_ctx;
}
+ if (opts.noreturns_read) {
+ ret = read_noreturns(file);
+ if (ret)
+ goto out;
+ }
+
ret = decode_file(file);
if (ret)
goto out;
@@ -4995,6 +5135,12 @@ int check(struct objtool_file *file)
goto out;
}
+ if (opts.noreturns_write && !opts.dryrun) {
+ ret = write_noreturns(file);
+ if (ret)
+ goto out;
+ }
+
if (opts.stats) {
printf("nr_insns_visited: %ld\n", nr_insns_visited);
printf("nr_cfi: %ld\n", nr_cfi);
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index 349690bb1c50e..fb33d57a54c95 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -41,6 +41,8 @@ struct opts {
bool mnop;
bool module;
bool no_unreachable;
+ const char *noreturns_read;
+ const char *noreturns_write;
const char *output;
bool sec_address;
bool stats;
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index 0e3593d993ac8..adf6e1322c6df 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -100,6 +100,7 @@ struct symbol {
u8 fake : 1;
u8 _noreturn : 1;
u8 ignore_noreturn : 1;
+ u8 exported : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 23/27] kbuild: Do the per-module objtool pass right before linking
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (21 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 22/27] objtool: Add options to write/read exported noreturns to/from a file Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 24/27] kbuild: Generate the noreturn list and validate modules against it Josh Poimboeuf
` (3 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
With CONFIG_OBJTOOL_CONTROL_FLOW, the objtool pass on vmlinux.o is going
to be generating a list of its exported noreturns which will then be
read by the modules' objtool pass, so there will need to be a build
dependency between them.
In preparation for that, run objtool right before the module link so its
no longer done on the descend in the deferred case.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
scripts/Makefile.build | 31 +++++++++++++++----------------
scripts/Makefile.modfinal | 20 +++++++++++++++-----
scripts/livepatch/klp-build | 4 ++--
3 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 84ef12f784330..9e672979a976c 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -270,15 +270,21 @@ cmd_record_mcount = $(if $(findstring $(strip $(CC_FLAGS_FTRACE)),$(_c_flags)),
$(sub_cmd_record_mcount))
endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT
-# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
-# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
-# 'OBJECT_FILES_NON_STANDARD_foo.o := 'n': override directory skip for a file
-
+ifdef CONFIG_OBJTOOL
+#
+# NOTE: these variables are deprecated, and have no effect when
+# CONFIG_OBJTOOL_DEFERRED is enabled. Objtool-allergic code can instead be
+# marked with STACK_FRAME_NON_STANDARD() or ANNOTATE_*().
+#
+# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
+# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
+# 'OBJECT_FILES_NON_STANDARD_foo.o := 'n': override directory skip for a file
+#
is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),$(is-kernel-object))
-ifdef CONFIG_OBJTOOL
-$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(CONFIG_OBJTOOL_DEFERRED),$(is-single-obj-m),y))
-endif
+# Enable per-TU objtool for !CONFIG_OBJTOOL_DEFERRED
+$(obj)/%.o: private objtool-enabled = $(if $(CONFIG_OBJTOOL_DEFERRED),,$(if $(is-standard-object),y))
+endif # CONFIG_OBJTOOL
ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),)
cmd_warn_shared_object = $(if $(word 2, $(modname-multi)),$(warning $(kbuild-file): $*.o is added to multiple modules: $(modname-multi)))
@@ -495,17 +501,10 @@ $(obj)/lib.a: $(lib-y) FORCE
$(call if_changed,ar)
quiet_cmd_ld_multi_m = LD [M] $@
- cmd_ld_multi_m = $(LD) $(ld_flags) -r -o $@ @$< $(cmd_objtool)
+ cmd_ld_multi_m = $(LD) $(ld_flags) -r -o $@ @$<
-define rule_ld_multi_m
- $(call cmd_and_savecmd,ld_multi_m)
- $(call cmd,gen_objtooldep)
-endef
-
-$(multi-obj-m): private objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
-$(multi-obj-m): private part-of-module := y
$(multi-obj-m): %.o: %.mod FORCE
- $(call if_changed_rule,ld_multi_m)
+ $(call if_changed,ld_multi_m)
$(call multi_depend, $(multi-obj-m), .o, -objs -y -m)
# Add intermediate targets:
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 01a37ec872b90..56b4227cc6863 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -32,11 +32,18 @@ ifneq ($(WARN_ON_UNUSED_TRACEPOINTS),)
cmd_check_tracepoint = $(objtree)/scripts/tracepoint-update --module $<;
endif
+$(modules:%.o=%.ko): private objtool-enabled = $(CONFIG_OBJTOOL_DEFERRED)
+
+mod-objtool = $(if $(objtool-enabled),$(objtool) $(objtool-args) -o $@.o $< ;)
+mod-obj = $(if $(objtool-enabled),$@.o,$<)
+
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o = \
+ $(mod-objtool) \
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
- -T $(objtree)/scripts/module.lds -o $@ $(filter %.o, $^)
+ -T $(objtree)/scripts/module.lds -o $@ \
+ $(mod-obj) $(filter-out $<, $(filter %.o, $^))
quiet_cmd_btf_ko = BTF [M] $@
cmd_btf_ko = \
@@ -46,12 +53,15 @@ quiet_cmd_btf_ko = BTF [M] $@
$(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \
fi;
+define rule_ld_ko_o
+ +$(call cmd_and_savecmd,ld_ko_o)
+ +$(call cmd,gen_objtooldep)
+ +$(if $(CONFIG_DEBUG_INFO_BTF_MODULES),$(if $(newer-prereqs),$(call cmd,btf_ko)))
+endef
+
# Re-generate module BTFs if either module's .ko or vmlinux changed
%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE
- +$(call if_changed,ld_ko_o)
-ifdef CONFIG_DEBUG_INFO_BTF_MODULES
- +$(if $(newer-prereqs),$(call cmd,btf_ko))
-endif
+ $(call if_changed_rule,ld_ko_o)
+$(call cmd,check_tracepoint)
targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b52a8489d9f67..5237bad90f2cb 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -600,7 +600,7 @@ copy_orig_objects() {
xtrace_save "copying original objects"
for _file in "${files[@]}"; do
local rel_file="${_file/.ko/.o}"
- local file="$PWD/$rel_file"
+ local file="$PWD/${_file/%.ko/.ko.o}"
local orig_file="$ORIG_DIR/$rel_file"
local orig_dir="$(dirname "$orig_file")"
@@ -636,7 +636,7 @@ copy_patched_objects() {
xtrace_save "copying changed objects"
for _file in "${files[@]}"; do
local rel_file="${_file/.ko/.o}"
- local file="$PWD/$rel_file"
+ local file="$PWD/${_file/%.ko/.ko.o}"
local orig_file="$ORIG_DIR/$rel_file"
local patched_file="$PATCHED_DIR/$rel_file"
local patched_dir="$(dirname "$patched_file")"
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 24/27] kbuild: Generate the noreturn list and validate modules against it
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (22 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 23/27] kbuild: Do the per-module objtool pass right before linking Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 25/27] objtool: Add ANNOTATE_EXPORTED_NORETURN() Josh Poimboeuf
` (2 subsequent siblings)
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Most of the remaining hard-coded noreturn functions are exported symbols
which live in vmlinux.o. The objtool vmlinux.o pass can detect them
automatically, but modules can't.
Instead of hard-coding them in noreturns.h, dynamically generate the
list in a new scripts/noreturns.builtin file, which objtool on modules
can then read. That allows most of the remaining hard-coded noreturn
entries to go away, including the Rust ones. Note this means that
modules need to be linked after vmlinux.o has been built and analyzed by
objtool.
Objtool running on OOT modules will also need to read the generated
file. Its location in scripts/ should ensure it gets installed with
kernel build files automatically.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
Makefile | 8 +++++
scripts/.gitignore | 1 +
scripts/Makefile.modfinal | 15 +++++++--
scripts/Makefile.vmlinux_o | 2 ++
tools/objtool/Documentation/objtool.txt | 8 ++---
tools/objtool/check.c | 44 -------------------------
tools/objtool/noreturns.h | 17 ++--------
7 files changed, 29 insertions(+), 66 deletions(-)
diff --git a/Makefile b/Makefile
index 902f3f3d54b71..1c443309dbc4d 100644
--- a/Makefile
+++ b/Makefile
@@ -1708,6 +1708,13 @@ KBUILD_BUILTIN := y
modules: vmlinux
endif
+# Objtool for modules (run before module link) needs to come after objtool for
+# vmlinux.o so it can read the generated exported noreturns file.
+ifdef CONFIG_OBJTOOL_CONTROL_FLOW
+KBUILD_BUILTIN := y
+modules: vmlinux.o
+endif
+
modules: modules_prepare
# Target to prepare building external modules
@@ -1738,6 +1745,7 @@ MRPROPER_FILES += include/config include/generated \
debian snap tar-install PKGBUILD pacman \
.config .config.old .version \
Module.symvers \
+ scripts/noreturns.builtin \
certs/signing_key.pem \
certs/x509.genkey \
vmlinux-gdb.py \
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 4215c2208f7e4..15d99dfd7c936 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -5,6 +5,7 @@
/insert-sys-cert
/kallsyms
/module.lds
+/noreturns.builtin
/recordmcount
/rustdoc_test_builder
/rustdoc_test_gen
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 56b4227cc6863..23b07ad7eb9bd 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -32,6 +32,15 @@ ifneq ($(WARN_ON_UNUSED_TRACEPOINTS),)
cmd_check_tracepoint = $(objtree)/scripts/tracepoint-update --module $<;
endif
+ifdef CONFIG_OBJTOOL_CONTROL_FLOW
+noreturns-builtin := $(wildcard $(objtree)/scripts/noreturns.builtin)
+ifeq ($(noreturns-builtin),)
+$(warning scripts/noreturns.builtin is missing)
+else
+objtool-args += --noreturns-read=$(noreturns-builtin)
+endif
+endif
+
$(modules:%.o=%.ko): private objtool-enabled = $(CONFIG_OBJTOOL_DEFERRED)
mod-objtool = $(if $(objtool-enabled),$(objtool) $(objtool-args) -o $@.o $< ;)
@@ -59,8 +68,10 @@ define rule_ld_ko_o
+$(if $(CONFIG_DEBUG_INFO_BTF_MODULES),$(if $(newer-prereqs),$(call cmd,btf_ko)))
endef
-# Re-generate module BTFs if either module's .ko or vmlinux changed
-%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE
+# Re-generate module BTFs if either module's .ko or vmlinux changed.
+# Re-run module objtool if the generated noreturn list changed.
+%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(noreturns-builtin) \
+ $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE
$(call if_changed_rule,ld_ko_o)
+$(call cmd,check_tracepoint)
diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o
index df1e3584883e6..9c60a4918a756 100644
--- a/scripts/Makefile.vmlinux_o
+++ b/scripts/Makefile.vmlinux_o
@@ -37,6 +37,8 @@ objtool-enabled := $(CONFIG_OBJTOOL_DEFERRED)
vmlinux-objtool-args-y += $(objtool-args-y)
+vmlinux-objtool-args-$(CONFIG_OBJTOOL_CONTROL_FLOW) += --noreturns-write=scripts/noreturns.builtin
+
vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \
$(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret)
diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt
index 9e97fc25b2d8a..c7ebfb9a8ca6b 100644
--- a/tools/objtool/Documentation/objtool.txt
+++ b/tools/objtool/Documentation/objtool.txt
@@ -334,8 +334,7 @@ the objtool maintainers.
The call from foo() to bar() doesn't return, but bar() is incorrectly
annotated. A noreturn function must be marked __noreturn in both its
- declaration and its definition, and must have a NORETURN() annotation
- in tools/objtool/noreturns.h.
+ declaration and its definition.
4. file.o: warning: objtool: func(): can't find starting instruction
@@ -393,9 +392,8 @@ the objtool maintainers.
reasons for this:
a) funcA()'s last instruction is a call to a "noreturn" function like
- panic(). In this case the noreturn function needs to be added to
- objtool's hard-coded global_noreturns array. Feel free to bug the
- objtool maintainer, or you can submit a patch.
+ panic(), and objtool doesn't know that function never returns. See
+ warning 3 above.
b) funcA() uses the unreachable() annotation in a section of code
that is actually reachable.
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 086e8f9eca867..583d86220e040 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -176,47 +176,6 @@ static bool is_sibling_call(struct instruction *insn)
return (is_static_jump(insn) && insn_call_dest(insn));
}
-/*
- * Checks if a function is a Rust "noreturn" one.
- */
-static bool is_rust_noreturn(const struct symbol *func)
-{
- /*
- * If it does not start with "_R", then it is not a Rust symbol.
- */
- if (strncmp(func->name, "_R", 2))
- return false;
-
- /*
- * These are just heuristics -- we do not control the precise symbol
- * name, due to the crate disambiguators (which depend on the compiler)
- * as well as changes to the source code itself between versions (since
- * these come from the Rust standard library).
- */
- return str_ends_with(func->name, "_4core3num20from_str_radix_panic") ||
- str_ends_with(func->name, "_4core3num22from_ascii_radix_panic") ||
- str_ends_with(func->name, "_4core5sliceSp15copy_from_slice17len_mismatch_fail") ||
- str_ends_with(func->name, "_4core6option13expect_failed") ||
- str_ends_with(func->name, "_4core6option13unwrap_failed") ||
- str_ends_with(func->name, "_4core6result13unwrap_failed") ||
- str_ends_with(func->name, "_4core9panicking5panic") ||
- str_ends_with(func->name, "_4core9panicking9panic_fmt") ||
- str_ends_with(func->name, "_4core9panicking14panic_explicit") ||
- str_ends_with(func->name, "_4core9panicking14panic_nounwind") ||
- str_ends_with(func->name, "_4core9panicking18panic_bounds_check") ||
- str_ends_with(func->name, "_4core9panicking18panic_nounwind_fmt") ||
- str_ends_with(func->name, "_4core9panicking19assert_failed_inner") ||
- str_ends_with(func->name, "_4core9panicking30panic_null_pointer_dereference") ||
- str_ends_with(func->name, "_4core9panicking32panic_null_reference_constructed") ||
- str_ends_with(func->name, "_4core9panicking36panic_misaligned_pointer_dereference") ||
- str_ends_with(func->name, "_7___rustc17rust_begin_unwind") ||
- strstr(func->name, "_4core9panicking13assert_failed") ||
- strstr(func->name, "_4core9panicking11panic_const24panic_const_") ||
- (strstr(func->name, "_4core5slice5index") &&
- strstr(func->name, "slice_") &&
- str_ends_with(func->name, "_fail"));
-}
-
static bool is_listed_noreturn(struct symbol *func)
{
#define NORETURN(func) __stringify(func),
@@ -228,9 +187,6 @@ static bool is_listed_noreturn(struct symbol *func)
if (is_local_sym(func))
return false;
- if (is_rust_noreturn(func))
- return true;
-
for (int i = 0; i < ARRAY_SIZE(global_noreturns); i++)
if (!strcmp(func->name, global_noreturns[i]))
return true;
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
index 2dce56030b980..4e8007a8c2584 100644
--- a/tools/objtool/noreturns.h
+++ b/tools/objtool/noreturns.h
@@ -1,22 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
- * This is a (sorted!) list of all known __noreturn functions in the kernel.
- * It's needed for objtool to properly reverse-engineer the control flow graph.
- *
- * Yes, this is unfortunate. A better solution is in the works.
+ * This is a list of noreturn functions which are exported *by modules*.
+ * No other noreturns need to be listed here.
*/
-NORETURN(__fortify_panic)
NORETURN(__kunit_abort)
-NORETURN(__module_put_and_kthread_exit)
-NORETURN(__stack_chk_fail)
-NORETURN(__ubsan_handle_builtin_unreachable)
-NORETURN(abort)
-NORETURN(acpi_processor_ffh_play_dead)
-NORETURN(do_exit)
-NORETURN(kthread_complete_and_exit)
NORETURN(kunit_try_catch_throw)
NORETURN(mpt_halt_firmware)
-NORETURN(panic)
-NORETURN(vpanic)
-NORETURN(rust_helper_BUG)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 25/27] objtool: Add ANNOTATE_EXPORTED_NORETURN()
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (23 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 24/27] kbuild: Generate the noreturn list and validate modules against it Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 26/27] objtool: Annotate all module-exported noreturns and remove noreturns.h Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 27/27] objtool: Warn about missing/stale ANNOTATE_EXPORTED_NORETURN() usage Josh Poimboeuf
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Add ANNOTATE_EXPORTED_NORETURN() for the rare case where a module
exports a noreturn function.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
include/linux/annotate.h | 17 +++++++++++++++++
tools/objtool/check.c | 27 +++++++++++++++++++++++++++
tools/objtool/klp-diff.c | 32 ++++++++++++++++++++++++++++++++
3 files changed, 76 insertions(+)
diff --git a/include/linux/annotate.h b/include/linux/annotate.h
index a81ca04b4c501..70450eeec3186 100644
--- a/include/linux/annotate.h
+++ b/include/linux/annotate.h
@@ -25,6 +25,15 @@
"912: " \
__stringify(__ASM_ANNOTATE(.discard.annotate_data, 912b, type))
+/*
+ * Annotate a symbol by name rather than by relocation, so it can optionally be
+ * used in a header file.
+ */
+#define ASM_ANNOTATE_NAME(section, sym) \
+ ".pushsection " section ", \"MS\", @progbits, 1\n\t" \
+ ".asciz \"" __stringify(sym) "\"\n\t" \
+ ".popsection"
+
#else /* __ASSEMBLY__ */
.macro ANNOTATE type
@@ -44,6 +53,7 @@
#define ASM_ANNOTATE_LABEL(label, type) ""
#define ASM_ANNOTATE(type)
#define ASM_ANNOTATE_DATA(type)
+#define ASM_ANNOTATE_NAME(section, sym) ""
#else /* __ASSEMBLY__ */
.macro ANNOTATE type
.endm
@@ -112,6 +122,13 @@
*/
#define ANNOTATE_IGNORE_NORETURN(sym) asm(ASM_ANNOTATE_LABEL(sym, ANNOTYPE_IGNORE_NORETURN))
+/*
+ * Tell objtool running on a module that a function exported by another module
+ * is __noreturn. Objtool has no way of communicating that between modules due
+ * to the parallel nature of module linking in kbuild.
+ */
+#define ANNOTATE_EXPORTED_NORETURN(sym) asm(ASM_ANNOTATE_NAME(".discard.annotate_noreturn", sym))
+
/*
* Annotate a special section entry. This emables livepatch module generation
* to find and extract individual special section entries as needed.
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 583d86220e040..6eaac32374722 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -409,6 +409,31 @@ static int read_noreturns(struct objtool_file *file)
return 0;
}
+static void read_annotate_noreturn(struct objtool_file *file)
+{
+ struct section *sec;
+ struct symbol *func;
+ unsigned long off;
+ const char *name;
+
+ sec = find_section_by_name(file->elf, ".discard.annotate_noreturn");
+ if (!sec || !sec->data)
+ return;
+
+ for (off = 0; off < sec_size(sec); off += strlen(name) + 1) {
+ name = sec->data->d_buf + off;
+ if (!*name)
+ continue;
+
+ func = find_global_symbol_by_name(file->elf, name);
+ if (!func)
+ continue;
+
+ if (is_undef_sym(func))
+ func->_noreturn = 1;
+ }
+}
+
static void init_cfi_state(struct cfi_state *cfi)
{
int i;
@@ -4984,6 +5009,8 @@ int check(struct objtool_file *file)
goto out;
}
+ read_annotate_noreturn(file);
+
ret = decode_file(file);
if (ret)
goto out;
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index 16681a76f13d0..5719a8e016cb3 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -361,6 +361,7 @@ static bool is_special_section(struct section *sec)
static const char * const non_special_discards[] = {
".discard.addressable",
+ ".discard.annotate_noreturn",
".discard.sym_checksum",
};
@@ -2217,6 +2218,34 @@ static int copy_import_ns(struct elfs *e)
return 0;
}
+/*
+ * ANNOTATE_EXPORTED_NORETURN() annotations reference their functions by name
+ * rather than by relocation, so individual entries can't be extracted. Copy
+ * the (tiny) section as-is.
+ */
+static int copy_annotate_noreturn(struct elfs *e)
+{
+ struct section *patched_sec, *out_sec;
+
+ patched_sec = find_section_by_name(e->patched, ".discard.annotate_noreturn");
+ if (!patched_sec || !patched_sec->data || !sec_size(patched_sec))
+ return 0;
+
+ out_sec = elf_create_section(e->out, patched_sec->name, 0,
+ patched_sec->sh.sh_entsize,
+ patched_sec->sh.sh_type,
+ patched_sec->sh.sh_addralign,
+ patched_sec->sh.sh_flags);
+ if (!out_sec)
+ return -1;
+
+ if (!elf_add_data(e->out, out_sec, patched_sec->data->d_buf,
+ sec_size(patched_sec)))
+ return -1;
+
+ return 0;
+}
+
int cmd_klp_diff(int argc, const char **argv)
{
struct elfs e = {0};
@@ -2288,6 +2317,9 @@ int cmd_klp_diff(int argc, const char **argv)
if (copy_import_ns(&e))
return -1;
+ if (copy_annotate_noreturn(&e))
+ return -1;
+
if (elf_write(e.out))
return -1;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 26/27] objtool: Annotate all module-exported noreturns and remove noreturns.h
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (24 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 25/27] objtool: Add ANNOTATE_EXPORTED_NORETURN() Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 27/27] objtool: Warn about missing/stale ANNOTATE_EXPORTED_NORETURN() usage Josh Poimboeuf
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
The vmlinux.o objtool pass detects its exported noreturns and writes
them to scripts/noreturns.builtins to avoid needing to keep them
hard-coded in noreturns.h.
However, due to the parallel nature of module linking, module-exported
noreturns are detected too late to be included in the generated file, so
they still need noreturns.h entries.
Instead of hard-coding them in noreturns.h, annotate the functions next
to their declaration sites with ANNOTATE_EXPORTED_NORETURN(), which is a
more robust annotation, and allows us to finally be rid of noreturns.h.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
drivers/message/fusion/mptbase.h | 2 ++
include/kunit/test.h | 1 +
include/kunit/try-catch.h | 2 ++
include/linux/rtc/ds1685.h | 2 ++
tools/objtool/Documentation/objtool.txt | 2 +-
tools/objtool/check.c | 23 +----------------------
tools/objtool/noreturns.h | 9 ---------
7 files changed, 9 insertions(+), 32 deletions(-)
delete mode 100644 tools/objtool/noreturns.h
diff --git a/drivers/message/fusion/mptbase.h b/drivers/message/fusion/mptbase.h
index b406fd676da09..387b707a66742 100644
--- a/drivers/message/fusion/mptbase.h
+++ b/drivers/message/fusion/mptbase.h
@@ -49,6 +49,7 @@
#define MPTBASE_H_INCLUDED
/*{-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+#include <linux/annotate.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/mutex.h>
@@ -942,6 +943,7 @@ extern int mpt_raid_phys_disk_get_num_paths(MPT_ADAPTER *ioc,
extern int mpt_set_taskmgmt_in_progress_flag(MPT_ADAPTER *ioc);
extern void mpt_clear_taskmgmt_in_progress_flag(MPT_ADAPTER *ioc);
extern void __noreturn mpt_halt_firmware(MPT_ADAPTER *ioc);
+ANNOTATE_EXPORTED_NORETURN(mpt_halt_firmware);
/*
diff --git a/include/kunit/test.h b/include/kunit/test.h
index e52452e58305a..01a1531f8fe54 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -722,6 +722,7 @@ void __printf(2, 3) kunit_log_append(struct string_stream *log, const char *fmt,
#define KUNIT_SUCCEED(test) _KUNIT_SAVE_LOC(test)
void __noreturn __kunit_abort(struct kunit *test);
+ANNOTATE_EXPORTED_NORETURN(__kunit_abort);
void __printf(6, 7) __kunit_do_failed_assertion(struct kunit *test,
const struct kunit_loc *loc,
diff --git a/include/kunit/try-catch.h b/include/kunit/try-catch.h
index d4e1a5b98ed67..f3c0af4650080 100644
--- a/include/kunit/try-catch.h
+++ b/include/kunit/try-catch.h
@@ -10,6 +10,7 @@
#ifndef _KUNIT_TRY_CATCH_H
#define _KUNIT_TRY_CATCH_H
+#include <linux/annotate.h>
#include <linux/types.h>
typedef void (*kunit_try_catch_func_t)(void *);
@@ -54,6 +55,7 @@ struct kunit_try_catch {
void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context);
void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch);
+ANNOTATE_EXPORTED_NORETURN(kunit_try_catch_throw);
static inline int kunit_try_catch_get_result(struct kunit_try_catch *try_catch)
{
diff --git a/include/linux/rtc/ds1685.h b/include/linux/rtc/ds1685.h
index 8ec0ebfaef04f..a0e1bf99407b7 100644
--- a/include/linux/rtc/ds1685.h
+++ b/include/linux/rtc/ds1685.h
@@ -21,6 +21,7 @@
#ifndef _LINUX_RTC_DS1685_H_
#define _LINUX_RTC_DS1685_H_
+#include <linux/annotate.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
@@ -362,5 +363,6 @@ struct ds1685_rtc_platform_data {
*/
extern void __noreturn
ds1685_rtc_poweroff(struct platform_device *pdev);
+ANNOTATE_EXPORTED_NORETURN(ds1685_rtc_poweroff);
#endif /* _LINUX_RTC_DS1685_H_ */
diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt
index c7ebfb9a8ca6b..d5ac48bc203ec 100644
--- a/tools/objtool/Documentation/objtool.txt
+++ b/tools/objtool/Documentation/objtool.txt
@@ -330,7 +330,7 @@ the objtool maintainers.
traces and want objtool to ignore it, see "Adding exceptions" below.
-3. file.o: warning: objtool: foo+0x48c: bar() missing __noreturn in .c/.h or NORETURN() in noreturns.h
+3. file.o: warning: objtool: foo+0x48c: bar() is missing __noreturn in .c/.h
The call from foo() to bar() doesn't return, but bar() is incorrectly
annotated. A noreturn function must be marked __noreturn in both its
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 6eaac32374722..aced52ad6fc81 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -176,24 +176,6 @@ static bool is_sibling_call(struct instruction *insn)
return (is_static_jump(insn) && insn_call_dest(insn));
}
-static bool is_listed_noreturn(struct symbol *func)
-{
-#define NORETURN(func) __stringify(func),
- static const char * const global_noreturns[] = {
-#include "noreturns.h"
- };
-#undef NORETURN
-
- if (is_local_sym(func))
- return false;
-
- for (int i = 0; i < ARRAY_SIZE(global_noreturns); i++)
- if (!strcmp(func->name, global_noreturns[i]))
- return true;
-
- return false;
-}
-
/*
* Use this rather than reading sym->_noreturn directly: the noreturn status
* lives on the primary alias, and ANNOTATE_IGNORE_NORETURN() overrides it.
@@ -205,9 +187,6 @@ static bool is_noreturn(struct symbol *func)
if (func->ignore_noreturn)
return false;
- if (is_listed_noreturn(func))
- return true;
-
return func->_noreturn;
}
@@ -4891,7 +4870,7 @@ static int validate_reachable_instructions(struct objtool_file *file)
if (prev_insn && prev_insn->dead_end) {
call_dest = insn_call_dest(prev_insn);
if (call_dest) {
- WARN_INSN(insn, "%s() missing __noreturn in .c/.h or NORETURN() in noreturns.h",
+ WARN_INSN(insn, "%s() is missing __noreturn in .c/.h",
call_dest->name);
warnings++;
continue;
diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h
deleted file mode 100644
index 4e8007a8c2584..0000000000000
--- a/tools/objtool/noreturns.h
+++ /dev/null
@@ -1,9 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-/*
- * This is a list of noreturn functions which are exported *by modules*.
- * No other noreturns need to be listed here.
- */
-NORETURN(__kunit_abort)
-NORETURN(kunit_try_catch_throw)
-NORETURN(mpt_halt_firmware)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread* [PATCH 27/27] objtool: Warn about missing/stale ANNOTATE_EXPORTED_NORETURN() usage
2026-08-28 4:51 [PATCH 00/27] objtool: dynamically detect noreturns Josh Poimboeuf
` (25 preceding siblings ...)
2026-08-28 4:51 ` [PATCH 26/27] objtool: Annotate all module-exported noreturns and remove noreturns.h Josh Poimboeuf
@ 2026-08-28 4:51 ` Josh Poimboeuf
26 siblings, 0 replies; 31+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 4:51 UTC (permalink / raw)
To: x86
Cc: linux-kernel, Peter Zijlstra, Gary Guo, rust-for-linux,
Ard Biesheuvel, Miguel Ojeda, Nathan Chancellor, Nicolas Schier,
linux-kbuild, Huacai Chen
Add some warnings to keep ANNOTATE_EXPORTED_NORETURN() annotations
correct.
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
tools/objtool/Documentation/objtool.txt | 22 ++++++++-
tools/objtool/check.c | 59 ++++++++++++++++++++-----
tools/objtool/include/objtool/elf.h | 1 +
3 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt
index d5ac48bc203ec..950e88c713d1d 100644
--- a/tools/objtool/Documentation/objtool.txt
+++ b/tools/objtool/Documentation/objtool.txt
@@ -393,7 +393,7 @@ the objtool maintainers.
a) funcA()'s last instruction is a call to a "noreturn" function like
panic(), and objtool doesn't know that function never returns. See
- warning 3 above.
+ warnings 3 and 13.
b) funcA() uses the unreachable() annotation in a section of code
that is actually reachable.
@@ -455,6 +455,26 @@ the objtool maintainers.
macros to create them.
+13. file.o: warning: objtool: func() is exported and noreturn, its declaration needs __noreturn and ANNOTATE_EXPORTED_NORETURN()
+
+ func() is exported and never returns, but is defined in a module, so
+ its noreturn status needs to be manually annotated. Put an
+ ANNOTATE_EXPORTED_NORETURN() next to its declaration.
+
+ Mark the declaration __noreturn while you're there, if it isn't
+ already. The two go together: __noreturn stops the compiler from
+ emitting code after the call, and the annotation tells objtool the
+ call doesn't return.
+
+
+14. file.o: warning: objtool: func() has ANNOTATE_EXPORTED_NORETURN() but returns
+
+ func() is annotated as never returning, but it does. Usually this
+ means the annotation outlived the fact: the function grew a return path
+ and the ANNOTATE_EXPORTED_NORETURN() next to its declaration was left
+ behind. Remove it, along with the __noreturn.
+
+
If the error doesn't seem to make sense, it could be a bug in objtool.
Feel free to ask objtool maintainers for help.
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index aced52ad6fc81..ac6ae823a2d45 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -224,6 +224,22 @@ static bool might_return(struct objtool_file *file, struct symbol *func)
return false;
}
+static bool is_noreturn_candidate(struct objtool_file *file, struct symbol *func)
+{
+ struct instruction *insn;
+
+ if (!is_func_sym(func) || is_undef_sym(func) ||
+ is_prefix_func(func) || func->embedded_insn ||
+ func != func->alias->pfunc)
+ return false;
+
+ insn = find_insn(file, func->sec, func->offset);
+ if (!insn || insn_func(insn) != func)
+ return false;
+
+ return true;
+}
+
static void detect_noreturns(struct objtool_file *file)
{
struct symbol *func, *dest;
@@ -232,15 +248,7 @@ static void detect_noreturns(struct objtool_file *file)
/* Mark all functions guilty until proven innocent */
for_each_sym(file->elf, func) {
-
- /* Aliases and cold subfunctions inherit the parent's verdict */
- if (!is_func_sym(func) || is_undef_sym(func) ||
- is_prefix_func(func) || func->embedded_insn ||
- func != func->alias->pfunc)
- continue;
-
- insn = find_insn(file, func->sec, func->offset);
- if (!insn || insn_func(insn) != func)
+ if (!is_noreturn_candidate(file, func))
continue;
func->_noreturn = 1;
@@ -410,9 +418,38 @@ static void read_annotate_noreturn(struct objtool_file *file)
if (is_undef_sym(func))
func->_noreturn = 1;
+
+ func->annotate_noreturn = 1;
}
}
+static int validate_noreturns(struct objtool_file *file)
+{
+ struct symbol *func;
+ int warnings = 0;
+
+ for_each_sym(file->elf, func) {
+ if (!is_noreturn_candidate(file, func))
+ continue;
+
+ if (opts.module && is_noreturn(func) && func->exported &&
+ !func->annotate_noreturn) {
+ WARN("%s() is exported and noreturn, its declaration needs __noreturn and ANNOTATE_EXPORTED_NORETURN()",
+ func->name);
+ warnings++;
+ }
+
+ if (func->annotate_noreturn && !func->ignore_noreturn &&
+ !is_noreturn(func)) {
+ WARN("%s() has ANNOTATE_EXPORTED_NORETURN() but returns",
+ func->name);
+ warnings++;
+ }
+ }
+
+ return warnings;
+}
+
static void init_cfi_state(struct cfi_state *cfi)
{
int i;
@@ -5005,8 +5042,10 @@ int check(struct objtool_file *file)
w += validate_functions(file);
w += validate_unwind_hints(file, NULL);
- if (!w)
+ if (!w) {
w += validate_reachable_instructions(file);
+ w += validate_noreturns(file);
+ }
warnings += w;
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index adf6e1322c6df..e79cdabf09c26 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -100,6 +100,7 @@ struct symbol {
u8 fake : 1;
u8 _noreturn : 1;
u8 ignore_noreturn : 1;
+ u8 annotate_noreturn : 1;
u8 exported : 1;
struct list_head pv_target;
struct reloc *relocs;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread