From: "Chang S. Bae" <chang.seok.bae@intel.com>
To: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Cc: x86@kernel.org, tglx@linutronix.de, mingo@redhat.com,
bp@alien8.de, dave.hansen@linux.intel.com, shuah@kernel.org,
chang.seok.bae@intel.com
Subject: [PATCH 5/6] selftests/x86/xstate: Add coredump test
Date: Tue, 10 Nov 2026 03:16:02 +0000 [thread overview]
Message-ID: <20261110031603.698635-6-chang.seok.bae@intel.com> (raw)
In-Reply-To: <20261110031603.698635-1-chang.seok.bae@intel.com>
The existing ptrace test exercises much of the XSTATE ABI handling, but
it does not cover the coredump path entirely. Add a dedicated test to
validate XSTATE handling in core dumps.
Refactor the XSAVE buffer validation code from the ptrace test for reuse.
Also, move most of the coredump-specific preparation and handling into a
header so that the PKRU test outside the x86 selftest can reuse it.
Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
---
tools/testing/selftests/x86/coredump.h | 229 +++++++++++++++++++++++++
tools/testing/selftests/x86/xstate.c | 99 ++++++++---
2 files changed, 308 insertions(+), 20 deletions(-)
create mode 100644 tools/testing/selftests/x86/coredump.h
diff --git a/tools/testing/selftests/x86/coredump.h b/tools/testing/selftests/x86/coredump.h
new file mode 100644
index 000000000000..8aef96667f87
--- /dev/null
+++ b/tools/testing/selftests/x86/coredump.h
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#ifndef __SELFTESTS_X86_COREDUMP_H
+#define __SELFTESTS_X86_COREDUMP_H
+
+#include <link.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sys/resource.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include "kselftest.h"
+
+#define CORE_PATTERN_PATH "/proc/sys/kernel/core_pattern"
+#ifndef CORENAME_MAX_SIZE
+#define CORENAME_MAX_SIZE 128
+#endif
+
+static char orig_pattern[CORENAME_MAX_SIZE];
+
+static inline bool get_core_pattern(char *buf, int size)
+{
+ bool ret;
+ FILE *fp;
+
+ if (!buf)
+ return false;
+
+ fp = fopen(CORE_PATTERN_PATH, "r");
+ if (!fp)
+ return false;
+
+ ret = fgets(buf, size, fp) != NULL;
+ if (ret)
+ buf[strcspn(buf, "\n")] = 0;
+
+ fclose(fp);
+
+ return ret;
+}
+
+static inline bool set_core_pattern(const char *pattern)
+{
+ bool ret;
+ FILE *fp;
+
+ if (!pattern)
+ return false;
+
+ fp = fopen(CORE_PATTERN_PATH, "w");
+ if (!fp)
+ return false;
+
+ ret = fprintf(fp, "%s\n", pattern) >= 0;
+
+ fclose(fp);
+
+ return ret;
+}
+
+static inline bool save_core_pattern(void)
+{
+ return get_core_pattern(orig_pattern, CORENAME_MAX_SIZE);
+}
+
+static inline bool set_test_core_pattern(void)
+{
+ const char test_pattern[] = "core.%p";
+
+ if (!strcmp(orig_pattern, test_pattern))
+ return true;
+
+ return set_core_pattern(test_pattern);
+}
+
+static inline bool restore_core_pattern(void)
+{
+ return set_core_pattern(orig_pattern);
+}
+
+static char core_dir[] = "/tmp/x86-coredump-XXXXXX";
+
+static struct corefile {
+ bool dumped;
+ int fd;
+ struct stat st;
+ char *image;
+ char path[PATH_MAX];
+} core_file = { false, -1, { }, NULL, "\0" };
+
+static inline void corefile_reset(void)
+{
+ close(core_file.fd);
+ unlink(core_file.path);
+
+ if (core_file.image != NULL && core_file.image != MAP_FAILED)
+ munmap((void *)core_file.image, core_file.st.st_size);
+
+ core_file = (struct corefile){ .dumped = false, .fd = -1, .st = { },
+ .image = NULL, .path = "\0" };
+}
+
+static inline void coredump_cleanup(void)
+{
+ if (core_file.dumped)
+ corefile_reset();
+
+ rmdir(core_dir);
+ strcpy(core_dir, "/tmp/x86-coredump-XXXXXX");
+ restore_core_pattern();
+}
+
+/*
+ * Create a temporary directory for the core file and configure core_pattern to
+ * use a predictable file name. Save the original pattern so it can be restored
+ * after the test.
+ */
+static inline bool coredump_prepare(void)
+{
+ struct rlimit lim = { RLIM_INFINITY, RLIM_INFINITY };
+
+ if (setrlimit(RLIMIT_CORE, &lim)) {
+ ksft_test_result_skip("Failed to set rlimit. core Try sudo.\n");
+ return false;
+ }
+
+ if (!save_core_pattern()) {
+ ksft_test_result_skip("Failed to save core pattern.\n");
+ return false;
+ }
+
+ if (!set_test_core_pattern()) {
+ ksft_test_result_skip("Failed to update core pattern. Try sudo.\n");
+ return false;
+ }
+
+ if (!mkdtemp(core_dir)) {
+ coredump_cleanup();
+ ksft_test_result_skip("Failed to create test folder.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/* Always call this for any failed exit after coredump_prepare() */
+static inline void coredump_exit_fail_msg(const char *msg)
+{
+ coredump_cleanup();
+ ksft_exit_fail_msg("%s\n", msg);
+}
+
+static inline void coredump_trigger(void)
+{
+ volatile unsigned int *ptr = NULL;
+
+ *ptr = 0xDEAD;
+}
+
+static inline void corefile_map(pid_t child)
+{
+ snprintf(core_file.path, sizeof(core_file.path), "%s/core.%d", core_dir, child);
+
+ core_file.fd = open(core_file.path, O_RDONLY);
+ if (core_file.fd < 0)
+ coredump_exit_fail_msg("Cannot open core file.");
+
+ core_file.dumped = true;
+
+ if (fstat(core_file.fd, &core_file.st))
+ coredump_exit_fail_msg("Invalid core file.");
+
+ core_file.image = mmap(NULL, core_file.st.st_size, PROT_READ, MAP_PRIVATE, core_file.fd, 0);
+ if (core_file.image == MAP_FAILED)
+ coredump_exit_fail_msg("Cannot map core file.");
+}
+
+#ifndef NT_X86_XSTATE
+#define NT_X86_XSTATE 0x202
+#endif
+
+#define next_pos(pos, size) ((pos) + (((size) + 3) & ~3))
+
+/* Program Header */
+#define phdr_ptr(core, offset) (ElfW(Phdr) *)(core + offset)
+
+/* Note Header */
+#define nhdr_ptr(core, offset) (ElfW(Nhdr) *)(core + offset)
+
+/*
+ * Locate the NT_X86_XSTATE note in the ELF core image and return a pointer to
+ * its XSTATE data.
+ */
+static inline char *retrieve_corefile_xbuf(void)
+{
+ ElfW(Ehdr) *ehdr;
+ ElfW(Phdr) *phdr;
+ ElfW(Nhdr) *nhdr;
+ size_t pos, end;
+ int i;
+
+ ehdr = (ElfW(Ehdr) *)core_file.image;
+ phdr = phdr_ptr(core_file.image, ehdr->e_phoff);
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ if (phdr[i].p_type != PT_NOTE)
+ continue;
+
+ end = phdr[i].p_offset + phdr[i].p_filesz;
+ pos = phdr[i].p_offset;
+
+ while (pos + sizeof(*nhdr) <= end) {
+ nhdr = nhdr_ptr(core_file.image, pos);
+ pos = next_pos(pos + sizeof(*nhdr), nhdr->n_namesz);
+
+ if (nhdr->n_type == NT_X86_XSTATE)
+ return core_file.image + pos;
+
+ pos = next_pos(pos, nhdr->n_descsz);
+ }
+ }
+
+ coredump_exit_fail_msg("No NT_X86_XSTATE was found.");
+ _exit(1);
+}
+
+#endif /* __SELFTESTS_X86_COREDUMP_H */
diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c
index a2f25ca848fb..fd04fd6fa631 100644
--- a/tools/testing/selftests/x86/xstate.c
+++ b/tools/testing/selftests/x86/xstate.c
@@ -14,6 +14,7 @@
#include "helpers.h"
#include "xstate.h"
+#include "coredump.h"
/*
* The userspace xstate test suite is designed to be generic and operates
@@ -243,29 +244,11 @@ static struct xsave_buffer *stashed_xbuf;
* Ptrace test for the ABI format as described in arch/x86/include/asm/user.h
*/
-/*
- * Make sure the ptracee has the expanded kernel buffer on the first use.
- * Then, initialize the state before performing the state injection from
- * the ptracer. For non-dynamic states, this is benign.
- */
-static inline void ptracee_touch_xstate(void)
-{
- load_rand_xstate(&xstate, stashed_xbuf);
- load_init_xstate(&xstate, stashed_xbuf);
-}
-
-static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
- struct xsave_buffer *xbuf,
- struct xsave_buffer *expected)
+static bool validate_abi_xstate(struct xsave_buffer *xbuf,
+ struct xsave_buffer *expected)
{
unsigned int failed = 0;
- if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
-
- if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
- ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
-
/*
* To track this whole result only, use printf() for skipping sub-test
* counting.
@@ -295,6 +278,30 @@ static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
return failed == 0;
}
+/*
+ * Make sure the ptracee has the expanded kernel buffer on the first use.
+ * Then, initialize the state before performing the state injection from
+ * the ptracer. For non-dynamic states, this is benign.
+ */
+static inline void ptracee_touch_xstate(void)
+{
+ load_rand_xstate(&xstate, stashed_xbuf);
+ load_init_xstate(&xstate, stashed_xbuf);
+}
+
+static bool validate_ptracer_xstate(pid_t target, struct iovec iov,
+ struct xsave_buffer *xbuf,
+ struct xsave_buffer *expected)
+{
+ if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
+
+ if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
+ ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
+
+ return validate_abi_xstate(xbuf, expected);
+}
+
/*
* Ptracer injects the randomized xstate data. It also reads before and
* after that, which will execute the kernel's state copy functions.
@@ -388,6 +395,57 @@ static void test_ptrace(void)
free(stashed_xbuf);
}
+static void test_coredump(void)
+{
+ struct xsave_buffer *xbuf, *corefile_xbuf;
+ pid_t child;
+ int status;
+
+ if (!coredump_prepare())
+ return;
+
+ ksft_print_msg("[RUN]\t%s: validate coredump.\n", xstate.name);
+
+ /*
+ * Use a shared buffer to retain the expected XSTATE across fork() and
+ * compare it against the XSTATE saved in the core dump.
+ */
+ xbuf = mmap(NULL, get_xbuf_size(),
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (xbuf == MAP_FAILED)
+ coredump_exit_fail_msg("mmap() for the shared xsave buffer failed.");
+
+ child = fork();
+ if (child < 0)
+ coredump_exit_fail_msg("fork() failed.");
+
+ if (!child) {
+ if (chdir(core_dir)) {
+ printf("\tchdir() failed.\n");
+ _exit(1);
+ }
+ load_rand_xstate(&xstate, xbuf);
+ coredump_trigger();
+ _exit(1);
+ }
+
+ if (waitpid(child, &status, 0) != child ||
+ !WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV)
+ coredump_exit_fail_msg("Child didn't SIGSEGV successfully.");
+
+ corefile_map(child);
+ corefile_xbuf = (struct xsave_buffer *)retrieve_corefile_xbuf();
+
+ if (validate_abi_xstate(corefile_xbuf, xbuf))
+ ksft_test_result_pass("%s for %s\n", __func__, xstate.name);
+ else
+ ksft_test_result_fail("%s for %s\n", __func__, xstate.name);
+
+ munmap(xbuf, get_xbuf_size());
+ coredump_cleanup();
+}
+
/*
* Test signal delivery for the ABI compatibility.
* See the ABI format: arch/x86/include/uapi/asm/sigcontext.h
@@ -511,6 +569,7 @@ static struct test_info {
{ .nr = 1, .fn = test_context_switch, },
{ .nr = 2, .fn = test_ptrace, },
{ .nr = 2, .fn = test_signal, },
+ { .nr = 1, .fn = test_coredump, },
};
int test_xstate(uint32_t feature_num)
--
2.53.0
next prev parent reply other threads:[~2026-11-10 3:43 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 3:15 [PATCH 0/6] selftests/x86/xstate: more test cases for coredump and ptrace Chang S. Bae
2026-11-10 3:15 ` [PATCH 1/6] selftests/x86/xstate: Track testing status by kselftest API Chang S. Bae
2026-11-10 3:15 ` [PATCH 2/6] selftests/x86/xstate: Pre-allocate ptracee XSAVE buffer Chang S. Bae
2026-11-10 3:16 ` [PATCH 3/6] selftests/x86/xstate: Validate XSTATE_BV in ptrace test Chang S. Bae
2026-11-10 3:16 ` [PATCH 4/6] selftests/x86/xstate: Validate state reset " Chang S. Bae
2026-11-10 3:16 ` Chang S. Bae [this message]
2026-11-10 3:16 ` [PATCH 6/6] selftests/mm: Add coredump test for x86 Chang S. Bae
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=20261110031603.698635-6-chang.seok.bae@intel.com \
--to=chang.seok.bae@intel.com \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=shuah@kernel.org \
--cc=tglx@linutronix.de \
--cc=x86@kernel.org \
/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
all inboxes | Powered by JetHome®