Kexec Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension
@ 2026-08-20 23:31 Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 1/9] Do not call extensions for tail pages Stephen Brennan
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Hello all,

This is v2 of my series of improvements for makedumpfile extensions.
v1 can be found here: https://lore.kernel.org/kexec/20260714004550.3698175-1-stephen.s.brennan@oracle.com/

There have only been a few changes from v1:
- Updated extension/sample.c to use the new API in patch 3.
- Reordered and reworded the extension retained pages statistic per Tao's
  suggestion, so that it is more clear.
- Included a more complete commit message in patch 7.
- In patch 1, removed the "nr_pages = 1" when extensions return PG_EXCLUDE.
- Significantly reworded patch 1's commit message to explain the different cases
  and the alternative approach.

Notable improvements in the "userstack.c" extension are:
- Leverage "detect_cycle.h" API to apply Brent's algorithm for cycle detection
  in linked list iteration, to avoid infinite loops while iterating tasks &
  threads on corrupt vmcores.
- Set a limit to the number of retained anon_vma entries, to avoid hitting OOM
  issues in case of a huge vmcore or a bug in the extension.

As discussed on v1, the final two patches containing the extensions are not to
be merged. I will publish them in a Github repository as soon as I can get it
arranged with my employer. They are more for demonstration of the API and
continued sharing until the repository is available.

The major discussion on v1 was on patch 4, dealing with how extensions are
called, between:

(a) Extensions are called for all base pages, and their decisions may conflict
with the head page decision.
(b) Extensions are not called for tail pages, and the head page decision is used
instead.

As I explained in my last reply, I believe that (b) is still the best way. It is
less complex than (a). It is also more efficient, because makedumpfile today
skips processing many excluded tail pages, which adds up for large systems with
many huge pages. We do not have any extension which requires any policy more
complex than PG_INCLUDE_HEAD.

- elfheader requires PG_INCLUDE_HEAD
- userstack only uses PG_INCLUDE. I could imagine potentially wanting to include
  just the sub-pages of compound pages that are actually used by the stack. But
  it hasn't been necessary so far.
- amdgpu buffers may be compound pages but if they are, then the entire page
  would be excluded.

I do have an implementation of (a) as I showed in the thread. If I'm wrong here,
I can always fall back to that.

Thank you,
Stephen


Stephen Brennan (9):
  Do not call extensions for tail pages
  Honor CFLAGS in extension/Makefile
  Share page information with extension callbacks
  Introduce a stat for pages retained by extension
  Move page checks into makedumpfile.h
  Simplify arguments for page checks
  Add PG_INCLUDE_HEAD extension return status
  Add userstack extension
  Add elfheader extension

 extension.c             |  10 +-
 extension.h             |  10 +-
 extensions/Makefile     |   8 +-
 extensions/elfheader.c  |  93 +++++++++++
 extensions/list.h       | 106 ++++++++++++
 extensions/sample.c     |   2 +-
 extensions/userstack.c  | 351 ++++++++++++++++++++++++++++++++++++++++
 extensions/vma_mtree.c  | 140 ++++++++++++++++
 extensions/vma_mtree.h  |   7 +
 extensions/vma_rbtree.c |  56 +++++++
 extensions/vma_rbtree.h |  12 ++
 makedumpfile.c          | 189 +++++++++-------------
 makedumpfile.h          |  78 ++++++++-
 13 files changed, 931 insertions(+), 131 deletions(-)
 create mode 100644 extensions/elfheader.c
 create mode 100644 extensions/list.h
 create mode 100644 extensions/userstack.c
 create mode 100644 extensions/vma_mtree.c
 create mode 100644 extensions/vma_mtree.h
 create mode 100644 extensions/vma_rbtree.c
 create mode 100644 extensions/vma_rbtree.h

-- 
2.52.0



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

* [PATCH makedumpfile v2 1/9] Do not call extensions for tail pages
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 2/9] Honor CFLAGS in extension/Makefile Stephen Brennan
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Currently, extensions are called for every page which the loop does not
skip over. However, there is no case where the decision an extension
makes on a tail page will affect whether the tail page is included in
the dump:

  (1) If a compound head is excluded by the dump level (not an
  extension), we will create an exclusion in the 2nd bitmap and skip
  ahead in the loop, so we won't even call the extensions on tail pages.
  Even if we did not skip ahead, we have no consideration for removing
  the exclusion set by the compound head, so extensions would have no
  way to include specific sub-pages.

  (2) If a compound head is excluded by an extension returning
  PG_EXCLUDE, then we only exclude one page, and we do not skip forward
  in the loop. As a result, we will call extensions on each tail page,
  but regardless of the extension decision, the pages will be included,
  because we hit the "(compound_head & 1)" check before any exclusion
  can be done.

  (3) If a compound head is included by makedumpfile, we do not skip
  ahead in the loop, and so we do call extensions for each tail page.
  However, just as in (2), regardless of the decision of the extension
  we will include the tail page.

To sum up as succinctly as possible: while makedumpfile itself tries to
make filter decisions based on the head page, in some cases it calls
extensions on tail pages, and yet it disregards their decisions on them.
We can fix this in one of two ways:

  (a) Call extensions for every page and allow them to make any
  decision, including different decisions on head & tail pages.

  (b) Do not call extensions on tail pages at all; just apply the
  decision from the head page.

Since option (b) is the most similar to our existing behavior, it is the
simplest to implement. It is also more efficient, because it allows us
to skip compound tails, which is especially helpful for systems with
many excluded huge pages. Option (a) provides more flexibility for
extensions, but as of now there is no extension use case for allowing
arbitrary decisions on head & tail pages.

Thus, let's ensure we don't call extensions for any tail pages. Further,
let's treat PG_EXCLUDE as an exclusion of the entire compound page,
so that extension decisions have the same scope as any other filter
decision.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 makedumpfile.c | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)

diff --git a/makedumpfile.c b/makedumpfile.c
index 46d9ac7..1aafd4f 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -6543,14 +6543,6 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 			pfn_read_end   = pfn + pfn_mm - 1;
 		}
 
-		/*
-		 * Include pages that specified by user via
-		 * makedumpfile extensions
-		 */
-		filter_pg = run_extension_callback(pfn, pcache);
-		if (filter_pg == PG_INCLUDE)
-			continue;
-
 		flags   = ULONG(pcache + OFFSET(page.flags));
 		_count  = UINT(pcache + OFFSET(page._refcount));
 		mapping = ULONG(pcache + OFFSET(page.mapping));
@@ -6637,14 +6629,22 @@ check_order:
 		 * Excludable compound tail pages must have already been excluded by
 		 * exclude_range(), don't need to check them here.
 		 */
-		if (compound_head & 1) {
+		if (compound_head & 1)
 			continue;
-		}
+
+		/*
+		 * Include pages that specified by user via
+		 * makedumpfile extensions
+		 */
+		filter_pg = run_extension_callback(pfn, pcache);
+		if (filter_pg == PG_INCLUDE)
+			continue;
+
 		/*
 		 * Exclude the free page managed by a buddy
 		 * Use buddy identification of free pages whether cyclic or not.
 		 */
-		else if ((info->dump_level & DL_EXCLUDE_FREE)
+		if ((info->dump_level & DL_EXCLUDE_FREE)
 		    && info->page_is_buddy
 		    && info->page_is_buddy(flags, _mapcount, private, _count)) {
 			if ((ARRAY_LENGTH(zone.free_area) != NOT_FOUND_STRUCTURE) &&
@@ -6712,7 +6712,6 @@ check_order:
 		 * makedumpfile extensions
 		 */
 		else if (filter_pg == PG_EXCLUDE) {
-			nr_pages = 1;
 			pfn_counter = &pfn_extension;
 		}
 		/*
-- 
2.52.0



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

* [PATCH makedumpfile v2 2/9] Honor CFLAGS in extension/Makefile
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 1/9] Do not call extensions for tail pages Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 3/9] Share page information with extension callbacks Stephen Brennan
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Currently CFLAGS are specified manually, so anything already provided in
the environment or from the calling make process is ignored.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 extensions/Makefile | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/extensions/Makefile b/extensions/Makefile
index c112d56..25e0a07 100644
--- a/extensions/Makefile
+++ b/extensions/Makefile
@@ -3,8 +3,10 @@ CONTRIB_SO := sample.so erase_sample.so
 
 all: $(CONTRIB_SO)
 
+CFLAGS += -fPIC -shared -Wl,-T,../makedumpfile.ld
+
 $(CONTRIB_SO): %.so: %.c
-	$(CC) -O2 -g -fPIC -shared -Wl,-T,../makedumpfile.ld -o $@ $^
+	$(CC) $(CFLAGS) -o $@ $^
 
 clean:
 	rm -f $(CONTRIB_SO)
-- 
2.52.0



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

* [PATCH makedumpfile v2 3/9] Share page information with extension callbacks
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 1/9] Do not call extensions for tail pages Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 2/9] Honor CFLAGS in extension/Makefile Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 4/9] Introduce a stat for pages retained by extension Stephen Brennan
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

In __exclude_unnecessary_pages(), we extract several fields related
to the page. Some of these, like compound_order and compound_dtor, have
logic specific to the kernel version.

Extensions can, of course, determine these values for themselves, but
it's extra work, and duplicates logic that may need to be updated
frequently with new kernel versions. What's more, if we put all the
values together in a single structure, helpers like isSlab() and others
can be implemented in terms of that structure and shared with the
extensions in order to further simplify their implementation.

With that in mind, group the per-page variables into a structure and
share them with extension callbacks. This breaks the extension API,
but since a release hasn't yet happened, it seems reasonable to do so.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 extension.c         |  8 ++---
 extension.h         |  3 +-
 extensions/sample.c |  2 +-
 makedumpfile.c      | 83 +++++++++++++++++++++++----------------------
 makedumpfile.h      | 16 +++++++++
 5 files changed, 65 insertions(+), 47 deletions(-)

diff --git a/extension.c b/extension.c
index 5188c1f..9b29f0c 100644
--- a/extension.c
+++ b/extension.c
@@ -10,7 +10,7 @@
 #include "kallsyms.h"
 #include "btf_info.h"
 
-typedef int (*callback_fn)(unsigned long, const void *);
+typedef int (*callback_fn)(unsigned long, const void *, const struct pginfo *);
 
 struct extension_handle_cb {
 	void *handle;
@@ -306,14 +306,14 @@ fail:
  * 1) Include the page if anyone says PG_INCLUDE, and
  * 2) Exclude the page if no one says PG_INCLUDE, but one or more say PG_EXCLUDE.
  */
-int run_extension_callback(unsigned long pfn, const void *pcache)
+int run_extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *inf)
 {
 	int result;
 	int ret = PG_UNDECID;
 
 	for (int i = 0; i < handle_cbs_len; i++) {
 		if (handle_cbs[i]->cb) {
-			result = handle_cbs[i]->cb(pfn, pcache);
+			result = handle_cbs[i]->cb(pfn, pcache, inf);
 			if (result == PG_INCLUDE) {
 				ret = result;
 				goto out;
@@ -341,7 +341,7 @@ bool add_extension_opts(char *opt)
 	return false;
 }
 
-int run_extension_callback(unsigned long pfn, const void *pcache)
+int run_extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i)
 {
 	return PG_UNDECID;
 }
diff --git a/extension.h b/extension.h
index ba8d32a..22af9a6 100644
--- a/extension.h
+++ b/extension.h
@@ -2,12 +2,13 @@
 #define _EXTENSION_H
 #include <stdbool.h>
 
+struct pginfo;
 enum {
 	PG_INCLUDE,	// Exntesion will keep the page
 	PG_EXCLUDE,	// Exntesion will discard the page
 	PG_UNDECID,	// Exntesion makes no decision
 };
-int run_extension_callback(unsigned long pfn, const void *pcache);
+int run_extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i);
 void init_extensions(void);
 void cleanup_extensions(void);
 bool add_extension_opts(char *opt);
diff --git a/extensions/sample.c b/extensions/sample.c
index 4bc7f9e..a96898a 100644
--- a/extensions/sample.c
+++ b/extensions/sample.c
@@ -36,7 +36,7 @@ INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, mm_struct, mm_rb);
  * PG_UNDECID to let every page fallbacks to traditinal page-flags
  * check routine or let other extensions make the decision.
  */
-int extension_callback(unsigned long pfn, const void *pcache)
+int extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i)
 {
 	return PG_UNDECID;
 }
diff --git a/makedumpfile.c b/makedumpfile.c
index 1aafd4f..e3a4a6f 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -6466,12 +6466,13 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 	mdf_pfn_t pfn_read_start, pfn_read_end;
 	unsigned char *page_cache;
 	unsigned char *pcache;
-	unsigned int _count, _mapcount = 0, compound_order = 0;
+	struct pginfo i;
 	unsigned int order_offset, dtor_offset;
-	unsigned long flags, mapping, private = 0;
-	unsigned long compound_dtor, compound_head = 0;
 	int filter_pg;
 
+	i._mapcount = i.compound_order = 0;
+	i.private = i.compound_dtor = i.compound_head = 0;
+
 	/*
 	 * If a multi-page exclusion is pending, do it first
 	 */
@@ -6543,21 +6544,21 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 			pfn_read_end   = pfn + pfn_mm - 1;
 		}
 
-		flags   = ULONG(pcache + OFFSET(page.flags));
-		_count  = UINT(pcache + OFFSET(page._refcount));
-		mapping = ULONG(pcache + OFFSET(page.mapping));
+		i.flags   = ULONG(pcache + OFFSET(page.flags));
+		i._count  = UINT(pcache + OFFSET(page._refcount));
+		i.mapping = ULONG(pcache + OFFSET(page.mapping));
 
 		if (OFFSET(page._mapcount) != NOT_FOUND_STRUCTURE)
-			_mapcount = UINT(pcache + OFFSET(page._mapcount));
+			i._mapcount = UINT(pcache + OFFSET(page._mapcount));
 
-		compound_order = 0;
-		compound_dtor = 0;
+		i.compound_order = 0;
+		i.compound_dtor = 0;
 		/*
 		 * The last pfn of the mem_map cache must not be compound head
 		 * page since all compound pages are aligned to its page order
 		 * and PGMM_CACHED is a power of 2.
 		 */
-		if ((index_pg < PGMM_CACHED - 1) && isCompoundHead(flags)) {
+		if ((index_pg < PGMM_CACHED - 1) && isCompoundHead(i.flags)) {
 			unsigned char *addr = pcache + SIZE(page);
 
 			/*
@@ -6567,10 +6568,10 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 			if (NUMBER(PAGE_HUGETLB_MAPCOUNT_VALUE) != NOT_FOUND_NUMBER) {
 				unsigned long _flags_1 = ULONG(addr + OFFSET(page.flags));
 
-				compound_order = _flags_1 & 0xff;
+				i.compound_order = _flags_1 & 0xff;
 
-				if (_mapcount == (int)NUMBER(PAGE_HUGETLB_MAPCOUNT_VALUE))
-					compound_dtor = IS_HUGETLB;
+				if (i._mapcount == (int)NUMBER(PAGE_HUGETLB_MAPCOUNT_VALUE))
+					i.compound_dtor = IS_HUGETLB;
 
 				goto check_order;
 			}
@@ -6582,19 +6583,19 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 			if (NUMBER(PG_hugetlb) != NOT_FOUND_NUMBER) {
 				unsigned long _flags_1 = ULONG(addr + OFFSET(page.flags));
 
-				compound_order = _flags_1 & 0xff;
+				i.compound_order = _flags_1 & 0xff;
 
 				if (_flags_1 & (1UL << NUMBER(PG_hugetlb)))
-					compound_dtor = IS_HUGETLB;
+					i.compound_dtor = IS_HUGETLB;
 
 				goto check_order;
 			}
 
 			if (order_offset) {
 				if (info->kernel_version >= KERNEL_VERSION(4, 16, 0))
-					compound_order = UCHAR(addr + order_offset);
+					i.compound_order = UCHAR(addr + order_offset);
 				else
-					compound_order = USHORT(addr + order_offset);
+					i.compound_order = USHORT(addr + order_offset);
 			}
 
 			if (dtor_offset) {
@@ -6603,40 +6604,40 @@ __exclude_unnecessary_pages(unsigned long mem_map,
 				 * to the ID of it since linux-4.4.
 				 */
 				if (info->kernel_version >= KERNEL_VERSION(4, 16, 0))
-					compound_dtor = UCHAR(addr + dtor_offset);
+					i.compound_dtor = UCHAR(addr + dtor_offset);
 				else if (info->kernel_version >= KERNEL_VERSION(4, 4, 0))
-					compound_dtor = USHORT(addr + dtor_offset);
+					i.compound_dtor = USHORT(addr + dtor_offset);
 				else
-					compound_dtor = ULONG(addr + dtor_offset);
+					i.compound_dtor = ULONG(addr + dtor_offset);
 			}
 check_order:
-			if ((compound_order >= sizeof(unsigned long) * 8)
-			    || ((pfn & ((1UL << compound_order) - 1)) != 0)) {
+			if ((i.compound_order >= sizeof(unsigned long) * 8)
+			    || ((pfn & ((1UL << i.compound_order) - 1)) != 0)) {
 				/* Invalid order */
-				compound_order = 0;
+				i.compound_order = 0;
 			}
 		}
 		if (OFFSET(page.compound_head) != NOT_FOUND_STRUCTURE)
-			compound_head = ULONG(pcache + OFFSET(page.compound_head));
+			i.compound_head = ULONG(pcache + OFFSET(page.compound_head));
 
 		if (OFFSET(page.private) != NOT_FOUND_STRUCTURE)
-			private = ULONG(pcache + OFFSET(page.private));
+			i.private = ULONG(pcache + OFFSET(page.private));
 
-		nr_pages = 1 << compound_order;
+		nr_pages = 1 << i.compound_order;
 		pfn_counter = NULL;
 
 		/*
 		 * Excludable compound tail pages must have already been excluded by
 		 * exclude_range(), don't need to check them here.
 		 */
-		if (compound_head & 1)
+		if (i.compound_head & 1)
 			continue;
 
 		/*
 		 * Include pages that specified by user via
 		 * makedumpfile extensions
 		 */
-		filter_pg = run_extension_callback(pfn, pcache);
+		filter_pg = run_extension_callback(pfn, pcache, &i);
 		if (filter_pg == PG_INCLUDE)
 			continue;
 
@@ -6646,14 +6647,14 @@ check_order:
 		 */
 		if ((info->dump_level & DL_EXCLUDE_FREE)
 		    && info->page_is_buddy
-		    && info->page_is_buddy(flags, _mapcount, private, _count)) {
+		    && info->page_is_buddy(i.flags, i._mapcount, i.private, i._count)) {
 			if ((ARRAY_LENGTH(zone.free_area) != NOT_FOUND_STRUCTURE) &&
-			    (private >= ARRAY_LENGTH(zone.free_area))) {
+			    (i.private >= ARRAY_LENGTH(zone.free_area))) {
 				MSG("WARNING: Invalid free page order: pfn=%llx, order=%lu, max order=%lu\n",
-				    pfn, private, ARRAY_LENGTH(zone.free_area) - 1);
+				    pfn, i.private, ARRAY_LENGTH(zone.free_area) - 1);
 				continue;
 			}
-			nr_pages = 1 << private;
+			nr_pages = 1 << i.private;
 			pfn_counter = &pfn_free;
 		}
 		/*
@@ -6663,7 +6664,7 @@ check_order:
 		 * accepted immediately without being on the list.
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_FREE)
-			&& isUnaccepted(_mapcount)) {
+			&& isUnaccepted(i._mapcount)) {
 			nr_pages = 1 << (ARRAY_LENGTH(zone.free_area) - 1);
 			pfn_counter = &pfn_free;
 		}
@@ -6671,17 +6672,17 @@ check_order:
 		 * Exclude the non-private cache page.
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_CACHE)
-		    && is_cache_page(flags)
-		    && !isPrivate(flags) && !isAnon(mapping, flags, _mapcount)) {
+		    && is_cache_page(i.flags)
+		    && !isPrivate(i.flags) && !isAnon(i.mapping, i.flags, i._mapcount)) {
 			pfn_counter = &pfn_cache;
 		}
 		/*
 		 * Exclude the cache page whether private or non-private.
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_CACHE_PRI)
-		    && is_cache_page(flags)
-		    && !isAnon(mapping, flags, _mapcount)) {
-			if (isPrivate(flags))
+		    && is_cache_page(i.flags)
+		    && !isAnon(i.mapping, i.flags, i._mapcount)) {
+			if (isPrivate(i.flags))
 				pfn_counter = &pfn_cache_private;
 			else
 				pfn_counter = &pfn_cache;
@@ -6692,19 +6693,19 @@ check_order:
 		 *  - hugetlbfs pages
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_USER_DATA)
-			 && (isAnon(mapping, flags, _mapcount) || isHugetlb(compound_dtor))) {
+			 && (isAnon(i.mapping, i.flags, i._mapcount) || isHugetlb(i.compound_dtor))) {
 			pfn_counter = &pfn_user;
 		}
 		/*
 		 * Exclude the hwpoison page.
 		 */
-		else if (isHWPOISON(flags)) {
+		else if (isHWPOISON(i.flags)) {
 			pfn_counter = &pfn_hwpoison;
 		}
 		/*
 		 * Exclude pages that are logically offline.
 		 */
-		else if (isOffline(flags, _mapcount)) {
+		else if (isOffline(i.flags, i._mapcount)) {
 			pfn_counter = &pfn_offline;
 		}
 		/*
diff --git a/makedumpfile.h b/makedumpfile.h
index 4f707c7..87f973d 100644
--- a/makedumpfile.h
+++ b/makedumpfile.h
@@ -1507,6 +1507,22 @@ struct ppc64_vmemmap {
 	unsigned long		virt;
 };
 
+/* Per-page information determined during page filtering which may be useful
+ * to extensions making their decisions */
+struct pginfo {
+	unsigned long flags;
+	unsigned long mapping;
+	/* Present whenever OFFSET(page.private) != NOT_FOUND_STRUCTURE */
+	unsigned long private;
+	unsigned long compound_dtor;
+	/* Present whenever OFFSET(page.compound_head) != NOT_FOUND_STRUCTURE */
+	unsigned long compound_head;
+	unsigned int _count;
+	/* Present whenever OFFSET(page._mapcount) != NOT_FOUND_STRUCTURE */
+	unsigned int _mapcount;
+	unsigned int compound_order;
+};
+
 struct DumpInfo {
 	int32_t		kernel_version;      /* version of first kernel*/
 	struct timeval	timestamp;
-- 
2.52.0



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

* [PATCH makedumpfile v2 4/9] Introduce a stat for pages retained by extension
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (2 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 3/9] Share page information with extension callbacks Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 5/9] Move page checks into makedumpfile.h Stephen Brennan
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Extensions can mark pages to be excluded, but those pages may already be
excluded due to the dump level. We have a statistic to count pages
excluded by extensions. It counts only pages which were excluded because
no other criteria excluded them.

Extensions can mark pages to be retained, but there is no statistic to
count them. Adding a counter to the code as-is would not give us the
value that we care about. Just as above, pages marked for inclusion may
have been included anyway due to the dump-level configuration. The most
useful statistic is the one that tells us how many pages were included
by the extension, which would not have been included otherwise.

Introduce a statistic that counts this amount.  To do so, we have to
skip the short-circuit evaluation when PG_INCLUDE is returned. This
seems like a worthwhile trade-off, since the dump-level checks are all
reasonably efficient.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 makedumpfile.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/makedumpfile.c b/makedumpfile.c
index e3a4a6f..fca42e1 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -106,6 +106,7 @@ mdf_pfn_t pfn_elf_excluded;
 mdf_pfn_t pfn_extension;
 
 mdf_pfn_t num_dumped;
+mdf_pfn_t num_extension_retained;
 
 int retcd = FAILED;	/* return code */
 
@@ -6638,8 +6639,6 @@ check_order:
 		 * makedumpfile extensions
 		 */
 		filter_pg = run_extension_callback(pfn, pcache, &i);
-		if (filter_pg == PG_INCLUDE)
-			continue;
 
 		/*
 		 * Exclude the free page managed by a buddy
@@ -6721,6 +6720,13 @@ check_order:
 		else
 			continue;
 
+		if (filter_pg == PG_INCLUDE) {
+			/* Account pages which would have been excluded, but were
+			 * retained by an extension. */
+			num_extension_retained += nr_pages;
+			continue;
+		}
+
 		/*
 		 * Execute exclusion
 		 */
@@ -8264,6 +8270,7 @@ write_elf_pages_cyclic(struct cache_data *cd_header, struct cache_data *cd_page)
 	if (info->flag_cyclic) {
 		pfn_zero = pfn_cache = pfn_cache_private = 0;
 		pfn_user = pfn_free = pfn_hwpoison = pfn_offline = pfn_extension = 0;
+		num_extension_retained = 0;
 		pfn_memhole = info->max_mapnr;
 	}
 
@@ -9609,6 +9616,7 @@ write_kdump_pages_and_bitmap_cyclic(struct cache_data *cd_header, struct cache_d
 		 */
 		pfn_zero = pfn_cache = pfn_cache_private = 0;
 		pfn_user = pfn_free = pfn_hwpoison = pfn_offline = pfn_extension = 0;
+		num_extension_retained = 0;
 		pfn_memhole = info->max_mapnr;
 
 		/*
@@ -10576,6 +10584,7 @@ print_report(void)
 	REPORT_MSG("    Extension filter pages  : 0x%016llx\n", pfn_extension);
 	REPORT_MSG("  Remaining pages  : 0x%016llx\n",
 	    pfn_original - pfn_excluded);
+	REPORT_MSG("    Extension retain pages  : 0x%016llx\n", num_extension_retained);
 
 	if (info->flag_elf_dumpfile) {
 		REPORT_MSG("     in ELF format : 0x%016llx\n",
-- 
2.52.0



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

* [PATCH makedumpfile v2 5/9] Move page checks into makedumpfile.h
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (3 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 4/9] Introduce a stat for pages retained by extension Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 6/9] Simplify arguments for page checks Stephen Brennan
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Most checks are already macros in makedumpfile.h, but some are simple
helper functions in makedumpfile.c. They are not accessible to
extensions as a result.

It's helpful to keep as much of this logic available to extensions as
possible, so that they do not need to re-implement more than necessary.
Move isHugetlb, isSlab, isOffline, and is_cache_page to the header.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 makedumpfile.c | 57 --------------------------------------------------
 makedumpfile.h | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 57 deletions(-)

diff --git a/makedumpfile.c b/makedumpfile.c
index fca42e1..b04a1e3 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -266,63 +266,6 @@ is_in_same_page(unsigned long vaddr1, unsigned long vaddr2)
 	return FALSE;
 }
 
-/* For Linux 6.6 and later */
-#define IS_HUGETLB	((unsigned long)-1)
-
-static inline int
-isHugetlb(unsigned long dtor)
-{
-	return (dtor == IS_HUGETLB)
-		|| ((NUMBER(HUGETLB_PAGE_DTOR) != NOT_FOUND_NUMBER)
-		   && (NUMBER(HUGETLB_PAGE_DTOR) == dtor))
-		|| ((SYMBOL(free_huge_page) != NOT_FOUND_SYMBOL)
-		   && (SYMBOL(free_huge_page) == dtor));
-}
-
-static inline int
-isSlab(unsigned long flags, unsigned int _mapcount)
-{
-	/* Linux 6.10 and later */
-	if (NUMBER(PAGE_SLAB_MAPCOUNT_VALUE) != NOT_FOUND_NUMBER) {
-		if (_mapcount == (int)NUMBER(PAGE_SLAB_MAPCOUNT_VALUE))
-			return TRUE;
-	}
-
-	return flags & (1UL << NUMBER(PG_slab));
-}
-
-static int
-isOffline(unsigned long flags, unsigned int _mapcount)
-{
-	if (NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE) == NOT_FOUND_NUMBER)
-		return FALSE;
-
-	if (isSlab(flags, _mapcount))
-		return FALSE;
-
-	if (_mapcount == (int)NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE))
-		return TRUE;
-
-	return FALSE;
-}
-
-static int
-is_cache_page(unsigned long flags)
-{
-	if (isLRU(flags))
-		return TRUE;
-
-	/* PG_swapcache is valid only if:
-	 *   a. PG_swapbacked bit is set, or
-	 *   b. PG_swapbacked did not exist (kernels before 4.10-rc1).
-	 */
-	if ((NUMBER(PG_swapbacked) == NOT_FOUND_NUMBER || isSwapBacked(flags))
-	    && isSwapCache(flags))
-		return TRUE;
-
-	return FALSE;
-}
-
 static inline unsigned long
 calculate_len_buf_out(long page_size)
 {
diff --git a/makedumpfile.h b/makedumpfile.h
index 87f973d..3d64677 100644
--- a/makedumpfile.h
+++ b/makedumpfile.h
@@ -2644,6 +2644,63 @@ is_zero_page(unsigned char *buf, long page_size)
 	return TRUE;
 }
 
+/* For Linux 6.6 and later */
+#define IS_HUGETLB	((unsigned long)-1)
+
+static inline int
+isHugetlb(unsigned long dtor)
+{
+	return (dtor == IS_HUGETLB)
+		|| ((NUMBER(HUGETLB_PAGE_DTOR) != NOT_FOUND_NUMBER)
+		   && (NUMBER(HUGETLB_PAGE_DTOR) == dtor))
+		|| ((SYMBOL(free_huge_page) != NOT_FOUND_SYMBOL)
+		   && (SYMBOL(free_huge_page) == dtor));
+}
+
+static inline int
+isSlab(unsigned long flags, unsigned int _mapcount)
+{
+	/* Linux 6.10 and later */
+	if (NUMBER(PAGE_SLAB_MAPCOUNT_VALUE) != NOT_FOUND_NUMBER) {
+		if (_mapcount == (int)NUMBER(PAGE_SLAB_MAPCOUNT_VALUE))
+			return TRUE;
+	}
+
+	return flags & (1UL << NUMBER(PG_slab));
+}
+
+static inline int
+isOffline(unsigned long flags, unsigned int _mapcount)
+{
+	if (NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE) == NOT_FOUND_NUMBER)
+		return FALSE;
+
+	if (isSlab(flags, _mapcount))
+		return FALSE;
+
+	if (_mapcount == (int)NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE))
+		return TRUE;
+
+	return FALSE;
+}
+
+static inline int
+is_cache_page(unsigned long flags)
+{
+	if (isLRU(flags))
+		return TRUE;
+
+	/* PG_swapcache is valid only if:
+	 *   a. PG_swapbacked bit is set, or
+	 *   b. PG_swapbacked did not exist (kernels before 4.10-rc1).
+	 */
+	if ((NUMBER(PG_swapbacked) == NOT_FOUND_NUMBER || isSwapBacked(flags))
+	    && isSwapCache(flags))
+		return TRUE;
+
+	return FALSE;
+}
+
 void write_vmcoreinfo_data(void);
 int set_bit_on_1st_bitmap(mdf_pfn_t pfn, struct cycle *cycle);
 int clear_bit_on_1st_bitmap(mdf_pfn_t pfn, struct cycle *cycle);
-- 
2.52.0



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

* [PATCH makedumpfile v2 6/9] Simplify arguments for page checks
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (4 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 5/9] Move page checks into makedumpfile.h Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 7/9] Add PG_INCLUDE_HEAD extension return status Stephen Brennan
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Many isSomething() helpers need to use several fields in order to make a
determination (e.g. flags, _mapcount, private, _count). All of the info
we extract about a page's identity is now grouped together into struct
pginfo, so we can instead pass a pointer to that struct directly. This
makes it easier to read and write the code, and it also sets an easy
calling convention that extensions can use.

For helpers that require a single field, e.g. flags, still pass it
directly. The benefits of replacing that value with a structure pointer
aren't so obvious to me.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 makedumpfile.c | 22 ++++++++++------------
 makedumpfile.h | 21 +++++++++------------
 2 files changed, 19 insertions(+), 24 deletions(-)

diff --git a/makedumpfile.c b/makedumpfile.c
index b04a1e3..6c62930 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -6008,10 +6008,9 @@ exclude_free_page(struct cycle *cycle)
  * For the kernel versions from v2.6.17 to v2.6.37.
  */
 static int
-page_is_buddy_v2(unsigned long flags, unsigned int _mapcount,
-			unsigned long private, unsigned int _count)
+page_is_buddy_v2(const struct pginfo *i)
 {
-	if (flags & (1UL << NUMBER(PG_buddy)))
+	if (i->flags & (1UL << NUMBER(PG_buddy)))
 		return TRUE;
 
 	return FALSE;
@@ -6021,13 +6020,12 @@ page_is_buddy_v2(unsigned long flags, unsigned int _mapcount,
  * For v2.6.38 and later kernel versions.
  */
 static int
-page_is_buddy_v3(unsigned long flags, unsigned int _mapcount,
-			unsigned long private, unsigned int _count)
+page_is_buddy_v3(const struct pginfo *i)
 {
-	if (isSlab(flags, _mapcount))
+	if (isSlab(i))
 		return FALSE;
 
-	if (_mapcount == (int)NUMBER(PAGE_BUDDY_MAPCOUNT_VALUE))
+	if (i->_mapcount == (int)NUMBER(PAGE_BUDDY_MAPCOUNT_VALUE))
 		return TRUE;
 
 	return FALSE;
@@ -6589,7 +6587,7 @@ check_order:
 		 */
 		if ((info->dump_level & DL_EXCLUDE_FREE)
 		    && info->page_is_buddy
-		    && info->page_is_buddy(i.flags, i._mapcount, i.private, i._count)) {
+		    && info->page_is_buddy(&i)) {
 			if ((ARRAY_LENGTH(zone.free_area) != NOT_FOUND_STRUCTURE) &&
 			    (i.private >= ARRAY_LENGTH(zone.free_area))) {
 				MSG("WARNING: Invalid free page order: pfn=%llx, order=%lu, max order=%lu\n",
@@ -6615,7 +6613,7 @@ check_order:
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_CACHE)
 		    && is_cache_page(i.flags)
-		    && !isPrivate(i.flags) && !isAnon(i.mapping, i.flags, i._mapcount)) {
+		    && !isPrivate(i.flags) && !isAnon(&i)) {
 			pfn_counter = &pfn_cache;
 		}
 		/*
@@ -6623,7 +6621,7 @@ check_order:
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_CACHE_PRI)
 		    && is_cache_page(i.flags)
-		    && !isAnon(i.mapping, i.flags, i._mapcount)) {
+		    && !isAnon(&i)) {
 			if (isPrivate(i.flags))
 				pfn_counter = &pfn_cache_private;
 			else
@@ -6635,7 +6633,7 @@ check_order:
 		 *  - hugetlbfs pages
 		 */
 		else if ((info->dump_level & DL_EXCLUDE_USER_DATA)
-			 && (isAnon(i.mapping, i.flags, i._mapcount) || isHugetlb(i.compound_dtor))) {
+			 && (isAnon(&i) || isHugetlb(i.compound_dtor))) {
 			pfn_counter = &pfn_user;
 		}
 		/*
@@ -6647,7 +6645,7 @@ check_order:
 		/*
 		 * Exclude pages that are logically offline.
 		 */
-		else if (isOffline(i.flags, i._mapcount)) {
+		else if (isOffline(&i)) {
 			pfn_counter = &pfn_offline;
 		}
 		/*
diff --git a/makedumpfile.h b/makedumpfile.h
index 3d64677..cf9d22b 100644
--- a/makedumpfile.h
+++ b/makedumpfile.h
@@ -161,8 +161,8 @@ test_bit(int nr, unsigned long addr)
 #define isSwapBacked(flags)	test_bit(NUMBER(PG_swapbacked), flags)
 #define isHWPOISON(flags)	(test_bit(NUMBER(PG_hwpoison), flags) \
 				&& (NUMBER(PG_hwpoison) != NOT_FOUND_NUMBER))
-#define isAnon(mapping, flags, _mapcount) \
-	(((unsigned long)mapping & PAGE_MAPPING_ANON) != 0 && !isSlab(flags, _mapcount))
+#define isAnon(i) \
+	(((unsigned long)(i)->mapping & PAGE_MAPPING_ANON) != 0 && !isSlab(i))
 #define isUnaccepted(_mapcount)	(_mapcount == (int)NUMBER(PAGE_UNACCEPTED_MAPCOUNT_VALUE) \
 				&& (NUMBER(PAGE_UNACCEPTED_MAPCOUNT_VALUE) != NOT_FOUND_NUMBER))
 
@@ -1763,8 +1763,7 @@ struct DumpInfo {
 	/*
 	 * for filtering free pages managed by buddy system:
 	 */
-	int (*page_is_buddy)(unsigned long flags, unsigned int _mapcount,
-			     unsigned long private, unsigned int _count);
+	int (*page_is_buddy)(const struct pginfo *);
 	/*
 	 * for cyclic_splitting mode, setup splitblock_size
 	 */
@@ -2657,28 +2656,26 @@ isHugetlb(unsigned long dtor)
 		   && (SYMBOL(free_huge_page) == dtor));
 }
 
-static inline int
-isSlab(unsigned long flags, unsigned int _mapcount)
+static inline int isSlab(const struct pginfo *i)
 {
 	/* Linux 6.10 and later */
 	if (NUMBER(PAGE_SLAB_MAPCOUNT_VALUE) != NOT_FOUND_NUMBER) {
-		if (_mapcount == (int)NUMBER(PAGE_SLAB_MAPCOUNT_VALUE))
+		if (i->_mapcount == (int)NUMBER(PAGE_SLAB_MAPCOUNT_VALUE))
 			return TRUE;
 	}
 
-	return flags & (1UL << NUMBER(PG_slab));
+	return i->flags & (1UL << NUMBER(PG_slab));
 }
 
-static inline int
-isOffline(unsigned long flags, unsigned int _mapcount)
+static inline int isOffline(const struct pginfo *i)
 {
 	if (NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE) == NOT_FOUND_NUMBER)
 		return FALSE;
 
-	if (isSlab(flags, _mapcount))
+	if (isSlab(i))
 		return FALSE;
 
-	if (_mapcount == (int)NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE))
+	if (i->_mapcount == (int)NUMBER(PAGE_OFFLINE_MAPCOUNT_VALUE))
 		return TRUE;
 
 	return FALSE;
-- 
2.52.0



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

* [PATCH makedumpfile v2 7/9] Add PG_INCLUDE_HEAD extension return status
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (5 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 6/9] Simplify arguments for page checks Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 8/9] Add userstack extension Stephen Brennan
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Prior to patch "Do not call extensions for tail pages", extensions were
called for tail pages in many cases, yet their decisions on tail pages
were not respected when they conflicted with our decision on the head
page. Since that patch, we do not call extensions for tail pages.
Instead, we apply the decision of the head page for the entirety of the
compound page, which is consistent with how we handle compound pages in
general.

However, some extensions may want more fine-grained control. One such
policy may be to include only the head page, excluding tail pages. For
example, this is useful for an extension which includes just the first
page of ELF file headers within the page cache. Since page cache data
frequently uses larger folios, including just the head can avoid
unnecessary overhead.

So, add a new return status PG_INCLUDE_HEAD, which behaves as described,
including just the head of a compound page.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 extension.c    | 2 +-
 extension.h    | 7 ++++---
 makedumpfile.c | 9 +++++++++
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/extension.c b/extension.c
index 9b29f0c..a7360d0 100644
--- a/extension.c
+++ b/extension.c
@@ -314,7 +314,7 @@ int run_extension_callback(unsigned long pfn, const void *pcache, const struct p
 	for (int i = 0; i < handle_cbs_len; i++) {
 		if (handle_cbs[i]->cb) {
 			result = handle_cbs[i]->cb(pfn, pcache, inf);
-			if (result == PG_INCLUDE) {
+			if (result == PG_INCLUDE || result == PG_INCLUDE_HEAD) {
 				ret = result;
 				goto out;
 			} else if (result == PG_EXCLUDE) {
diff --git a/extension.h b/extension.h
index 22af9a6..3edfbeb 100644
--- a/extension.h
+++ b/extension.h
@@ -4,9 +4,10 @@
 
 struct pginfo;
 enum {
-	PG_INCLUDE,	// Exntesion will keep the page
-	PG_EXCLUDE,	// Exntesion will discard the page
-	PG_UNDECID,	// Exntesion makes no decision
+	PG_INCLUDE,		// Extension will keep the full page
+	PG_INCLUDE_HEAD,	// Extension will keep just the head page
+	PG_EXCLUDE,		// Extension will discard the full page
+	PG_UNDECID,		// Extension makes no decision
 };
 int run_extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i);
 void init_extensions(void);
diff --git a/makedumpfile.c b/makedumpfile.c
index 6c62930..7277e42 100644
--- a/makedumpfile.c
+++ b/makedumpfile.c
@@ -6666,6 +6666,15 @@ check_order:
 			 * retained by an extension. */
 			num_extension_retained += nr_pages;
 			continue;
+		} else if (filter_pg == PG_INCLUDE_HEAD) {
+			num_extension_retained += 1;
+			if (nr_pages == 1)
+				continue;
+
+			/* FALL THROUGH and exclude tail pages */
+			pfn++;
+			mem_map += SIZE(page);
+			nr_pages--;
 		}
 
 		/*
-- 
2.52.0



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

* [PATCH makedumpfile v2 8/9] Add userstack extension
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (6 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 7/9] Add PG_INCLUDE_HEAD extension return status Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-20 23:31 ` [PATCH makedumpfile v2 9/9] Add elfheader extension Stephen Brennan
  2026-08-24 23:07 ` [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Tao Liu
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

Some kernel crashes are triggered by userspace. It can be helpful to
debug the user processes to understand why this occurred, but including
all userspace memory in is usually not feasible. The minimal useful
information is a stack trace, which can be pieced together from the top
of the userspace stack, and the unwind info from the executable and
DSOs. The drgn contrib/pstack.py script can be used to create such a
stack trace, assuming that the stack memory pages are available. Add
an extension which can include those memory pages into a vmcore without
including *all* of userspace memory.

To implement this extension, add helpers which walk the VMA tree for
both rbtree and maple tree implementations. To avoid issues with buggy
kernel data, use cycle detection when walking linked lists. Iterate each
task, identify the stack pointer, and find the corresponding VMA for the
region. Build a sorted list of anon_vmas along with the desired start
and end offset. When filtering pages, for any anon_vma, query this list
and include the desired pages.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 extensions/Makefile     |   4 +-
 extensions/list.h       | 106 ++++++++++++
 extensions/userstack.c  | 351 ++++++++++++++++++++++++++++++++++++++++
 extensions/vma_mtree.c  | 140 ++++++++++++++++
 extensions/vma_mtree.h  |   7 +
 extensions/vma_rbtree.c |  56 +++++++
 extensions/vma_rbtree.h |  12 ++
 7 files changed, 675 insertions(+), 1 deletion(-)
 create mode 100644 extensions/list.h
 create mode 100644 extensions/userstack.c
 create mode 100644 extensions/vma_mtree.c
 create mode 100644 extensions/vma_mtree.h
 create mode 100644 extensions/vma_rbtree.c
 create mode 100644 extensions/vma_rbtree.h

diff --git a/extensions/Makefile b/extensions/Makefile
index 25e0a07..e8c41d8 100644
--- a/extensions/Makefile
+++ b/extensions/Makefile
@@ -1,5 +1,5 @@
 CC ?= gcc
-CONTRIB_SO := sample.so erase_sample.so
+CONTRIB_SO := sample.so erase_sample.so userstack.so
 
 all: $(CONTRIB_SO)
 
@@ -8,6 +8,8 @@ CFLAGS += -fPIC -shared -Wl,-T,../makedumpfile.ld
 $(CONTRIB_SO): %.so: %.c
 	$(CC) $(CFLAGS) -o $@ $^
 
+userstack.so: vma_rbtree.c vma_mtree.c
+
 clean:
 	rm -f $(CONTRIB_SO)
 
diff --git a/extensions/list.h b/extensions/list.h
new file mode 100644
index 0000000..75c63b0
--- /dev/null
+++ b/extensions/list.h
@@ -0,0 +1,106 @@
+#ifndef MAKEDUMPFILE_EXT_LIST_H
+#define MAKEDUMPFILE_EXT_LIST_H
+
+#include "../makedumpfile.h"
+#include "../btf_info.h"
+#include "../detect_cycle.h"
+
+#define LIST_READERR  0UL
+#define LIST_CYCLE    1UL
+#define LIST_INVAL    2UL
+#define LIST_ALLOCERR 3UL
+#define LIST_END      4UL
+
+#define LIST_ERR(pos) ((pos) <= LIST_ALLOCERR)
+#define LIST_STOP(pos) ((pos) <= LIST_END)
+
+DECLARE_MOD_STRUCT_MEMBER(vmlinux, list_head, next);
+DECLARE_MOD_STRUCT_MEMBER(vmlinux, list_head, prev);
+
+static void *list_next(void *prev, void *data)
+{
+	unsigned long head = (unsigned long)data;
+	unsigned long next;
+	unsigned long nextprev;
+
+	if (!readmem(VADDR, (unsigned long)prev, &next, sizeof(next)))
+		return (void *)LIST_READERR;
+	if (!readmem(VADDR, next + (GET_MOD_STRUCT_MEMBER_MOFF(vmlinux, list_head, prev) / 8),
+		     &nextprev, sizeof(nextprev)))
+		return (void *)LIST_READERR;
+	if ((unsigned long)prev != nextprev)
+		return (void *)LIST_INVAL;
+	if (next == head)
+		return (void *)LIST_END;
+	return (void *)next;
+}
+
+static inline struct detect_cycle *list_iterator_start(unsigned long head)
+{
+	return dc_init((void *)head, (void *)head, list_next);
+}
+
+static inline unsigned long list_iterator_next(struct detect_cycle *dc, unsigned long offset)
+{
+	void *nextp;
+	int is_cycle;
+	if (!dc)
+		return LIST_ALLOCERR;
+	is_cycle = dc_next(dc, &nextp);
+	if (is_cycle) {
+		free(dc);
+		return LIST_CYCLE;
+	}
+	unsigned long next = (unsigned long) nextp;
+	if (!LIST_STOP(next))
+		next -= offset;
+	else
+		free(dc);
+	return next;
+}
+
+static inline int list_iterator_errmsg(unsigned long pos, const char *prefix) {
+	switch (pos) {
+	case LIST_READERR:
+		ERRMSG("%s: error reading next pointer\n", prefix);
+		break;
+	case LIST_CYCLE:
+		ERRMSG("%s: detected cycle\n", prefix);
+		break;
+	case LIST_INVAL:
+		ERRMSG("%s: corrupt list (invalid prev pointer)\n", prefix);
+		break;
+	case LIST_ALLOCERR:
+		ERRMSG("%s: allocation error\n", prefix);
+		break;
+	default:
+		ERRMSG("%s: BUG: list_iterator_errmsg() with no error\n", prefix);
+		break;
+	}
+	return FALSE;
+}
+
+/*
+ * Iterate over each object in a linked list.
+ *
+ * pos: name of an unsigned long variable which is set to the address of
+ *    each object
+ * head: address of the list_head anchoring the list
+ * offset: offset of the list_head within each object
+ *
+ * Unlike the standard kernel list_for_each_entry() implementation, we perform
+ * list validation. That is, we ensure that (a) each prev pointer points back to
+ * the correct head, and (b) there are no cycles. The loop terminates for errors
+ * or success, so the "pos" variable does double-duty as a status variable. At
+ * the end of the loop, users must use LIST_ERR() to check whether an iteration
+ * error occurred, and if so, they are encouraged to use list_iterator_errmsg to
+ * report an error and return FALSE:
+ *
+ *    if (LIST_ERR(pos))
+ *        return list_iterator_errmsg(pos, "descriptive prefix");
+ */
+#define list_for_each_entry(pos, head, offset) \
+	for (struct detect_cycle *__dc = list_iterator_start(head); \
+	     !LIST_STOP(pos = list_iterator_next(__dc, offset));)
+
+#endif // MAKEDUMPFILE_EXT_LIST_H
diff --git a/extensions/userstack.c b/extensions/userstack.c
new file mode 100644
index 0000000..a26656a
--- /dev/null
+++ b/extensions/userstack.c
@@ -0,0 +1,351 @@
+/*
+ * userstack.c: An extension for preserving userspace stack memory pages
+ *
+ * It can be useful to know what userspace tasks were doing at the time of a
+ * crash, but including all userspace memory is usually too much: usually a
+ * simple stack trace would do the trick. This extension preserves the topmost
+ * userspace stack pages for each thread in each process, making it possible
+ * to create a stack trace with a tool such as contrib/pstack.py in drgn.
+ */
+#include <assert.h>
+#include <stdbool.h>
+
+#include "../extension.h"
+#include "../makedumpfile.h"
+#include "../btf_info.h"
+#include "../kallsyms.h"
+#include "vma_mtree.h"
+#include "vma_rbtree.h"
+#include "list.h"
+
+/* Required struct fields */
+INIT_MOD_STRUCT_MEMBER(vmlinux, task_struct, tasks);
+INIT_MOD_STRUCT_MEMBER(vmlinux, task_struct, signal);
+INIT_MOD_STRUCT_MEMBER(vmlinux, task_struct, thread_node);
+INIT_MOD_STRUCT_MEMBER(vmlinux, task_struct, stack);
+INIT_MOD_STRUCT_MEMBER(vmlinux, task_struct, mm);
+INIT_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, anon_vma);
+INIT_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_pgoff);
+INIT_MOD_STRUCT_MEMBER(vmlinux, page, index);
+INIT_MOD_STRUCT_MEMBER(vmlinux, signal_struct, thread_head);
+INIT_MOD_STRUCT_MEMBER(vmlinux, list_head, next);
+INIT_MOD_STRUCT_MEMBER(vmlinux, list_head, prev);
+INIT_MOD_STRUCT_MEMBER(vmlinux, pt_regs, sp);
+INIT_MOD_STRUCT(vmlinux, pt_regs);
+
+/* Optional struct fields */
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, thread_union, stack);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, mm_struct, mm_mt);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, mm_struct, mm_rb);
+
+/* Required symbols */
+INIT_MOD_SYM(vmlinux, init_task);
+
+/* Optional symbols */
+INIT_OPT_MOD_SYM(vmlinux, fred_rsp0);
+INIT_OPT_MOD_SYM(vmlinux, __start_init_stack);
+INIT_OPT_MOD_SYM(vmlinux, __end_init_stack);
+INIT_OPT_MOD_SYM(vmlinux, __start_init_task);
+INIT_OPT_MOD_SYM(vmlinux, __end_init_task);
+
+unsigned long THREAD_SIZE;
+bool ready;
+
+#define MEMBER_OFF(S, M) \
+	(GET_MOD_STRUCT_MEMBER_MOFF(vmlinux, S, M) / 8)
+
+
+static int for_each_task(bool (*task_fn)(unsigned long))
+{
+	unsigned long curr_proc;
+
+	// NOTE: this explicitly skips "init_task" because it treats it as the
+	// head of the list. This is fine: init_task is a kernel thread, so
+	// never has a stack to retain.
+	list_for_each_entry(curr_proc,
+			    GET_MOD_SYM(vmlinux, init_task) + MEMBER_OFF(task_struct, tasks),
+			    MEMBER_OFF(task_struct, tasks)) {
+		unsigned long mm;
+		if (!readmem(VADDR, curr_proc + MEMBER_OFF(task_struct, mm),
+			     &mm, sizeof(mm))) {
+			ERRMSG("error: failed to read task.mm\n");
+		}
+		if (!mm)
+			continue;
+
+		unsigned long signal;
+		if (!readmem(VADDR, curr_proc + MEMBER_OFF(task_struct, signal),
+			     &signal, sizeof(signal))) {
+			ERRMSG("error: failed to read task.signal\n");
+			break;
+		}
+
+		unsigned long curr_thread;
+		list_for_each_entry(curr_thread,
+				    signal + MEMBER_OFF(signal_struct, thread_head),
+				    MEMBER_OFF(task_struct, thread_node)) {
+			if (!task_fn(curr_thread))
+				return FALSE;
+		}
+		if (LIST_ERR(curr_thread))
+			return list_iterator_errmsg(curr_thread, "iterating thread list");
+	}
+	if (LIST_ERR(curr_proc))
+		return list_iterator_errmsg(curr_proc, "iterating task list");
+
+	return TRUE;
+}
+
+static unsigned long task_sp(unsigned long taskp)
+{
+	// The stack pointer is stored on entry to the kernel at the top of the
+	// kernel stack. If the task in on-cpu, the stack pointer will be in the
+	// PRSTATUS, but the stale value is very likely to be useful enough.
+	unsigned long user_sp_loc;
+	if (!readmem(VADDR, taskp + MEMBER_OFF(task_struct, stack),
+		     &user_sp_loc, sizeof(user_sp_loc)))
+		return 0;
+
+	user_sp_loc += THREAD_SIZE;
+	user_sp_loc -= GET_MOD_STRUCT_SSIZE(vmlinux, pt_regs);
+	if (MOD_SYM_EXIST(vmlinux, fred_rsp0))
+		user_sp_loc -= 16;
+	user_sp_loc += MEMBER_OFF(pt_regs, sp);
+
+	unsigned long sp;
+	if (!readmem(VADDR, user_sp_loc, &sp, sizeof(sp)))
+		return 0;
+	return sp;
+}
+
+struct task_stack {
+	unsigned long anon_vma;
+	unsigned long index_start;
+	unsigned long index_end;
+};
+
+static struct task_stack *stacks;
+static size_t stacks_count;
+static size_t stacks_alloc;
+
+// Avoid using too much memory when processing an especially large vmcore, or in
+// the case of a bug that causes us to create too many entries. 1M threads
+// requires 24 MiB of memory to track. While it's not the maximum amount we
+// could see, by a long shot, it's enough where most common workloads won't hit
+// it, and any more than this will make it far more likely that the dump will
+// hit an OOM issue.
+#define MAX_TASK_STACKS (1LU << 20) /* 1M * 24 bytes = 24 MiB */
+
+static bool append_task_stack(struct task_stack *newstack)
+{
+	if (stacks_count >= MAX_TASK_STACKS) {
+		ERRMSG("userstack error: hit maximum stack count %lu, aborting collection\n", MAX_TASK_STACKS);
+		goto fail;
+	}
+	if (stacks_count == stacks_alloc) {
+		if (stacks_alloc)
+			stacks_alloc *= 2;
+		else
+			stacks_alloc = 512;
+		struct task_stack *newarr = realloc(stacks, stacks_alloc * sizeof(stacks[0]));
+		if (!newarr) {
+			ERRMSG("userstack: allocation error for stack tracking (size %lu)\n", stacks_alloc);
+			goto fail;
+		}
+		stacks = newarr;
+	}
+	stacks[stacks_count++] = *newstack;
+	return TRUE;
+
+fail:
+	free(stacks);
+	stacks = NULL;
+	stacks_count = stacks_alloc = 0;
+	return FALSE;
+}
+
+static bool record_task_stack(unsigned long taskp)
+{
+	unsigned long task_mm;
+	if (!readmem(VADDR, taskp + MEMBER_OFF(task_struct, mm), &task_mm, sizeof(task_mm)))
+		/* Propagate failure to read a value from the task_struct */
+		return FALSE;
+	if (!task_mm)
+		/* NULL task_mm is expected, continue */
+		return TRUE;
+
+	unsigned long sp = task_sp(taskp);
+	if (!sp)
+		/* Propagate error reading SP */
+		return FALSE;
+
+	int ret;
+	unsigned long vma = 0;
+	if (MOD_STRUCT_MEMBER_EXIST(vmlinux, mm_struct, mm_mt))
+		ret = find_vma_mtree(task_mm + MEMBER_OFF(mm_struct, mm_mt), sp, &vma);
+	else if (MOD_STRUCT_MEMBER_EXIST(vmlinux, mm_struct, mm_rb))
+		ret = find_vma_rbtree(task_mm + MEMBER_OFF(mm_struct, mm_rb), sp, &vma);
+	else
+		assert(FALSE); /* should be impossible */
+	if (!ret)
+		/* propagate error from find_vma_xxx() */
+		return FALSE;
+	else if (!vma)
+		/* No VMA found for the stack. This is unexpected, but gracefully
+		 * handle the condition and continue. */
+		return TRUE;
+
+	unsigned long vm_start, vm_end, anon_vma, vm_pgoff;
+	if (!readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, anon_vma), &anon_vma, sizeof(anon_vma)))
+		return FALSE;
+
+	if (!anon_vma)
+		/* Not an anonymous VMA. This is unexpected but valid. Move on to
+		 * the next task. */
+		return TRUE;
+
+	if (!readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, vm_start), &vm_start, sizeof(vm_start)) ||
+	    !readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, vm_end), &vm_end, sizeof(vm_end)) ||
+	    !readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, vm_pgoff), &vm_pgoff, sizeof(vm_pgoff)))
+		return FALSE;
+
+	/* Construct a range of indices we would like to retain. This is the
+	 * range of stack pages starting with the stack pointer, and continuing
+	 * to the top of the stack vma, or until a limit of 128 pages per task
+	 * is reached. */
+	unsigned long pgoff_start = (sp - vm_start) >> PAGESHIFT();
+	pgoff_start += vm_pgoff;
+	unsigned long pgoff_end = (vm_end - vm_start) >> PAGESHIFT();
+	pgoff_end += vm_pgoff;
+	if (pgoff_start + 128 < pgoff_end)
+		pgoff_end = pgoff_start + 128;
+
+	struct task_stack stack = {anon_vma | 1, pgoff_start, pgoff_end};
+	if (!append_task_stack(&stack))
+		return FALSE;
+
+	return TRUE;
+}
+
+static int stack_compar(const void *lhs, const void *rhs)
+{
+	const struct task_stack *lhss = lhs, *rhss = rhs;
+	if (lhss->anon_vma < rhss->anon_vma)
+		return -1;
+	else if (lhss->anon_vma > rhss->anon_vma)
+		return 1;
+	else
+		return 0;
+}
+
+void extension_init(void)
+{
+	if (MOD_STRUCT_MEMBER_EXIST(vmlinux, mm_struct, mm_mt)) {
+		if (!vma_mtree_init())
+			return;
+	} else if (MOD_STRUCT_MEMBER_EXIST(vmlinux, mm_struct, mm_rb)) {
+		if (!vma_rbtree_init())
+			return;
+	} else {
+		ERRMSG("error: Neither mtree nor rbtree available for VMA walking\n");
+		return;
+	}
+	if (!MOD_STRUCT_EXIST(vmlinux, pt_regs)) {
+		ERRMSG("error: missing pt_regs incfo\n");
+		return;
+	}
+	if (!MOD_SYM_EXIST(vmlinux, init_task)) {
+		ERRMSG("error: missing init_task symbol\n");
+		return;
+	}
+
+	// Determine THREAD_SIZE, which is necessary to find the offset of the
+	// userspace stack pointer register from the kernel thread stack.
+	//
+	// - Prior to v4.16, 0500871f21b23 ("Construct init thread stack in the
+	//   linker script rather than by union"), it was found in thread_union.
+	// - Between v4.16 and v6.10, 8f69cba096b5c ("x86: Rename
+	//   __{start,end}_init_task to __{start,end}_init_stack"), the stack
+	//   size can be inferred by the __{start,end}_init_task symbols.
+	// - Since v6.10, the size is inferred by __{start,end}_init_stack.
+	if (MOD_STRUCT_MEMBER_EXIST(vmlinux, thread_union, stack)) {
+		THREAD_SIZE = GET_MOD_STRUCT_MEMBER_MSIZE(vmlinux, thread_union, stack);
+	} else if (MOD_SYM_EXIST(vmlinux, __start_init_stack) &&
+		   MOD_SYM_EXIST(vmlinux, __end_init_stack) &&
+		   GET_MOD_SYM(vmlinux, __end_init_stack) > GET_MOD_SYM(vmlinux, __start_init_stack)) {
+		THREAD_SIZE = GET_MOD_SYM(vmlinux, __end_init_stack) - GET_MOD_SYM(vmlinux, __start_init_stack);
+	} else if (MOD_SYM_EXIST(vmlinux, __start_init_task) &&
+		   MOD_SYM_EXIST(vmlinux, __end_init_task) &&
+		   GET_MOD_SYM(vmlinux, __end_init_task) > GET_MOD_SYM(vmlinux, __start_init_task)) {
+		THREAD_SIZE = GET_MOD_SYM(vmlinux, __end_init_task) - GET_MOD_SYM(vmlinux, __start_init_task);
+	} else {
+		ERRMSG("Could not determine THREAD_SIZE: neither __start_init_stack "
+		       "nor __start_init_task found in kallsyms, nor is thread_union "
+		       "found in BTF.\n");
+		return;
+	}
+
+	if (!for_each_task(&record_task_stack)) {
+		free(stacks);
+		stacks = NULL;
+		stacks_alloc = stacks_count = 0;
+		ERRMSG("Could not gather all task stack VMAs, userstack disabled\n");
+		return;
+	}
+	struct task_stack *tmp = realloc(stacks, stacks_count * sizeof(*tmp));
+	if (tmp) {
+		stacks = tmp;
+		stacks_alloc = stacks_count;
+	}
+	qsort(stacks, stacks_count, sizeof(*stacks), &stack_compar);
+	ready = TRUE;
+}
+
+static int count_retained;
+static int count_checked;
+static int count_cached;
+int extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i)
+{
+	unsigned long index;
+	static struct {
+		unsigned long mapping;
+		struct task_stack *result;
+	} cache;
+
+	if (!ready || !isAnon(i))
+		return PG_UNDECID;
+
+	index = ULONG(pcache + MEMBER_OFF(page, index));
+	if (!(i->mapping & 1))
+		return PG_UNDECID;
+
+	if (i->mapping != cache.mapping) {
+		count_checked++;
+		struct task_stack search = {i->mapping, 0, 0};
+		struct task_stack *result = bsearch(&search, stacks, stacks_count,
+						sizeof(search), &stack_compar);
+		if (!result)
+			return PG_UNDECID;
+
+		cache.mapping = i->mapping;
+		cache.result = result;
+	} else {
+		count_cached++;
+	}
+
+	if (cache.result->index_start <= index && index < cache.result->index_end) {
+		count_retained++;
+		return PG_INCLUDE;
+	} else {
+		return PG_UNDECID;
+	}
+}
+
+__attribute__((destructor))
+static void userstack_exit(void) {
+	if (count_retained || count_checked || count_cached || stacks_count) {
+		REPORT_MSG("Extension userstack:\n");
+		REPORT_MSG("  PFNs retained: %d searched: %d, cached: %d\n", count_retained, count_checked, count_cached);
+		REPORT_MSG("  Recorded %zu stack anon_vmas\n", stacks_count);
+	}
+}
diff --git a/extensions/vma_mtree.c b/extensions/vma_mtree.c
new file mode 100644
index 0000000..ac8912e
--- /dev/null
+++ b/extensions/vma_mtree.c
@@ -0,0 +1,140 @@
+#include <stdbool.h>
+#include "../btf_info.h"
+#include "../kallsyms.h"
+#include "../makedumpfile.h"
+
+INIT_OPT_MOD_STRUCT(vmlinux, maple_tree);
+INIT_OPT_MOD_STRUCT(vmlinux, maple_node);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_tree, ma_root);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_arange_64, pivot);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_arange_64, slot);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_arange_64, meta);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_range_64, pivot);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_range_64, slot);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_range_64, meta);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, maple_metadata, end);
+
+#define MEMBER_OFF(S, M) \
+	GET_MOD_STRUCT_MEMBER_MOFF(vmlinux, S, M) / 8
+#define KERN_STRUCT_MEMBER_EXIST(S, M) \
+	MOD_STRUCT_MEMBER_EXIST(vmlinux, S, M)
+#define GET_KERN_SYM(SYM) GET_MOD_SYM(vmlinux, SYM)
+#define KERN_SYM_EXIST(SYM) MOD_SYM_EXIST(vmlinux, SYM)
+#define GET_KERN_STRUCT_SSIZE(S) \
+	GET_MOD_STRUCT_SSIZE(vmlinux, S)
+#define KERN_STRUCT_EXIST(SYM) MOD_STRUCT_EXIST(vmlinux, SYM)
+
+#define MAPLE_NODE_MASK			255UL
+#define MAPLE_NODE_TYPE_MASK		0x0F
+#define MAPLE_NODE_TYPE_SHIFT		0x03
+#define XA_ZERO_ENTRY			xa_mk_internal(257)
+
+static unsigned long xa_mk_internal(unsigned long v)
+{
+	return (v << 2) | 2;
+}
+
+static bool xa_is_internal(unsigned long entry)
+{
+	return (entry & 3) == 2;
+}
+
+static bool xa_is_node(unsigned long entry)
+{
+	return xa_is_internal(entry) && entry > 4096;
+}
+
+bool vma_mtree_init(void)
+{
+	if (!KERN_STRUCT_EXIST(maple_tree) ||
+	    !KERN_STRUCT_EXIST(maple_node) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_tree, ma_root) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_arange_64, pivot) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_arange_64, slot) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_arange_64, meta) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_range_64, pivot) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_range_64, slot) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_range_64, meta) ||
+	    !KERN_STRUCT_MEMBER_EXIST(maple_metadata, end)) {
+		ERRMSG("Missing required maple tree syms/types\n");
+		return false;
+	}
+
+	return true;
+}
+
+int find_vma_mtree(unsigned long mt, unsigned long index, unsigned long *ret)
+{
+	unsigned long long entry;
+
+	if (!readmem(VADDR, mt + MEMBER_OFF(maple_tree, ma_root), &entry, sizeof(entry)))
+		return FALSE;
+
+	if (!xa_is_node(entry)) {
+		if (index == 0)
+			*ret = entry;
+		else
+			*ret = 0;
+		return TRUE;
+	}
+	unsigned long long max = ULONGLONG_MAX;
+	void *node = malloc(GET_KERN_STRUCT_SSIZE(maple_node));
+	if (!node) {
+		ERRMSG("failed to allocate memory\n");
+		return FALSE;
+	}
+
+	for (;;) {
+		if (!readmem(VADDR, entry & ~MAPLE_NODE_MASK, node, GET_KERN_STRUCT_SSIZE(maple_node))) {
+			ERRMSG("failed to read maple node: %llx\n", entry & ~MAPLE_NODE_MASK);
+			free(node);
+			return FALSE;
+		}
+
+		int node_type = (entry >> MAPLE_NODE_TYPE_SHIFT) & MAPLE_NODE_TYPE_MASK;
+		unsigned long long *pivot, *slot;
+		uint8_t end;
+		if (node_type == 3) {
+			pivot = node + MEMBER_OFF(maple_arange_64, pivot);
+			slot = node + MEMBER_OFF(maple_arange_64, slot);
+			end = ((uint8_t *)node)[MEMBER_OFF(maple_arange_64, meta) + MEMBER_OFF(maple_metadata, end)];
+		} else if (node_type == 1 || node_type == 2) {
+			pivot = node + MEMBER_OFF(maple_range_64, pivot);
+			slot = node + MEMBER_OFF(maple_range_64, slot);
+			unsigned long long p = *(slot - 1);
+			if (!p)
+				end = ((uint8_t *)node)[MEMBER_OFF(maple_range_64, meta) + MEMBER_OFF(maple_metadata, end)];
+			else {
+				end = slot - pivot;
+				if (p == max)
+					end--;
+			}
+		} else {
+			ERRMSG("unrecognized maple node type: %d\n", node_type);
+			free(node);
+			return FALSE;
+		}
+		int offset = 0;
+		for (offset = 0; offset < end; offset++) {
+			if (pivot[offset] >= index) {
+				max = pivot[offset];
+				break;
+			}
+		}
+		if (&pivot[offset] >= slot)
+			offset = end;
+
+		entry = slot[offset];
+		if (node_type == 1) {
+			// leaf:
+			free(node);
+			if (entry == XA_ZERO_ENTRY)
+				*ret = 0;
+			else
+				*ret = entry;
+			return TRUE;
+		}
+	}
+	*ret = 0;
+	return TRUE;
+}
diff --git a/extensions/vma_mtree.h b/extensions/vma_mtree.h
new file mode 100644
index 0000000..69d9448
--- /dev/null
+++ b/extensions/vma_mtree.h
@@ -0,0 +1,7 @@
+#ifndef _MAPLE_TREE_H
+#define _MAPLE_TREE_H
+#include <stdbool.h>
+bool vma_mtree_init(void);
+int find_vma_mtree(unsigned long mt, unsigned long address, unsigned long *ret);
+#endif /* _MAPLE_TREE_H */
+
diff --git a/extensions/vma_rbtree.c b/extensions/vma_rbtree.c
new file mode 100644
index 0000000..c947fbc
--- /dev/null
+++ b/extensions/vma_rbtree.c
@@ -0,0 +1,56 @@
+#include "../makedumpfile.h"
+#include "../btf_info.h"
+#include "vma_rbtree.h"
+
+INIT_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_start);
+INIT_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_end);
+
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_rb);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, rb_root, rb_node);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, rb_node, rb_left);
+INIT_OPT_MOD_STRUCT_MEMBER(vmlinux, rb_node, rb_right);
+
+#define MEMBER_OFF(S, M) \
+	(GET_MOD_STRUCT_MEMBER_MOFF(vmlinux, S, M) / 8)
+
+#define MEMBER_EXIST(S, M) \
+	(MOD_STRUCT_MEMBER_EXIST(vmlinux, S, M))
+
+int find_vma_rbtree(unsigned long rb_root, unsigned long address, unsigned long *ret)
+{
+	unsigned long node, vma, vm_start, vm_end, rb_left, rb_right;
+	if (!readmem(VADDR, rb_root, &node, sizeof(node)))
+		return FALSE;
+
+	*ret = 0;
+	while (node > MEMBER_OFF(vm_area_struct, vm_rb)) {
+		vma = node - MEMBER_OFF(vm_area_struct, vm_rb);
+		if (!readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, vm_start), &vm_start, sizeof(vm_start)) ||
+		    !readmem(VADDR, vma + MEMBER_OFF(vm_area_struct, vm_end), &vm_end, sizeof(vm_end)) ||
+		    !readmem(VADDR, node + MEMBER_OFF(rb_node, rb_left), &rb_left, sizeof(rb_left)) ||
+		    !readmem(VADDR, node + MEMBER_OFF(rb_node, rb_right), &rb_right, sizeof(rb_right)))
+			return FALSE;
+
+		if (address < vm_start) {
+			node = rb_left;
+		} else if (address >= vm_end) {
+			node = rb_right;
+		} else {
+			*ret = vma;
+			break;
+		}
+	}
+	return TRUE;
+}
+
+bool vma_rbtree_init(void)
+{
+	if (!MEMBER_EXIST(vm_area_struct, vm_rb) ||
+	    !MEMBER_EXIST(rb_root, rb_node) ||
+	    !MEMBER_EXIST(rb_node, rb_left) ||
+	    !MEMBER_EXIST(rb_node, rb_right)) {
+		ERRMSG("error: missing required vm_area_struct & rbtree definitions");
+		return false;
+	}
+	return true;
+}
diff --git a/extensions/vma_rbtree.h b/extensions/vma_rbtree.h
new file mode 100644
index 0000000..a8b8d4f
--- /dev/null
+++ b/extensions/vma_rbtree.h
@@ -0,0 +1,12 @@
+#ifndef RBTREE_H_
+#define RBTREE_H_
+#include <stdbool.h>
+
+#include "../btf_info.h"
+
+DECLARE_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_start);
+DECLARE_MOD_STRUCT_MEMBER(vmlinux, vm_area_struct, vm_end);
+
+int find_vma_rbtree(unsigned long rb_root, unsigned long address, unsigned long *ret);
+bool vma_rbtree_init(void);
+#endif // RBTREE_H_
-- 
2.52.0



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

* [PATCH makedumpfile v2 9/9] Add elfheader extension
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (7 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 8/9] Add userstack extension Stephen Brennan
@ 2026-08-20 23:31 ` Stephen Brennan
  2026-08-24 23:07 ` [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Tao Liu
  9 siblings, 0 replies; 12+ messages in thread
From: Stephen Brennan @ 2026-08-20 23:31 UTC (permalink / raw)
  To: k-hagio-ab, kexec; +Cc: stephen.s.brennan, ltao

This extension preserves the resident ELF header pages from the page
cache, which are mapped by userspace processes. It significantly
increases the chances that an ELF file can be identified by its build ID
and thus a userspace stack trace could be extracted from a vmcore (used
in combination with the userstack.so extension).

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
 extensions/Makefile    |  2 +-
 extensions/elfheader.c | 93 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 1 deletion(-)
 create mode 100644 extensions/elfheader.c

diff --git a/extensions/Makefile b/extensions/Makefile
index e8c41d8..c7918c7 100644
--- a/extensions/Makefile
+++ b/extensions/Makefile
@@ -1,5 +1,5 @@
 CC ?= gcc
-CONTRIB_SO := sample.so erase_sample.so userstack.so
+CONTRIB_SO := sample.so erase_sample.so userstack.so elfheader.so
 
 all: $(CONTRIB_SO)
 
diff --git a/extensions/elfheader.c b/extensions/elfheader.c
new file mode 100644
index 0000000..ccb17fa
--- /dev/null
+++ b/extensions/elfheader.c
@@ -0,0 +1,93 @@
+/*
+ * elfheader.c: Preserve resident ELF header pages from page cache.
+ *
+ * In order to accurately unwind userspace stacks, user debuginfo may be
+ * necessary. At a minimum, it is needed to translate addresses to symbols or
+ * function names, but it may also be needed for unwinding when frame pointers
+ * are not in use. Normally, userspace core dumps will retain the first page of
+ * mapped executable files, since they usually contain the build ID necessary
+ * for identifying the executable and debuginfo. This extension takes
+ * inspiration from this approach, with modifications suitable for the kdump
+ * environment:
+ * - Include non-anonymous page cache pages corresponding to executable files
+ * - Only include the page at index 0
+ * - Only include pages which start with the ELF magic header
+ *
+ * None of this guarantees that all ELF headers will be included. They may not
+ * be resident in memory. And even if the first page is included, there's no
+ * guarantee that the build ID resides in the first page. However, the best
+ * effort is already good enough to make many userspace stacks intelligible with
+ * minimal extra dump time or space.
+ */
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "../extension.h"
+#include "../makedumpfile.h"
+#include "../btf_info.h"
+
+INIT_MOD_STRUCT_MEMBER(vmlinux, page, index);
+INIT_MOD_STRUCT_MEMBER(vmlinux, address_space, host);
+INIT_MOD_STRUCT_MEMBER(vmlinux, inode, i_mode);
+
+#define MEMBER_OFF(S, M) \
+	(GET_MOD_STRUCT_MEMBER_MOFF(vmlinux, S, M) / 8)
+
+static int retained;
+static int checked;
+
+void extension_init(void)
+{
+}
+
+int extension_callback(unsigned long pfn, const void *pcache, const struct pginfo *i)
+{
+	unsigned long index;
+	unsigned long inode;
+	unsigned short mode;
+	char elfmag[SELFMAG];
+
+	/* Only consider non-anonymous file cache pages */
+	if (!(is_cache_page(i->flags) && !isAnon(i)))
+		return PG_UNDECID;
+
+	/* Only consider the first page in the file */
+	index = ULONG(pcache + MEMBER_OFF(page, index));
+	if (index != 0)
+		return PG_UNDECID;
+
+	/* Only retain pages which are actually mapped to userspace */
+	if (OFFSET(page._mapcount) != NOT_FOUND_STRUCTURE &&
+	    i->_mapcount == UINT_MAX)
+		return PG_UNDECID;
+
+	/* Only retain pages for executable inodes */
+	checked++;
+	if (!readmem(VADDR, i->mapping + MEMBER_OFF(address_space, host),
+		     &inode, sizeof(inode)) || !inode)
+		return PG_UNDECID;
+	if (!readmem(VADDR, inode + MEMBER_OFF(inode, i_mode),
+		     &mode, sizeof(mode)))
+		return PG_UNDECID;
+	if (!(mode & 0111))
+		return PG_UNDECID;
+
+	/* Only retain the page if its content looks like ELF */
+	if (!readmem(PADDR, pfn_to_paddr(pfn), elfmag, sizeof(elfmag)) ||
+	    memcmp(elfmag, ELFMAG, SELFMAG))
+		return PG_UNDECID;
+
+	retained++;
+	return PG_INCLUDE_HEAD;
+}
+
+__attribute__((destructor))
+static void elfheader_exit(void)
+{
+	if (checked || retained) {
+		REPORT_MSG("Extension elfheader:\n");
+		REPORT_MSG("  ELF headers retained: %d (candidates checked: %d)\n",
+			   retained, checked);
+	}
+}
-- 
2.52.0



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

* Re: [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension
  2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
                   ` (8 preceding siblings ...)
  2026-08-20 23:31 ` [PATCH makedumpfile v2 9/9] Add elfheader extension Stephen Brennan
@ 2026-08-24 23:07 ` Tao Liu
  2026-08-28  0:55   ` HAGIO KAZUHITO(萩尾 一仁)
  9 siblings, 1 reply; 12+ messages in thread
From: Tao Liu @ 2026-08-24 23:07 UTC (permalink / raw)
  To: Stephen Brennan; +Cc: k-hagio-ab, kexec

Hi Stephen,

I have tested the patchset upon my filtering extension, working good!
And I agree with your current PG_INCLUDE_HEAD design, let's go with
this.

Reviewed-by: Tao Liu <ltao@redhat.com>

Thanks,
Tao Liu

On Fri, Aug 21, 2026 at 11:31 AM Stephen Brennan
<stephen.s.brennan@oracle.com> wrote:
>
> Hello all,
>
> This is v2 of my series of improvements for makedumpfile extensions.
> v1 can be found here: https://lore.kernel.org/kexec/20260714004550.3698175-1-stephen.s.brennan@oracle.com/
>
> There have only been a few changes from v1:
> - Updated extension/sample.c to use the new API in patch 3.
> - Reordered and reworded the extension retained pages statistic per Tao's
>   suggestion, so that it is more clear.
> - Included a more complete commit message in patch 7.
> - In patch 1, removed the "nr_pages = 1" when extensions return PG_EXCLUDE.
> - Significantly reworded patch 1's commit message to explain the different cases
>   and the alternative approach.
>
> Notable improvements in the "userstack.c" extension are:
> - Leverage "detect_cycle.h" API to apply Brent's algorithm for cycle detection
>   in linked list iteration, to avoid infinite loops while iterating tasks &
>   threads on corrupt vmcores.
> - Set a limit to the number of retained anon_vma entries, to avoid hitting OOM
>   issues in case of a huge vmcore or a bug in the extension.
>
> As discussed on v1, the final two patches containing the extensions are not to
> be merged. I will publish them in a Github repository as soon as I can get it
> arranged with my employer. They are more for demonstration of the API and
> continued sharing until the repository is available.
>
> The major discussion on v1 was on patch 4, dealing with how extensions are
> called, between:
>
> (a) Extensions are called for all base pages, and their decisions may conflict
> with the head page decision.
> (b) Extensions are not called for tail pages, and the head page decision is used
> instead.
>
> As I explained in my last reply, I believe that (b) is still the best way. It is
> less complex than (a). It is also more efficient, because makedumpfile today
> skips processing many excluded tail pages, which adds up for large systems with
> many huge pages. We do not have any extension which requires any policy more
> complex than PG_INCLUDE_HEAD.
>
> - elfheader requires PG_INCLUDE_HEAD
> - userstack only uses PG_INCLUDE. I could imagine potentially wanting to include
>   just the sub-pages of compound pages that are actually used by the stack. But
>   it hasn't been necessary so far.
> - amdgpu buffers may be compound pages but if they are, then the entire page
>   would be excluded.
>
> I do have an implementation of (a) as I showed in the thread. If I'm wrong here,
> I can always fall back to that.
>
> Thank you,
> Stephen
>
>
> Stephen Brennan (9):
>   Do not call extensions for tail pages
>   Honor CFLAGS in extension/Makefile
>   Share page information with extension callbacks
>   Introduce a stat for pages retained by extension
>   Move page checks into makedumpfile.h
>   Simplify arguments for page checks
>   Add PG_INCLUDE_HEAD extension return status
>   Add userstack extension
>   Add elfheader extension
>
>  extension.c             |  10 +-
>  extension.h             |  10 +-
>  extensions/Makefile     |   8 +-
>  extensions/elfheader.c  |  93 +++++++++++
>  extensions/list.h       | 106 ++++++++++++
>  extensions/sample.c     |   2 +-
>  extensions/userstack.c  | 351 ++++++++++++++++++++++++++++++++++++++++
>  extensions/vma_mtree.c  | 140 ++++++++++++++++
>  extensions/vma_mtree.h  |   7 +
>  extensions/vma_rbtree.c |  56 +++++++
>  extensions/vma_rbtree.h |  12 ++
>  makedumpfile.c          | 189 +++++++++-------------
>  makedumpfile.h          |  78 ++++++++-
>  13 files changed, 931 insertions(+), 131 deletions(-)
>  create mode 100644 extensions/elfheader.c
>  create mode 100644 extensions/list.h
>  create mode 100644 extensions/userstack.c
>  create mode 100644 extensions/vma_mtree.c
>  create mode 100644 extensions/vma_mtree.h
>  create mode 100644 extensions/vma_rbtree.c
>  create mode 100644 extensions/vma_rbtree.h
>
> --
> 2.52.0
>



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

* Re: [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension
  2026-08-24 23:07 ` [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Tao Liu
@ 2026-08-28  0:55   ` HAGIO KAZUHITO(萩尾 一仁)
  0 siblings, 0 replies; 12+ messages in thread
From: HAGIO KAZUHITO(萩尾 一仁) @ 2026-08-28  0:55 UTC (permalink / raw)
  To: Stephen Brennan, Tao Liu; +Cc: kexec@lists.infradead.org

On 2026/08/25 8:07, Tao Liu wrote:
> Hi Stephen,
> 
> I have tested the patchset upon my filtering extension, working good!
> And I agree with your current PG_INCLUDE_HEAD design, let's go with
> this.
> 
> Reviewed-by: Tao Liu <ltao@redhat.com>

Hi Stephen, Tao,

thank you for the v2 patch set and reviewing.
I understood that your current extensions don't need that flexibility,
so I agree to stop a premature enhancement at this time.

The patch set looks good to me and tested ok, I've applied Patch 1 to 7
with Tao's Reviewed-by tag and a few of comment style tweaks.

Thanks,
Kazu

> 
> Thanks,
> Tao Liu
> 
> On Fri, Aug 21, 2026 at 11:31 AM Stephen Brennan
> <stephen.s.brennan@oracle.com> wrote:
>>
>> Hello all,
>>
>> This is v2 of my series of improvements for makedumpfile extensions.
>> v1 can be found here: https://lore.kernel.org/kexec/20260714004550.3698175-1-stephen.s.brennan@oracle.com/
>>
>> There have only been a few changes from v1:
>> - Updated extension/sample.c to use the new API in patch 3.
>> - Reordered and reworded the extension retained pages statistic per Tao's
>>    suggestion, so that it is more clear.
>> - Included a more complete commit message in patch 7.
>> - In patch 1, removed the "nr_pages = 1" when extensions return PG_EXCLUDE.
>> - Significantly reworded patch 1's commit message to explain the different cases
>>    and the alternative approach.
>>
>> Notable improvements in the "userstack.c" extension are:
>> - Leverage "detect_cycle.h" API to apply Brent's algorithm for cycle detection
>>    in linked list iteration, to avoid infinite loops while iterating tasks &
>>    threads on corrupt vmcores.
>> - Set a limit to the number of retained anon_vma entries, to avoid hitting OOM
>>    issues in case of a huge vmcore or a bug in the extension.
>>
>> As discussed on v1, the final two patches containing the extensions are not to
>> be merged. I will publish them in a Github repository as soon as I can get it
>> arranged with my employer. They are more for demonstration of the API and
>> continued sharing until the repository is available.
>>
>> The major discussion on v1 was on patch 4, dealing with how extensions are
>> called, between:
>>
>> (a) Extensions are called for all base pages, and their decisions may conflict
>> with the head page decision.
>> (b) Extensions are not called for tail pages, and the head page decision is used
>> instead.
>>
>> As I explained in my last reply, I believe that (b) is still the best way. It is
>> less complex than (a). It is also more efficient, because makedumpfile today
>> skips processing many excluded tail pages, which adds up for large systems with
>> many huge pages. We do not have any extension which requires any policy more
>> complex than PG_INCLUDE_HEAD.
>>
>> - elfheader requires PG_INCLUDE_HEAD
>> - userstack only uses PG_INCLUDE. I could imagine potentially wanting to include
>>    just the sub-pages of compound pages that are actually used by the stack. But
>>    it hasn't been necessary so far.
>> - amdgpu buffers may be compound pages but if they are, then the entire page
>>    would be excluded.
>>
>> I do have an implementation of (a) as I showed in the thread. If I'm wrong here,
>> I can always fall back to that.
>>
>> Thank you,
>> Stephen
>>
>>
>> Stephen Brennan (9):
>>    Do not call extensions for tail pages
>>    Honor CFLAGS in extension/Makefile
>>    Share page information with extension callbacks
>>    Introduce a stat for pages retained by extension
>>    Move page checks into makedumpfile.h
>>    Simplify arguments for page checks
>>    Add PG_INCLUDE_HEAD extension return status
>>    Add userstack extension
>>    Add elfheader extension
>>
>>   extension.c             |  10 +-
>>   extension.h             |  10 +-
>>   extensions/Makefile     |   8 +-
>>   extensions/elfheader.c  |  93 +++++++++++
>>   extensions/list.h       | 106 ++++++++++++
>>   extensions/sample.c     |   2 +-
>>   extensions/userstack.c  | 351 ++++++++++++++++++++++++++++++++++++++++
>>   extensions/vma_mtree.c  | 140 ++++++++++++++++
>>   extensions/vma_mtree.h  |   7 +
>>   extensions/vma_rbtree.c |  56 +++++++
>>   extensions/vma_rbtree.h |  12 ++
>>   makedumpfile.c          | 189 +++++++++-------------
>>   makedumpfile.h          |  78 ++++++++-
>>   13 files changed, 931 insertions(+), 131 deletions(-)
>>   create mode 100644 extensions/elfheader.c
>>   create mode 100644 extensions/list.h
>>   create mode 100644 extensions/userstack.c
>>   create mode 100644 extensions/vma_mtree.c
>>   create mode 100644 extensions/vma_mtree.h
>>   create mode 100644 extensions/vma_rbtree.c
>>   create mode 100644 extensions/vma_rbtree.h
>>
>> --
>> 2.52.0
>>

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

end of thread, other threads:[~2026-08-28  0:56 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 23:31 [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 1/9] Do not call extensions for tail pages Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 2/9] Honor CFLAGS in extension/Makefile Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 3/9] Share page information with extension callbacks Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 4/9] Introduce a stat for pages retained by extension Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 5/9] Move page checks into makedumpfile.h Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 6/9] Simplify arguments for page checks Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 7/9] Add PG_INCLUDE_HEAD extension return status Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 8/9] Add userstack extension Stephen Brennan
2026-08-20 23:31 ` [PATCH makedumpfile v2 9/9] Add elfheader extension Stephen Brennan
2026-08-24 23:07 ` [PATCH makedumpfile v2 0/9] Improvements to makedumpfile extensions, plus userspace stack tracing extension Tao Liu
2026-08-28  0:55   ` HAGIO KAZUHITO(萩尾 一仁)

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