coredump: capture crashing thread ID and name

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>
This commit is contained in:
noxiouz
2026-03-18 10:27:27 +00:00
committed by Luca Boccassi
parent 8a2c423870
commit 4c7af7d9d1
6 changed files with 49 additions and 2 deletions
+24
View 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)
+4
View 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;
+4
View 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;
}
+11 -1
View 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;
+1 -1
View 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
@@ -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##*/}"