From: R Nageswara Sastry <rnsastry@linux.ibm.com>
To: Christian Brauner <brauner@kernel.org>, linux-fsdevel@vger.kernel.org
Cc: Jacob Lalonde <jalalonde@meta.com>,
Josef Bacik <josef@toxicpanda.com>, Jann Horn <jannh@google.com>,
Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Omar Sandoval <osandov@osandov.com>,
Jacob Lalonde <jalalonde@fb.com>, Shuah Khan <shuah@kernel.org>,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-kselftest@vger.kernel.org, linuxppc-dev@lists.ozlabs.org
Subject: Re: [PATCH v2 21/22] selftests/coredump: simulate a blob store
Date: Wed, 9 Sep 2026 21:36:39 +0530 [thread overview]
Message-ID: <5563177c-96ca-40bd-a513-378b759fad47@linux.ibm.com> (raw)
In-Reply-To: <20260820-work-coredump-sparse-v2-21-ba32dd718c51@kernel.org>
On 20.08.2026 4:39 AM, Christian Brauner wrote:
> A coredump server that uploads to a blob store must redescribe the
> coredump and fixup the phdr.
>
> A segment is split wherever a hole was left out and everything a segment
> covers past p_filesz is zeroes anyway. So the blob store ends up with an
> ordinary ELF core file that is missing nothing but holes. Nothing
> downstream of the server has to learn a container format.
>
> The coredump with its holes still in it is reassembled alongside the
> object so the two can be compared.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Tested-by: R Nageswara Sastry <rnsastry@linux.ibm.com>
System: ppc64le LPAR (IBM POWER), Linux 7.3-rc2
> ---
> .../coredump/coredump_socket_protocol_test.c | 156 +++++
> .../selftests/coredump/coredump_test_helpers.c | 765 +++++++++++++++++++++
> .../selftests/coredump/coredump_test_helpers.h | 3 +
> 3 files changed, 924 insertions(+)
>
> diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
> index abf6e2c4c354..f33eaf2fa93d 100644
> --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
> +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
> @@ -1882,6 +1882,162 @@ TEST_F(coredump, socket_request_records_truncated)
> ASSERT_GT(received, 0);
> }
>
> +/*
> + * A coredump server that uploads to a blob store can't upload a sparse
> + * file. It doesn't have to: it streams the data records into the object
> + * as they arrive, leaves the holes out, and uploads the corrected
> + * program header table last. What it ends up with is an ordinary ELF
> + * core file that describes the same memory as the coredump the records
> + * came from, minus the holes.
> + */
> +TEST_F(coredump, socket_request_sparse_blob_upload)
> +{
> + int fd_core_file, pidfd, status;
> + pid_t pid, pid_coredump_server;
> + struct pidfd_info info = {};
> + off_t coredump_size = 0;
> + ssize_t received = 0;
> + int ipc_sockets[2];
> + int pipefds[2];
> + struct stat st;
> + char c;
> +
> + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
> + ASSERT_EQ(pipe(pipefds), 0);
> + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
> +
> + pid_coredump_server = fork();
> + ASSERT_GE(pid_coredump_server, 0);
> + if (pid_coredump_server == 0) {
> + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
> + int fd_object = -1, fd_reference = -1;
> + int exit_code = EXIT_FAILURE;
> + struct coredump_req req = {};
> + off_t size = 0;
> + ssize_t ret;
> +
> + close(ipc_sockets[0]);
> + close(pipefds[0]);
> +
> + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
> + if (fd_server < 0)
> + goto out;
> +
> + if (write_nointr(ipc_sockets[1], "1", 1) < 0)
> + goto out;
> +
> + close(ipc_sockets[1]);
> +
> + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
> + if (fd_coredump < 0)
> + goto out;
> +
> + fd_peer_pidfd = get_peer_pidfd(fd_coredump);
> + if (fd_peer_pidfd < 0)
> + goto out;
> +
> + /* The object is a plain file. It never sees a hole. */
> + fd_object = open("/tmp/coredump.file",
> + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
> + if (fd_object < 0)
> + goto out;
> +
> + /*
> + * The coredump with its holes still in it is bigger than
> + * the mapping the child made, so keep it on the detached
> + * tmpfs and sparse.
> + */
> + fd_reference = open_coredump_tmpfile(self->fd_tmpfs_detached);
> + if (fd_reference < 0)
> + goto out;
> +
> + if (!read_coredump_req(fd_coredump, &req))
> + goto out;
> +
> + if (!check_coredump_req(&req))
> + goto out;
> +
> + if (!send_coredump_ack(fd_coredump, &req,
> + COREDUMP_KERNEL | COREDUMP_RECORDS |
> + COREDUMP_SPARSE | COREDUMP_WAIT, 0))
> + goto out;
> +
> + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
> + goto out;
> +
> + ret = recv_coredump_compact(fd_coredump, fd_object,
> + fd_reference, &size);
> + if (ret < 0)
> + goto out;
> +
> + if (check_compact_coredump(fd_object, fd_reference))
> + goto out;
> +
> + if (write_nointr(pipefds[1], &ret, sizeof(ret)) != sizeof(ret))
> + goto out;
> + if (write_nointr(pipefds[1], &size, sizeof(size)) != sizeof(size))
> + goto out;
> +
> + exit_code = EXIT_SUCCESS;
> +out:
> + close(pipefds[1]);
> + if (fd_reference >= 0)
> + close(fd_reference);
> + if (fd_object >= 0)
> + close(fd_object);
> + if (fd_peer_pidfd >= 0)
> + close(fd_peer_pidfd);
> + if (fd_coredump >= 0)
> + close(fd_coredump);
> + if (fd_server >= 0)
> + close(fd_server);
> + _exit(exit_code);
> + }
> + self->pid_coredump_server = pid_coredump_server;
> +
> + EXPECT_EQ(close(ipc_sockets[1]), 0);
> + EXPECT_EQ(close(pipefds[1]), 0);
> + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
> + EXPECT_EQ(close(ipc_sockets[0]), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0)
> + crashing_child_sparse(SPARSE_MAPPING_SIZE);
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFSIGNALED(status));
> + ASSERT_TRUE(WCOREDUMP(status));
> +
> + ASSERT_EQ(read_nointr(pipefds[0], &received, sizeof(received)),
> + sizeof(received));
> + ASSERT_EQ(read_nointr(pipefds[0], &coredump_size, sizeof(coredump_size)),
> + sizeof(coredump_size));
> + EXPECT_EQ(close(pipefds[0]), 0);
> +
> + ASSERT_TRUE(get_pidfd_info(pidfd, &info));
> + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
> + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
> +
> + wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
> +
> + /* The mapping is in the coredump, holes included. */
> + ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
> +
> + /* The object isn't sparse and doesn't carry them. */
> + ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
> + ASSERT_LT(st.st_size, coredump_size / 8);
> +
> + /* And a debugger still sees an ordinary ELF core file. */
> + fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
> + ASSERT_GE(fd_core_file, 0);
> + ASSERT_TRUE(is_elf_core(fd_core_file));
> + EXPECT_EQ(close(fd_core_file), 0);
> +}
> +
> /* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
> static void check_conflicting_ack(struct __test_metadata *const _metadata,
> FIXTURE_DATA(coredump) *self, __u64 ack_mask)
> diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
> index 89f3954c5607..9346b8f688e2 100644
> --- a/tools/testing/selftests/coredump/coredump_test_helpers.c
> +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
> @@ -330,6 +330,771 @@ bool is_elf_core(int fd)
> return true;
> }
>
> +/*
> + * A coredump server that uploads to a blob store can't upload a sparse
> + * file and can't seek in the object it is uploading. It streams the data
> + * records into the object as they arrive, remembers the holes it left
> + * out, and uploads the program header table that describes the result
> + * last. What comes out is an ordinary ELF core file without the holes.
> + */
> +
> +/* A run of the coredump the object doesn't carry. */
> +struct compact_hole {
> + __u64 offset;
> + __u64 len;
> +};
> +
> +/* A program header of the object and where its bytes sat in the coredump. */
> +struct compact_piece {
> + ElfW(Phdr) phdr;
> + __u64 src;
> +};
> +
> +struct compact_ctx {
> + int fd_body; /* the object's payload, append only */
> + int fd_reference; /* the coredump with its holes, for the test */
> + unsigned char *head; /* everything ahead of the segment data */
> + size_t head_len;
> + size_t head_cap;
> + __u64 data_offset; /* where the segment data starts, 0 while unknown */
> + struct compact_hole *holes;
> + size_t nr_holes;
> + size_t holes_cap;
> + __u64 body_len;
> +};
> +
> +/* Write @len bytes out, short writes and all. */
> +static int compact_write(int fd, const void *buf, size_t len)
> +{
> + const unsigned char *pos = buf;
> +
> + while (len) {
> + ssize_t ret = write(fd, pos, len);
> +
> + if (ret <= 0) {
> + fprintf(stderr, "%s: write failed: %m\n", __func__);
> + return -1;
> + }
> +
> + pos += ret;
> + len -= ret;
> + }
> +
> + return 0;
> +}
> +
> +/* Keep @len bytes of the head, or @len zeroes if @buf is NULL. */
> +static int compact_head_append(struct compact_ctx *ctx, const void *buf,
> + size_t len)
> +{
> + if (ctx->head_len + len > ctx->head_cap) {
> + size_t cap = ctx->head_cap ? ctx->head_cap : PAGE_SIZE;
> + unsigned char *head;
> +
> + while (cap < ctx->head_len + len)
> + cap *= 2;
> +
> + head = realloc(ctx->head, cap);
> + if (!head) {
> + fprintf(stderr, "%s: out of memory\n", __func__);
> + return -1;
> + }
> + ctx->head = head;
> + ctx->head_cap = cap;
> + }
> +
> + if (buf)
> + memcpy(ctx->head + ctx->head_len, buf, len);
> + else
> + memset(ctx->head + ctx->head_len, 0, len);
> + ctx->head_len += len;
> +
> + return 0;
> +}
> +
> +/* Remember a hole so the program header table can account for it later. */
> +static int compact_keep_hole(struct compact_ctx *ctx, __u64 offset, __u64 len)
> +{
> + if (ctx->nr_holes == ctx->holes_cap) {
> + size_t cap = ctx->holes_cap ? ctx->holes_cap * 2 : 64;
> + struct compact_hole *holes;
> +
> + holes = realloc(ctx->holes, cap * sizeof(*holes));
> + if (!holes) {
> + fprintf(stderr, "%s: out of memory\n", __func__);
> + return -1;
> + }
> + ctx->holes = holes;
> + ctx->holes_cap = cap;
> + }
> +
> + ctx->holes[ctx->nr_holes].offset = offset;
> + ctx->holes[ctx->nr_holes].len = len;
> + ctx->nr_holes++;
> +
> + return 0;
> +}
> +
> +/* The segment data starts where the first PT_LOAD points. */
> +static int compact_probe(struct compact_ctx *ctx)
> +{
> + const ElfW(Ehdr) *ehdr = (const ElfW(Ehdr) *)ctx->head;
> + const ElfW(Phdr) *phdr;
> + size_t i;
> +
> + if (ctx->data_offset || ctx->head_len < sizeof(*ehdr))
> + return 0;
> +
> + if (!is_core_ehdr(ehdr)) {
> + fprintf(stderr, "%s: not an ELF core file\n", __func__);
> + return -1;
> + }
> +
> + if (ehdr->e_phoff != sizeof(*ehdr) ||
> + ehdr->e_phentsize != sizeof(ElfW(Phdr)) ||
> + ehdr->e_phnum == 0 || ehdr->e_phnum == PN_XNUM) {
> + fprintf(stderr, "%s: unhandled program header table\n", __func__);
> + return -1;
> + }
> +
> + if (ctx->head_len < ehdr->e_phoff +
> + (size_t)ehdr->e_phnum * ehdr->e_phentsize)
> + return 0;
> +
> + phdr = (const ElfW(Phdr) *)(ctx->head + ehdr->e_phoff);
> + for (i = 0; i < ehdr->e_phnum; i++) {
> + if (phdr[i].p_type != PT_LOAD)
> + continue;
> + if (!ctx->data_offset || phdr[i].p_offset < ctx->data_offset)
> + ctx->data_offset = phdr[i].p_offset;
> + }
> +
> + if (!ctx->data_offset) {
> + fprintf(stderr, "%s: coredump without a single segment\n",
> + __func__);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Take whatever of [@offset, @offset + @len) still belongs to the head.
> + * @buf is NULL for a hole. Returns how much was taken.
> + */
> +static ssize_t compact_head_take(struct compact_ctx *ctx, const void *buf,
> + __u64 offset, __u64 len)
> +{
> + __u64 chunk;
> +
> + if (!len || (ctx->data_offset && offset >= ctx->data_offset))
> + return 0;
> +
> + chunk = len;
> + if (ctx->data_offset && offset + chunk > ctx->data_offset)
> + chunk = ctx->data_offset - offset;
> +
> + if (offset != ctx->head_len) {
> + fprintf(stderr, "%s: head has a gap at %llu\n", __func__,
> + (unsigned long long)offset);
> + return -1;
> + }
> +
> + if (compact_head_append(ctx, buf, chunk))
> + return -1;
> +
> + return chunk;
> +}
> +
> +static int compact_data(void *arg, const void *buf, size_t len, __u64 offset)
> +{
> + struct compact_ctx *ctx = arg;
> + const unsigned char *pos = buf;
> + ssize_t head;
> +
> + /* Only the test needs a coredump with the holes still in it. */
> + if (pwrite(ctx->fd_reference, pos, len, offset) != (ssize_t)len) {
> + fprintf(stderr, "%s: pwrite failed: %m\n", __func__);
> + return -1;
> + }
> +
> + /* The head has to be rewritten at the end, so hold on to it. */
> + head = compact_head_take(ctx, pos, offset, len);
> + if (head < 0)
> + return -1;
> + if (head && compact_probe(ctx))
> + return -1;
> +
> + pos += head;
> + len -= head;
> + if (!len)
> + return 0;
> +
> + /* Everything else goes into the object as it arrives. */
> + if (compact_write(ctx->fd_body, pos, len))
> + return -1;
> + ctx->body_len += len;
> +
> + return 0;
> +}
> +
> +static int compact_zero(void *arg, __u64 offset, __u64 len)
> +{
> + struct compact_ctx *ctx = arg;
> + ssize_t head;
> +
> + /* A hole in the head is alignment padding. Write it out. */
> + head = compact_head_take(ctx, NULL, offset, len);
> + if (head < 0)
> + return -1;
> +
> + offset += head;
> + len -= head;
> + if (!len)
> + return 0;
> +
> + /* This is what the object doesn't have to carry. */
> + return compact_keep_hole(ctx, offset, len);
> +}
> +
> +/* Where @offset ends up in the object once the holes ahead of it are gone. */
> +static __u64 compact_offset(const struct compact_ctx *ctx, __u64 body_start,
> + __u64 offset)
> +{
> + __u64 elided = 0;
> + size_t i;
> +
> + for (i = 0; i < ctx->nr_holes; i++) {
> + __u64 len = ctx->holes[i].len;
> +
> + if (ctx->holes[i].offset >= offset)
> + break;
> + if (ctx->holes[i].offset + len > offset)
> + len = offset - ctx->holes[i].offset;
> + elided += len;
> + }
> +
> + return body_start + (offset - ctx->data_offset) - elided;
> +}
> +
> +/* A run of segment data that made it into the object. */
> +static void compact_add_data(struct compact_piece *pieces, size_t *nr,
> + const ElfW(Phdr) *phdr, __u64 start, __u64 end)
> +{
> + struct compact_piece *piece = &pieces[(*nr)++];
> +
> + piece->phdr = *phdr;
> + piece->phdr.p_vaddr = phdr->p_vaddr + (start - phdr->p_offset);
> + piece->phdr.p_paddr = 0;
> + piece->phdr.p_filesz = end - start;
> + piece->phdr.p_memsz = end - start;
> + piece->src = start;
> +}
> +
> +/*
> + * A run of @len bytes the object doesn't carry. It grows the piece in
> + * front of it if this segment already has one, because everything a
> + * segment covers past p_filesz is zeroes anyway.
> + */
> +static void compact_add_zero(struct compact_piece *pieces, size_t *nr,
> + size_t first, const ElfW(Phdr) *phdr, __u64 vaddr,
> + __u64 len)
> +{
> + struct compact_piece *piece;
> +
> + if (*nr > first) {
> + pieces[*nr - 1].phdr.p_memsz += len;
> + return;
> + }
> +
> + piece = &pieces[(*nr)++];
> + piece->phdr = *phdr;
> + piece->phdr.p_vaddr = vaddr;
> + piece->phdr.p_paddr = 0;
> + piece->phdr.p_filesz = 0;
> + piece->phdr.p_memsz = len;
> + piece->src = 0;
> +}
> +
> +/* Split the segments at the holes and write out what the object became. */
> +static int compact_build(struct compact_ctx *ctx, int fd_object)
> +{
> + __u64 note_offset = 0, note_len = 0, note_new;
> + __u64 align = 0, head_len, body_start, pos;
> + size_t nr_old, nr_new = 0, note_piece = 0, i;
> + struct compact_piece *pieces;
> + char buffer[PAGE_SIZE];
> + const ElfW(Phdr) *old;
> + ElfW(Ehdr) ehdr;
> + int ret = -1;
> +
> + if (!ctx->data_offset) {
> + fprintf(stderr, "%s: coredump without segment data\n", __func__);
> + return -1;
> + }
> +
> + memcpy(&ehdr, ctx->head, sizeof(ehdr));
> + if (ehdr.e_shoff) {
> + fprintf(stderr, "%s: section headers are not handled\n",
> + __func__);
> + return -1;
> + }
> +
> + old = (const ElfW(Phdr) *)(ctx->head + ehdr.e_phoff);
> + nr_old = ehdr.e_phnum;
> +
> + pieces = calloc(nr_old + 2 * ctx->nr_holes + 1, sizeof(*pieces));
> + if (!pieces) {
> + fprintf(stderr, "%s: out of memory\n", __func__);
> + return -1;
> + }
> +
> + for (i = 0; i < nr_old; i++) {
> + ElfW(Phdr) phdr = old[i];
> + __u64 end = phdr.p_offset + phdr.p_filesz;
> + __u64 cur = phdr.p_offset;
> + size_t first = nr_new, h;
> +
> + /* The notes move because the table in front of them grows. */
> + if (phdr.p_type == PT_NOTE) {
> + if (note_len) {
> + fprintf(stderr, "%s: more than one note segment\n",
> + __func__);
> + goto out;
> + }
> + note_offset = phdr.p_offset;
> + note_len = phdr.p_filesz;
> + note_piece = nr_new;
> + pieces[nr_new].phdr = phdr;
> + pieces[nr_new++].src = 0;
> + continue;
> + }
> +
> + if (phdr.p_type != PT_LOAD) {
> + if (phdr.p_filesz && phdr.p_offset < ctx->data_offset) {
> + fprintf(stderr, "%s: segment %zu is in the head\n",
> + __func__, i);
> + goto out;
> + }
> + pieces[nr_new].phdr = phdr;
> + pieces[nr_new++].src = phdr.p_offset;
> + continue;
> + }
> +
> + if (!align)
> + align = phdr.p_align;
> +
> + for (h = 0; h < ctx->nr_holes && cur < end; h++) {
> + __u64 start = ctx->holes[h].offset;
> + __u64 stop = start + ctx->holes[h].len;
> +
> + if (stop <= cur)
> + continue;
> + if (start >= end)
> + break;
> +
> + /* A hole can span more than this one segment. */
> + if (start < cur)
> + start = cur;
> + if (stop > end)
> + stop = end;
> +
> + if (start > cur) {
> + compact_add_data(pieces, &nr_new, &phdr, cur,
> + start);
> + cur = start;
> + }
> + compact_add_zero(pieces, &nr_new, first, &phdr,
> + phdr.p_vaddr + (cur - phdr.p_offset),
> + stop - cur);
> + cur = stop;
> + }
> +
> + if (cur < end)
> + compact_add_data(pieces, &nr_new, &phdr, cur, end);
> +
> + /* Whatever the kernel didn't dump of this mapping. */
> + if (phdr.p_memsz > phdr.p_filesz)
> + compact_add_zero(pieces, &nr_new, first, &phdr,
> + phdr.p_vaddr + phdr.p_filesz,
> + phdr.p_memsz - phdr.p_filesz);
> + }
> +
> + if (!note_len || note_offset + note_len > ctx->head_len) {
> + fprintf(stderr, "%s: notes aren't where they should be\n",
> + __func__);
> + goto out;
> + }
> +
> + if (nr_new >= PN_XNUM) {
> + fprintf(stderr, "%s: %zu program headers don't fit\n", __func__,
> + nr_new);
> + goto out;
> + }
> +
> + if (!align || (align & (align - 1)))
> + align = sysconf(_SC_PAGESIZE);
> +
> + note_new = sizeof(ehdr) + (__u64)nr_new * sizeof(ElfW(Phdr));
> + head_len = note_new + note_len;
> + body_start = (head_len + align - 1) & ~(align - 1);
> +
> + for (i = 0; i < nr_new; i++) {
> + struct compact_piece *piece = &pieces[i];
> +
> + if (i == note_piece)
> + piece->phdr.p_offset = note_new;
> + else if (piece->phdr.p_filesz)
> + piece->phdr.p_offset = compact_offset(ctx, body_start,
> + piece->src);
> + else
> + piece->phdr.p_offset = 0;
> + }
> +
> + /* Only now is the head known. That's why it is uploaded last. */
> + ehdr.e_phnum = nr_new;
> + if (compact_write(fd_object, &ehdr, sizeof(ehdr)))
> + goto out;
> +
> + for (i = 0; i < nr_new; i++)
> + if (compact_write(fd_object, &pieces[i].phdr,
> + sizeof(pieces[i].phdr)))
> + goto out;
> +
> + if (compact_write(fd_object, ctx->head + note_offset, note_len))
> + goto out;
> +
> + /* Keep the segments aligned the way a debugger expects them. */
> + memset(buffer, 0, sizeof(buffer));
> + for (pos = head_len; pos < body_start; ) {
> + __u64 chunk = body_start - pos;
> +
> + if (chunk > sizeof(buffer))
> + chunk = sizeof(buffer);
> + if (compact_write(fd_object, buffer, chunk))
> + goto out;
> + pos += chunk;
> + }
> +
> + /* Putting the parts together is the blob store's job. Do it here. */
> + for (pos = 0; pos < ctx->body_len; ) {
> + ssize_t chunk = pread(ctx->fd_body, buffer, sizeof(buffer), pos);
> +
> + if (chunk <= 0) {
> + fprintf(stderr, "%s: short read %zd: %m\n", __func__,
> + chunk);
> + goto out;
> + }
> + if (compact_write(fd_object, buffer, chunk))
> + goto out;
> + pos += chunk;
> + }
> +
> + fprintf(stderr, "Object is %llu bytes in %zu program headers, %zu holes left out\n",
> + (unsigned long long)(body_start + ctx->body_len), nr_new,
> + ctx->nr_holes);
> + ret = 0;
> +out:
> + free(pieces);
> + return ret;
> +}
> +
> +/*
> + * Reassemble a record stream into an ELF core file that has no holes in
> + * it, the way a coredump server that uploads to a blob store has to. If
> + * @fd_reference is valid it gets the coredump the records describe,
> + * holes and all, so the test can compare the two.
> + */
> +ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference,
> + off_t *coredump_size)
> +{
> + struct compact_ctx ctx = {
> + .fd_body = -1,
> + .fd_reference = fd_reference,
> + };
> + struct coredump_record_sink sink = {
> + .data = compact_data,
> + .zero = compact_zero,
> + .ctx = &ctx,
> + };
> + ssize_t received;
> + off_t size = 0;
> + FILE *body;
> +
> + body = tmpfile();
> + if (!body) {
> + fprintf(stderr, "%s: tmpfile failed: %m\n", __func__);
> + return -1;
> + }
> + ctx.fd_body = fileno(body);
> +
> + /* An upload is appended to. Make sure nothing here can seek. */
> + if (fcntl(ctx.fd_body, F_SETFL, O_APPEND)) {
> + fprintf(stderr, "%s: F_SETFL failed: %m\n", __func__);
> + received = -1;
> + goto out;
> + }
> +
> + received = __recv_coredump_records(fd_coredump, &sink, &size, NULL, -1);
> + if (received < 0)
> + goto out;
> +
> + /*
> + * Nothing is written for a hole, so grow the reference to the size
> + * the records describe in case the coredump ended in one.
> + */
> + if (ftruncate(fd_reference, size) < 0) {
> + fprintf(stderr, "%s: ftruncate to %llu failed: %m\n",
> + __func__, (unsigned long long)size);
> + received = -1;
> + goto out;
> + }
> +
> + if (compact_build(&ctx, fd_object)) {
> + received = -1;
> + goto out;
> + }
> +
> + if (coredump_size)
> + *coredump_size = size;
> +out:
> + fclose(body);
> + free(ctx.head);
> + free(ctx.holes);
> + return received;
> +}
> +
> +/* Read the ELF header and the program header table of @fd. */
> +static ElfW(Phdr) *read_phdrs(int fd, size_t *nr)
> +{
> + ElfW(Ehdr) ehdr;
> + ElfW(Phdr) *phdr;
> + size_t size;
> +
> + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) {
> + fprintf(stderr, "%s: no ELF header: %m\n", __func__);
> + return NULL;
> + }
> +
> + if (!is_core_ehdr(&ehdr) || !ehdr.e_phnum ||
> + ehdr.e_phentsize != sizeof(*phdr)) {
> + fprintf(stderr, "%s: not an ELF core file\n", __func__);
> + return NULL;
> + }
> +
> + size = (size_t)ehdr.e_phnum * ehdr.e_phentsize;
> + phdr = malloc(size);
> + if (!phdr) {
> + fprintf(stderr, "%s: out of memory\n", __func__);
> + return NULL;
> + }
> +
> + if (pread(fd, phdr, size, ehdr.e_phoff) != (ssize_t)size) {
> + fprintf(stderr, "%s: short program header table: %m\n", __func__);
> + free(phdr);
> + return NULL;
> + }
> +
> + *nr = ehdr.e_phnum;
> + return phdr;
> +}
> +
> +/* The segment @vaddr falls into. */
> +static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr,
> + __u64 vaddr)
> +{
> + size_t i;
> +
> + for (i = 0; i < nr; i++) {
> + if (phdr[i].p_type != PT_LOAD)
> + continue;
> + if (vaddr >= phdr[i].p_vaddr &&
> + vaddr < phdr[i].p_vaddr + phdr[i].p_memsz)
> + return &phdr[i];
> + }
> +
> + return NULL;
> +}
> +
> +/* The next stretch of memory the segments cover, split ones merged back. */
> +static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i,
> + __u64 *start, __u64 *end)
> +{
> + while (*i < nr && phdr[*i].p_type != PT_LOAD)
> + (*i)++;
> +
> + if (*i >= nr)
> + return false;
> +
> + *start = phdr[*i].p_vaddr;
> + *end = phdr[*i].p_vaddr + phdr[*i].p_memsz;
> + (*i)++;
> +
> + while (*i < nr) {
> + if (phdr[*i].p_type != PT_LOAD) {
> + (*i)++;
> + continue;
> + }
> + if (phdr[*i].p_vaddr != *end)
> + break;
> + *end = phdr[*i].p_vaddr + phdr[*i].p_memsz;
> + (*i)++;
> + }
> +
> + return true;
> +}
> +
> +/* Compare @len bytes at @offset against @len bytes at @offset_ref. */
> +static int compare_range(int fd, __u64 offset, int fd_ref, __u64 offset_ref,
> + __u64 len)
> +{
> + char buffer[PAGE_SIZE], buffer_ref[PAGE_SIZE];
> +
> + while (len) {
> + size_t chunk = len < sizeof(buffer) ? len : sizeof(buffer);
> +
> + if (pread(fd, buffer, chunk, offset) != (ssize_t)chunk ||
> + pread(fd_ref, buffer_ref, chunk, offset_ref) != (ssize_t)chunk) {
> + fprintf(stderr, "%s: short read at %llu: %m\n",
> + __func__, (unsigned long long)offset);
> + return -1;
> + }
> +
> + if (memcmp(buffer, buffer_ref, chunk)) {
> + fprintf(stderr, "%s: %llu differs from %llu\n", __func__,
> + (unsigned long long)offset,
> + (unsigned long long)offset_ref);
> + return -1;
> + }
> +
> + offset += chunk;
> + offset_ref += chunk;
> + len -= chunk;
> + }
> +
> + return 0;
> +}
> +
> +/* The @len bytes at @offset the object left out have to have been zeroes. */
> +static int check_zero_range(int fd, __u64 offset, __u64 len)
> +{
> + static const char zeroes[PAGE_SIZE];
> + char buffer[PAGE_SIZE];
> +
> + while (len) {
> + size_t chunk = len < sizeof(buffer) ? len : sizeof(buffer);
> +
> + if (pread(fd, buffer, chunk, offset) != (ssize_t)chunk) {
> + fprintf(stderr, "%s: short read at %llu: %m\n",
> + __func__, (unsigned long long)offset);
> + return -1;
> + }
> +
> + if (memcmp(buffer, zeroes, chunk)) {
> + fprintf(stderr, "%s: %llu isn't a hole\n", __func__,
> + (unsigned long long)offset);
> + return -1;
> + }
> +
> + offset += chunk;
> + len -= chunk;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * The object has to describe the same memory as the coredump it was built
> + * from, and it has to describe it correctly.
> + */
> +int check_compact_coredump(int fd_object, int fd_reference)
> +{
> + ElfW(Phdr) *object = NULL, *reference = NULL;
> + size_t nr_object, nr_reference, i;
> + size_t io = 0, ir = 0;
> + int ret = -1;
> +
> + object = read_phdrs(fd_object, &nr_object);
> + reference = read_phdrs(fd_reference, &nr_reference);
> + if (!object || !reference)
> + goto out;
> +
> + /* Nothing may have been dropped and nothing may have been added. */
> + for (;;) {
> + __u64 start = 0, end = 0, start_ref = 0, end_ref = 0;
> + bool has, has_ref;
> +
> + has = next_range(object, nr_object, &io, &start, &end);
> + has_ref = next_range(reference, nr_reference, &ir, &start_ref,
> + &end_ref);
> + if (!has && !has_ref)
> + break;
> +
> + if (has != has_ref || start != start_ref || end != end_ref) {
> + fprintf(stderr, "%s: object covers 0x%llx-0x%llx, coredump 0x%llx-0x%llx\n",
> + __func__, (unsigned long long)start,
> + (unsigned long long)end,
> + (unsigned long long)start_ref,
> + (unsigned long long)end_ref);
> + goto out;
> + }
> + }
> +
> + for (i = 0; i < nr_object; i++) {
> + const ElfW(Phdr) *segment;
> + __u64 offset, dumped;
> +
> + if (object[i].p_type != PT_LOAD || !object[i].p_memsz)
> + continue;
> +
> + segment = find_segment(reference, nr_reference,
> + object[i].p_vaddr);
> + if (!segment) {
> + fprintf(stderr, "%s: 0x%llx isn't in the coredump\n",
> + __func__,
> + (unsigned long long)object[i].p_vaddr);
> + goto out;
> + }
> +
> + offset = object[i].p_vaddr - segment->p_vaddr;
> + dumped = offset < segment->p_filesz ?
> + segment->p_filesz - offset : 0;
> +
> + /* What the object carries is what the coredump had. */
> + if (object[i].p_filesz > dumped) {
> + fprintf(stderr, "%s: object carries %llu bytes the coredump doesn't have\n",
> + __func__,
> + (unsigned long long)(object[i].p_filesz - dumped));
> + goto out;
> + }
> +
> + if (compare_range(fd_object, object[i].p_offset, fd_reference,
> + segment->p_offset + offset,
> + object[i].p_filesz))
> + goto out;
> +
> + /* And what it left out was a hole. */
> + if (object[i].p_memsz > object[i].p_filesz &&
> + dumped > object[i].p_filesz) {
> + __u64 left_out = dumped - object[i].p_filesz;
> +
> + if (left_out > object[i].p_memsz - object[i].p_filesz)
> + left_out = object[i].p_memsz - object[i].p_filesz;
> +
> + if (check_zero_range(fd_reference,
> + segment->p_offset + offset +
> + object[i].p_filesz, left_out))
> + goto out;
> + }
> + }
> +
> + ret = 0;
> +out:
> + free(object);
> + free(reference);
> + return ret;
> +}
> +
> int create_detached_tmpfs(void)
> {
> int fd_context, fd_tmpfs;
> diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h
> index fe0a88a71b05..00d695b67b3f 100644
> --- a/tools/testing/selftests/coredump/coredump_test_helpers.h
> +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h
> @@ -26,6 +26,9 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file,
> off_t *coredump_size, bool *truncated,
> int fd_peer_pidfd);
> bool is_elf_core(int fd);
> +ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference,
> + off_t *coredump_size);
> +int check_compact_coredump(int fd_object, int fd_reference);
> int create_detached_tmpfs(void);
> int create_and_listen_unix_socket(const char *path);
> bool set_core_pattern(const char *pattern);
>
--
Thanks and Regards
R.Nageswara Sastry
next prev parent reply other threads:[~2026-09-09 16:07 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 23:09 [PATCH v2 00/22] coredump: allow to create sparse coredumps on the coredump socket Christian Brauner
2026-08-19 23:09 ` [PATCH v2 01/22] powerpc/spufs: don't dump more than the note supports Christian Brauner
2026-09-09 15:52 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 02/22] coredump: refuse negative skips Christian Brauner
2026-09-09 15:53 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 03/22] coredump: set the minimum send buffer size Christian Brauner
2026-09-09 15:53 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 04/22] selftests/coredump: discard the right amount after the coredump request Christian Brauner
2026-09-09 15:54 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 05/22] selftests/coredump: collapse the expected request check into the helper Christian Brauner
2026-09-09 15:54 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 06/22] selftests/coredump: add a separate helper header Christian Brauner
2026-09-09 15:55 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 07/22] coredump: pin the protocol struct sizes Christian Brauner
2026-09-09 15:55 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 08/22] coredump: move the negotiated mask into struct coredump_params Christian Brauner
2026-09-09 15:56 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 09/22] coredump: deduplicate the to_skip flush Christian Brauner
2026-09-09 15:56 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 10/22] coredump: make the dump helper return bool Christian Brauner
2026-09-09 15:59 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 11/22] coredump: always chunk writes Christian Brauner
2026-09-09 16:00 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 12/22] coredump: clean up coredump state handling Christian Brauner
2026-09-09 16:01 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 13/22] coredump: add COREDUMP_RECORDS to the coredump socket protocol Christian Brauner
2026-09-09 16:02 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 14/22] coredump: add COREDUMP_SPARSE " Christian Brauner
2026-09-09 16:02 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 15/22] tools: sync coredump.h header Christian Brauner
2026-09-09 16:03 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 16/22] coredump: send the coredump in records if requested Christian Brauner
2026-09-09 16:04 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 17/22] coredump: describe the holes when COREDUMP_SPARSE is negotiated Christian Brauner
2026-09-09 16:04 ` R Nageswara Sastry
2026-09-09 16:21 ` David Hildenbrand (Arm)
2026-08-19 23:09 ` [PATCH v2 18/22] selftests/coredump: test COREDUMP_RECORDS and COREDUMP_SPARSE Christian Brauner
2026-09-09 16:05 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 19/22] selftests/coredump: hand the record stream to a sink Christian Brauner
2026-09-09 16:05 ` R Nageswara Sastry
2026-08-19 23:09 ` [PATCH v2 20/22] selftests/coredump: put a hole in the middle of a sparse mapping Christian Brauner
2026-09-09 16:06 ` R Nageswara Sastry
2026-09-09 16:18 ` David Hildenbrand (Arm)
2026-08-19 23:09 ` [PATCH v2 21/22] selftests/coredump: simulate a blob store Christian Brauner
2026-09-09 16:06 ` R Nageswara Sastry [this message]
2026-08-19 23:09 ` [PATCH v2 22/22] selftests/coredump: show how to inspect the task to decide how the coredump should be sent Christian Brauner
2026-09-09 16:07 ` R Nageswara Sastry
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5563177c-96ca-40bd-a513-378b759fad47@linux.ibm.com \
--to=rnsastry@linux.ibm.com \
--cc=akpm@linux-foundation.org \
--cc=brauner@kernel.org \
--cc=david@kernel.org \
--cc=jack@suse.cz \
--cc=jalalonde@fb.com \
--cc=jalalonde@meta.com \
--cc=jannh@google.com \
--cc=josef@toxicpanda.com \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=osandov@osandov.com \
--cc=rppt@kernel.org \
--cc=shuah@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox