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:
@@ -32,10 +32,12 @@ static const char * const metadata_field_table[_META_MAX] = {
|
|||||||
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
||||||
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
||||||
[META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
|
[META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
|
||||||
|
[META_ARGV_TID] = "COREDUMP_TID=",
|
||||||
[META_COMM] = "COREDUMP_COMM=",
|
[META_COMM] = "COREDUMP_COMM=",
|
||||||
[META_EXE] = "COREDUMP_EXE=",
|
[META_EXE] = "COREDUMP_EXE=",
|
||||||
[META_UNIT] = "COREDUMP_UNIT=",
|
[META_UNIT] = "COREDUMP_UNIT=",
|
||||||
[META_PROC_AUXV] = "COREDUMP_PROC_AUXV=",
|
[META_PROC_AUXV] = "COREDUMP_PROC_AUXV=",
|
||||||
|
[META_THREAD_NAME] = "COREDUMP_THREAD_NAME=",
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField);
|
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->exe);
|
||||||
free(context->unit);
|
free(context->unit);
|
||||||
free(context->auxv);
|
free(context->auxv);
|
||||||
|
free(context->thread_name);
|
||||||
safe_close(context->mount_tree_fd);
|
safe_close(context->mount_tree_fd);
|
||||||
iovw_done_free(&context->iovw);
|
iovw_done_free(&context->iovw);
|
||||||
safe_close(context->input_fd);
|
safe_close(context->input_fd);
|
||||||
@@ -228,6 +231,12 @@ int coredump_context_build_iovw(CoredumpContext *context) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to add COREDUMP_COMM= field: %m");
|
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)
|
if (context->exe)
|
||||||
(void) iovw_put_string_field(&context->iovw, "COREDUMP_EXE=", 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)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to get COMM: %m");
|
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);
|
r = get_process_exe(pid, &context->exe);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
log_warning_errno(r, "Failed to get EXE, ignoring: %m");
|
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;
|
context->got_pidfd = 1;
|
||||||
return 0;
|
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:
|
case META_COMM:
|
||||||
return free_and_strdup_warn(&context->comm, s);
|
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:
|
case META_UNIT:
|
||||||
return free_and_strdup_warn(&context->unit, s);
|
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: {
|
case META_PROC_AUXV: {
|
||||||
char *t = memdup_suffix0(s, size);
|
char *t = memdup_suffix0(s, size);
|
||||||
if (!t)
|
if (!t)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ typedef enum MetadataField {
|
|||||||
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
||||||
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
||||||
META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */
|
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
|
/* If new fields are added, they should be added here, to maintain compatibility
|
||||||
* with callers which don't know about the new fields. */
|
* with callers which don't know about the new fields. */
|
||||||
_META_ARGV_MAX,
|
_META_ARGV_MAX,
|
||||||
@@ -40,6 +41,7 @@ typedef enum MetadataField {
|
|||||||
META_EXE,
|
META_EXE,
|
||||||
META_UNIT,
|
META_UNIT,
|
||||||
META_PROC_AUXV,
|
META_PROC_AUXV,
|
||||||
|
META_THREAD_NAME,
|
||||||
_META_MAX,
|
_META_MAX,
|
||||||
_META_INVALID = -EINVAL,
|
_META_INVALID = -EINVAL,
|
||||||
} MetadataField;
|
} MetadataField;
|
||||||
@@ -53,11 +55,13 @@ struct CoredumpContext {
|
|||||||
uint64_t rlimit; /* META_ARGV_RLIMIT */
|
uint64_t rlimit; /* META_ARGV_RLIMIT */
|
||||||
char *hostname; /* META_ARGV_HOSTNAME */
|
char *hostname; /* META_ARGV_HOSTNAME */
|
||||||
unsigned dumpable; /* META_ARGV_DUMPABLE */
|
unsigned dumpable; /* META_ARGV_DUMPABLE */
|
||||||
|
pid_t tid; /* META_ARGV_TID */
|
||||||
char *comm; /* META_COMM */
|
char *comm; /* META_COMM */
|
||||||
char *exe; /* META_EXE */
|
char *exe; /* META_EXE */
|
||||||
char *unit; /* META_UNIT */
|
char *unit; /* META_UNIT */
|
||||||
char *auxv; /* META_PROC_AUXV */
|
char *auxv; /* META_PROC_AUXV */
|
||||||
size_t auxv_size; /* META_PROC_AUXV */
|
size_t auxv_size; /* META_PROC_AUXV */
|
||||||
|
char *thread_name; /* META_THREAD_NAME */
|
||||||
bool got_pidfd; /* META_ARGV_PIDFD */
|
bool got_pidfd; /* META_ARGV_PIDFD */
|
||||||
bool same_pidns;
|
bool same_pidns;
|
||||||
bool forwarded;
|
bool forwarded;
|
||||||
|
|||||||
@@ -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.hostname", context->hostname));
|
||||||
RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm));
|
RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm));
|
||||||
RET_GATHER(r, fix_xattr_one(fd, "user.coredump.exe", context->exe));
|
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;
|
return r;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -633,7 +633,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
|
|||||||
*slice = NULL, *cgroup = NULL, *owner_uid = NULL,
|
*slice = NULL, *cgroup = NULL, *owner_uid = NULL,
|
||||||
*message = NULL, *timestamp = NULL, *filename = NULL,
|
*message = NULL, *timestamp = NULL, *filename = NULL,
|
||||||
*truncated = NULL, *coredump = 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;
|
const void *d;
|
||||||
size_t l;
|
size_t l;
|
||||||
bool normal_coredump;
|
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_NAME", pkgmeta_name);
|
||||||
RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version);
|
RETRIEVE(d, l, "COREDUMP_PACKAGE_VERSION", pkgmeta_version);
|
||||||
RETRIEVE(d, l, "COREDUMP_PACKAGE_JSON", pkgmeta_json);
|
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, "_BOOT_ID", boot_id);
|
||||||
RETRIEVE(d, l, "_MACHINE_ID", machine_id);
|
RETRIEVE(d, l, "_MACHINE_ID", machine_id);
|
||||||
RETRIEVE(d, l, "MESSAGE", message);
|
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",
|
" PID: %s%s%s\n",
|
||||||
ansi_highlight(), strna(pid), ansi_normal());
|
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) {
|
if (uid) {
|
||||||
uid_t n;
|
uid_t n;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# the core dump.
|
# the core dump.
|
||||||
#
|
#
|
||||||
# See systemd-coredump(8) and core(5).
|
# 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.
|
# 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
|
# 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="$CORE_TEST_BIN"
|
||||||
coredumpctl info COREDUMP_EXE=aaaaa COREDUMP_EXE= 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"
|
coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN"
|
||||||
SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$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##*/}"
|
coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_BIN##*/}"
|
||||||
|
|||||||
Reference in New Issue
Block a user