]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
coredump: add COREDUMP_CODE field for signal reason 42019/head
authorEmanuele Rocca <emanuele.rocca@arm.com>
Wed, 6 May 2026 15:46:21 +0000 (15:46 +0000)
committerEmanuele Rocca <emanuele.rocca@arm.com>
Tue, 19 May 2026 08:28:53 +0000 (08:28 +0000)
Introduce COREDUMP_CODE as a new captured field alongside the existing
COREDUMP_SIGNAL. While COREDUMP_SIGNAL identifies the signal number that
terminated the process, COREDUMP_CODE provides the reason the signal was sent.

For example, a process terminated by SIGSEGV due to invalid permissions would
produce COREDUMP_SIGNAL=11 and COREDUMP_CODE=2 (SEGV_ACCERR).

The kernel exposes coredump_code via pidfd starting with v7.1:
https://git.kernel.org/torvalds/c/701f7f4fbabbf4989ba6fbf033b160dd943221d5

System administrators can find both the signal and code in coredumpctl info:

$ coredumpctl info | grep Signal:
        Signal: 11 (SEGV) si_code: SEGV_MAPERR

Signed-off-by: Emanuele Rocca <emanuele.rocca@arm.com>
man/systemd-coredump.xml
src/coredump/coredump-context.c
src/coredump/coredump-context.h
src/coredump/coredumpctl.c
test/units/TEST-87-AUX-UTILS-VM.coredump.sh

index d499600dc15d9d452f2ae523f65543a0da24e439..c27981bd89b5fe956e37591aa9e51f29b45defbf 100644 (file)
@@ -201,6 +201,7 @@ COREDUMP_UID=1000
 COREDUMP_GID=1000
 COREDUMP_SIGNAL_NAME=SIGSEGV
 COREDUMP_SIGNAL=11
+COREDUMP_CODE=2
 COREDUMP_TIMESTAMP=1614342930000000
 COREDUMP_COMM=Web Content
 COREDUMP_EXE=/usr/lib64/firefox/firefox
@@ -327,6 +328,19 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>COREDUMP_CODE=</varname></term>
+
+        <listitem><para>The reason why the signal was sent as a numerical value. The code is defined as
+        field <varname>si_code</varname> in the <varname>siginfo_t</varname> structure and
+        documented in <citerefentry
+        project='man-pages'><refentrytitle>sigaction</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
+        </para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>COREDUMP_CWD=</varname></term>
         <term><varname>COREDUMP_ROOT=</varname></term>
index 6cacae4eff1ae28d151095f654a486de0c6978d9..24d273499a3c60e37b2f6cc9eb1732285299f8f8 100644 (file)
@@ -14,6 +14,7 @@
 #include "memstream-util.h"
 #include "namespace-util.h"
 #include "parse-util.h"
+#include "pidfd-util.h"
 #include "process-util.h"
 #include "signal-util.h"
 #include "special.h"
@@ -38,6 +39,7 @@ static const char * const metadata_field_table[_META_MAX] = {
         [META_UNIT]           = "COREDUMP_UNIT=",
         [META_PROC_AUXV]      = "COREDUMP_PROC_AUXV=",
         [META_THREAD_NAME]    = "COREDUMP_THREAD_NAME=",
+        [META_CODE]           = "COREDUMP_CODE=",
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField);
@@ -176,6 +178,34 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline)
         return 1;
 }
 
+/* The kernel passes si_signo through core_pattern (%s). Starting with v7.1,
+ * si_code is reported as well via pidfd. */
+static int coredump_context_read_pidfd_info(CoredumpContext *context) {
+        struct pidfd_info info = {
+                .mask = PIDFD_INFO_COREDUMP,
+        };
+        int r;
+
+        assert(context);
+        assert(pidref_is_set(&context->pidref));
+
+        if (!context->got_pidfd || context->pidref.fd < 0)
+                return 0;
+
+        r = pidfd_get_info(context->pidref.fd, &info);
+        if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
+                return log_debug_errno(r, "PIDFD_INFO_COREDUMP not supported, ignoring: %m");
+        if (r < 0)
+                return log_debug_errno(r, "Failed to get pidfd coredump info, ignoring: %m");
+
+        if (FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP_CODE)) {
+                context->code = (int) info.coredump_code;
+                context->got_code = true;
+        }
+
+        return 0;
+}
+
 int coredump_context_build_iovw(CoredumpContext *context) {
         char *t;
         int r;
@@ -213,6 +243,10 @@ int coredump_context_build_iovw(CoredumpContext *context) {
                         return log_error_errno(r, "Failed to add COREDUMP_SIGNAL= field: %m");
 
                 (void) iovw_put_string_field(&context->iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo));
+
+                /* Emit si_code if we learned it from pidfd_info */
+                if (context->got_code)
+                        (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_CODE=", "%i", context->code);
         }
 
         r = iovw_put_string_fieldf(&context->iovw, "COREDUMP_TIMESTAMP=", USEC_FMT, context->timestamp);
@@ -367,6 +401,8 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) {
         if (r < 0)
                 log_warning_errno(r, "Failed to get auxv, ignoring: %m");
 
+        (void) coredump_context_read_pidfd_info(context);
+
         r = pidref_verify(&context->pidref);
         if (r < 0)
                 return log_error_errno(r, "PIDFD validation failed: %m");
@@ -499,6 +535,17 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool
         case META_THREAD_NAME:
                 return free_and_strdup_warn(&context->thread_name, s);
 
+        case META_CODE:
+                /* We must accept both positive and negative values. The former
+                 * are reserved for kernel-generated signals, the latter for signals requested
+                 * from userspace. See /usr/include/bits/siginfo-consts.h. */
+                r = safe_atoi(s, &context->code);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse code \"%s\", ignoring: %m", s);
+                else
+                        context->got_code = true;
+                return 0;
+
         case META_PROC_AUXV: {
                 char *t = memdup_suffix0(s, size);
                 if (!t)
index 7fedfde2adbf7296a88e1ecb0cb65e9014913d5d..a2ce16b8531cb1ea743a4a1488c5ccba83c44ff5 100644 (file)
@@ -42,6 +42,7 @@ typedef enum MetadataField {
         META_UNIT,
         META_PROC_AUXV,
         META_THREAD_NAME,
+        META_CODE, /* code of signal causing dump (eg: 2 for SEGV_ACCERR). Since v7.1. */
         _META_MAX,
         _META_INVALID = -EINVAL,
 } MetadataField;
@@ -51,6 +52,7 @@ struct CoredumpContext {
         uid_t uid;         /* META_ARGV_UID */
         gid_t gid;         /* META_ARGV_GID */
         int signo;         /* META_ARGV_SIGNAL */
+        int code;          /* META_CODE */
         usec_t timestamp;  /* META_ARGV_TIMESTAMP */
         uint64_t rlimit;   /* META_ARGV_RLIMIT */
         char *hostname;    /* META_ARGV_HOSTNAME */
@@ -63,6 +65,7 @@ struct CoredumpContext {
         size_t auxv_size;  /* META_PROC_AUXV */
         char *thread_name; /* META_THREAD_NAME */
         bool got_pidfd;    /* META_ARGV_PIDFD */
+        bool got_code;     /* META_CODE */
         bool same_pidns;
         bool forwarded;
         int input_fd;
index 3b31471ab4dc79b678298c429342996d2c1c51e8..9b26e23d629303877d3383feb5a9e0590a5d6a87 100644 (file)
@@ -585,6 +585,7 @@ typedef enum CoredumpField {
         COREDUMP_FIELD_UID,
         COREDUMP_FIELD_GID,
         COREDUMP_FIELD_SGNL,
+        COREDUMP_FIELD_CODE,
         COREDUMP_FIELD_EXE,
         COREDUMP_FIELD_COMM,
         COREDUMP_FIELD_CMDLINE,
@@ -615,6 +616,7 @@ static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = {
         [COREDUMP_FIELD_UID]              = "COREDUMP_UID",
         [COREDUMP_FIELD_GID]              = "COREDUMP_GID",
         [COREDUMP_FIELD_SGNL]             = "COREDUMP_SIGNAL",
+        [COREDUMP_FIELD_CODE]             = "COREDUMP_CODE",
         [COREDUMP_FIELD_EXE]              = "COREDUMP_EXE",
         [COREDUMP_FIELD_COMM]             = "COREDUMP_COMM",
         [COREDUMP_FIELD_CMDLINE]          = "COREDUMP_CMDLINE",
@@ -770,9 +772,23 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
                 int sig;
                 const char *name = f.normal_coredump ? "Signal" : "Reason";
 
-                if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0)
-                        fprintf(file, "        %s: %s (%s)\n", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig));
-                else
+                if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0) {
+                        fprintf(file, "        %s: %s (%s)", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig));
+
+                        if (f.fields[COREDUMP_FIELD_CODE]) {
+                                int n;
+                                const char *s;
+
+                                if (safe_atoi(f.fields[COREDUMP_FIELD_CODE], &n) >= 0)
+                                        s = signal_code_to_string(sig, n);
+                                else
+                                        s = NULL;
+
+                                fprintf(file, " si_code: %s", s ?: f.fields[COREDUMP_FIELD_CODE]);
+                        }
+
+                        fputc('\n', file);
+                } else
                         fprintf(file, "        %s: %s\n", name, f.fields[COREDUMP_FIELD_SGNL]);
         }
 
@@ -893,7 +909,8 @@ static int print_info_json(FILE *file, sd_journal *j) {
         pid_t pid_as_int = 0, tid_as_int = 0;
         uid_t uid_as_int = UID_INVALID, owner_uid_as_int = UID_INVALID;
         gid_t gid_as_int = GID_INVALID;
-        int sig_as_int = 0;
+        int sig_as_int = 0, code_as_int = 0;
+        bool code_is_valid = false;
         usec_t ts = USEC_INFINITY;
         int r;
 
@@ -916,6 +933,8 @@ static int print_info_json(FILE *file, sd_journal *j) {
                 (void) parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &owner_uid_as_int);
         if (f.normal_coredump && f.fields[COREDUMP_FIELD_SGNL])
                 (void) safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig_as_int);
+        if (f.normal_coredump && f.fields[COREDUMP_FIELD_CODE])
+                code_is_valid = safe_atoi(f.fields[COREDUMP_FIELD_CODE], &code_as_int) >= 0;
         if (f.fields[COREDUMP_FIELD_TIMESTAMP])
                 (void) safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &ts);
 
@@ -932,6 +951,8 @@ static int print_info_json(FILE *file, sd_journal *j) {
                 SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0, "Signal", SD_JSON_BUILD_INTEGER(sig_as_int)),
                 SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0 && !!signal_to_string(sig_as_int), "SignalName", SD_JSON_BUILD_STRING(signal_to_string(sig_as_int))),
                 SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int <= 0 && !!f.fields[COREDUMP_FIELD_SGNL], "Signal", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])),
+                SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && code_is_valid, "SignalCode", SD_JSON_BUILD_INTEGER(code_as_int)),
+                SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && !code_is_valid && !!f.fields[COREDUMP_FIELD_CODE], "SignalCode", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CODE])),
                 SD_JSON_BUILD_PAIR_CONDITION(!f.normal_coredump && !!f.fields[COREDUMP_FIELD_SGNL], "Reason", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])),
                 SD_JSON_BUILD_PAIR_CONDITION(ts != USEC_INFINITY, "Timestamp", SD_JSON_BUILD_UNSIGNED(ts)),
                 SD_JSON_BUILD_PAIR_CONDITION(ts == USEC_INFINITY && !!f.fields[COREDUMP_FIELD_TIMESTAMP], "Timestamp", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TIMESTAMP])),
index 30252132ff85f9a8366090c12d57bf6560fe8131..675d37184b5716a9c30ba1693153436166428000 100755 (executable)
@@ -166,6 +166,12 @@ coredumpctl info "$CORE_TEST_BIN" | grep "TID:" >/dev/null
 # Check the field is queryable in the journal
 coredumpctl -F COREDUMP_TID
 
+# 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
+    coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'any(.[]; .SignalCode == 0)'
+fi
+
 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##*/}"