fs/coredump.c | 44 +++++++++++++++++++++++++++++++++++++--- include/linux/mm_types.h | 3 ++- 2 files changed, 43 insertions(+), 4 deletions(-)
From: Jacob Lalonde <jalalonde@fb.com>
This adds opt-in truncation of Coredump segments to
`/proc/pid/coredump_filter`. The primary motivation here is to reduce the
number of zero pages written via pipe to a user space coredump process. In
my testing this reduces coredump size, and thus IO by ~40% at Meta. With
some outliers such as PyTorch's TBE weights being loaded in right before a
crash being closer to 99%.
We achieve this by having the Kernel not emit the trailing zeros of any
VMA, resulting in a PT_LOAD with filesz < memsz. I verified that both GDB
from v16 onward and trunk LLDB support loading cores with truncated
sections, however LLDB won't report zeros for the truncated region
I elected to iterate page by page for simplicity, and because we
short-circuit when encountering the first faulted in page, meaning time
spent iterating should directly replace slower copying and pipe IPC.
Below is an example program where the entire 1 GB VMA would be emitted,
where as with the patch only the first page would be written.
int main(void) {
void *p = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int* p2 = (int*)p;
/* write a sentinel to make sure it's not fully sparse */
p2[0] = 0xDEADBEEF;
abort();
}
Signed-off-by: Jacob Lalonde <jalalonde@fb.com>
---
fs/coredump.c | 44 +++++++++++++++++++++++++++++++++++++---
include/linux/mm_types.h | 3 ++-
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/fs/coredump.c b/fs/coredump.c
index e68a76ff92a3..b6cee412eb99 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1588,6 +1588,42 @@ static bool always_dump_vma(struct vm_area_struct *vma)
#define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1
+/*
+ * Truncate the file_sz of the VMA to the last faulted page
+ */
+static unsigned long truncate_vma(struct vm_area_struct *vma)
+{
+ /*
+ * Same logic as dump_user_range, where we enumerate the pages
+ * in a VMA, but instead of skipping un-faulted pages, we move the VMA
+ * end
+ */
+ struct page *page;
+ unsigned long truncated_end;
+
+ /* We're already under the mmap lock */
+ int locked = 1;
+
+ for (truncated_end = vma->vm_end; truncated_end > vma->vm_start; truncated_end -= PAGE_SIZE) {
+ /*
+ * Because we're iterating backwards, we need to
+ * look at the page before the current address
+ */
+ unsigned long probe_addr = truncated_end - PAGE_SIZE;
+
+ page = get_dump_page(probe_addr, &locked);
+ /*
+ * We hit a faulted page, exit
+ */
+ if (page) {
+ put_page(page);
+ break;
+ }
+ }
+
+ return truncated_end - vma->vm_start;
+}
+
/*
* Decide how much of @vma's contents should be included in a core dump.
*/
@@ -1662,13 +1698,15 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
-#undef FILTER
-
return 0;
whole:
- return vma->vm_end - vma->vm_start;
+ if (FILTER(TRUNCATE_SPARSE_VMAS))
+ return truncate_vma(vma);
+ else
+ return vma->vm_end - vma->vm_start;
}
+#undef FILTER
/*
* Helper function for iterating across a vma list. It ensures that the caller
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index b18c2b2e7d2c..18cf8304f698 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1932,9 +1932,10 @@ enum {
#define MMF_DUMP_HUGETLB_SHARED 8
#define MMF_DUMP_DAX_PRIVATE 9
#define MMF_DUMP_DAX_SHARED 10
+#define MMF_DUMP_TRUNCATE_SPARSE_VMAS 11
#define MMF_DUMP_FILTER_SHIFT MMF_DUMPABLE_BITS
-#define MMF_DUMP_FILTER_BITS 9
+#define MMF_DUMP_FILTER_BITS 10
#define MMF_DUMP_FILTER_MASK \
((BIT(MMF_DUMP_FILTER_BITS) - 1) << MMF_DUMP_FILTER_SHIFT)
#define MMF_DUMP_FILTER_DEFAULT \
--
2.53.0-Meta
On Fri, Jul 31, 2026 at 10:13:36AM -0700, Jacob Lalonde wrote:
> From: Jacob Lalonde <jalalonde@fb.com>
>
> This adds opt-in truncation of Coredump segments to
> `/proc/pid/coredump_filter`. The primary motivation here is to reduce the
> number of zero pages written via pipe to a user space coredump process. In
> my testing this reduces coredump size, and thus IO by ~40% at Meta. With
> some outliers such as PyTorch's TBE weights being loaded in right before a
> crash being closer to 99%.
>
> We achieve this by having the Kernel not emit the trailing zeros of any
> VMA, resulting in a PT_LOAD with filesz < memsz. I verified that both GDB
> from v16 onward and trunk LLDB support loading cores with truncated
> sections, however LLDB won't report zeros for the truncated region
Hey!
So I remember having talked to Josef (Bacik) about this a few months
ago. He had a similar goal. I don't think this is the right place to fix
this, though.
coredump_filter allows exclusion/inclusion per-memory-type. So it says
which mappings will end up in the dump. And such filters are inherited
across fork.
The new bit isn't a memory type at all though. All it does is to change
the encoding of the data that's sent.
So the receiver has to understand the new bit and not the crashing
process. This scheme will only work with a subset of debuggers. That's
kinda annoying. Basically the tail of a mapping can show up as
unavailable memory rather than zeroes. And it's inherited across fork
and exec so some unsuspecting crashing process does start generating
such coredumps purely because its parent did opt in.
Some time ago I added the coredump socket. There's a simple protocol
built on top of it. See coredump_req and coredump_ack in
include/uapi/linux/coredump.h. The kernel advertises the features it
supports in coredump_req->mask. Userspace picks the ones it wants in
coredump_ack->mask. It's explicitly extensible. And it's per-coredump.
What you want to do can actually be achieved by just using the coredump
socket today:
Set coredump_filter to 0. The kernel now streams a complete skeleton:
ELF header, the full PT_LOAD table and every note. The dumping process
then blocks in coredump_sock_wait(). And now the coredump server in
userspace can fill in the memory from /proc/<pid>/mem driven by
PAGEMAP_SCAN and then writes the sparse file itself (also works with
threads as they're also parked and you can always trace threads within
your thread-group).
That obviously means you have to assemble the ELF in userspace. But you
get full control over what memory is collected and how.
I find that quite appealing but if you wanted kernel-based zero
filtering I have a proposal as well.
Right now the mask only carries COREDUMP_{KERNEL,USERSPACE,REJECT,WAIT}.
Adding memory options is simple enough. Extend the enum and raise the
bit in coredump_sock_request(). Add a new bit COREDUMP_SPARSE to the
protocol.
The coredump socket stops streaming raw core data and becomes a sequence
of records. The coredump server can reassemble the data as
needed/wanted.
So every__dump_emit() gets a data record and every __dump_skip() becomes
a zero record with no payload. That can live entirely fs/coredump.c
below binfmt. So it applies to every binfmt and needs no elf knowledge
at all.
© 2016 - 2026 Red Hat, Inc.