mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] ORC unwinder cleanup & UML support
@ 2026-09-24 12:35 Johannes Berg
  2026-09-24 12:35 ` [PATCH 1/4] s390: remove unwind_module_init() Johannes Berg
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Johannes Berg @ 2026-09-24 12:35 UTC (permalink / raw)
  To: linux-kernel, loongarch, linux-um, linux-s390
  Cc: Heiko Carstens, Huacai Chen, Josh Poimboeuf, Peter Zijlstra

Since UML is pretty much "just x86" I'd figured for years it
should be "simple" to add ORC unwinder support. It's not quite
as simple as I thought, but I managed - though to not have to
copy/paste _quite_ as much code, this series first cleans up
the already common code between x86 and loongarch.

Most of the code is indeed copy/paste, but with adjustments
that are not really abstractable for making common code.

It touches s390 because I thought we could end up with some
compilation errors from s390 unnecessarily (and accidentally?)
declaring unwind_module_init(), maybe it's not needed since
the new orc.h isn't included anywhere it could conflict.

Works for x86 and um, builds for loongarch (but I don't see
a reason it wouldn't work).

No idea how we'd merge this and if the MAINTAINER entry is
really where it should be, but I'm also not in a hurry :)

johannes


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] s390: remove unwind_module_init()
  2026-09-24 12:35 [PATCH 0/4] ORC unwinder cleanup & UML support Johannes Berg
@ 2026-09-24 12:35 ` Johannes Berg
  2026-09-24 12:48   ` Heiko Carstens
  2026-09-24 12:35 ` [PATCH 2/4] ORC: pull out module handling from x86/loongarch Johannes Berg
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 6+ messages in thread
From: Johannes Berg @ 2026-09-24 12:35 UTC (permalink / raw)
  To: linux-kernel, loongarch, linux-um, linux-s390
  Cc: Heiko Carstens, Huacai Chen, Josh Poimboeuf, Peter Zijlstra,
	Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

This is dead code, but will likely conflict with
the ORC unwinder unification.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 arch/s390/include/asm/unwind.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/arch/s390/include/asm/unwind.h b/arch/s390/include/asm/unwind.h
index b8ecf04e3468..021056d5982c 100644
--- a/arch/s390/include/asm/unwind.h
+++ b/arch/s390/include/asm/unwind.h
@@ -91,8 +91,5 @@ static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)
 	     unwind_next_frame(state))
 
 static inline void unwind_init(void) {}
-static inline void unwind_module_init(struct module *mod, void *orc_ip,
-				      size_t orc_ip_size, void *orc,
-				      size_t orc_size) {}
 
 #endif /* _ASM_S390_UNWIND_H */
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/4] ORC: pull out module handling from x86/loongarch
  2026-09-24 12:35 [PATCH 0/4] ORC unwinder cleanup & UML support Johannes Berg
  2026-09-24 12:35 ` [PATCH 1/4] s390: remove unwind_module_init() Johannes Berg
@ 2026-09-24 12:35 ` Johannes Berg
  2026-09-24 12:35 ` [PATCH 3/4] ORC: unify orc_hash.h generation Johannes Berg
  2026-09-24 12:35 ` [PATCH 4/4] um: ORC unwinder support Johannes Berg
  3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2026-09-24 12:35 UTC (permalink / raw)
  To: linux-kernel, loongarch, linux-um, linux-s390
  Cc: Heiko Carstens, Huacai Chen, Josh Poimboeuf, Peter Zijlstra,
	Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

The x86 and loongarch code for ORC unwinder are just
copied code, and I don't want to add another copy for
UML. Pull out the common module section handling into
the regular module code to reduce duplication.

I used the x86 version, but loongarch is identical
except for variable declaration order.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 MAINTAINERS                         |   2 +
 arch/loongarch/include/asm/module.h |   7 --
 arch/loongarch/include/asm/unwind.h |   2 -
 arch/loongarch/kernel/module.c      |  10 +--
 arch/loongarch/kernel/unwind_orc.c  | 128 +---------------------------
 arch/x86/include/asm/module.h       |   6 --
 arch/x86/include/asm/unwind.h       |   5 --
 arch/x86/kernel/module.c            |  10 ---
 arch/x86/kernel/unwind_orc.c        | 127 +--------------------------
 include/linux/module.h              |   7 ++
 include/linux/moduleloader.h        |   6 ++
 include/linux/orc.h                 |  24 ++++++
 kernel/module/main.c                |  23 ++++-
 lib/Makefile                        |   3 +
 lib/orc.c                           | 119 ++++++++++++++++++++++++++
 15 files changed, 186 insertions(+), 293 deletions(-)
 create mode 100644 include/linux/orc.h
 create mode 100644 lib/orc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index cc3cae2e378b..f1911d21a2eb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29636,6 +29636,8 @@ F:	arch/x86/include/asm/unwind*.h
 F:	arch/x86/kernel/dumpstack.c
 F:	arch/x86/kernel/stacktrace.c
 F:	arch/x86/kernel/unwind_*.c
+F:	include/linux/orc.h
+F:	lib/orc.c
 
 X86 TRUST DOMAIN EXTENSIONS (TDX)
 M:	Kiryl Shutsemau <kas@kernel.org>
diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
index d56a968273de..cc421db73ed4 100644
--- a/arch/loongarch/include/asm/module.h
+++ b/arch/loongarch/include/asm/module.h
@@ -6,7 +6,6 @@
 #define _ASM_MODULE_H
 
 #include <asm/inst.h>
-#include <asm/orc_types.h>
 #include <asm-generic/module.h>
 
 #define RELA_STACK_DEPTH 16
@@ -22,12 +21,6 @@ struct mod_arch_specific {
 	struct mod_section plt;
 	struct mod_section plt_idx;
 
-#ifdef CONFIG_UNWINDER_ORC
-	unsigned int num_orcs;
-	int *orc_unwind_ip;
-	struct orc_entry *orc_unwind;
-#endif
-
 	/* For CONFIG_DYNAMIC_FTRACE */
 	struct plt_entry *ftrace_trampolines;
 };
diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
index 40a6763c5aec..7a5f1458373f 100644
--- a/arch/loongarch/include/asm/unwind.h
+++ b/arch/loongarch/include/asm/unwind.h
@@ -89,10 +89,8 @@ static __always_inline unsigned long __unwind_get_return_address(struct unwind_s
 
 #ifdef CONFIG_UNWINDER_ORC
 void unwind_init(void);
-void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size);
 #else
 static inline void unwind_init(void) {}
-static inline void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size) {}
 #endif
 
 #endif /* _ASM_UNWIND_H */
diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
index 7d4d571ee55e..21f7542003d4 100644
--- a/arch/loongarch/kernel/module.c
+++ b/arch/loongarch/kernel/module.c
@@ -20,7 +20,6 @@
 #include <linux/kernel.h>
 #include <asm/alternative.h>
 #include <asm/inst.h>
-#include <asm/unwind.h>
 
 /*
  * reloc_rela_handler() - Apply a particular relocation to a module
@@ -607,15 +606,11 @@ int module_finalize(const Elf_Ehdr *hdr,
 		    const Elf_Shdr *sechdrs, struct module *mod)
 {
 	const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
-	const Elf_Shdr *s, *alt = NULL, *orc = NULL, *orc_ip = NULL, *ftrace = NULL;
+	const Elf_Shdr *s, *alt = NULL, *ftrace = NULL;
 
 	for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
 		if (!strcmp(".altinstructions", secstrs + s->sh_name))
 			alt = s;
-		if (!strcmp(".orc_unwind", secstrs + s->sh_name))
-			orc = s;
-		if (!strcmp(".orc_unwind_ip", secstrs + s->sh_name))
-			orc_ip = s;
 		if (!strcmp(".ftrace_trampoline", secstrs + s->sh_name))
 			ftrace = s;
 	}
@@ -623,9 +618,6 @@ int module_finalize(const Elf_Ehdr *hdr,
 	if (alt)
 		apply_alternatives((void *)alt->sh_addr, (void *)alt->sh_addr + alt->sh_size);
 
-	if (orc && orc_ip)
-		unwind_module_init(mod, (void *)orc_ip->sh_addr, orc_ip->sh_size, (void *)orc->sh_addr, orc->sh_size);
-
 	if (ftrace)
 		module_init_ftrace_plt(hdr, ftrace, mod);
 
diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
index 9cfb5bb1991f..129f248836a7 100644
--- a/arch/loongarch/kernel/unwind_orc.c
+++ b/arch/loongarch/kernel/unwind_orc.c
@@ -2,7 +2,7 @@
 #include <linux/export.h>
 #include <linux/module.h>
 #include <linux/objtool.h>
-#include <linux/sort.h>
+#include <linux/orc.h>
 #include <asm/exception.h>
 #include <asm/orc_header.h>
 #include <asm/orc_lookup.h>
@@ -51,58 +51,6 @@ static struct orc_entry orc_null_entry = {
 	.type		= ORC_TYPE_CALL
 };
 
-static inline unsigned long orc_ip(const int *ip)
-{
-	return (unsigned long)ip + *ip;
-}
-
-static struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
-				    unsigned int num_entries, unsigned long ip)
-{
-	int *first = ip_table;
-	int *mid = first, *found = first;
-	int *last = ip_table + num_entries - 1;
-
-	if (!num_entries)
-		return NULL;
-
-	/*
-	 * Do a binary range search to find the rightmost duplicate of a given
-	 * starting address.  Some entries are section terminators which are
-	 * "weak" entries for ensuring there are no gaps.  They should be
-	 * ignored when they conflict with a real entry.
-	 */
-	while (first <= last) {
-		mid = first + ((last - first) / 2);
-
-		if (orc_ip(mid) <= ip) {
-			found = mid;
-			first = mid + 1;
-		} else
-			last = mid - 1;
-	}
-
-	return u_table + (found - ip_table);
-}
-
-#ifdef CONFIG_MODULES
-static struct orc_entry *orc_module_find(unsigned long ip)
-{
-	struct module *mod;
-
-	mod = __module_address(ip);
-	if (!mod || !mod->arch.orc_unwind || !mod->arch.orc_unwind_ip)
-		return NULL;
-
-	return __orc_find(mod->arch.orc_unwind_ip, mod->arch.orc_unwind, mod->arch.num_orcs, ip);
-}
-#else
-static struct orc_entry *orc_module_find(unsigned long ip)
-{
-	return NULL;
-}
-#endif
-
 #ifdef CONFIG_DYNAMIC_FTRACE
 static struct orc_entry *orc_find(unsigned long ip);
 
@@ -194,80 +142,6 @@ static struct orc_entry *orc_find(unsigned long ip)
 	return orc_ftrace_find(ip);
 }
 
-#ifdef CONFIG_MODULES
-
-static DEFINE_MUTEX(sort_mutex);
-static int *cur_orc_ip_table = __start_orc_unwind_ip;
-static struct orc_entry *cur_orc_table = __start_orc_unwind;
-
-static void orc_sort_swap(void *_a, void *_b, int size)
-{
-	int delta = _b - _a;
-	int *a = _a, *b = _b, tmp;
-	struct orc_entry *orc_a, *orc_b;
-
-	/* Swap the .orc_unwind_ip entries: */
-	tmp = *a;
-	*a = *b + delta;
-	*b = tmp - delta;
-
-	/* Swap the corresponding .orc_unwind entries: */
-	orc_a = cur_orc_table + (a - cur_orc_ip_table);
-	orc_b = cur_orc_table + (b - cur_orc_ip_table);
-	swap(*orc_a, *orc_b);
-}
-
-static int orc_sort_cmp(const void *_a, const void *_b)
-{
-	const int *a = _a, *b = _b;
-	unsigned long a_val = orc_ip(a);
-	unsigned long b_val = orc_ip(b);
-	struct orc_entry *orc_a;
-
-	if (a_val > b_val)
-		return 1;
-	if (a_val < b_val)
-		return -1;
-
-	/*
-	 * The "weak" section terminator entries need to always be first
-	 * to ensure the lookup code skips them in favor of real entries.
-	 * These terminator entries exist to handle any gaps created by
-	 * whitelisted .o files which didn't get objtool generation.
-	 */
-	orc_a = cur_orc_table + (a - cur_orc_ip_table);
-
-	return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
-}
-
-void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
-			void *_orc, size_t orc_size)
-{
-	int *orc_ip = _orc_ip;
-	struct orc_entry *orc = _orc;
-	unsigned int num_entries = orc_ip_size / sizeof(int);
-
-	WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
-		     orc_size % sizeof(*orc) != 0 ||
-		     num_entries != orc_size / sizeof(*orc));
-
-	/*
-	 * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
-	 * associate an .orc_unwind_ip table entry with its corresponding
-	 * .orc_unwind entry so they can both be swapped.
-	 */
-	mutex_lock(&sort_mutex);
-	cur_orc_ip_table = orc_ip;
-	cur_orc_table = orc;
-	sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
-	mutex_unlock(&sort_mutex);
-
-	mod->arch.orc_unwind_ip = orc_ip;
-	mod->arch.orc_unwind = orc;
-	mod->arch.num_orcs = num_entries;
-}
-#endif
-
 void __init unwind_init(void)
 {
 	int i;
diff --git a/arch/x86/include/asm/module.h b/arch/x86/include/asm/module.h
index 3c2de4ce3b10..6717e85e8605 100644
--- a/arch/x86/include/asm/module.h
+++ b/arch/x86/include/asm/module.h
@@ -3,7 +3,6 @@
 #define _ASM_X86_MODULE_H
 
 #include <asm-generic/module.h>
-#include <asm/orc_types.h>
 
 struct its_array {
 #ifdef CONFIG_MITIGATION_ITS
@@ -13,11 +12,6 @@ struct its_array {
 };
 
 struct mod_arch_specific {
-#ifdef CONFIG_UNWINDER_ORC
-	unsigned int num_orcs;
-	int *orc_unwind_ip;
-	struct orc_entry *orc_unwind;
-#endif
 	struct its_array its_pages;
 };
 
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h
index 7cede4dc21f0..71af8246c69e 100644
--- a/arch/x86/include/asm/unwind.h
+++ b/arch/x86/include/asm/unwind.h
@@ -94,13 +94,8 @@ static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state,
 
 #ifdef CONFIG_UNWINDER_ORC
 void unwind_init(void);
-void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size,
-			void *orc, size_t orc_size);
 #else
 static inline void unwind_init(void) {}
-static inline
-void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size,
-			void *orc, size_t orc_size) {}
 #endif
 
 static inline
diff --git a/arch/x86/kernel/module.c b/arch/x86/kernel/module.c
index 33080ad889b7..cf59f9f42d0b 100644
--- a/arch/x86/kernel/module.c
+++ b/arch/x86/kernel/module.c
@@ -23,7 +23,6 @@
 #include <asm/text-patching.h>
 #include <asm/page.h>
 #include <asm/setup.h>
-#include <asm/unwind.h>
 
 #if 0
 #define DEBUGP(fmt, ...)				\
@@ -228,7 +227,6 @@ int module_finalize(const Elf_Ehdr *hdr,
 		    struct module *me)
 {
 	const Elf_Shdr *s, *alt = NULL,
-		*orc = NULL, *orc_ip = NULL,
 		*retpolines = NULL, *returns = NULL, *ibt_endbr = NULL,
 		*calls = NULL, *cfi = NULL;
 	char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
@@ -236,10 +234,6 @@ int module_finalize(const Elf_Ehdr *hdr,
 	for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
 		if (!strcmp(".altinstructions", secstrings + s->sh_name))
 			alt = s;
-		if (!strcmp(".orc_unwind", secstrings + s->sh_name))
-			orc = s;
-		if (!strcmp(".orc_unwind_ip", secstrings + s->sh_name))
-			orc_ip = s;
 		if (!strcmp(".retpoline_sites", secstrings + s->sh_name))
 			retpolines = s;
 		if (!strcmp(".return_sites", secstrings + s->sh_name))
@@ -299,10 +293,6 @@ int module_finalize(const Elf_Ehdr *hdr,
 		apply_seal_endbr(iseg, iseg + ibt_endbr->sh_size);
 	}
 
-	if (orc && orc_ip)
-		unwind_module_init(me, (void *)orc_ip->sh_addr, orc_ip->sh_size,
-				   (void *)orc->sh_addr, orc->sh_size);
-
 	return 0;
 }
 
diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c
index 6407bc9256bf..6ea1ea28e9df 100644
--- a/arch/x86/kernel/unwind_orc.c
+++ b/arch/x86/kernel/unwind_orc.c
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <linux/objtool.h>
 #include <linux/module.h>
-#include <linux/sort.h>
+#include <linux/orc.h>
 #include <linux/bpf.h>
 #include <asm/ptrace.h>
 #include <asm/stacktrace.h>
@@ -76,58 +76,6 @@ static void unwind_dump(struct unwind_state *state)
 	}
 }
 
-static inline unsigned long orc_ip(const int *ip)
-{
-	return (unsigned long)ip + *ip;
-}
-
-static struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
-				    unsigned int num_entries, unsigned long ip)
-{
-	int *first = ip_table;
-	int *last = ip_table + num_entries - 1;
-	int *mid, *found = first;
-
-	if (!num_entries)
-		return NULL;
-
-	/*
-	 * Do a binary range search to find the rightmost duplicate of a given
-	 * starting address.  Some entries are section terminators which are
-	 * "weak" entries for ensuring there are no gaps.  They should be
-	 * ignored when they conflict with a real entry.
-	 */
-	while (first <= last) {
-		mid = first + ((last - first) / 2);
-
-		if (orc_ip(mid) <= ip) {
-			found = mid;
-			first = mid + 1;
-		} else
-			last = mid - 1;
-	}
-
-	return u_table + (found - ip_table);
-}
-
-#ifdef CONFIG_MODULES
-static struct orc_entry *orc_module_find(unsigned long ip)
-{
-	struct module *mod;
-
-	mod = __module_address(ip);
-	if (!mod || !mod->arch.orc_unwind || !mod->arch.orc_unwind_ip)
-		return NULL;
-	return __orc_find(mod->arch.orc_unwind_ip, mod->arch.orc_unwind,
-			  mod->arch.num_orcs, ip);
-}
-#else
-static struct orc_entry *orc_module_find(unsigned long ip)
-{
-	return NULL;
-}
-#endif
-
 #ifdef CONFIG_DYNAMIC_FTRACE
 static struct orc_entry *orc_find(unsigned long ip);
 
@@ -257,79 +205,6 @@ static struct orc_entry *orc_find(unsigned long ip)
 	return orc_ftrace_find(ip);
 }
 
-#ifdef CONFIG_MODULES
-
-static DEFINE_MUTEX(sort_mutex);
-static int *cur_orc_ip_table = __start_orc_unwind_ip;
-static struct orc_entry *cur_orc_table = __start_orc_unwind;
-
-static void orc_sort_swap(void *_a, void *_b, int size)
-{
-	struct orc_entry *orc_a, *orc_b;
-	int *a = _a, *b = _b, tmp;
-	int delta = _b - _a;
-
-	/* Swap the .orc_unwind_ip entries: */
-	tmp = *a;
-	*a = *b + delta;
-	*b = tmp - delta;
-
-	/* Swap the corresponding .orc_unwind entries: */
-	orc_a = cur_orc_table + (a - cur_orc_ip_table);
-	orc_b = cur_orc_table + (b - cur_orc_ip_table);
-	swap(*orc_a, *orc_b);
-}
-
-static int orc_sort_cmp(const void *_a, const void *_b)
-{
-	struct orc_entry *orc_a;
-	const int *a = _a, *b = _b;
-	unsigned long a_val = orc_ip(a);
-	unsigned long b_val = orc_ip(b);
-
-	if (a_val > b_val)
-		return 1;
-	if (a_val < b_val)
-		return -1;
-
-	/*
-	 * The "weak" section terminator entries need to always be first
-	 * to ensure the lookup code skips them in favor of real entries.
-	 * These terminator entries exist to handle any gaps created by
-	 * whitelisted .o files which didn't get objtool generation.
-	 */
-	orc_a = cur_orc_table + (a - cur_orc_ip_table);
-	return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
-}
-
-void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
-			void *_orc, size_t orc_size)
-{
-	int *orc_ip = _orc_ip;
-	struct orc_entry *orc = _orc;
-	unsigned int num_entries = orc_ip_size / sizeof(int);
-
-	WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
-		     orc_size % sizeof(*orc) != 0 ||
-		     num_entries != orc_size / sizeof(*orc));
-
-	/*
-	 * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
-	 * associate an .orc_unwind_ip table entry with its corresponding
-	 * .orc_unwind entry so they can both be swapped.
-	 */
-	mutex_lock(&sort_mutex);
-	cur_orc_ip_table = orc_ip;
-	cur_orc_table = orc;
-	sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
-	mutex_unlock(&sort_mutex);
-
-	mod->arch.orc_unwind_ip = orc_ip;
-	mod->arch.orc_unwind = orc;
-	mod->arch.num_orcs = num_entries;
-}
-#endif
-
 void __init unwind_init(void)
 {
 	size_t orc_ip_size = (void *)__stop_orc_unwind_ip - (void *)__start_orc_unwind_ip;
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea..089b34c83eb0 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -42,6 +42,7 @@ struct modversion_info {
 
 struct module;
 struct exception_table_entry;
+struct orc_entry;
 
 struct module_kobject {
 	struct kobject kobj;
@@ -465,6 +466,12 @@ struct module {
 	struct bug_entry *bug_table;
 #endif
 
+#ifdef CONFIG_UNWINDER_ORC
+	unsigned int num_orcs;
+	int *orc_unwind_ip;
+	struct orc_entry *orc_unwind;
+#endif
+
 #ifdef CONFIG_KALLSYMS
 	/* Protected by RCU and/or module_mutex: use rcu_dereference() */
 	struct mod_kallsyms __rcu *kallsyms;
diff --git a/include/linux/moduleloader.h b/include/linux/moduleloader.h
index e395461d59e5..893ca5717383 100644
--- a/include/linux/moduleloader.h
+++ b/include/linux/moduleloader.h
@@ -108,6 +108,12 @@ int module_finalize(const Elf_Ehdr *hdr,
 		    const Elf_Shdr *sechdrs,
 		    struct module *mod);
 
+#ifdef CONFIG_UNWINDER_ORC
+/* Sort the module's ORC tables and make them available to the unwinder. */
+void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size,
+			void *orc, size_t orc_size);
+#endif
+
 #ifdef CONFIG_MODULES
 void flush_module_init_free_work(void);
 #else
diff --git a/include/linux/orc.h b/include/linux/orc.h
new file mode 100644
index 000000000000..c2e84cf85132
--- /dev/null
+++ b/include/linux/orc.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_ORC_H
+#define _LINUX_ORC_H
+
+#include <asm/orc_types.h>
+
+static inline unsigned long orc_ip(const int *ip)
+{
+	return (unsigned long)ip + *ip;
+}
+
+struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
+			     unsigned int num_entries, unsigned long ip);
+
+#ifdef CONFIG_MODULES
+struct orc_entry *orc_module_find(unsigned long ip);
+#else
+static inline struct orc_entry *orc_module_find(unsigned long ip)
+{
+	return NULL;
+}
+#endif
+
+#endif /* _LINUX_ORC_H */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index d0e1e0bd2ad0..d0cbaa2a5d0c 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3015,8 +3015,24 @@ int __weak module_finalize(const Elf_Ehdr *hdr,
 	return 0;
 }
 
+static void module_unwind_init(struct module *mod, const struct load_info *info)
+{
+#ifdef CONFIG_UNWINDER_ORC
+	unsigned int orc = find_sec(info, ".orc_unwind");
+	unsigned int orc_ip = find_sec(info, ".orc_unwind_ip");
+
+	if (orc && orc_ip)
+		unwind_module_init(mod, (void *)info->sechdrs[orc_ip].sh_addr,
+				   info->sechdrs[orc_ip].sh_size,
+				   (void *)info->sechdrs[orc].sh_addr,
+				   info->sechdrs[orc].sh_size);
+#endif
+}
+
 static int post_relocation(struct module *mod, const struct load_info *info)
 {
+	int err;
+
 	/* Sort exception table now relocations are done. */
 	sort_extable(mod->extable, mod->extable + mod->num_exentries);
 
@@ -3028,7 +3044,12 @@ static int post_relocation(struct module *mod, const struct load_info *info)
 	add_kallsyms(mod, info);
 
 	/* Arch-specific module finalizing. */
-	return module_finalize(info->hdr, info->sechdrs, mod);
+	err = module_finalize(info->hdr, info->sechdrs, mod);
+	if (err)
+		return err;
+
+	module_unwind_init(mod, info);
+	return 0;
 }
 
 /* Call module constructors. */
diff --git a/lib/Makefile b/lib/Makefile
index dfab958327c5..1ccd88ca9e7b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -255,6 +255,9 @@ KASAN_SANITIZE_stackdepot.o := n
 KMSAN_SANITIZE_stackdepot.o := n
 KCOV_INSTRUMENT_stackdepot.o := n
 
+obj-$(CONFIG_UNWINDER_ORC) += orc.o
+KCOV_INSTRUMENT_orc.o := n
+
 obj-$(CONFIG_REF_TRACKER) += ref_tracker.o
 
 libfdt_files = fdt.o fdt_ro.o fdt_wip.o fdt_rw.o fdt_sw.o fdt_strerror.o \
diff --git a/lib/orc.c b/lib/orc.c
new file mode 100644
index 000000000000..980bd563b2cd
--- /dev/null
+++ b/lib/orc.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/module.h>
+#include <linux/moduleloader.h>
+#include <linux/mutex.h>
+#include <linux/orc.h>
+#include <linux/sort.h>
+
+struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
+			     unsigned int num_entries, unsigned long ip)
+{
+	int *first = ip_table;
+	int *last = ip_table + num_entries - 1;
+	int *mid, *found = first;
+
+	if (!num_entries)
+		return NULL;
+
+	/*
+	 * Do a binary range search to find the rightmost duplicate of a given
+	 * starting address.  Some entries are section terminators which are
+	 * "weak" entries for ensuring there are no gaps.  They should be
+	 * ignored when they conflict with a real entry.
+	 */
+	while (first <= last) {
+		mid = first + ((last - first) / 2);
+
+		if (orc_ip(mid) <= ip) {
+			found = mid;
+			first = mid + 1;
+		} else {
+			last = mid - 1;
+		}
+	}
+
+	return u_table + (found - ip_table);
+}
+
+#ifdef CONFIG_MODULES
+struct orc_entry *orc_module_find(unsigned long ip)
+{
+	struct module *mod;
+
+	mod = __module_address(ip);
+	if (!mod || !mod->orc_unwind || !mod->orc_unwind_ip)
+		return NULL;
+	return __orc_find(mod->orc_unwind_ip, mod->orc_unwind,
+			  mod->num_orcs, ip);
+}
+
+static DEFINE_MUTEX(sort_mutex);
+static int *cur_orc_ip_table;
+static struct orc_entry *cur_orc_table;
+
+static void orc_sort_swap(void *_a, void *_b, int size)
+{
+	struct orc_entry *orc_a, *orc_b;
+	int *a = _a, *b = _b, tmp;
+	int delta = _b - _a;
+
+	/* Swap the .orc_unwind_ip entries: */
+	tmp = *a;
+	*a = *b + delta;
+	*b = tmp - delta;
+
+	/* Swap the corresponding .orc_unwind entries: */
+	orc_a = cur_orc_table + (a - cur_orc_ip_table);
+	orc_b = cur_orc_table + (b - cur_orc_ip_table);
+	swap(*orc_a, *orc_b);
+}
+
+static int orc_sort_cmp(const void *_a, const void *_b)
+{
+	struct orc_entry *orc_a;
+	const int *a = _a, *b = _b;
+	unsigned long a_val = orc_ip(a);
+	unsigned long b_val = orc_ip(b);
+
+	if (a_val > b_val)
+		return 1;
+	if (a_val < b_val)
+		return -1;
+
+	/*
+	 * The "weak" section terminator entries need to always be first
+	 * to ensure the lookup code skips them in favor of real entries.
+	 * These terminator entries exist to handle any gaps created by
+	 * whitelisted .o files which didn't get objtool generation.
+	 */
+	orc_a = cur_orc_table + (a - cur_orc_ip_table);
+	return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
+}
+
+void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
+			void *_orc, size_t orc_size)
+{
+	int *orc_ip = _orc_ip;
+	struct orc_entry *orc = _orc;
+	unsigned int num_entries = orc_ip_size / sizeof(int);
+
+	WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
+		     orc_size % sizeof(*orc) != 0 ||
+		     num_entries != orc_size / sizeof(*orc));
+
+	/*
+	 * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
+	 * associate an .orc_unwind_ip table entry with its corresponding
+	 * .orc_unwind entry so they can both be swapped.
+	 */
+	mutex_lock(&sort_mutex);
+	cur_orc_ip_table = orc_ip;
+	cur_orc_table = orc;
+	sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
+	mutex_unlock(&sort_mutex);
+
+	mod->orc_unwind_ip = orc_ip;
+	mod->orc_unwind = orc;
+	mod->num_orcs = num_entries;
+}
+#endif
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 3/4] ORC: unify orc_hash.h generation
  2026-09-24 12:35 [PATCH 0/4] ORC unwinder cleanup & UML support Johannes Berg
  2026-09-24 12:35 ` [PATCH 1/4] s390: remove unwind_module_init() Johannes Berg
  2026-09-24 12:35 ` [PATCH 2/4] ORC: pull out module handling from x86/loongarch Johannes Berg
@ 2026-09-24 12:35 ` Johannes Berg
  2026-09-24 12:35 ` [PATCH 4/4] um: ORC unwinder support Johannes Berg
  3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2026-09-24 12:35 UTC (permalink / raw)
  To: linux-kernel, loongarch, linux-um, linux-s390
  Cc: Heiko Carstens, Huacai Chen, Josh Poimboeuf, Peter Zijlstra,
	Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

There's no real reason this needs to be duplicated,
and while not a lot of code, I'm adding it yet again
for UML. Move it out to the common Makefile.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 Makefile                | 13 +++++++++++++
 arch/loongarch/Makefile | 12 ------------
 arch/x86/Makefile       | 12 ------------
 3 files changed, 13 insertions(+), 24 deletions(-)

diff --git a/Makefile b/Makefile
index 751a08643bf8..110aa0036e3f 100644
--- a/Makefile
+++ b/Makefile
@@ -1490,6 +1490,19 @@ filechk_compile.h = $(srctree)/scripts/mkcompile_h \
 include/generated/compile.h: FORCE
 	$(call filechk,compile.h)
 
+ifdef CONFIG_UNWINDER_ORC
+orc_hash_dir := arch/$(or $(HEADER_ARCH),$(SRCARCH))/include
+orc_hash_h := $(orc_hash_dir)/generated/asm/orc_hash.h
+orc_hash_sh := $(srctree)/scripts/orc_hash.sh
+targets += $(orc_hash_h)
+quiet_cmd_orc_hash = GEN     $@
+      cmd_orc_hash = mkdir -p $(dir $@); \
+		     $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
+$(orc_hash_h): $(srctree)/$(orc_hash_dir)/asm/orc_types.h $(orc_hash_sh) FORCE
+	$(call if_changed,orc_hash)
+archprepare: $(orc_hash_h)
+endif
+
 PHONY += headerdep
 headerdep:
 	$(Q)find $(srctree)/include/ -name '*.h' | xargs --max-args 1 \
diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile
index 4b22f95aaafb..96ceda11e307 100644
--- a/arch/loongarch/Makefile
+++ b/arch/loongarch/Makefile
@@ -35,18 +35,6 @@ endif
 CC_FLAGS_FPU		:= -mfpu=64
 CC_FLAGS_NO_FPU		:= -msoft-float
 
-ifdef CONFIG_UNWINDER_ORC
-orc_hash_h := arch/$(SRCARCH)/include/generated/asm/orc_hash.h
-orc_hash_sh := $(srctree)/scripts/orc_hash.sh
-targets += $(orc_hash_h)
-quiet_cmd_orc_hash = GEN     $@
-      cmd_orc_hash = mkdir -p $(dir $@); \
-		     $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
-$(orc_hash_h): $(srctree)/arch/loongarch/include/asm/orc_types.h $(orc_hash_sh) FORCE
-	$(call if_changed,orc_hash)
-archprepare: $(orc_hash_h)
-endif
-
 ifdef CONFIG_DYNAMIC_FTRACE
 KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
 CC_FLAGS_FTRACE := -fpatchable-function-entry=2
diff --git a/arch/x86/Makefile b/arch/x86/Makefile
index 8af6b80cffdd..9089318a621c 100644
--- a/arch/x86/Makefile
+++ b/arch/x86/Makefile
@@ -331,18 +331,6 @@ ifeq ($(RETPOLINE_CFLAGS),)
 endif
 endif
 
-ifdef CONFIG_UNWINDER_ORC
-orc_hash_h := arch/$(SRCARCH)/include/generated/asm/orc_hash.h
-orc_hash_sh := $(srctree)/scripts/orc_hash.sh
-targets += $(orc_hash_h)
-quiet_cmd_orc_hash = GEN     $@
-      cmd_orc_hash = mkdir -p $(dir $@); \
-		     $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
-$(orc_hash_h): $(srctree)/arch/x86/include/asm/orc_types.h $(orc_hash_sh) FORCE
-	$(call if_changed,orc_hash)
-archprepare: $(orc_hash_h)
-endif
-
 archclean:
 	$(Q)rm -rf $(objtree)/arch/i386
 	$(Q)rm -rf $(objtree)/arch/x86_64
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 4/4] um: ORC unwinder support
  2026-09-24 12:35 [PATCH 0/4] ORC unwinder cleanup & UML support Johannes Berg
                   ` (2 preceding siblings ...)
  2026-09-24 12:35 ` [PATCH 3/4] ORC: unify orc_hash.h generation Johannes Berg
@ 2026-09-24 12:35 ` Johannes Berg
  3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2026-09-24 12:35 UTC (permalink / raw)
  To: linux-kernel, loongarch, linux-um, linux-s390
  Cc: Heiko Carstens, Huacai Chen, Josh Poimboeuf, Peter Zijlstra,
	Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

Add support for the ORC unwinder on 64-bit UML. Since that's just
normal x86_64 code, the objtool etc. is the same and the unwinder
itself is mostly copied from x86, except
 - register accesses, obviously;
 - no need for (entry) assembly support
 - interrupts are host signals, so we can unwind across them
   by detecting the signal restorer and using the ucontext

Note that due to the indirect calls with -mcmodel=large objtool
gets confused in some places resulting in some warnings, but I
couldn't find a way to suppress that and it seemed harmless.

Assisted-by: LLM
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 arch/um/Kconfig.debug            |  15 +
 arch/um/include/asm/common.lds.S |   2 +
 arch/um/include/asm/stacktrace.h |  87 ++++++
 arch/um/include/asm/unwind.h     |  70 ++++-
 arch/um/include/shared/os.h      |   1 +
 arch/um/kernel/Makefile          |   3 +
 arch/um/kernel/dyn.lds.S         |   1 +
 arch/um/kernel/skas/Makefile     |   2 +
 arch/um/kernel/stacktrace.c      |  21 ++
 arch/um/kernel/um_arch.c         |   3 +
 arch/um/kernel/uml.lds.S         |   1 +
 arch/um/kernel/unwind_orc.c      | 478 +++++++++++++++++++++++++++++++
 arch/um/os-Linux/signal.c        |   6 +
 arch/x86/um/user-offsets.c       |   4 +
 scripts/Makefile                 |   4 +
 tools/scripts/Makefile.arch      |   5 +
 16 files changed, 700 insertions(+), 3 deletions(-)
 create mode 100644 arch/um/kernel/unwind_orc.c

diff --git a/arch/um/Kconfig.debug b/arch/um/Kconfig.debug
index 1dfb2959c73b..1228738fc228 100644
--- a/arch/um/Kconfig.debug
+++ b/arch/um/Kconfig.debug
@@ -36,3 +36,18 @@ config EARLY_PRINTK
 
 	  This is useful for kernel debugging when your machine crashes very
 	  early before the console code is initialized.
+
+config UNWINDER_ORC
+	bool "ORC unwinder"
+	depends on X86_64
+	select OBJTOOL
+	select BUILDTIME_TABLE_SORT
+	help
+	  This option enables the ORC (Oops Rewind Capability) unwinder for
+	  kernel stack traces, instead of scanning the stack for anything
+	  that looks like a kernel text address.  It uses unwind tables that
+	  objtool generates at compile time, so traces are exact and don't
+	  depend on frame pointers.  This increases the kernel image size by
+	  roughly 1-4 MB, depending on the configuration.
+
+	  If you're unsure, say N.
diff --git a/arch/um/include/asm/common.lds.S b/arch/um/include/asm/common.lds.S
index fd481ac371de..f7c05291fc12 100644
--- a/arch/um/include/asm/common.lds.S
+++ b/arch/um/include/asm/common.lds.S
@@ -20,6 +20,8 @@
 
   BUG_TABLE
 
+  ORC_UNWIND_TABLE
+
   .uml.setup.init : {
 	__uml_setup_start = .;
 	*(.uml.setup.init)
diff --git a/arch/um/include/asm/stacktrace.h b/arch/um/include/asm/stacktrace.h
index 436b55952c3a..2a5e86eac3bf 100644
--- a/arch/um/include/asm/stacktrace.h
+++ b/arch/um/include/asm/stacktrace.h
@@ -4,6 +4,7 @@
 
 #include <linux/uaccess.h>
 #include <linux/ptrace.h>
+#include <linux/sched/task_stack.h>
 
 struct stack_frame {
 	struct stack_frame *next_frame;
@@ -40,4 +41,90 @@ static inline unsigned long
 
 void dump_trace(struct task_struct *tsk, const struct stacktrace_ops *ops, void *data);
 
+#ifdef CONFIG_UNWINDER_ORC
+
+#include <linux/smp-internal.h>
+
+/* host signal handlers run on the per-CPU cpu_irqstacks (sigaltstack) */
+enum stack_type {
+	STACK_TYPE_UNKNOWN,
+	STACK_TYPE_TASK,
+	STACK_TYPE_IRQ,
+};
+
+struct stack_info {
+	enum stack_type type;
+	unsigned long *begin, *end, *next_sp;
+};
+
+static inline bool on_stack(struct stack_info *info, void *addr, size_t len)
+{
+	void *begin = info->begin;
+	void *end = info->end;
+
+	return info->type != STACK_TYPE_UNKNOWN &&
+	       addr >= begin && addr < end &&
+	       addr + len > begin && addr + len <= end;
+}
+
+static inline bool in_task_stack(unsigned long *stack, struct task_struct *task,
+				 struct stack_info *info)
+{
+	unsigned long *begin = task_stack_page(task);
+	unsigned long *end = task_stack_page(task) + THREAD_SIZE;
+
+	if (stack < begin || stack >= end)
+		return false;
+
+	info->type = STACK_TYPE_TASK;
+	info->begin = begin;
+	info->end = end;
+	info->next_sp = NULL;
+
+	return true;
+}
+
+static inline bool in_irq_stack(unsigned long *stack, struct stack_info *info)
+{
+	void *begin = cpu_irqstacks;
+	void *end = cpu_irqstacks + NR_CPUS;
+
+	if ((void *)stack < begin || (void *)stack >= end)
+		return false;
+
+	begin = PTR_ALIGN_DOWN(stack, THREAD_SIZE);
+	info->type = STACK_TYPE_IRQ;
+	info->begin = begin;
+	info->end = begin + THREAD_SIZE;
+	info->next_sp = NULL;
+
+	return true;
+}
+
+static inline int get_stack_info(unsigned long *stack, struct task_struct *task,
+				 struct stack_info *info, unsigned long *visit_mask)
+{
+	task = task ? : current;
+
+	if (!stack)
+		goto unknown;
+
+	if (!in_task_stack(stack, task, info) &&
+	    (task != current || !in_irq_stack(stack, info)))
+		goto unknown;
+
+	if (visit_mask) {
+		if (*visit_mask & (1UL << info->type))
+			goto unknown;
+		*visit_mask |= 1UL << info->type;
+	}
+
+	return 0;
+
+unknown:
+	info->type = STACK_TYPE_UNKNOWN;
+	return -EINVAL;
+}
+#endif /* CONFIG_UNWINDER_ORC */
+
 #endif /* _ASM_UML_STACKTRACE_H */
diff --git a/arch/um/include/asm/unwind.h b/arch/um/include/asm/unwind.h
index 7ffa5437b761..848013ba2f23 100644
--- a/arch/um/include/asm/unwind.h
+++ b/arch/um/include/asm/unwind.h
@@ -1,8 +1,72 @@
 #ifndef _ASM_UML_UNWIND_H
 #define _ASM_UML_UNWIND_H
 
-static inline void
-unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size,
-		   void *orc, size_t orc_size) {}
+#ifdef CONFIG_UNWINDER_ORC
+
+#include <linux/sched.h>
+#include <linux/ftrace.h>
+#include <asm/ptrace.h>
+#include <asm/stacktrace.h>
+
+struct unwind_state {
+	struct stack_info stack_info;
+	unsigned long stack_mask;
+	struct task_struct *task;
+	int graph_idx;
+	bool error;
+	bool signal;
+	unsigned long sp, bp, ip;
+	struct pt_regs *regs;
+};
+
+void __unwind_start(struct unwind_state *state, struct task_struct *task,
+		    struct pt_regs *regs, unsigned long *first_frame);
+bool unwind_next_frame(struct unwind_state *state);
+unsigned long unwind_get_return_address(struct unwind_state *state);
+
+static inline bool unwind_done(struct unwind_state *state)
+{
+	return state->stack_info.type == STACK_TYPE_UNKNOWN;
+}
+
+static inline bool unwind_error(struct unwind_state *state)
+{
+	return state->error;
+}
+
+static inline
+void unwind_start(struct unwind_state *state, struct task_struct *task,
+		  struct pt_regs *regs, unsigned long *first_frame)
+{
+	first_frame = first_frame ? : get_stack_pointer(task, regs);
+
+	__unwind_start(state, task, regs, first_frame);
+}
+
+void unwind_init(void);
+
+/* No HAVE_RETHOOK on UML, so only ftrace_graph needs to be undone. */
+static inline
+unsigned long unwind_recover_ret_addr(struct unwind_state *state,
+				      unsigned long addr, unsigned long *addr_p)
+{
+	return ftrace_graph_ret_addr(state->task, &state->graph_idx,
+				     addr, addr_p);
+}
+
+static inline bool task_on_another_cpu(struct task_struct *task)
+{
+#ifdef CONFIG_SMP
+	return task != current && task->on_cpu;
+#else
+	return false;
+#endif
+}
+
+#else /* !CONFIG_UNWINDER_ORC */
+
+static inline void unwind_init(void) {}
+
+#endif /* CONFIG_UNWINDER_ORC */
 
 #endif /* _ASM_UML_UNWIND_H */
diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index b26e94292fc1..e2ed2be96e4e 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -239,6 +239,7 @@ extern int set_umid(char *name);
 extern char *get_umid(void);
 
 /* signal.c */
+extern unsigned long os_sigreturn_ip;
 extern void timer_set_signal_handler(void);
 extern void set_sigstack(void *sig_stack, int size);
 extern void set_handler(int sig);
diff --git a/arch/um/kernel/Makefile b/arch/um/kernel/Makefile
index be60bc451b3f..1dcea482a03a 100644
--- a/arch/um/kernel/Makefile
+++ b/arch/um/kernel/Makefile
@@ -25,8 +25,11 @@ obj-$(CONFIG_GPROF)	+= gprof_syms.o
 obj-$(CONFIG_OF) += dtb.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
 obj-$(CONFIG_STACKTRACE) += stacktrace.o
+obj-$(CONFIG_UNWINDER_ORC) += unwind_orc.o
 obj-$(CONFIG_SMP) += smp.o
 
+KCOV_INSTRUMENT_unwind_orc.o := n
+
 USER_OBJS := config.o
 
 include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/um/kernel/dyn.lds.S b/arch/um/kernel/dyn.lds.S
index 042e8d7fba0a..8cb28e997c22 100644
--- a/arch/um/kernel/dyn.lds.S
+++ b/arch/um/kernel/dyn.lds.S
@@ -1,5 +1,6 @@
 #include <asm/vmlinux.lds.h>
 #include <asm/page.h>
+#include <asm/orc_lookup.h>
 
 OUTPUT_FORMAT(ELF_FORMAT)
 OUTPUT_ARCH(ELF_ARCH)
diff --git a/arch/um/kernel/skas/Makefile b/arch/um/kernel/skas/Makefile
index 3384be42691f..467e64c1a820 100644
--- a/arch/um/kernel/skas/Makefile
+++ b/arch/um/kernel/skas/Makefile
@@ -46,5 +46,7 @@ CFLAGS_stub_exe.o += $(call cc-option, -ftrivial-auto-var-init=uninitialized)
 
 UNPROFILE_OBJS := stub.o stub_exe.o
 KCOV_INSTRUMENT := n
+# not really linked as code and shouldn't have ORC data created
+OBJECT_FILES_NON_STANDARD_stub.o := y
 
 include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/um/kernel/stacktrace.c b/arch/um/kernel/stacktrace.c
index fd3b61b3d4d2..d4489b4c191b 100644
--- a/arch/um/kernel/stacktrace.c
+++ b/arch/um/kernel/stacktrace.c
@@ -12,7 +12,27 @@
 #include <linux/module.h>
 #include <linux/uaccess.h>
 #include <asm/stacktrace.h>
+#include <asm/unwind.h>
 
+#ifdef CONFIG_UNWINDER_ORC
+void dump_trace(struct task_struct *tsk,
+		const struct stacktrace_ops *ops,
+		void *data)
+{
+	struct pt_regs *segv_regs = tsk == current ? tsk->thread.segv_regs : NULL;
+	struct unwind_state state;
+	unsigned long addr;
+
+	for (unwind_start(&state, tsk, segv_regs, NULL);
+	     !unwind_done(&state); unwind_next_frame(&state)) {
+		addr = unwind_get_return_address(&state);
+		/* e.g. a signal interrupted libc, keep going with the fallback */
+		if (!addr)
+			continue;
+		ops->address(data, addr, !unwind_error(&state));
+	}
+}
+#else
 void dump_trace(struct task_struct *tsk,
 		const struct stacktrace_ops *ops,
 		void *data)
@@ -40,6 +60,7 @@ void dump_trace(struct task_struct *tsk,
 		sp++;
 	}
 }
+#endif
 
 static void save_addr(void *data, unsigned long address, int reliable)
 {
diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c
index e4ee693961e4..2f9ae739d182 100644
--- a/arch/um/kernel/um_arch.c
+++ b/arch/um/kernel/um_arch.c
@@ -26,6 +26,7 @@
 #include <asm/sections.h>
 #include <asm/setup.h>
 #include <asm/text-patching.h>
+#include <asm/unwind.h>
 #include <as-layout.h>
 #include <arch.h>
 #include <init.h>
@@ -422,6 +423,8 @@ void __init setup_arch(char **cmdline_p)
 		add_bootloader_randomness(rng_seed, sizeof(rng_seed));
 		memzero_explicit(rng_seed, sizeof(rng_seed));
 	}
+
+	unwind_init();
 }
 
 void __init arch_cpu_finalize_init(void)
diff --git a/arch/um/kernel/uml.lds.S b/arch/um/kernel/uml.lds.S
index ac53e0fa9efe..a73bb3782aa7 100644
--- a/arch/um/kernel/uml.lds.S
+++ b/arch/um/kernel/uml.lds.S
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #include <asm/vmlinux.lds.h>
 #include <asm/page.h>
+#include <asm/orc_lookup.h>
 
 OUTPUT_FORMAT(ELF_FORMAT)
 OUTPUT_ARCH(ELF_ARCH)
diff --git a/arch/um/kernel/unwind_orc.c b/arch/um/kernel/unwind_orc.c
new file mode 100644
index 000000000000..e1883ed4e07f
--- /dev/null
+++ b/arch/um/kernel/unwind_orc.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/objtool.h>
+#include <linux/module.h>
+#include <linux/orc.h>
+#include <linux/bpf.h>
+#include <asm/ptrace.h>
+#include <asm/stacktrace.h>
+#include <asm/unwind.h>
+#include <asm/orc_types.h>
+#include <asm/orc_lookup.h>
+#include <asm/orc_header.h>
+#include <generated/user_constants.h>
+#include <os.h>
+
+ORC_HEADER;
+
+#define orc_warn(fmt, ...) \
+	printk_deferred_once(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__)
+
+#define orc_warn_current(args...)					\
+({									\
+	static bool dumped_before;					\
+	if (state->task == current && !state->error) {			\
+		orc_warn(args);						\
+		if (unwind_debug && !dumped_before) {			\
+			dumped_before = true;				\
+			unwind_dump(state);				\
+		}							\
+	}								\
+})
+
+extern int __start_orc_unwind_ip[];
+extern int __stop_orc_unwind_ip[];
+extern struct orc_entry __start_orc_unwind[];
+extern struct orc_entry __stop_orc_unwind[];
+
+static bool orc_init __ro_after_init;
+static bool unwind_debug __ro_after_init;
+static unsigned int lookup_num_blocks __ro_after_init;
+
+static int __init unwind_debug_cmdline(char *str)
+{
+	unwind_debug = true;
+
+	return 0;
+}
+early_param("unwind_debug", unwind_debug_cmdline);
+
+static void unwind_dump(struct unwind_state *state)
+{
+	static bool dumped_before;
+	unsigned long word, *sp;
+	struct stack_info stack_info = {0};
+	unsigned long visit_mask = 0;
+
+	if (dumped_before)
+		return;
+
+	dumped_before = true;
+
+	printk_deferred("unwind stack type:%d next_sp:%p mask:0x%lx graph_idx:%d\n",
+			state->stack_info.type, state->stack_info.next_sp,
+			state->stack_mask, state->graph_idx);
+
+	for (sp = __builtin_frame_address(0); sp;
+	     sp = PTR_ALIGN(stack_info.next_sp, sizeof(long))) {
+		if (get_stack_info(sp, state->task, &stack_info, &visit_mask))
+			break;
+
+		for (; sp < stack_info.end; sp++) {
+			word = READ_ONCE_NOCHECK(*sp);
+
+			printk_deferred("%0*lx: %0*lx (%pB)\n", BITS_PER_LONG / 4,
+					(unsigned long)sp, BITS_PER_LONG / 4,
+					word, (void *)word);
+		}
+	}
+}
+
+/* Fake frame pointer entry -- used as a fallback for generated code */
+static struct orc_entry orc_fp_entry = {
+	.type		= ORC_TYPE_CALL,
+	.sp_reg		= ORC_REG_BP,
+	.sp_offset	= 16,
+	.bp_reg		= ORC_REG_PREV_SP,
+	.bp_offset	= -16,
+};
+
+static struct orc_entry *orc_bpf_find(unsigned long ip)
+{
+#ifdef CONFIG_BPF_JIT
+	if (bpf_has_frame_pointer(ip))
+		return &orc_fp_entry;
+#endif
+
+	return NULL;
+}
+
+/*
+ * If we crash with IP==0, the last successfully executed instruction
+ * was probably an indirect function call with a NULL function pointer,
+ * and we don't have unwind information for NULL.
+ * This hardcoded ORC entry for IP==0 allows us to unwind from a NULL function
+ * pointer into its parent and then continue normally from there.
+ */
+static struct orc_entry null_orc_entry = {
+	.sp_offset = sizeof(long),
+	.sp_reg = ORC_REG_SP,
+	.bp_reg = ORC_REG_UNDEFINED,
+	.type = ORC_TYPE_CALL
+};
+
+static struct orc_entry *orc_find(unsigned long ip)
+{
+	struct orc_entry *orc;
+
+	if (ip == 0)
+		return &null_orc_entry;
+
+	/* For non-init vmlinux addresses, use the fast lookup table: */
+	if (ip >= LOOKUP_START_IP && ip < LOOKUP_STOP_IP) {
+		unsigned int idx, start, stop;
+
+		idx = (ip - LOOKUP_START_IP) / LOOKUP_BLOCK_SIZE;
+
+		if (unlikely((idx >= lookup_num_blocks - 1))) {
+			orc_warn("bad lookup idx: idx=%u num=%u ip=%pB\n",
+				 idx, lookup_num_blocks, (void *)ip);
+			return NULL;
+		}
+
+		start = orc_lookup[idx];
+		stop = orc_lookup[idx + 1] + 1;
+
+		if (unlikely((__start_orc_unwind + start >= __stop_orc_unwind) ||
+			     (__start_orc_unwind + stop > __stop_orc_unwind))) {
+			orc_warn("bad lookup value: idx=%u num=%u start=%u stop=%u ip=%pB\n",
+				 idx, lookup_num_blocks, start, stop, (void *)ip);
+			return NULL;
+		}
+
+		return __orc_find(__start_orc_unwind_ip + start,
+				  __start_orc_unwind + start, stop - start, ip);
+	}
+
+	/* vmlinux .init slow lookup: */
+	if (is_kernel_inittext(ip))
+		return __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
+				  __stop_orc_unwind_ip - __start_orc_unwind_ip, ip);
+
+	/* Module lookup: */
+	orc = orc_module_find(ip);
+	if (orc)
+		return orc;
+
+	/* BPF lookup: */
+	return orc_bpf_find(ip);
+}
+
+void __init unwind_init(void)
+{
+	size_t orc_ip_size = (void *)__stop_orc_unwind_ip - (void *)__start_orc_unwind_ip;
+	size_t orc_size = (void *)__stop_orc_unwind - (void *)__start_orc_unwind;
+	size_t num_entries = orc_ip_size / sizeof(int);
+	struct orc_entry *orc;
+	int i;
+
+	if (!num_entries || orc_ip_size % sizeof(int) != 0 ||
+	    orc_size % sizeof(struct orc_entry) != 0 ||
+	    num_entries != orc_size / sizeof(struct orc_entry)) {
+		orc_warn("Bad or missing .orc_unwind table.  Disabling unwinder.\n");
+		return;
+	}
+
+	/*
+	 * Note, the orc_unwind and orc_unwind_ip tables were already
+	 * sorted at build time via the 'sorttable' tool.
+	 * It's ready for binary search straight away, no need to sort it.
+	 */
+
+	/* Initialize the fast lookup table: */
+	lookup_num_blocks = orc_lookup_end - orc_lookup;
+	for (i = 0; i < lookup_num_blocks - 1; i++) {
+		orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
+				 num_entries,
+				 LOOKUP_START_IP + (LOOKUP_BLOCK_SIZE * i));
+		if (!orc) {
+			orc_warn("Corrupt .orc_unwind table.  Disabling unwinder.\n");
+			return;
+		}
+
+		orc_lookup[i] = orc - __start_orc_unwind;
+	}
+
+	/* Initialize the ending block: */
+	orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind, num_entries,
+			 LOOKUP_STOP_IP);
+	if (!orc) {
+		orc_warn("Corrupt .orc_unwind table.  Disabling unwinder.\n");
+		return;
+	}
+	orc_lookup[lookup_num_blocks - 1] = orc - __start_orc_unwind;
+
+	orc_init = true;
+}
+
+unsigned long unwind_get_return_address(struct unwind_state *state)
+{
+	if (unwind_done(state))
+		return 0;
+
+	return __kernel_text_address(state->ip) ? state->ip : 0;
+}
+EXPORT_SYMBOL_GPL(unwind_get_return_address);
+
+static bool stack_access_ok(struct unwind_state *state, unsigned long _addr,
+			    size_t len)
+{
+	struct stack_info *info = &state->stack_info;
+	void *addr = (void *)_addr;
+
+	if (on_stack(info, addr, len))
+		return true;
+
+	return !get_stack_info(addr, state->task, info, &state->stack_mask) &&
+		on_stack(info, addr, len);
+}
+
+static bool deref_stack_reg(struct unwind_state *state, unsigned long addr,
+			    unsigned long *val)
+{
+	if (!stack_access_ok(state, addr, sizeof(long)))
+		return false;
+
+	*val = READ_ONCE_NOCHECK(*(unsigned long *)addr);
+	return true;
+}
+
+bool unwind_next_frame(struct unwind_state *state)
+{
+	unsigned long ip_p, sp, orig_ip = state->ip, prev_sp = state->sp;
+	enum stack_type prev_type = state->stack_info.type;
+	struct orc_entry *orc;
+	bool indirect = false;
+
+	if (unwind_done(state))
+		return false;
+
+	/* Don't let modules unload while we're reading their ORC data. */
+	guard(rcu)();
+
+	/* End-of-stack check for user tasks: */
+	if (state->regs && user_mode(state->regs))
+		goto the_end;
+
+	/*
+	 * Find the orc_entry associated with the text address.
+	 *
+	 * For a call frame (as opposed to a signal frame), state->ip points to
+	 * the instruction after the call.  That instruction's stack layout
+	 * could be different from the call instruction's layout, for example
+	 * if the call was to a noreturn function.  So get the ORC data for the
+	 * call instruction itself.
+	 */
+	orc = orc_find(state->signal ? state->ip : state->ip - 1);
+	if (!orc) {
+		/*
+		 * As a fallback, try to assume this code uses a frame pointer.
+		 * This is just a guess, so the rest of the unwind is no longer
+		 * considered reliable.
+		 */
+		orc = &orc_fp_entry;
+		state->error = true;
+	} else {
+		if (orc->type == ORC_TYPE_UNDEFINED)
+			goto err;
+
+		if (orc->type == ORC_TYPE_END_OF_STACK)
+			goto the_end;
+	}
+
+	state->signal = orc->signal;
+
+	/* Find the previous frame's stack: */
+	switch (orc->sp_reg) {
+	case ORC_REG_SP:
+		sp = state->sp + orc->sp_offset;
+		break;
+
+	case ORC_REG_BP:
+		sp = state->bp + orc->sp_offset;
+		break;
+
+	case ORC_REG_SP_INDIRECT:
+		sp = state->sp;
+		indirect = true;
+		break;
+
+	case ORC_REG_BP_INDIRECT:
+		sp = state->bp + orc->sp_offset;
+		indirect = true;
+		break;
+
+	default:
+		/* thee rest are only used in x86 asm code, not in UML */
+		orc_warn("unsupported SP base reg %d at %pB\n",
+			 orc->sp_reg, (void *)state->ip);
+		goto err;
+	}
+
+	if (indirect) {
+		if (!deref_stack_reg(state, sp, &sp))
+			goto err;
+
+		if (orc->sp_reg == ORC_REG_SP_INDIRECT)
+			sp += orc->sp_offset;
+	}
+
+	/* Find IP and SP: */
+	switch (orc->type) {
+	case ORC_TYPE_CALL:
+		ip_p = sp - sizeof(long);
+
+		if (!deref_stack_reg(state, ip_p, &state->ip))
+			goto err;
+
+		state->ip = unwind_recover_ret_addr(state, state->ip,
+						    (unsigned long *)ip_p);
+		state->sp = sp;
+		state->regs = NULL;
+		break;
+
+	default:
+		/* also only in x86 asm code */
+		orc_warn("unsupported .orc_unwind entry type %d at %pB\n",
+			 orc->type, (void *)orig_ip);
+		goto err;
+	}
+
+	/* Find BP: */
+	switch (orc->bp_reg) {
+	case ORC_REG_UNDEFINED:
+		if (state->regs)
+			state->bp = PT_REGS_BP(state->regs);
+		break;
+
+	case ORC_REG_PREV_SP:
+		if (!deref_stack_reg(state, sp + orc->bp_offset, &state->bp))
+			goto err;
+		break;
+
+	case ORC_REG_BP:
+		if (!deref_stack_reg(state, state->bp + orc->bp_offset, &state->bp))
+			goto err;
+		break;
+
+	default:
+		orc_warn("unknown BP base reg %d for ip %pB\n",
+			 orc->bp_reg, (void *)orig_ip);
+		goto err;
+	}
+
+	/*
+	 * If we find sigreturn then we're unwinding across a single, which
+	 * means the ucontext of the interrupted code is on the stack too.
+	 */
+	if (os_sigreturn_ip && state->ip == os_sigreturn_ip) {
+		unsigned long uc = state->sp;
+
+		if (!deref_stack_reg(state, uc + HOST_UC_IP, &state->ip) ||
+		    !deref_stack_reg(state, uc + HOST_UC_SP, &state->sp) ||
+		    !deref_stack_reg(state, uc + HOST_UC_BP, &state->bp))
+			goto err;
+		state->signal = true;
+	}
+
+	/* Prevent a recursive loop due to bad ORC data: */
+	if (state->stack_info.type == prev_type &&
+	    on_stack(&state->stack_info, (void *)state->sp, sizeof(long)) &&
+	    state->sp <= prev_sp) {
+		orc_warn_current("stack going in the wrong direction? at %pB\n",
+				 (void *)orig_ip);
+		goto err;
+	}
+
+	return true;
+
+err:
+	state->error = true;
+
+the_end:
+	state->stack_info.type = STACK_TYPE_UNKNOWN;
+	return false;
+}
+EXPORT_SYMBOL_GPL(unwind_next_frame);
+
+void __unwind_start(struct unwind_state *state, struct task_struct *task,
+		    struct pt_regs *regs, unsigned long *first_frame)
+{
+	memset(state, 0, sizeof(*state));
+	state->task = task;
+
+	if (!orc_init)
+		goto err;
+
+	/*
+	 * Refuse to unwind the stack of a task while it's executing on another
+	 * CPU.  This check is racy, but that's ok: the unwinder has other
+	 * checks to prevent it from going off the rails.
+	 */
+	if (task_on_another_cpu(task))
+		goto err;
+
+	if (regs) {
+		if (user_mode(regs))
+			goto the_end;
+
+		state->ip = PT_REGS_IP(regs);
+		state->sp = PT_REGS_SP(regs);
+		state->bp = PT_REGS_BP(regs);
+		state->regs = regs;
+		state->signal = true;
+	} else if (task == current) {
+		/* all three must come from the same instruction */
+		asm volatile("lea (%%rip), %0\n\t"
+			     "mov %%rsp, %1\n\t"
+			     "mov %%rbp, %2\n\t"
+			     : "=r" (state->ip), "=r" (state->sp),
+			       "=r" (state->bp));
+	} else {
+		/* registers saved by switch_threads() when it was switched out */
+		state->ip = KSTK_EIP(task);
+		state->sp = KSTK_ESP(task);
+		state->bp = KSTK_EBP(task);
+	}
+
+	if (get_stack_info((unsigned long *)state->sp, state->task,
+			   &state->stack_info, &state->stack_mask)) {
+		/*
+		 * We weren't on a valid stack.  It's possible that
+		 * we overflowed a valid stack into a guard page.
+		 * See if the next page up is valid so that we can
+		 * generate some kind of backtrace if this happens.
+		 */
+		void *next_page = (void *)PAGE_ALIGN((unsigned long)state->sp);
+
+		state->error = true;
+		if (get_stack_info(next_page, state->task, &state->stack_info,
+				   &state->stack_mask))
+			return;
+	}
+
+	/*
+	 * The caller can provide the address of the first frame directly
+	 * (first_frame) or indirectly (regs->sp) to indicate which stack frame
+	 * to start unwinding at.  Skip ahead until we reach it.
+	 */
+
+	/* When starting from regs, skip the regs frame: */
+	if (regs) {
+		unwind_next_frame(state);
+		return;
+	}
+
+	/* Otherwise, skip ahead to the user-specified starting frame: */
+	while (!unwind_done(state) &&
+	       (!on_stack(&state->stack_info, first_frame, sizeof(long)) ||
+			state->sp <= (unsigned long)first_frame))
+		unwind_next_frame(state);
+
+	return;
+
+err:
+	state->error = true;
+the_end:
+	state->stack_info.type = STACK_TYPE_UNKNOWN;
+}
+EXPORT_SYMBOL_GPL(__unwind_start);
diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
index 6c993bc8c78e..687b8a3a8ba9 100644
--- a/arch/um/os-Linux/signal.c
+++ b/arch/um/os-Linux/signal.c
@@ -212,6 +212,8 @@ static void hard_handler(int sig, siginfo_t *si, void *p)
 	errno = save_errno;
 }
 
+unsigned long os_sigreturn_ip;
+
 void set_handler(int sig)
 {
 	struct sigaction action;
@@ -239,6 +241,10 @@ void set_handler(int sig)
 	if (sigaction(sig, &action, NULL) < 0)
 		panic("sigaction failed - errno = %d\n", errno);
 
+	/* libc installs its own restorer, find it for the unwinder */
+	if (!os_sigreturn_ip && !sigaction(sig, NULL, &action))
+		os_sigreturn_ip = (unsigned long)action.sa_restorer;
+
 	sigemptyset(&sig_mask);
 	sigaddset(&sig_mask, sig);
 	if (sigprocmask(SIG_UNBLOCK, &sig_mask, NULL) < 0)
diff --git a/arch/x86/um/user-offsets.c b/arch/x86/um/user-offsets.c
index d6e1cd9956bf..003bcf6eeea3 100644
--- a/arch/x86/um/user-offsets.c
+++ b/arch/x86/um/user-offsets.c
@@ -66,6 +66,10 @@ void foo(void)
 
 	DEFINE_LONGS(HOST_IP, RIP);
 	DEFINE_LONGS(HOST_SP, RSP);
+
+	DEFINE(HOST_UC_IP, offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP]));
+	DEFINE(HOST_UC_SP, offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP]));
+	DEFINE(HOST_UC_BP, offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP]));
 #endif
 
 	DEFINE(UM_FRAME_SIZE, sizeof(struct user_regs_struct));
diff --git a/scripts/Makefile b/scripts/Makefile
index 3434a82a119f..8fbbc77c3860 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -45,6 +45,10 @@ endif
 ifeq ($(ARCH),loongarch)
 SRCARCH := loongarch
 endif
+# UML runs host code, and only supports x86 as the host arch
+ifeq ($(ARCH),um)
+SRCARCH := x86
+endif
 HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(SRCARCH)/include
 HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED
 endif
diff --git a/tools/scripts/Makefile.arch b/tools/scripts/Makefile.arch
index ed5f008d400e..d13c22be53a8 100644
--- a/tools/scripts/Makefile.arch
+++ b/tools/scripts/Makefile.arch
@@ -38,6 +38,11 @@ ifeq ($(ARCH),loongarch64)
        SRCARCH := loongarch
 endif
 
+# UML runs host code, and only supports x86 as the host arch
+ifeq ($(ARCH),um)
+       SRCARCH := x86
+endif
+
 # Probe for __LP64__ only when the compiler is installed: this runs at
 # parse time for every target, including ones that never compile, e.g.
 # install-build-deps, and would otherwise spew "gcc: not found" when the
-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/4] s390: remove unwind_module_init()
  2026-09-24 12:35 ` [PATCH 1/4] s390: remove unwind_module_init() Johannes Berg
@ 2026-09-24 12:48   ` Heiko Carstens
  0 siblings, 0 replies; 6+ messages in thread
From: Heiko Carstens @ 2026-09-24 12:48 UTC (permalink / raw)
  To: Johannes Berg
  Cc: linux-kernel, loongarch, linux-um, linux-s390, Huacai Chen,
	Josh Poimboeuf, Peter Zijlstra, Johannes Berg

On Thu, Sep 24, 2026 at 02:35:50PM +0200, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
> 
> This is dead code, but will likely conflict with
> the ORC unwinder unification.
> 
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
>  arch/s390/include/asm/unwind.h | 3 ---
>  1 file changed, 3 deletions(-)
> 
> diff --git a/arch/s390/include/asm/unwind.h b/arch/s390/include/asm/unwind.h
> index b8ecf04e3468..021056d5982c 100644
> --- a/arch/s390/include/asm/unwind.h
> +++ b/arch/s390/include/asm/unwind.h
> @@ -91,8 +91,5 @@ static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)
>  	     unwind_next_frame(state))
>  
>  static inline void unwind_init(void) {}
> -static inline void unwind_module_init(struct module *mod, void *orc_ip,
> -				      size_t orc_ip_size, void *orc,
> -				      size_t orc_size) {}

This probably went upstream by mistake a couple of years ago.

Acked-by: Heiko Carstens <hca@linux.ibm.com>

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-09-24 12:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 12:35 [PATCH 0/4] ORC unwinder cleanup & UML support Johannes Berg
2026-09-24 12:35 ` [PATCH 1/4] s390: remove unwind_module_init() Johannes Berg
2026-09-24 12:48   ` Heiko Carstens
2026-09-24 12:35 ` [PATCH 2/4] ORC: pull out module handling from x86/loongarch Johannes Berg
2026-09-24 12:35 ` [PATCH 3/4] ORC: unify orc_hash.h generation Johannes Berg
2026-09-24 12:35 ` [PATCH 4/4] um: ORC unwinder support Johannes Berg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®