From: Jacob Lalonde 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 --- 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