]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
coredump: capture crashing thread ID and name
authornoxiouz <atiurin@proton.me>
Fri, 13 Mar 2026 00:36:08 +0000 (00:36 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Wed, 18 Mar 2026 10:27:27 +0000 (10:27 +0000)
Add %I (TID in initial PID namespace) to the core_pattern, so the
kernel passes the crashing thread's TID to systemd-coredump. Use it
to read the thread's comm name from /proc/<tid>/comm and log both as
new journal fields:

  COREDUMP_TID=       — TID of the crashing thread
  COREDUMP_THREAD_NAME= — comm name of the crashing thread

These fields are also stored as xattrs on external coredump files
(user.coredump.tid, user.coredump.thread_name) and displayed by
coredumpctl info alongside the PID line.

For single-threaded processes the TID equals the PID and thread_name
equals comm; for multi-threaded programs with named worker threads
(pthread_setname_np / PR_SET_NAME) this identifies which thread
crashed without needing to open the coredump file itself.

The new fields are optional in the socket forwarding path, so older
systemd-coredump senders are handled gracefully.

Co-developed-by: Claude <claude@anthropic.com>
src/coredump/coredump-context.c
src/coredump/coredump-context.h
src/coredump/coredump-submit.c
src/coredump/coredumpctl.c
sysctl.d/50-coredump.conf.in
test/units/TEST-87-AUX-UTILS-VM.coredump.sh

index 921cfe5de76505129a70022a204727b8ffb039c6..574d3ecd5292892c31705b9dab85d47ab66fde8b 100644 (file)
@@ -32,10 +32,12 @@ static const char * const metadata_field_table[_META_MAX] = {
         [META_ARGV_HOSTNAME]  = "COREDUMP_HOSTNAME=",
         [META_ARGV_DUMPABLE]  = "COREDUMP_DUMPABLE=",
         [META_ARGV_PIDFD]     = "COREDUMP_BY_PIDFD=",
+        [META_ARGV_TID]       = "COREDUMP_TID=",
         [META_COMM]           = "COREDUMP_COMM=",
         [META_EXE]            = "COREDUMP_EXE=",
         [META_UNIT]           = "COREDUMP_UNIT=",
         [META_PROC_AUXV]      = "COREDUMP_PROC_AUXV=",
+        [META_THREAD_NAME]    = "COREDUMP_THREAD_NAME=",
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField);
@@ -49,6 +51,7 @@ void coredump_context_done(CoredumpContext *context) {
         free(context->exe);
         free(context->unit);
         free(context->auxv);
+        free(context->thread_name);
         safe_close(context->mount_tree_fd);
         iovw_done_free(&context->iovw);
         safe_close(context->input_fd);
@@ -228,6 +231,12 @@ int coredump_context_build_iovw(CoredumpContext *context) {
         if (r < 0)
                 return log_error_errno(r, "Failed to add COREDUMP_COMM= field: %m");
 
+        if (context->tid > 0)
+                (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_TID=", PID_FMT, context->tid);
+
+        if (context->thread_name)
+                (void) iovw_put_string_field(&context->iovw, "COREDUMP_THREAD_NAME=", context->thread_name);
+
         if (context->exe)
                 (void) iovw_put_string_field(&context->iovw, "COREDUMP_EXE=", context->exe);
 
@@ -339,6 +348,12 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) {
         if (r < 0)
                 return log_error_errno(r, "Failed to get COMM: %m");
 
+        if (context->tid > 0) {
+                r = pid_get_comm(context->tid, &context->thread_name);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to get comm for thread "PID_FMT", ignoring: %m", context->tid);
+        }
+
         r = get_process_exe(pid, &context->exe);
         if (r < 0)
                 log_warning_errno(r, "Failed to get EXE, ignoring: %m");
@@ -465,6 +480,12 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool
                 context->got_pidfd = 1;
                 return 0;
         }
+        case META_ARGV_TID:
+                r = parse_pid(s, &context->tid);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse TID \"%s\", ignoring: %m", s);
+                return 0;
+
         case META_COMM:
                 return free_and_strdup_warn(&context->comm, s);
 
@@ -474,6 +495,9 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool
         case META_UNIT:
                 return free_and_strdup_warn(&context->unit, s);
 
+        case META_THREAD_NAME:
+                return free_and_strdup_warn(&context->thread_name, s);
+
         case META_PROC_AUXV: {
                 char *t = memdup_suffix0(s, size);
                 if (!t)
index f2d44c141c6237e7f53566befda8b489aa25bb4f..7fedfde2adbf7296a88e1ecb0cb65e9014913d5d 100644 (file)
@@ -23,6 +23,7 @@ typedef enum MetadataField {
         META_ARGV_HOSTNAME = _META_ARGV_REQUIRED,  /* %h: hostname */
         META_ARGV_DUMPABLE,     /* %d: as set by the kernel */
         META_ARGV_PIDFD,        /* %F: pidfd of the process, since v6.16 */
+        META_ARGV_TID,          /* %I: TID of the crashing thread, as seen in the initial pid namespace */
         /* If new fields are added, they should be added here, to maintain compatibility
          * with callers which don't know about the new fields. */
         _META_ARGV_MAX,
@@ -40,6 +41,7 @@ typedef enum MetadataField {
         META_EXE,
         META_UNIT,
         META_PROC_AUXV,
+        META_THREAD_NAME,
         _META_MAX,
         _META_INVALID = -EINVAL,
 } MetadataField;
@@ -53,11 +55,13 @@ struct CoredumpContext {
         uint64_t rlimit;   /* META_ARGV_RLIMIT */
         char *hostname;    /* META_ARGV_HOSTNAME */
         unsigned dumpable; /* META_ARGV_DUMPABLE */
+        pid_t tid;         /* META_ARGV_TID */
         char *comm;        /* META_COMM */
         char *exe;         /* META_EXE */
         char *unit;        /* META_UNIT */
         char *auxv;        /* META_PROC_AUXV */
         size_t auxv_size;  /* META_PROC_AUXV */
+        char *thread_name; /* META_THREAD_NAME */
         bool got_pidfd;    /* META_ARGV_PIDFD */
         bool same_pidns;
         bool forwarded;
index 9134697d5b8b74d1ba2b5b211fdcf721db718898..6cfbda0f46b59ae26884e8656c3517c9e62c0302 100644 (file)
@@ -201,6 +201,10 @@ static int fix_xattr(int fd, const CoredumpContext *context) {
         RET_GATHER(r, fix_xattr_one(fd, "user.coredump.hostname", context->hostname));
         RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm));
         RET_GATHER(r, fix_xattr_one(fd, "user.coredump.exe", context->exe));
+        if (context->tid > 0) {
+                RET_GATHER(r, fix_xattr_format(fd, "user.coredump.tid", PID_FMT, context->tid));
+                RET_GATHER(r, fix_xattr_one(fd, "user.coredump.thread_name", context->thread_name));
+        }
 
         return r;
 }
index 64f7ae99a10f0237df6d280ad1d5a4304ed2e99e..618a464fcc9bf9bf453813221c80e51a8480c1f3 100644 (file)
@@ -633,7 +633,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
                 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
                 *message = NULL, *timestamp = NULL, *filename = NULL,
                 *truncated = NULL, *coredump = NULL,
-                *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL;
+                *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL,
+                *tid = NULL, *thread_name = NULL;
         const void *d;
         size_t l;
         bool normal_coredump;
@@ -667,6 +668,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
                 RETRIEVE(d, l, "COREDUMP_PACKAGE_NAME", pkgmeta_name);
                 RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version);
                 RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json);
+                RETRIEVE(d, l, "COREDUMP_TID", tid);
+                RETRIEVE(d, l, "COREDUMP_THREAD_NAME", thread_name);
                 RETRIEVE(d, l, "_BOOT_ID", boot_id);
                 RETRIEVE(d, l, "_MACHINE_ID", machine_id);
                 RETRIEVE(d, l, "MESSAGE", message);
@@ -686,6 +689,13 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
                         "           PID: %s%s%s\n",
                         ansi_highlight(), strna(pid), ansi_normal());
 
+        if (tid) {
+                if (thread_name)
+                        fprintf(file, "           TID: %s (%s)\n", tid, thread_name);
+                else
+                        fprintf(file, "           TID: %s\n", tid);
+        }
+
         if (uid) {
                 uid_t n;
 
index fe8f7670b06379c0053ffd61dcb43ef5524dc111..0e6f370f473815f72e12ca25e0b237c172858abe 100644 (file)
@@ -13,7 +13,7 @@
 # the core dump.
 #
 # See systemd-coredump(8) and core(5).
-kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F
+kernel.core_pattern=|{{LIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F %I
 
 # Allow 16 coredumps to be dispatched in parallel by the kernel.
 # We collect metadata from /proc/%P/, and thus need to make sure the crashed
index b3f702a2b0d7fe73a5e0234ef637f820147a6425..b3a3de76960a3c51df893136022dbd7f7bd67d85 100755 (executable)
@@ -150,6 +150,11 @@ coredumpctl info foo bar baz "${CORE_TEST_BIN##*/}"
 coredumpctl info COREDUMP_EXE="$CORE_TEST_BIN"
 coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= COREDUMP_EXE="$CORE_TEST_BIN"
 
+# Check that COREDUMP_TID= is present and displayed by coredumpctl info
+coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null
+# Check the field is queryable in the journal
+coredumpctl -F COREDUMP_TID
+
 coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN"
 SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN"
 coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}"