From 0d195ea9316163f2a4fda17e6ecfc2282efbe4e7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 23 Aug 2026 03:42:55 +0900 Subject: [PATCH] coredump: assign random UUID for each crash Then, make it possible to specify core by the UUID in coredumpctl. This is typically useful to specify a crash that the same executable crashes multiple times, as PID may not be unique. --- man/coredumpctl.xml | 10 ++++++- shell-completion/zsh/_coredumpctl | 3 +- src/coredump/coredump-backtrace.c | 5 ++++ src/coredump/coredump-submit.c | 4 +++ src/coredump/coredumpctl-info.c | 31 ++++++++++++++++++++- src/coredump/coredumpctl-journal.c | 8 +++++- src/coredump/coredumpctl-list.c | 8 ++++-- test/units/TEST-87-AUX-UTILS-VM.coredump.sh | 24 ++++++++++++++++ 8 files changed, 86 insertions(+), 7 deletions(-) diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml index 3695d13323b..8300c51b454 100644 --- a/man/coredumpctl.xml +++ b/man/coredumpctl.xml @@ -26,7 +26,7 @@ coredumpctl OPTIONS COMMAND - PID|COMM|EXE|MATCH + ID|PID|COMM|EXE|MATCH @@ -55,6 +55,14 @@ The output is designed to be human-readable and contains a table with the following columns: + + ID + The UUID of the crash assigned by systemd-coredump. + + + + + TIME The timestamp of the crash, as reported by the kernel. diff --git a/shell-completion/zsh/_coredumpctl b/shell-completion/zsh/_coredumpctl index ad4ba107f53..92d3593bf14 100644 --- a/shell-completion/zsh/_coredumpctl +++ b/shell-completion/zsh/_coredumpctl @@ -17,7 +17,8 @@ _coredumpctl_commands(){ local -a _dumps cmd="${${_coredumpctl_cmds[(r)$words[1]:*]%%:*}}" if (( $#cmd )); then - _dumps=( "${(f)$(coredumpctl list -q --no-legend 2>/dev/null | awk 'BEGIN{OFS=":"} {sub(/[[ \t]+/, ""); print $4,$0}')}" ) + # $5 is PID: columns are ID($1), DATE($2), TIME($3), TIMEZONE($4), PID($5) + _dumps=( "${(f)$(coredumpctl list -q --no-legend 2>/dev/null | awk 'BEGIN{OFS=":"} {sub(/[[ \t]+/, ""); print $5,$0}')}" ) if [[ -n "$_dumps" ]]; then _describe -V -t pids 'coredumps' _dumps else diff --git a/src/coredump/coredump-backtrace.c b/src/coredump/coredump-backtrace.c index 9af7b5adb96..6396c8d28bc 100644 --- a/src/coredump/coredump-backtrace.c +++ b/src/coredump/coredump-backtrace.c @@ -2,6 +2,7 @@ #include +#include "sd-id128.h" #include "sd-journal.h" #include "sd-messages.h" @@ -32,6 +33,10 @@ int coredump_backtrace(int argc, char *argv[]) { (void) iovw_replace_string_field(&context.iovw, "MESSAGE_ID=", SD_MESSAGE_BACKTRACE_STR); + sd_id128_t id; + if (sd_id128_randomize(&id) >= 0) + (void) iovw_replace_string_field(&context.iovw, "COREDUMP_ID=", SD_ID128_TO_STRING(id)); + for (;;) { r = journal_importer_process_data(&importer); if (r < 0) diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 55f8b6e9e19..c0a1dadcca3 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -726,6 +726,10 @@ int coredump_submit(const CoredumpConfig *config, CoredumpContext *context) { (void) iovw_put_string_field(&context->iovw, "MESSAGE=", core_message); + sd_id128_t id; + if (sd_id128_randomize(&id) >= 0) + (void) iovw_put_string_field(&context->iovw, "COREDUMP_ID=", SD_ID128_TO_STRING(id)); + if (filename) (void) iovw_put_string_field(&context->iovw, "COREDUMP_FILENAME=", filename); if (truncated) diff --git a/src/coredump/coredumpctl-info.c b/src/coredump/coredumpctl-info.c index 8d98271d2d5..30d455bf262 100644 --- a/src/coredump/coredumpctl-info.c +++ b/src/coredump/coredumpctl-info.c @@ -117,7 +117,7 @@ error: static int print_list(FILE *file, sd_journal *j, Table *t) { _cleanup_free_ char - *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, + *mid = NULL, *cid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, *sgnl = NULL, *exe = NULL, *comm = NULL, *filename = NULL, *truncated = NULL; const void *d; @@ -127,6 +127,7 @@ static int print_list(FILE *file, sd_journal *j, Table *t) { const char *present = NULL, *color = NULL; uint64_t size = UINT64_MAX; bool normal_coredump, has_inline_coredump; + sd_id128_t coredump_id = SD_ID128_NULL; uid_t uid_as_int = UID_INVALID; gid_t gid_as_int = GID_INVALID; pid_t pid_as_int = 0; @@ -137,6 +138,7 @@ static int print_list(FILE *file, sd_journal *j, Table *t) { SD_JOURNAL_FOREACH_DATA(j, d, l) { RETRIEVE(d, l, "MESSAGE_ID", mid); + RETRIEVE(d, l, "COREDUMP_ID", cid); RETRIEVE(d, l, "COREDUMP_PID", pid); RETRIEVE(d, l, "COREDUMP_UID", uid); RETRIEVE(d, l, "COREDUMP_GID", gid); @@ -156,6 +158,8 @@ static int print_list(FILE *file, sd_journal *j, Table *t) { return 0; } + if (cid) + (void) sd_id128_from_string(cid, &coredump_id); (void) parse_uid(uid, &uid_as_int); (void) parse_gid(gid, &gid_as_int); (void) parse_pid(pid, &pid_as_int); @@ -184,6 +188,13 @@ static int print_list(FILE *file, sd_journal *j, Table *t) { if (STRPTR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0) present = "truncated"; + if (sd_id128_is_null(coredump_id)) + r = table_add_cell(t, /* ret_cell= */ NULL, TABLE_EMPTY, /* data= */ NULL); + else + r = table_add_cell(t, /* ret_cell= */ NULL, TABLE_ID128, &coredump_id); + if (r < 0) + return table_log_add_error(r); + r = table_add_many( t, TABLE_TIMESTAMP, ts, @@ -203,6 +214,7 @@ static int print_list(FILE *file, sd_journal *j, Table *t) { typedef enum CoredumpField { COREDUMP_FIELD_MID, + COREDUMP_FIELD_ID, COREDUMP_FIELD_PID, COREDUMP_FIELD_UID, COREDUMP_FIELD_GID, @@ -234,6 +246,7 @@ typedef enum CoredumpField { static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = { [COREDUMP_FIELD_MID] = "MESSAGE_ID", + [COREDUMP_FIELD_ID] = "COREDUMP_ID", [COREDUMP_FIELD_PID] = "COREDUMP_PID", [COREDUMP_FIELD_UID] = "COREDUMP_UID", [COREDUMP_FIELD_GID] = "COREDUMP_GID", @@ -265,6 +278,7 @@ static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = { typedef struct CoredumpFields { char *fields[_COREDUMP_FIELD_MAX]; + sd_id128_t coredump_id; bool normal_coredump; const char *storage_state; /* points to a static string, not owned */ const char *storage_color; /* points to a static string, not owned */ @@ -298,6 +312,12 @@ static int coredump_fields_load(sd_journal *j, CoredumpFields *f) { f->normal_coredump = streq_ptr(f->fields[COREDUMP_FIELD_MID], SD_MESSAGE_COREDUMP_STR); + if (f->fields[COREDUMP_FIELD_ID]) { + r = sd_id128_from_string(f->fields[COREDUMP_FIELD_ID], &f->coredump_id); + if (r < 0) + log_debug_errno(r, "Failed to parse coredump ID '%s', ignoring: %m", f->fields[COREDUMP_FIELD_ID]); + } + if (f->fields[COREDUMP_FIELD_FILENAME]) { r = resolve_filename(arg_root, &f->fields[COREDUMP_FIELD_FILENAME]); if (r < 0) @@ -453,6 +473,14 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (arg_full) table_set_width(table, 0); + if (!sd_id128_is_null(f.coredump_id)) { + r = table_add_many(table, + TABLE_FIELD, "ID", + TABLE_ID128, f.coredump_id); + if (r < 0) + return table_log_add_error(r); + } + if (f.fields[COREDUMP_FIELD_COMM]) r = table_add_string_fieldf_full( table, "PID", TABLE_STRING_WITH_ANSI, "%s%s%s (%s)", @@ -672,6 +700,7 @@ static int print_info_json(FILE *file, sd_journal *j) { (void) safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &ts); r = sd_json_build(&v, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(f.coredump_id), "ID", SD_JSON_BUILD_ID128(f.coredump_id)), SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(pid_as_int), "PID", SD_JSON_BUILD_UNSIGNED(pid_as_int)), SD_JSON_BUILD_PAIR_CONDITION(!pid_is_valid(pid_as_int) && !!f.fields[COREDUMP_FIELD_PID], "PID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PID])), SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(tid_as_int), "TID", SD_JSON_BUILD_UNSIGNED(tid_as_int)), diff --git a/src/coredump/coredumpctl-journal.c b/src/coredump/coredumpctl-journal.c index a2c71a7b844..ac07ce6b609 100644 --- a/src/coredump/coredumpctl-journal.c +++ b/src/coredump/coredumpctl-journal.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-id128.h" #include "sd-messages.h" #include "coredumpctl.h" @@ -15,11 +16,16 @@ static int add_match(sd_journal *j, const char *match) { _cleanup_free_ char *p = NULL; const char *field; + char id_str[SD_ID128_STRING_MAX]; + sd_id128_t id; int r; if (strchr(match, '=')) field = NULL; - else if (is_path(match)) { + else if (sd_id128_from_string(match, &id) >= 0) { + match = sd_id128_to_string(id, id_str); + field = "COREDUMP_ID"; + } else if (is_path(match)) { r = path_make_absolute_cwd(match, &p); if (r < 0) return log_error_errno(r, "path_make_absolute_cwd(\"%s\"): %m", match); diff --git a/src/coredump/coredumpctl-list.c b/src/coredump/coredumpctl-list.c index 63cf9db1f84..a3f21a878fd 100644 --- a/src/coredump/coredumpctl-list.c +++ b/src/coredump/coredumpctl-list.c @@ -25,17 +25,19 @@ int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; if (!verb_is_info && !arg_field) { - t = table_new("time", "pid", "uid", "gid", "sig", "corefile", "exe", "size"); + t = table_new("id", "time", "pid", "uid", "gid", "sig", "corefile", "exe", "size"); if (!t) return log_oom(); if (arg_full) table_set_width(t, 0); - (void) table_set_align_percent(t, TABLE_HEADER_CELL(1), 100); + (void) table_set_ellipsize_percent(t, TABLE_HEADER_CELL(1), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(2), 100); (void) table_set_align_percent(t, TABLE_HEADER_CELL(3), 100); - (void) table_set_align_percent(t, TABLE_HEADER_CELL(7), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(4), 100); + (void) table_set_align_percent(t, TABLE_HEADER_CELL(8), 100); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); } else if (!sd_json_format_enabled(arg_json_format_flags)) diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index cc0e2db01a2..8389a448a6c 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -199,6 +199,23 @@ coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null # Check the field is queryable in the journal coredumpctl -F COREDUMP_TID +# Test cases for Coredump ID +LATEST_ID=$(coredumpctl list -1 --json=short "$CORE_TEST_BIN" | jq -r '.[0].id') +[[ -n "$LATEST_ID" ]] +[[ "$LATEST_ID" != null ]] +# Test with explicit COREDUMP_ID= field +coredumpctl list COREDUMP_ID="$LATEST_ID" | grep "$LATEST_ID" >/dev/null +coredumpctl info COREDUMP_ID="$LATEST_ID" | grep --fixed-strings "ID: $LATEST_ID" >/dev/null +coredumpctl info --json=short COREDUMP_ID="$LATEST_ID" | jq -e ".ID == \"$LATEST_ID\"" +coredumpctl info --json=pretty COREDUMP_ID="$LATEST_ID" | jq -e ".ID == \"$LATEST_ID\"" +# Test with bare UUID (without COREDUMP_ID= prefix) +coredumpctl info "$LATEST_ID" | grep --fixed-strings "ID: $LATEST_ID" >/dev/null +coredumpctl info --json=short "$LATEST_ID" | jq -e ".ID == \"$LATEST_ID\"" +# Test with dashed UUID format +LATEST_ID_DASHED="${LATEST_ID:0:8}-${LATEST_ID:8:4}-${LATEST_ID:12:4}-${LATEST_ID:16:4}-${LATEST_ID:20:12}" +coredumpctl info "$LATEST_ID_DASHED" | grep --fixed-strings "ID: $LATEST_ID" >/dev/null +coredumpctl info --json=short "$LATEST_ID_DASHED" | jq -e ".ID == \"$LATEST_ID\"" + # If COREDUMP_CODE= is present, check that the expected code is SI_USER (0). if coredumpctl -F COREDUMP_CODE | grep "^0$" >/dev/null; then coredumpctl info "$CORE_TEST_BIN" | grep --fixed-strings "Signal: 5 (TRAP) si_code: SI_USER" >/dev/null @@ -282,6 +299,13 @@ coredumpctl info COREDUMP_HOSTNAME="mymachine" coredumpctl info COREDUMP_TIMESTAMP=1679509902000000 coredumpctl info COREDUMP_HOSTNAME="youmachine" coredumpctl info COREDUMP_DUMPABLE="1" +# Verify that backtrace entries have COREDUMP_ID set +coredumpctl info --json=short COREDUMP_TIMESTAMP=1679509900000000 | jq -e '.ID != null' +coredumpctl info --json=short COREDUMP_TIMESTAMP=1679509901000000 | jq -e '.ID != null' +coredumpctl info --json=short COREDUMP_TIMESTAMP=1679509902000000 | jq -e '.ID != null' +# Verify that backtrace entries can be queried by ID +BACKTRACE_ID=$(coredumpctl list -n 1 --json=short COREDUMP_TIMESTAMP=1679509900000000 | jq -r '.[0].id') +coredumpctl info "$BACKTRACE_ID" | grep --fixed-strings "ID: $BACKTRACE_ID" >/dev/null # This used to cause a stack overflow systemd-run -t --property CoredumpFilter=all ls /tmp