From: Josh Poimboeuf <jpoimboe@kernel.org>
To: x86@kernel.org
Cc: linux-kernel@vger.kernel.org,
Peter Zijlstra <peterz@infradead.org>,
Gary Guo <gary@garyguo.net>,
rust-for-linux@vger.kernel.org, Ard Biesheuvel <ardb@kernel.org>,
Miguel Ojeda <ojeda@kernel.org>,
Nathan Chancellor <nathan@kernel.org>,
Nicolas Schier <nsc@kernel.org>,
linux-kbuild@vger.kernel.org, Huacai Chen <chenhuacai@kernel.org>
Subject: [PATCH 05/27] objtool: Refactor the noreturn/dead-end detection
Date: Thu, 27 Aug 2026 21:51:34 -0700 [thread overview]
Message-ID: <b2c1ae6163e7ec6e50dc70b98fde4ecff4d9848f.1787890035.git.jpoimboe@kernel.org> (raw)
In-Reply-To: <cover.1787890035.git.jpoimboe@kernel.org>
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
next prev parent reply other threads:[~2026-08-28 4:52 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` [PATCH 03/27] kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW Josh Poimboeuf
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 [this message]
2026-08-28 4:51 ` [PATCH 06/27] objtool: Ignore traps after noreturn calls in STT_CODE Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 07/27] objtool: Make .discard.stack_frame_non_standard non-allocatable Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 08/27] efi/libstub: Drop .discard.addressable from the stub objects Josh Poimboeuf
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 ` [PATCH 10/27] objtool: Add ANNOTATE_IGNORE_NORETURN() Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 11/27] LoongArch: Annotate reboot and kexec paths as returnable Josh Poimboeuf
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
2026-08-28 19:27 ` Nathan Chancellor
2026-08-28 4:51 ` [PATCH 13/27] rust: Annotate the intrinsic stubs as returnable Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 14/27] panic: Mark abort() __noreturn Josh Poimboeuf
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 ` [PATCH 16/27] objtool: Detect noreturns in weak functions Josh Poimboeuf
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 ` [PATCH 18/27] x86/xen: Make xen_cpu_bringup_again() " Josh Poimboeuf
2026-08-28 4:51 ` [PATCH 19/27] x86/xen: Make xen_start_kernel() noreturn Josh Poimboeuf
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 ` [PATCH 21/27] objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE Josh Poimboeuf
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 ` [PATCH 23/27] kbuild: Do the per-module objtool pass right before linking Josh Poimboeuf
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 ` [PATCH 25/27] objtool: Add ANNOTATE_EXPORTED_NORETURN() 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=b2c1ae6163e7ec6e50dc70b98fde4ecff4d9848f.1787890035.git.jpoimboe@kernel.org \
--to=jpoimboe@kernel.org \
--cc=ardb@kernel.org \
--cc=chenhuacai@kernel.org \
--cc=gary@garyguo.net \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=ojeda@kernel.org \
--cc=peterz@infradead.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox