diff --git a/docs/COREDUMP_PACKAGE_METADATA.md b/docs/COREDUMP_PACKAGE_METADATA.md new file mode 100644 index 00000000000..54ff31ee78f --- /dev/null +++ b/docs/COREDUMP_PACKAGE_METADATA.md @@ -0,0 +1,110 @@ +--- +title: Package Metadata for Core Files +category: Interfaces +layout: default +--- + +# Package Metadata for Core Files + +*Intended audience: hackers working on userspace subsystems that create ELF binaries +or parse ELF core files.* + +## Motivation + +ELF binaries get stamped with a unique, build-time generated hex string identifier called +`build-id`, [which gets embedded as an ELF note called `.note.gnu.build-id`](https://fedoraproject.org/wiki/Releases/FeatureBuildId). +In most cases, this allows to associate a stripped binary with its debugging information. +It is used, for example, to dynamically fetch DWARF symbols from a debuginfo server, or +to query the local package manager and find out the package metadata or, again, the DWARF +symbols or program sources. + +However, this usage of the `build-id` requires either local metadata, usually set up by +the package manager, or access to a remote server over the network. Both of those might +be unavailable or forbidden. + +Thus it becomes desirable to add additional metadata to a binary at build time, so that +`systemd-coredump` and other services analyzing core files are able to extract said +metadata simply from the core file itself, without external dependencies. + +## Implementation + +This document will attempt to define a common metadata format specification, so that +multiple implementers might use it when building packages, or core file analyzers, and +so on. + +The metadata will be embedded in a single, new ELF header section, in a key-value JSON +format. Implementers working on parsing core files should not assume a specific list of +keys, but parse anything that is included in the section. +Implementers working on build tools should strive to use the same key names, for +consistency. The most common will be listed here. When corresponding to the content of +os-release, the values should match, again for consistency. + +* Section header + +``` +SECTION: `.note.package` +node-id: `0xcafe1a7e` +Owner: `FDO` (FreeDesktop.org) +Value: a JSON string with the structure described below +``` + +* JSON payload + +```json +{ + "packageType":"rpm", # this provides a namespace for the package+package-version fields + "packageDistro":"fedora", + "packageDistroVersion":"33", + "package":"coreutils", + "packageVersion": "4711.0815.fc13.arm32", + "cpe": # A CPE name for the operating system, `CPE_NAME` from os-release is a good default +} +``` + +A reference implementations of a [build-time tool is provided](https://github.com/keszybz/rpm-version-note/) +and can be used to generate a linker script, which can then be used at build time via +```LDFLAGS="-Wl,-T,/path/to/generated/script"``` to include the note in the binary. + +Generator: +```console +$ ./generate-package-notes.py --rpm systemd-248~rc2-1.fc34 +SECTIONS +{ + .note.package : ALIGN(4) { + BYTE(0x04) BYTE(0x00) BYTE(0x00) BYTE(0x00) /* Length of Owner including NUL */ + BYTE(0x73) BYTE(0x00) BYTE(0x00) BYTE(0x00) /* Length of Value including NUL */ + BYTE(0x7e) BYTE(0x1a) BYTE(0xfe) BYTE(0xca) /* Note ID */ + BYTE(0x46) BYTE(0x44) BYTE(0x4f) BYTE(0x00) /* Owner: 'FDO\x00' */ + BYTE(0x7b) BYTE(0x22) BYTE(0x70) BYTE(0x61) /* Value: '{"packageType":"rpm","package":"systemd","packageVersion":"248~rc2-1.fc34","cpe":"cpe:/o:fedoraproject:fedora:33"}\x00\x00' */ + BYTE(0x63) BYTE(0x6b) BYTE(0x61) BYTE(0x67) + BYTE(0x65) BYTE(0x54) BYTE(0x79) BYTE(0x70) + BYTE(0x65) BYTE(0x22) BYTE(0x3a) BYTE(0x22) + BYTE(0x72) BYTE(0x70) BYTE(0x6d) BYTE(0x22) + BYTE(0x2c) BYTE(0x22) BYTE(0x70) BYTE(0x61) + BYTE(0x63) BYTE(0x6b) BYTE(0x61) BYTE(0x67) + BYTE(0x65) BYTE(0x22) BYTE(0x3a) BYTE(0x22) + BYTE(0x73) BYTE(0x79) BYTE(0x73) BYTE(0x74) + BYTE(0x65) BYTE(0x6d) BYTE(0x64) BYTE(0x22) + BYTE(0x2c) BYTE(0x22) BYTE(0x70) BYTE(0x61) + BYTE(0x63) BYTE(0x6b) BYTE(0x61) BYTE(0x67) + BYTE(0x65) BYTE(0x56) BYTE(0x65) BYTE(0x72) + BYTE(0x73) BYTE(0x69) BYTE(0x6f) BYTE(0x6e) + BYTE(0x22) BYTE(0x3a) BYTE(0x22) BYTE(0x32) + BYTE(0x34) BYTE(0x38) BYTE(0x7e) BYTE(0x72) + BYTE(0x63) BYTE(0x32) BYTE(0x2d) BYTE(0x31) + BYTE(0x2e) BYTE(0x66) BYTE(0x63) BYTE(0x33) + BYTE(0x34) BYTE(0x22) BYTE(0x2c) BYTE(0x22) + BYTE(0x63) BYTE(0x70) BYTE(0x65) BYTE(0x22) + BYTE(0x3a) BYTE(0x22) BYTE(0x63) BYTE(0x70) + BYTE(0x65) BYTE(0x3a) BYTE(0x2f) BYTE(0x6f) + BYTE(0x3a) BYTE(0x66) BYTE(0x65) BYTE(0x64) + BYTE(0x6f) BYTE(0x72) BYTE(0x61) BYTE(0x70) + BYTE(0x72) BYTE(0x6f) BYTE(0x6a) BYTE(0x65) + BYTE(0x63) BYTE(0x74) BYTE(0x3a) BYTE(0x66) + BYTE(0x65) BYTE(0x64) BYTE(0x6f) BYTE(0x72) + BYTE(0x61) BYTE(0x3a) BYTE(0x33) BYTE(0x33) + BYTE(0x22) BYTE(0x7d) BYTE(0x00) BYTE(0x00) + } +} +INSERT AFTER .note.gnu.build-id; +``` diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml index 117b9eb6d08..d994a21d9b5 100644 --- a/man/systemd-coredump.xml +++ b/man/systemd-coredump.xml @@ -352,6 +352,20 @@ flags: ... + + COREDUMP_PKGMETA_PACKAGE= + COREDUMP_PKGMETA_PACKAGEVERSION= + COREDUMP_PKGMETA_JSON= + + If the executable contained .package metadata ELF notes, they will be + parsed and attached. The package and packageVersion + of the 'main' ELF module (ie: the excutable) will be appended individually. The + JSON-formatted content of all modules will be appended as a single JSON object, each with + the module name as the key. For more information about this metadata format and content, see + the coredump metadata spec. + + + MESSAGE= diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 2fb2404500e..be813f57f19 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -703,14 +703,16 @@ static int submit_coredump( struct iovec_wrapper *iovw, int input_fd) { + _cleanup_(json_variant_unrefp) JsonVariant *json_metadata = NULL; _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; _cleanup_free_ char *filename = NULL, *coredump_data = NULL; _cleanup_free_ char *stacktrace = NULL; char *core_message; + const char *module_name; uint64_t coredump_size = UINT64_MAX; bool truncated = false; + JsonVariant *module_json; int r; - assert(context); assert(iovw); assert(input_fd >= 0); @@ -757,7 +759,7 @@ static int submit_coredump( "than %"PRIu64" (the configured maximum)", coredump_size, arg_process_size_max); } else - coredump_make_stack_trace(coredump_fd, context->meta[META_EXE], &stacktrace); + coredump_parse_core(coredump_fd, context->meta[META_EXE], &stacktrace, &json_metadata); #endif log: @@ -781,6 +783,67 @@ log: if (truncated) (void) iovw_put_string_field(iovw, "COREDUMP_TRUNCATED=", "1"); + /* If we managed to parse any ELF metadata (build-id, ELF package meta), + * attach it as journal metadata. */ + if (json_metadata) { + _cleanup_free_ char *formatted_json = NULL; + + r = json_variant_format(json_metadata, 0, &formatted_json); + if (r < 0) + return log_error_errno(r, "Failed to format JSON package metadata: %m"); + + (void) iovw_put_string_field(iovw, "COREDUMP_PKGMETA_JSON=", formatted_json); + } + + JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, json_metadata) { + _cleanup_free_ char *module_basename = NULL, *exe_basename = NULL; + const char *key; + JsonVariant *w; + + /* The module name, most likely parsed from the ELF core file, + * sometimes contains the full path and sometimes does not. */ + r = path_extract_filename(module_name, &module_basename); + if (r < 0) + return log_error_errno(r, "Failed to parse module basename: %m"); + r = path_extract_filename(context->meta[META_EXE], &exe_basename); + if (r < 0) + return log_error_errno(r, "Failed to parse executable basename: %m"); + + /* We only add structured fields for the 'main' ELF module */ + if (!streq(module_basename, exe_basename)) + continue; + + /* Cannot nest two JSON_VARIANT_OBJECT_FOREACH as they define the same + * iterator variable '_state' */ + for (struct json_variant_foreach_state _state2 = { (module_json), 0 }; \ + json_variant_is_object(_state2.variant) && \ + _state2.idx < json_variant_elements(_state2.variant) && \ + ({ key = json_variant_string(json_variant_by_index(_state2.variant, _state2.idx)); \ + w = json_variant_by_index(_state2.variant, _state2.idx + 1); \ + true; }); \ + _state2.idx += 2) { + _cleanup_free_ char *metadata_id = NULL, *key_upper = NULL; + + if (!json_variant_is_string(w)) + continue; + + if (!STR_IN_SET(key, "package", "packageVersion")) + continue; + + /* Journal metadata field names need to be upper case */ + key_upper = strdup(key); + if (!key_upper) + return log_oom(); + key_upper = ascii_strupper(key_upper); + + metadata_id = strjoin("COREDUMP_PKGMETA_", key_upper, "="); + if (!metadata_id) + return log_oom(); + + (void) iovw_put_string_field(iovw, metadata_id, json_variant_string(w)); + } + } + /* Optionally store the entire coredump in the journal */ if (arg_storage == COREDUMP_STORAGE_JOURNAL) { if (coredump_size <= arg_journal_size_max) { diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 0c4ef2e1234..3418753a31b 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -545,7 +545,8 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { *boot_id = NULL, *machine_id = NULL, *hostname = NULL, *slice = NULL, *cgroup = NULL, *owner_uid = NULL, *message = NULL, *timestamp = NULL, *filename = NULL, - *truncated = NULL, *coredump = NULL; + *truncated = NULL, *coredump = NULL, + *pkgmeta_name = NULL, *pkgmeta_version = NULL, *pkgmeta_json = NULL; const void *d; size_t l; bool normal_coredump; @@ -574,6 +575,9 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { RETRIEVE(d, l, "COREDUMP_FILENAME", filename); RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated); RETRIEVE(d, l, "COREDUMP", coredump); + RETRIEVE(d, l, "COREDUMP_PKGMETA_PACKAGE", pkgmeta_name); + RETRIEVE(d, l, "COREDUMP_PKGMETA_PACKAGEVERSION", pkgmeta_version); + RETRIEVE(d, l, "COREDUMP_PKGMETA_JSON", pkgmeta_json); RETRIEVE(d, l, "_BOOT_ID", boot_id); RETRIEVE(d, l, "_MACHINE_ID", machine_id); RETRIEVE(d, l, "MESSAGE", message); @@ -716,6 +720,68 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { else fprintf(file, " Storage: none\n"); + if (pkgmeta_name && pkgmeta_version) + fprintf(file, " Package: %s/%s\n", pkgmeta_name, pkgmeta_version); + + /* Print out the build-id of the 'main' ELF module, by matching the JSON key + * with the 'exe' field. */ + if (exe && pkgmeta_json) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = json_parse(pkgmeta_json, 0, &v, NULL, NULL); + if (r < 0) { + log_warning_errno(r, "json_parse on %s failed, ignoring: %m", pkgmeta_json); + } else { + const char *module_name; + JsonVariant *module_json; + + /* Cannot nest two JSON_VARIANT_OBJECT_FOREACH as they define the same + * iterator variable '_state' */ + for (struct json_variant_foreach_state _state2 = { (v), 0 }; \ + json_variant_is_object(_state2.variant) && \ + _state2.idx < json_variant_elements(_state2.variant) && \ + ({ module_name = json_variant_string(json_variant_by_index(_state2.variant, _state2.idx)); \ + module_json = json_variant_by_index(_state2.variant, _state2.idx + 1); \ + true; }); \ + _state2.idx += 2) { + _cleanup_free_ char *module_basename = NULL, *exe_basename = NULL; + const char *key; + JsonVariant *w; + + /* The module name, most likely parsed from the ELF core file, + * sometimes contains the full path and sometimes does not. */ + r = path_extract_filename(module_name, &module_basename); + if (r < 0) { + log_warning_errno(r, "Failed to parse module basename: %m"); + break; + } + r = path_extract_filename(exe, &exe_basename); + if (r < 0) { + log_warning_errno(r, "Failed to parse executable basename: %m"); + break; + } + + /* We only print the build-id for the 'main' ELF module */ + if (!streq(module_basename, exe_basename)) + continue; + + JSON_VARIANT_OBJECT_FOREACH(key, w, module_json) { + if (!json_variant_is_string(w)) + continue; + + if (!streq(key, "buildid")) + continue; + + fprintf(file, " build-id: %s\n", json_variant_string(w)); + + break; + } + + break; + } + } + } + if (message) { _cleanup_free_ char *m = NULL; diff --git a/src/coredump/stacktrace.c b/src/coredump/stacktrace.c index a29ab1211c2..c16f12f09b4 100644 --- a/src/coredump/stacktrace.c +++ b/src/coredump/stacktrace.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include +#include #include #include @@ -9,6 +11,7 @@ #include "fileio.h" #include "fd-util.h" #include "format-util.h" +#include "hexdecoct.h" #include "macro.h" #include "stacktrace.h" #include "string-util.h" @@ -16,6 +19,7 @@ #define FRAMES_MAX 64 #define THREADS_MAX 64 +#define ELF_PACKAGE_METADATA_ID 0xcafe1a7e struct stack_context { FILE *f; @@ -23,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) { @@ -111,14 +117,227 @@ static int thread_callback(Dwfl_Thread *thread, void *userdata) { return DWARF_CB_OK; } -static int 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); + + while (note_offset < data->d_size && + (note_offset = gelf_getnote(data, note_offset, ¬e_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; + char *name_key = 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 apperas 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. */ + name_key = strdup(name); + if (!name_key) { + log_oom(); + return DWARF_CB_ABORT; + } + r = set_ensure_consume(c->modules, &string_hash_ops, name_key); + if (r < 0) { + log_error_errno(r, "set_ensure_consume 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 { + _cleanup_free_ char *id_hex = NULL, *id_hex_prefixed = NULL; + + id_hex = hexmem(id, id_len); + if (!id_hex) { + log_oom(); + return DWARF_CB_ABORT; + } + + fprintf(c->f, "Found module %s with build-id: %s\n", name, id_hex); + + /* 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. */ + id_hex_prefixed = strjoin("{\"buildid\":\"", id_hex, "\"}"); + if (!id_hex_prefixed) { + log_oom(); + return DWARF_CB_ABORT; + } + r = json_parse(id_hex_prefixed, 0, &id_json, NULL, NULL); + if (r < 0) { + log_error_errno(r, "json_parse on %s failed: %m", id_hex_prefixed); + return DWARF_CB_ABORT; + } + } + + /* 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; @@ -157,6 +376,11 @@ static int 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; @@ -170,6 +394,8 @@ static int 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; @@ -187,10 +413,10 @@ finish: return r; } -void coredump_make_stack_trace(int fd, const char *executable, char **ret) { +void coredump_parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) { int r; - r = make_stack_trace(fd, executable, ret); + 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) diff --git a/src/coredump/stacktrace.h b/src/coredump/stacktrace.h index b9357484042..5039b934dda 100644 --- a/src/coredump/stacktrace.h +++ b/src/coredump/stacktrace.h @@ -1,4 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -void coredump_make_stack_trace(int fd, const char *executable, char **ret); +#include "json.h" + +void coredump_parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata);