[PATCH v15 18/20] unwind_user/sframe: Duplicate registered .sframe section data on clone/fork
From: Jens Remus
Date: Wed May 20 2026 - 11:48:08 EST
When duplicating a process' virtual memory mappings also duplicate all
of its registered .sframe sections stored in the per-mm maple tree to
enable stacktracing using sframe of the child process.
Signed-off-by: Jens Remus <jremus@xxxxxxxxxxxxx>
---
include/linux/sframe.h | 5 ++++
kernel/unwind/sframe.c | 48 ++++++++++++++++++++++++++++++++++++
kernel/unwind/sframe_debug.h | 7 ++++++
mm/mmap.c | 9 +++++++
4 files changed, 69 insertions(+)
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index b79c5ec09229..91889b4fe3dd 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -28,6 +28,7 @@ struct sframe_section {
};
#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
+extern int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm);
extern void sframe_free_mm(struct mm_struct *mm);
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
@@ -45,6 +46,10 @@ static inline bool current_has_sframe(void)
#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
#define INIT_MM_SFRAME
+static inline int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+ return 0;
+}
static inline void sframe_free_mm(struct mm_struct *mm) {}
static inline int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end)
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index ec8318977a2e..0661090dd5bc 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -886,6 +886,54 @@ int sframe_remove_section(unsigned long sframe_start)
return 0;
}
+static void __sframe_dup_section(struct sframe_section *sec, struct sframe_section *oldsec)
+{
+ sec->sframe_start = oldsec->sframe_start;
+ sec->sframe_end = oldsec->sframe_end;
+ sec->text_start = oldsec->text_start;
+ sec->text_end = oldsec->text_end;
+
+ sec->fdes_start = oldsec->fdes_start;
+ sec->fres_start = oldsec->fres_start;
+ sec->fres_end = oldsec->fres_end;
+ sec->num_fdes = oldsec->num_fdes;
+
+ sec->ra_off = oldsec->ra_off;
+ sec->fp_off = oldsec->fp_off;
+
+ dbg_dup(sec, oldsec);
+}
+
+int sframe_dup_mm(struct mm_struct *mm, struct mm_struct *oldmm)
+{
+ struct sframe_section *sec, *oldsec;
+ unsigned long index = 0;
+ int ret;
+
+ guard(srcu)(&sframe_srcu);
+
+ mt_for_each(&oldmm->sframe_mt, oldsec, index, ULONG_MAX) {
+ sec = kzalloc(sizeof(*sec), GFP_KERNEL_ACCOUNT);
+ if (!sec)
+ return -ENOMEM;
+
+ __sframe_dup_section(sec, oldsec);
+
+ ret = mtree_insert_range(&mm->sframe_mt,
+ sec->text_start,
+ sec->text_end - 1,
+ sec, GFP_KERNEL_ACCOUNT);
+ if (ret)
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ free_section(sec);
+ return ret;
+}
+
void sframe_free_mm(struct mm_struct *mm)
{
struct sframe_section *sec;
diff --git a/kernel/unwind/sframe_debug.h b/kernel/unwind/sframe_debug.h
index a63e75cccc70..2503972155e8 100644
--- a/kernel/unwind/sframe_debug.h
+++ b/kernel/unwind/sframe_debug.h
@@ -48,6 +48,12 @@ static inline void dbg_init(struct sframe_section *sec)
sec->filename = kstrdup("(anonymous)", GFP_KERNEL_ACCOUNT);
}
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section *oldsec)
+{
+ if (oldsec->filename)
+ sec->filename = kstrdup(oldsec->filename, GFP_KERNEL_ACCOUNT);
+}
+
static inline void dbg_free(struct sframe_section *sec)
{
kfree(sec->filename);
@@ -61,6 +67,7 @@ static inline void dbg_free(struct sframe_section *sec)
static inline void dbg_print_header(struct sframe_section *sec) {}
static inline void dbg_init(struct sframe_section *sec) {}
+static inline void dbg_dup(struct sframe_section *sec, struct sframe_section *oldsec) {}
static inline void dbg_free(struct sframe_section *sec) {}
#endif /* !CONFIG_DYNAMIC_DEBUG */
diff --git a/mm/mmap.c b/mm/mmap.c
index 5754d1c36462..250e8bea73ef 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -48,6 +48,7 @@
#include <linux/sched/mm.h>
#include <linux/ksm.h>
#include <linux/memfd.h>
+#include <linux/sframe.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
@@ -1846,6 +1847,11 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
}
/* a new mm has just been created */
retval = arch_dup_mmap(oldmm, mm);
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+ if (!retval) {
+ retval = sframe_dup_mm(mm, oldmm);
+ }
+#endif
loop_out:
vma_iter_free(&vmi);
if (!retval) {
@@ -1893,6 +1899,9 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
vm_unacct_memory(charge);
}
__mt_destroy(&mm->mm_mt);
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+ sframe_free_mm(mm);
+#endif
/*
* The mm_struct is going to exit, but the locks will be dropped
* first. Set the mm_struct as unstable is advisable as it is
--
2.51.0