Skip to contentSkip to conversation

objtool: dynamically detect noreturns

PATCH Superseded Josh Poimboeuf 2 participants 29 messages Sep 8 – Sep 10

Subsystems (17)
Arm (32-bit) x86 Kbuild Library code +13 more subsystems

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Josh Poimboeuf

author maintainer
#0 lore raw

v2 cover objtool: dynamically detect noreturns

changes since v1 (https://lore.kernel.org/[email protected]):
- improve commit log for "kbuild: Defer running objtool to link time for all CFG features"
- rebase on v7.3-rc2 to get a proper Sashiko review

The C and rust manual noreturn lists are a maintenance nightmare. Get rid of them by taking advantage of the fact that objtool can run on linked objects (vmlinux.o and modules) and can thus detect noreturns dynamically:

1) When features are enabled which require the noreturn knowledge (i.e., control flow analysis) always defer objtool to run on linked objects. That obsoletes most of noreturns.h. (patch 12)

2) For noreturn functions which are exported by vmlinux, dynamically write their names to a file which is then read by objtool running on modules. That obsoletes most of the remaining noreturns.h entries and the entirety of is_rust_noreturn(). (patch 24)

3) For the (four) noreturn functions which are exported by modules, manually annotate them at their declaration sites with a new ANNOTATE_EXPORTED_NORETURN(). That removes the last few stragglers in noreturns.h. (patch 26)

Josh Poimboeuf (27):
objtool: Remove obsolete noreturns.h entries
kbuild: Add CONFIG_OBJTOOL_DEFERRED
kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW
objtool: Fix dead end detection for sibling calls
objtool: Refactor the noreturn/dead-end detection
objtool: Ignore traps after noreturn calls in STT_CODE
objtool: Make .discard.stack_frame_non_standard non-allocatable efi/libstub: Drop .discard.addressable from the stub objects efi/loongarch: Mark loongarch efi_boot_kernel() non-standard for
objtool
objtool: Add ANNOTATE_IGNORE_NORETURN()
LoongArch: Annotate reboot and kexec paths as returnable
kbuild: Defer running objtool to link time for all CFG features
rust: Annotate the intrinsic stubs as returnable
panic: Mark abort() __noreturn
x86/xen: Ignore noreturn status of weak mem_map_via_hcall()
objtool: Detect noreturns in weak functions
x86/entry: Make rewind_stack_and_make_dead() a real function x86/xen: Make xen_cpu_bringup_again() a real function x86/xen: Make xen_start_kernel() noreturn
x86/boot: Rework how pi startup symbols get exposed to vmlinux
objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE
objtool: Add options to write/read exported noreturns to/from a file
kbuild: Do the per-module objtool pass right before linking
kbuild: Generate the noreturn list and validate modules against it
objtool: Add ANNOTATE_EXPORTED_NORETURN()
objtool: Annotate all module-exported noreturns and remove noreturns.h
objtool: Warn about missing/stale ANNOTATE_EXPORTED_NORETURN() usage

 Makefile                                 |   8 +
 arch/arc/kernel/traps.c                  |   4 +-
 arch/arm/kernel/traps.c                  |   2 +-
 arch/loongarch/kernel/machine_kexec.c    |   2 +
 arch/loongarch/kernel/reset.c            |   4 +
 arch/x86/boot/startup/Makefile           |  16 +-
 arch/x86/boot/startup/exports.h          |  14 -
 arch/x86/boot/startup/unprefix.syms      |  13 +
 arch/x86/entry/entry_64.S                |   7 +-
 arch/x86/kernel/vmlinux.lds.S            |   2 -
 arch/x86/platform/pvh/enlighten.c        |   1 +
 arch/x86/xen/enlighten_pv.c              |   5 +-
 arch/x86/xen/xen-head.S                  |   5 +-
 arch/x86/xen/xen-ops.h                   |   2 +-
 drivers/firmware/efi/libstub/Makefile    |   4 +
 drivers/firmware/efi/libstub/loongarch.c |   8 +
 drivers/message/fusion/mptbase.h         |   2 +
 include/kunit/test.h                     |   1 +
 include/kunit/try-catch.h                |   2 +
 include/linux/annotate.h                 |  24 ++
 include/linux/objtool.h                  |  13 +-
 include/linux/objtool_types.h            |   1 +
 include/linux/panic.h                    |   2 +-
 include/linux/rtc/ds1685.h               |   2 +
 kernel/exit.c                            |   2 +-
 lib/Kconfig.debug                        |  14 +
 lib/Kconfig.ubsan                        |   2 +-
 rust/.gitignore                          |   1 +
 rust/Makefile                            |  17 +
 scripts/.gitignore                       |   1 +
 scripts/Makefile.build                   |  31 +-
 scripts/Makefile.lib                     |   4 +-
 scripts/Makefile.modfinal                |  35 +-
 scripts/Makefile.vmlinux_o               |  13 +-
 scripts/link-vmlinux.sh                  |   3 +-
 scripts/livepatch/klp-build              |   4 +-
 tools/include/linux/objtool_types.h      |   1 +
 tools/objtool/Documentation/objtool.txt  |  30 +-
 tools/objtool/builtin-check.c            |  12 +
 tools/objtool/check.c                    | 437 +++++++++++++++++------
 tools/objtool/include/objtool/builtin.h  |   2 +
 tools/objtool/include/objtool/elf.h      |   4 +
 tools/objtool/klp-diff.c                 |  32 ++
 tools/objtool/noreturns.h                |  56 ---
 44 files changed, 584 insertions(+), 261 deletions(-)
 delete mode 100644 arch/x86/boot/startup/exports.h
 create mode 100644 arch/x86/boot/startup/unprefix.syms
 delete mode 100644 tools/objtool/noreturns.h
Signature · 1 line
2.55.0
author maintainer
#1 lore raw

PATCH v2 1/27 objtool: Remove obsolete noreturns.h entries

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.

---
 tools/objtool/noreturns.h | 5 -----
 1 file changed, 5 deletions(-)
tools/objtool/noreturns.h +0 -5
index 0e9dfd0a2446c..72ffa71aa19b8 100644
@@ -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)
@@ -31,7 +28,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)
@@ -44,7 +40,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)
Signature · 1 line
2.55.0
author maintainer
#2 lore raw

PATCH v2 2/27 kbuild: Add CONFIG_OBJTOOL_DEFERRED

In reply to Josh Poimboeuf

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.

---
 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(-)
arch/x86/boot/startup/Makefile +1 -1
index 5e499cfb29b5c..ab6e9970d4f4e 100644
@@ -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
lib/Kconfig.debug +7 -0
index 134b15a44625e..566e5be4c3f57 100644
@@ -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
scripts/Makefile.build +2 -2
index 4349108e75e1f..87993362595ce 100644
@@ -278,7 +278,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)),)
@@ -504,7 +504,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)
scripts/Makefile.lib +1 -3
index 0a4fdd8bd975d..3622237e41d2e 100644
@@ -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)
 
scripts/Makefile.vmlinux_o +7 -6
index 24a3a4fd271c2..9bac917e8b819 100644
@@ -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
scripts/link-vmlinux.sh +1 -2
index ab0b8125c8cbc..07ba4360b2c89 100755
@@ -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=
Signature · 1 line
2.55.0
#3 lore raw

Re: [PATCH v2 02/27] kbuild: Add CONFIG_OBJTOOL_DEFERRED

Hi Josh,

Quoted 34 lines · Josh Poimboeuf

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.

---
 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(-)
arch/x86/boot/startup/Makefile +1 -1
index 5e499cfb29b5c..ab6e9970d4f4e 100644
@@ -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
lib/Kconfig.debug +0 -0
index 134b15a44625e..566e5be4c3f57 100644

lib/Kconfig.debug config NOINSTR_VALIDATION · line 597

Quoted 10 lines
lib/Kconfig.debug +7 -0
@@ -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
+

Just commenting: if you move this option OBJTOOL_DEFERRED (and OBJTOOL_CONTROL_FLOW in the next patch) a few entries up in this Kconfig file, menuconfig will render them as part of the OBJTOOL submenu because of the dependency on OBJTOOL.

E.g. this ordering:

OBJTOOL
OBJTOOL_WERROR
OBJTOOL_DEFERRED
OBJTOOL_CONTROL_FLOW
STACK_VALIDATION
NOINSTR_VALIDATION

would render better.

But this is minor, I don't think it necessitates a v3 on its own.

- Julian Braha

author maintainer
#4 lore raw

PATCH v2 3/27 kbuild: Add CONFIG_OBJTOOL_CONTROL_FLOW

In reply to Josh Poimboeuf

Add a config option for when objtool reverse engineers the control flow graph of every function.

No functional change.

---
 lib/Kconfig.debug | 10 ++++++++--
 lib/Kconfig.ubsan |  2 +-
 2 files changed, 9 insertions(+), 3 deletions(-)
lib/Kconfig.debug +8 -2
index 566e5be4c3f57..5e58671be8fbd 100644
@@ -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
lib/Kconfig.ubsan +1 -1
index 1ecaae7064d2a..61e8c86ef63be 100644
@@ -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
Signature · 1 line
2.55.0
author maintainer
#5 lore raw

PATCH v2 4/27 objtool: Fix dead end detection for sibling calls

In reply to Josh Poimboeuf

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.

---
 tools/objtool/check.c | 37 +++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)
tools/objtool/check.c +19 -18
index 464f6c9d9ff0b..1fe7efa934ebf 100644
@@ -274,30 +274,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;
Signature · 1 line
2.55.0
author maintainer
#6 lore raw

PATCH v2 5/27 objtool: Refactor the noreturn/dead-end detection

In reply to Josh Poimboeuf

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.

---
 tools/objtool/check.c               | 159 ++++++++++++++++------------
 tools/objtool/include/objtool/elf.h |   1 +
 2 files changed, 93 insertions(+), 67 deletions(-)
tools/objtool/check.c +92 -67
index 1fe7efa934ebf..83b4e748d33ba 100644
@@ -219,94 +219,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)
@@ -1422,9 +1440,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;
 }
 
@@ -2669,6 +2684,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;
 
@@ -2676,8 +2702,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;
tools/objtool/include/objtool/elf.h +1 -0
index a82517a76a0f6..46fb2230ca743 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#7 lore raw

PATCH v2 6/27 objtool: Ignore traps after noreturn calls in STT_CODE

In reply to Josh Poimboeuf

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.

---
 tools/objtool/check.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
tools/objtool/check.c +6 -6
index 83b4e748d33ba..f65b1b01b4fe9 100644
@@ -4271,12 +4271,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
@@ -4292,6 +4286,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.
Signature · 1 line
2.55.0
author maintainer
#8 lore raw

PATCH v2 7/27 objtool: Make .discard.stack_frame_non_standard non-allocatable

In reply to Josh Poimboeuf

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.

---
 include/linux/objtool.h | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)
include/linux/objtool.h +8 -5
index af2e68e496e5d..e9c78e026c7a3 100644
@@ -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
 
Signature · 1 line
2.55.0
author maintainer
#9 lore raw

PATCH v2 8/27 efi/libstub: Drop .discard.addressable from the stub objects

In reply to Josh Poimboeuf

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.

---
 drivers/firmware/efi/libstub/Makefile | 4 ++++
 1 file changed, 4 insertions(+)
drivers/firmware/efi/libstub/Makefile +4 -0
index 77a2b2d74f3f6..1344d08b420a2 100644
@@ -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)
 
Signature · 1 line
2.55.0
author maintainer
#10 lore raw

PATCH v2 9/27 efi/loongarch: Mark loongarch efi_boot_kernel() non-standard for objtool

In reply to Josh Poimboeuf

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.

---
 drivers/firmware/efi/libstub/loongarch.c | 8 ++++++++
 1 file changed, 8 insertions(+)
drivers/firmware/efi/libstub/loongarch.c +8 -0
index 2b0c87dc99088..737de9afd6a40 100644
@@ -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);
Signature · 1 line
2.55.0
author maintainer
#11 lore raw

PATCH v2 10/27 objtool: Add ANNOTATE_IGNORE_NORETURN()

In reply to Josh Poimboeuf

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.

---
 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(+)
include/linux/annotate.h +7 -0
index 2f1599c9e5732..a81ca04b4c501 100644
@@ -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.
include/linux/objtool_types.h +1 -0
index c24e9ea392696..ce08ecc808c67 100644
@@ -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
 
tools/include/linux/objtool_types.h +1 -0
index c24e9ea392696..ce08ecc808c67 100644
@@ -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
 
tools/objtool/check.c +19 -0
index f65b1b01b4fe9..d3b5c372cba4a 100644
@@ -248,6 +248,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;
 
@@ -2391,6 +2394,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;
 	}
@@ -2437,6 +2452,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 &&
tools/objtool/include/objtool/elf.h +1 -0
index 46fb2230ca743..0e3593d993ac8 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#12 lore raw

PATCH v2 11/27 LoongArch: Annotate reboot and kexec paths as returnable

In reply to Josh Poimboeuf

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.

---
 arch/loongarch/kernel/machine_kexec.c | 2 ++
 arch/loongarch/kernel/reset.c         | 4 ++++
 2 files changed, 6 insertions(+)
arch/loongarch/kernel/machine_kexec.c +2 -0
index 1883cae93bc31..e40b6767df24e 100644
@@ -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);
arch/loongarch/kernel/reset.c +4 -0
index de8fa5a8a825c..89ee0edcf58d2 100644
@@ -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);
Signature · 1 line
2.55.0
author maintainer
#13 lore raw

PATCH v2 12/27 kbuild: Defer running objtool to link time for all CFG features

In reply to Josh Poimboeuf

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).

Note that NOINSTR_VALIDATION is a special case, where objtool runs on individual TUs (with all the objtool-args-y), and then again on vmlinux.o (without objtool-args-y except for --werror).

This change 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, and the special case for adding --werror on !OBJTOOL_DEFERRED is no longer needed.

---
 lib/Kconfig.debug          |  3 ++-
 scripts/Makefile.vmlinux_o | 14 +++-----------
 tools/objtool/noreturns.h  | 24 ------------------------
 3 files changed, 5 insertions(+), 36 deletions(-)
lib/Kconfig.debug +2 -1
index 5e58671be8fbd..6fd3182f5f458 100644
@@ -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"
scripts/Makefile.vmlinux_o +3 -11
index 9bac917e8b819..df1e3584883e6 100644
@@ -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)
tools/objtool/noreturns.h +0 -24
index 72ffa71aa19b8..cd41f580ccb72 100644
@@ -7,45 +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(efi_rts_park_worker)
-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)
Signature · 1 line
2.55.0
author maintainer
#14 lore raw

PATCH v2 13/27 rust: Annotate the intrinsic stubs as returnable

In reply to Josh Poimboeuf

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.

---
 rust/.gitignore |  1 +
 rust/Makefile   | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)
rust/.gitignore +1 -0
index d3829ffab80ba..c198d1a52e60e 100644
@@ -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/
rust/Makefile +17 -0
index da1a7409d9845..0aa9f298a2757 100644
@@ -36,6 +36,9 @@ endif
 
 obj-$(CONFIG_RUST) += exports.o
 
+always-$(CONFIG_RUST) += intrinsics_annotate_generated.c
+obj-$(CONFIG_RUST)    += intrinsics_annotate_generated.o
+
 always-$(CONFIG_RUST) += host/libproc_macro2.rlib host/libquote.rlib host/libsyn.rlib
 
 always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs
@@ -702,6 +705,20 @@ ifdef CONFIG_PPC32
 		__udivdi3 __umoddi3
 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") | \
Signature · 1 line
2.55.0
author maintainer
#15 lore raw

PATCH v2 14/27 panic: Mark abort() __noreturn

In reply to Josh Poimboeuf

abort() is a C compiler builtin interface, so compilers already know it doesn't return, but mark it as such explicitly for documentation purposes.

---
 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(-)
arch/arc/kernel/traps.c +3 -1
index 8d2ea2cbd98b0..803c131c6aa78 100644
@@ -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();
 }
arch/arm/kernel/traps.c +1 -1
index afbd2ebe5c39d..a3bd26d41612f 100644
@@ -777,7 +777,7 @@ asmlinkage void __div0(void)
 }
 EXPORT_SYMBOL(__div0);
 
-void abort(void)
+void __noreturn abort(void)
 {
 	BUG();
 
include/linux/panic.h +1 -1
index f1dd417e54b29..da8778360dadf 100644
@@ -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
kernel/exit.c +1 -1
index 97686af895013..e541e61945f9a 100644
@@ -2027,7 +2027,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();
 
Signature · 1 line
2.55.0
author maintainer
#16 lore raw

PATCH v2 15/27 x86/xen: Ignore noreturn status of weak mem_map_via_hcall()

In reply to Josh Poimboeuf

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.

---
 arch/x86/platform/pvh/enlighten.c | 1 +
 1 file changed, 1 insertion(+)
arch/x86/platform/pvh/enlighten.c +1 -0
index f2053cbe9b0ce..9f227f06cbaf5 100644
@@ -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)
 {
Signature · 1 line
2.55.0
author maintainer
#17 lore raw

PATCH v2 16/27 objtool: Detect noreturns in weak functions

In reply to Josh Poimboeuf

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.

---
 tools/objtool/check.c     | 3 ---
 tools/objtool/noreturns.h | 3 +--
 2 files changed, 1 insertion(+), 5 deletions(-)
tools/objtool/check.c +0 -3
index d3b5c372cba4a..35e24151f5785 100644
@@ -262,9 +262,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;
tools/objtool/noreturns.h +1 -2
index cd41f580ccb72..26f62f98bc8ef 100644
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#18 lore raw

PATCH v2 17/27 x86/entry: Make rewind_stack_and_make_dead() a real function

In reply to Josh Poimboeuf

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.

---
 arch/x86/entry/entry_64.S | 7 +++----
 tools/objtool/noreturns.h | 1 -
 2 files changed, 3 insertions(+), 5 deletions(-)
arch/x86/entry/entry_64.S +3 -4
index 253f0a585927f..2f4d930d9977d 100644
@@ -1486,9 +1486,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
 
@@ -1497,7 +1495,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
 
 /*
tools/objtool/noreturns.h +0 -1
index 26f62f98bc8ef..ff93e94ee09a0 100644
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#19 lore raw

PATCH v2 18/27 x86/xen: Make xen_cpu_bringup_again() a real function

In reply to Josh Poimboeuf

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.

---
 arch/x86/xen/xen-head.S   | 5 +++--
 tools/objtool/noreturns.h | 1 -
 2 files changed, 3 insertions(+), 3 deletions(-)
arch/x86/xen/xen-head.S +3 -2
index 5dad6c51cdc34..68d769772f309 100644
@@ -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
tools/objtool/noreturns.h +0 -1
index ff93e94ee09a0..01c94f20d1818 100644
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#20 lore raw

PATCH v2 19/27 x86/xen: Make xen_start_kernel() noreturn

In reply to Josh Poimboeuf

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.

---
 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(-)
arch/x86/xen/enlighten_pv.c +1 -4
index 2c64b388f6164..8ec78df9d1d5b 100644
@@ -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;
arch/x86/xen/xen-ops.h +1 -1
index 47eebbb3684ab..d830807cfffca 100644
@@ -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);
tools/objtool/noreturns.h +0 -1
index 01c94f20d1818..c4b93fc6e6299 100644
@@ -21,4 +21,3 @@ NORETURN(panic)
 NORETURN(vpanic)
 NORETURN(rust_helper_BUG)
 NORETURN(sev_es_terminate)
-NORETURN(xen_start_kernel)
Signature · 1 line
2.55.0
author maintainer
#21 lore raw

PATCH v2 20/27 x86/boot: Rework how pi startup symbols get exposed to vmlinux

In reply to Josh Poimboeuf

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.

---
 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
arch/x86/boot/startup/Makefile +10 -4
index ab6e9970d4f4e..5255abe2bd77f 100644
@@ -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))
arch/x86/boot/startup/exports.h deleted +0 -14
deleted file mode 100644
index 01d2363dc445f..0000000000000
@@ -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);
arch/x86/boot/startup/unprefix.syms new file +13 -0
new file mode 100644
index 0000000000000..fd80adf558a51
@@ -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
arch/x86/kernel/vmlinux.lds.S +0 -2
index 2438b89a4620a..4394ee6bd288f 100644
@@ -527,5 +527,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"
tools/objtool/noreturns.h +0 -1
index c4b93fc6e6299..2dce56030b980 100644
@@ -20,4 +20,3 @@ NORETURN(mpt_halt_firmware)
 NORETURN(panic)
 NORETURN(vpanic)
 NORETURN(rust_helper_BUG)
-NORETURN(sev_es_terminate)
Signature · 1 line
2.55.0
author maintainer
#22 lore raw

PATCH v2 21/27 objtool: Fix noreturn detection for non-sibling jumps to SYM_CODE

In reply to Josh Poimboeuf

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.

---
 tools/objtool/check.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
tools/objtool/check.c +15 -1
index 35e24151f5785..41df5f608535f 100644
@@ -266,8 +266,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))
Signature · 1 line
2.55.0
author maintainer
#23 lore raw

PATCH v2 22/27 objtool: Add options to write/read exported noreturns to/from a file

In reply to Josh Poimboeuf

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.

---
 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(-)
tools/objtool/builtin-check.c +12 -0
index 75b11dc85010e..c4c4210f84662 100644
@@ -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		||
tools/objtool/check.c +147 -1

179 lines not on this page · raw message

tools/objtool/include/objtool/builtin.h +2 -0
index 349690bb1c50e..fb33d57a54c95 100644
@@ -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;
tools/objtool/include/objtool/elf.h +1 -0
index 0e3593d993ac8..adf6e1322c6df 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#24 lore raw

PATCH v2 23/27 kbuild: Do the per-module objtool pass right before linking

In reply to Josh Poimboeuf

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.

---
 scripts/Makefile.build      | 31 +++++++++++++++----------------
 scripts/Makefile.modfinal   | 20 +++++++++++++++-----
 scripts/livepatch/klp-build |  4 ++--
 3 files changed, 32 insertions(+), 23 deletions(-)
scripts/Makefile.build +15 -16
index 87993362595ce..c1b22b4a4e217 100644
@@ -271,15 +271,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)))
@@ -497,17 +503,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:
scripts/Makefile.modfinal +15 -5
index 01a37ec872b90..56b4227cc6863 100644
@@ -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 [email protected] $< ;)
+mod-obj = $(if $(objtool-enabled),[email protected],$<)
+
 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
scripts/livepatch/klp-build +2 -2
index b52a8489d9f67..5237bad90f2cb 100755
@@ -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")"
Signature · 1 line
2.55.0
author maintainer
#25 lore raw

PATCH v2 24/27 kbuild: Generate the noreturn list and validate modules against it

In reply to Josh Poimboeuf

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.

---
 Makefile                                |  8 +++++
 scripts/.gitignore                      |  1 +
 scripts/Makefile.modfinal               | 15 ++++++--
 scripts/Makefile.vmlinux_o              |  2 ++
 tools/objtool/Documentation/objtool.txt |  8 ++---
 tools/objtool/check.c                   | 46 -------------------------
 tools/objtool/noreturns.h               | 17 ++-------
 7 files changed, 29 insertions(+), 68 deletions(-)
Makefile +8 -0
index 66654fa71655b..4e5de90c765e7 100644
@@ -1750,6 +1750,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
@@ -1780,6 +1787,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 \
scripts/.gitignore +1 -0
index 4215c2208f7e4..15d99dfd7c936 100644
@@ -5,6 +5,7 @@
 /insert-sys-cert
 /kallsyms
 /module.lds
+/noreturns.builtin
 /recordmcount
 /rustdoc_test_builder
 /rustdoc_test_gen
scripts/Makefile.modfinal +13 -2
index 56b4227cc6863..23b07ad7eb9bd 100644
@@ -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 [email protected] $< ;)
@@ -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)
 
scripts/Makefile.vmlinux_o +2 -0
index df1e3584883e6..9c60a4918a756 100644
@@ -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)
 
tools/objtool/Documentation/objtool.txt +3 -5
index 9e97fc25b2d8a..c7ebfb9a8ca6b 100644
@@ -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.
tools/objtool/check.c +0 -46

58 lines not on this page · raw message

tools/objtool/noreturns.h +2 -15
index 2dce56030b980..4e8007a8c2584 100644
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#26 lore raw

PATCH v2 25/27 objtool: Add ANNOTATE_EXPORTED_NORETURN()

In reply to Josh Poimboeuf

Add ANNOTATE_EXPORTED_NORETURN() for the rare case where a module exports a noreturn function.

---
 include/linux/annotate.h | 17 +++++++++++++++++
 tools/objtool/check.c    | 27 +++++++++++++++++++++++++++
 tools/objtool/klp-diff.c | 32 ++++++++++++++++++++++++++++++++
 3 files changed, 76 insertions(+)
include/linux/annotate.h +17 -0
index a81ca04b4c501..70450eeec3186 100644
@@ -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.
tools/objtool/check.c +27 -0
index 52630519bdbf2..94beedcdb49bb 100644
@@ -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;
@@ -4983,6 +5008,8 @@ int check(struct objtool_file *file)
 			goto out;
 	}
 
+	read_annotate_noreturn(file);
+
 	ret = decode_file(file);
 	if (ret)
 		goto out;
tools/objtool/klp-diff.c +32 -0
index 34da55e456097..8a30bf7d1eaf9 100644
@@ -360,6 +360,7 @@ static bool is_special_section(struct section *sec)
 
 	static const char * const non_special_discards[] = {
 		".discard.addressable",
+		".discard.annotate_noreturn",
 		".discard.sym_checksum",
 	};
 
@@ -2216,6 +2217,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};
@@ -2287,6 +2316,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;
 
Signature · 1 line
2.55.0
author maintainer
#27 lore raw

PATCH v2 26/27 objtool: Annotate all module-exported noreturns and remove noreturns.h

In reply to Josh Poimboeuf

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.

---
 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
drivers/message/fusion/mptbase.h +2 -0
index b406fd676da09..387b707a66742 100644
@@ -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);
 
 
 /*
include/kunit/test.h +1 -0
index da5312e0dfa57..9aa4c1d3a9821 100644
@@ -723,6 +723,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,
include/kunit/try-catch.h +2 -0
index d4e1a5b98ed67..f3c0af4650080 100644
@@ -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)
 {
include/linux/rtc/ds1685.h +2 -0
index 8ec0ebfaef04f..a0e1bf99407b7 100644
@@ -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_ */
tools/objtool/Documentation/objtool.txt +1 -1
index c7ebfb9a8ca6b..d5ac48bc203ec 100644
@@ -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
tools/objtool/check.c +1 -22
index 94beedcdb49bb..e6d33e57b7b33 100644
@@ -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;
 }
 
@@ -4890,7 +4869,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;
tools/objtool/noreturns.h deleted +0 -9
deleted file mode 100644
index 4e8007a8c2584..0000000000000
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#28 lore raw

PATCH v2 27/27 objtool: Warn about missing/stale ANNOTATE_EXPORTED_NORETURN() usage

In reply to Josh Poimboeuf

Add some warnings to keep ANNOTATE_EXPORTED_NORETURN() annotations correct.

---
 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(-)
tools/objtool/Documentation/objtool.txt +21 -1
index d5ac48bc203ec..950e88c713d1d 100644
@@ -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.
 
tools/objtool/check.c +49 -10

87 lines not on this page · raw message

tools/objtool/include/objtool/elf.h +1 -0
index adf6e1322c6df..e79cdabf09c26 100644
@@ -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;
Signature · 1 line
2.55.0