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.
This commit is contained in:
Yu Watanabe committed 2026-10-21 04:18:53 +09:00
1 parent c366d2550d
commit 0d195ea931
8 files changed
+86 -7

No files matched your search

+9 -1
View File
@@ -26,7 +26,7 @@
<command>coredumpctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="req">COMMAND</arg>
<arg choice="opt" rep="repeat">PID|COMM|EXE|MATCH</arg>
<arg choice="opt" rep="repeat">ID|PID|COMM|EXE|MATCH</arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -55,6 +55,14 @@
<para>The output is designed to be human-readable and contains a table with the following
columns:</para>
<variablelist>
<varlistentry>
<term>ID</term>
<listitem><para>The UUID of the crash assigned by <command>systemd-coredump</command>.</para>
<xi:include href="version-info.xml" xpointer="v263"/>
</listitem>
</varlistentry>
<varlistentry>
<term>TIME</term>
<listitem><para>The timestamp of the crash, as reported by the kernel.</para>
+2 -1
View File
@@ -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
+5
View File
@@ -2,6 +2,7 @@
#include <unistd.h>
#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)
+4
View File
@@ -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)
+30 -1
View File
@@ -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)),
+7 -1
View File
@@ -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);
+5 -3
View File
@@ -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))
@@ -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