]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/coredump/stacktrace.c
Merge pull request #20303 from andir/sysconfig-example
[thirdparty/systemd.git] / src / coredump / stacktrace.c
index ec39fb8499d1686157ab31f8495146c682bcd6dc..e46b324cdf77fb003f4f290a1978a2776ecb65c5 100644 (file)
@@ -1,30 +1,17 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  This file is part of systemd.
-
-  Copyright 2014 Lennart Poettering
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <dwarf.h>
+#include <elfutils/libdwelf.h>
 #include <elfutils/libdwfl.h>
-#include <stdio_ext.h>
+#include <libelf.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "alloc-util.h"
+#include "fileio.h"
 #include "fd-util.h"
 #include "format-util.h"
+#include "hexdecoct.h"
 #include "macro.h"
 #include "stacktrace.h"
 #include "string-util.h"
@@ -32,6 +19,7 @@
 
 #define FRAMES_MAX 64
 #define THREADS_MAX 64
+#define ELF_PACKAGE_METADATA_ID 0xcafe1a7e
 
 struct stack_context {
         FILE *f;
@@ -39,6 +27,8 @@ struct stack_context {
         Elf *elf;
         unsigned n_thread;
         unsigned n_frame;
+        JsonVariant **package_metadata;
+        Set **modules;
 };
 
 static int frame_callback(Dwfl_Frame *frame, void *userdata) {
@@ -48,6 +38,7 @@ static int frame_callback(Dwfl_Frame *frame, void *userdata) {
         const char *fname = NULL, *symbol = NULL;
         Dwfl_Module *module;
         bool is_activation;
+        uint64_t module_offset = 0;
 
         assert(frame);
         assert(c);
@@ -64,6 +55,7 @@ static int frame_callback(Dwfl_Frame *frame, void *userdata) {
         if (module) {
                 Dwarf_Die *s, *cudie;
                 int n;
+                Dwarf_Addr start;
 
                 cudie = dwfl_module_addrdie(module, pc_adjusted, &bias);
                 if (cudie) {
@@ -89,10 +81,11 @@ static int frame_callback(Dwfl_Frame *frame, void *userdata) {
                 if (!symbol)
                         symbol = dwfl_module_addrname(module, pc_adjusted);
 
-                fname = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+                fname = dwfl_module_info(module, NULL, &start, NULL, NULL, NULL, NULL, NULL);
+                module_offset = pc - start;
         }
 
-        fprintf(c->f, "#%-2u 0x%016" PRIx64 " %s (%s)\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname));
+        fprintf(c->f, "#%-2u 0x%016" PRIx64 " %s (%s + 0x%" PRIx64 ")\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname), module_offset);
         c->n_frame++;
 
         return DWARF_CB_OK;
@@ -124,14 +117,214 @@ static int thread_callback(Dwfl_Thread *thread, void *userdata) {
         return DWARF_CB_OK;
 }
 
-int coredump_make_stack_trace(int fd, const char *executable, char **ret) {
+static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *elf, struct stack_context *c) {
+        size_t n_program_headers;
+        int r;
+
+        assert(name);
+        assert(elf);
+        assert(c);
+
+        /* When iterating over PT_LOAD we will visit modules more than once */
+        if (set_contains(*c->modules, name))
+                return DWARF_CB_OK;
+
+        r = elf_getphdrnum(elf, &n_program_headers);
+        if (r < 0) /* Not the handle we are looking for - that's ok, skip it */
+                return DWARF_CB_OK;
+
+        /* Iterate over all program headers in that ELF object. These will have been copied by
+         * the kernel verbatim when the core file is generated. */
+        for (size_t i = 0; i < n_program_headers; ++i) {
+                size_t note_offset = 0, name_offset, desc_offset;
+                GElf_Phdr mem, *program_header;
+                GElf_Nhdr note_header;
+                Elf_Data *data;
+
+                /* Package metadata is in PT_NOTE headers. */
+                program_header = gelf_getphdr(elf, i, &mem);
+                if (!program_header || program_header->p_type != PT_NOTE)
+                        continue;
+
+                /* Fortunately there is an iterator we can use to walk over the
+                 * elements of a PT_NOTE program header. We are interested in the
+                 * note with type. */
+                data = elf_getdata_rawchunk(elf,
+                                            program_header->p_offset,
+                                            program_header->p_filesz,
+                                            ELF_T_NHDR);
+                if (!data)
+                        continue;
+
+                while (note_offset < data->d_size &&
+                       (note_offset = gelf_getnote(data, note_offset, &note_header, &name_offset, &desc_offset)) > 0) {
+                        const char *note_name = (const char *)data->d_buf + name_offset;
+                        const char *payload = (const char *)data->d_buf + desc_offset;
+
+                        if (note_header.n_namesz == 0 || note_header.n_descsz == 0)
+                                continue;
+
+                        /* Package metadata might have different owners, but the
+                         * magic ID is always the same. */
+                        if (note_header.n_type == ELF_PACKAGE_METADATA_ID) {
+                                _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+
+                                r = json_parse(payload, 0, &v, NULL, NULL);
+                                if (r < 0) {
+                                        log_error_errno(r, "json_parse on %s failed: %m", payload);
+                                        return DWARF_CB_ABORT;
+                                }
+
+                                /* First pretty-print to the buffer, so that the metadata goes as
+                                 * plaintext in the journal. */
+                                fprintf(c->f, "Metadata for module %s owned by %s found: ",
+                                        name, note_name);
+                                json_variant_dump(v, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, c->f, NULL);
+                                fputc('\n', c->f);
+
+                                /* Secondly, if we have a build-id, merge it in the same JSON object
+                                 * so that it appears all nicely together in the logs/metadata. */
+                                if (id_json) {
+                                        r = json_variant_merge(&v, id_json);
+                                        if (r < 0) {
+                                                log_error_errno(r, "json_variant_merge of package meta with buildid failed: %m");
+                                                return DWARF_CB_ABORT;
+                                        }
+                                }
+
+                                /* Then we build a new object using the module name as the key, and merge it
+                                 * with the previous parses, so that in the end it all fits together in a single
+                                 * JSON blob. */
+                                r = json_build(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR(name, JSON_BUILD_VARIANT(v))));
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to build JSON object: %m");
+                                        return DWARF_CB_ABORT;
+                                }
+                                r = json_variant_merge(c->package_metadata, w);
+                                if (r < 0) {
+                                        log_error_errno(r, "json_variant_merge of package meta with buildid failed: %m");
+                                        return DWARF_CB_ABORT;
+                                }
+
+                                /* Finally stash the name, so we avoid double visits. */
+                                r = set_put_strdup(c->modules, name);
+                                if (r < 0) {
+                                        log_error_errno(r, "set_put_strdup failed: %m");
+                                        return DWARF_CB_ABORT;
+                                }
+
+                                return DWARF_CB_OK;
+                        }
+                }
+        }
+
+        /* Didn't find package metadata for this module - that's ok, just go to the next. */
+        return DWARF_CB_OK;
+}
+
+static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, Dwarf_Addr start, void *arg) {
+        _cleanup_(json_variant_unrefp) JsonVariant *id_json = NULL;
+        struct stack_context *c = arg;
+        size_t n_program_headers;
+        GElf_Addr id_vaddr, bias;
+        const unsigned char *id;
+        int id_len, r;
+        Elf *elf;
+
+        assert(mod);
+        assert(c);
+
+        if (!name)
+                name = "(unnamed)"; /* For logging purposes */
+
+        /* We are iterating on each "module", which is what dwfl calls ELF objects contained in the
+         * core file, and extracting the build-id first and then the package metadata.
+         * We proceed in a best-effort fashion - not all ELF objects might contain both or either.
+         * The build-id is easy, as libdwfl parses it during the dwfl_core_file_report() call and
+         * stores it separately in an internal library struct. */
+        id_len = dwfl_module_build_id(mod, &id, &id_vaddr);
+        if (id_len <= 0)
+                /* If we don't find a build-id, note it in the journal message, and try
+                 * anyway to find the package metadata. It's unlikely to have the latter
+                 * without the former, but there's no hard rule. */
+                fprintf(c->f, "Found module %s without build-id.\n", name);
+        else {
+                JsonVariant *build_id;
+
+                /* We will later parse package metadata json and pass it to our caller. Prepare the
+                * build-id in json format too, so that it can be appended and parsed cleanly. It
+                * will then be added as metadata to the journal message with the stack trace. */
+                r = json_build(&id_json, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("buildId", JSON_BUILD_HEX(id, id_len))));
+                if (r < 0) {
+                        log_error_errno(r, "json_build on build-id failed: %m");
+                        return DWARF_CB_ABORT;
+                }
+
+                build_id = json_variant_by_key(id_json, "buildId");
+                assert_se(build_id);
+                fprintf(c->f, "Found module %s with build-id: %s\n", name, json_variant_string(build_id));
+        }
+
+        /* The .note.package metadata is more difficult. From the module, we need to get a reference
+         * to the ELF object first. We might be lucky and just get it from elfutils. */
+        elf = dwfl_module_getelf(mod, &bias);
+        if (elf)
+                return parse_package_metadata(name, id_json, elf, c);
+
+        /* We did not get the ELF object. That is likely because we didn't get direct
+         * access to the executable, and the version of elfutils does not yet support
+         * parsing it out of the core file directly.
+         * So fallback to manual extraction - get the PT_LOAD section from the core,
+         * and if it's the right one we can interpret it as an Elf object, and parse
+         * its notes manually. */
+
+        r = elf_getphdrnum(c->elf, &n_program_headers);
+        if (r < 0) {
+                log_warning("Could not parse number of program headers from core file: %s",
+                            elf_errmsg(-1)); /* -1 retrieves the most recent error */
+                return DWARF_CB_OK;
+        }
+
+        for (size_t i = 0; i < n_program_headers; ++i) {
+                GElf_Phdr mem, *program_header;
+                Elf_Data *data;
+
+                /* The core file stores the ELF files in the PT_LOAD segment. */
+                program_header = gelf_getphdr(c->elf, i, &mem);
+                if (!program_header || program_header->p_type != PT_LOAD)
+                        continue;
+
+                /* Now get a usable Elf reference, and parse the notes from it. */
+                data = elf_getdata_rawchunk(c->elf,
+                                            program_header->p_offset,
+                                            program_header->p_filesz,
+                                            ELF_T_NHDR);
+
+                Elf *memelf = elf_memory(data->d_buf, data->d_size);
+                if (!memelf)
+                        continue;
+                r = parse_package_metadata(name, id_json, memelf, c);
+                if (r != DWARF_CB_OK)
+                        return r;
+        }
+
+        return DWARF_CB_OK;
+}
+
+static int parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) {
 
         static const Dwfl_Callbacks callbacks = {
                 .find_elf = dwfl_build_id_find_elf,
+                .section_address = dwfl_offline_section_address,
                 .find_debuginfo = dwfl_standard_find_debuginfo,
         };
 
-        struct stack_context c = {};
+        _cleanup_(json_variant_unrefp) JsonVariant *package_metadata = NULL;
+        _cleanup_(set_freep) Set *modules = NULL;
+        struct stack_context c = {
+                .package_metadata = &package_metadata,
+                .modules = &modules,
+        };
         char *buf = NULL;
         size_t sz = 0;
         int r;
@@ -142,12 +335,10 @@ int coredump_make_stack_trace(int fd, const char *executable, char **ret) {
         if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
                 return -errno;
 
-        c.f = open_memstream(&buf, &sz);
+        c.f = open_memstream_unlocked(&buf, &sz);
         if (!c.f)
                 return -ENOMEM;
 
-        (void) __fsetlocking(c.f, FSETLOCKING_BYCALLER);
-
         elf_version(EV_CURRENT);
 
         c.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
@@ -172,6 +363,11 @@ int coredump_make_stack_trace(int fd, const char *executable, char **ret) {
                 goto finish;
         }
 
+        if (dwfl_getmodules(c.dwfl, &module_callback, &c, 0) < 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
         if (dwfl_core_file_attach(c.dwfl, c.elf) < 0) {
                 r = -EINVAL;
                 goto finish;
@@ -185,6 +381,8 @@ int coredump_make_stack_trace(int fd, const char *executable, char **ret) {
         c.f = safe_fclose(c.f);
 
         *ret = TAKE_PTR(buf);
+        if (ret_package_metadata)
+                *ret_package_metadata = TAKE_PTR(package_metadata);
 
         r = 0;
 
@@ -201,3 +399,13 @@ finish:
 
         return r;
 }
+
+void coredump_parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) {
+        int r;
+
+        r = parse_core(fd, executable, ret, ret_package_metadata);
+        if (r == -EINVAL)
+                log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno()));
+        else if (r < 0)
+                log_warning_errno(r, "Failed to generate stack trace: %m");
+}