mirror of
https://github.com/systemd/systemd.git
synced 2025-01-26 14:04:03 +03:00
Merge pull request #19135 from bluca/coredump_decode
coredump: parse build-id and .note.package
This commit is contained in:
commit
3e3ab11b9e
110
docs/COREDUMP_PACKAGE_METADATA.md
Normal file
110
docs/COREDUMP_PACKAGE_METADATA.md
Normal file
@ -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;
|
||||
```
|
@ -352,6 +352,20 @@ flags: ...
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>COREDUMP_PKGMETA_PACKAGE=</varname></term>
|
||||
<term><varname>COREDUMP_PKGMETA_PACKAGEVERSION=</varname></term>
|
||||
<term><varname>COREDUMP_PKGMETA_JSON=</varname></term>
|
||||
|
||||
<listitem><para>If the executable contained .package metadata ELF notes, they will be
|
||||
parsed and attached. The <varname>package</varname> and <varname>packageVersion</varname>
|
||||
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
|
||||
<ulink url="https://systemd.io/COREDUMP_PACKAGE_METADATA/">the coredump metadata spec</ulink>.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>MESSAGE=</varname></term>
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <dwarf.h>
|
||||
#include <elfutils/libdwelf.h>
|
||||
#include <elfutils/libdwfl.h>
|
||||
#include <libelf.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user