mirror of
https://github.com/systemd/systemd
synced 2025-10-06 20:24:45 +02:00
Compare commits
7 Commits
ee579e6454
...
65d09d575c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
65d09d575c | ||
![]() |
3e3ab11b9e | ||
![]() |
a7ea0a460b | ||
![]() |
d1b5a0c691 | ||
![]() |
c546154a44 | ||
![]() |
95f7180773 | ||
![]() |
965e095546 |
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>
|
</para></listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><varname>MESSAGE=</varname></term>
|
<term><varname>MESSAGE=</varname></term>
|
||||||
|
|
||||||
|
@ -703,14 +703,16 @@ static int submit_coredump(
|
|||||||
struct iovec_wrapper *iovw,
|
struct iovec_wrapper *iovw,
|
||||||
int input_fd) {
|
int input_fd) {
|
||||||
|
|
||||||
|
_cleanup_(json_variant_unrefp) JsonVariant *json_metadata = NULL;
|
||||||
_cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1;
|
_cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1;
|
||||||
_cleanup_free_ char *filename = NULL, *coredump_data = NULL;
|
_cleanup_free_ char *filename = NULL, *coredump_data = NULL;
|
||||||
_cleanup_free_ char *stacktrace = NULL;
|
_cleanup_free_ char *stacktrace = NULL;
|
||||||
char *core_message;
|
char *core_message;
|
||||||
|
const char *module_name;
|
||||||
uint64_t coredump_size = UINT64_MAX;
|
uint64_t coredump_size = UINT64_MAX;
|
||||||
bool truncated = false;
|
bool truncated = false;
|
||||||
|
JsonVariant *module_json;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(context);
|
assert(context);
|
||||||
assert(iovw);
|
assert(iovw);
|
||||||
assert(input_fd >= 0);
|
assert(input_fd >= 0);
|
||||||
@ -757,7 +759,7 @@ static int submit_coredump(
|
|||||||
"than %"PRIu64" (the configured maximum)",
|
"than %"PRIu64" (the configured maximum)",
|
||||||
coredump_size, arg_process_size_max);
|
coredump_size, arg_process_size_max);
|
||||||
} else
|
} 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
|
#endif
|
||||||
|
|
||||||
log:
|
log:
|
||||||
@ -781,6 +783,67 @@ log:
|
|||||||
if (truncated)
|
if (truncated)
|
||||||
(void) iovw_put_string_field(iovw, "COREDUMP_TRUNCATED=", "1");
|
(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 */
|
/* Optionally store the entire coredump in the journal */
|
||||||
if (arg_storage == COREDUMP_STORAGE_JOURNAL) {
|
if (arg_storage == COREDUMP_STORAGE_JOURNAL) {
|
||||||
if (coredump_size <= arg_journal_size_max) {
|
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,
|
*boot_id = NULL, *machine_id = NULL, *hostname = NULL,
|
||||||
*slice = NULL, *cgroup = NULL, *owner_uid = NULL,
|
*slice = NULL, *cgroup = NULL, *owner_uid = NULL,
|
||||||
*message = NULL, *timestamp = NULL, *filename = 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;
|
const void *d;
|
||||||
size_t l;
|
size_t l;
|
||||||
bool normal_coredump;
|
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_FILENAME", filename);
|
||||||
RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
|
RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
|
||||||
RETRIEVE(d, l, "COREDUMP", coredump);
|
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, "_BOOT_ID", boot_id);
|
||||||
RETRIEVE(d, l, "_MACHINE_ID", machine_id);
|
RETRIEVE(d, l, "_MACHINE_ID", machine_id);
|
||||||
RETRIEVE(d, l, "MESSAGE", message);
|
RETRIEVE(d, l, "MESSAGE", message);
|
||||||
@ -716,6 +720,68 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) {
|
|||||||
else
|
else
|
||||||
fprintf(file, " Storage: none\n");
|
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) {
|
if (message) {
|
||||||
_cleanup_free_ char *m = NULL;
|
_cleanup_free_ char *m = NULL;
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
#include <dwarf.h>
|
#include <dwarf.h>
|
||||||
|
#include <elfutils/libdwelf.h>
|
||||||
#include <elfutils/libdwfl.h>
|
#include <elfutils/libdwfl.h>
|
||||||
|
#include <libelf.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@ -9,6 +11,7 @@
|
|||||||
#include "fileio.h"
|
#include "fileio.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "format-util.h"
|
#include "format-util.h"
|
||||||
|
#include "hexdecoct.h"
|
||||||
#include "macro.h"
|
#include "macro.h"
|
||||||
#include "stacktrace.h"
|
#include "stacktrace.h"
|
||||||
#include "string-util.h"
|
#include "string-util.h"
|
||||||
@ -16,6 +19,7 @@
|
|||||||
|
|
||||||
#define FRAMES_MAX 64
|
#define FRAMES_MAX 64
|
||||||
#define THREADS_MAX 64
|
#define THREADS_MAX 64
|
||||||
|
#define ELF_PACKAGE_METADATA_ID 0xcafe1a7e
|
||||||
|
|
||||||
struct stack_context {
|
struct stack_context {
|
||||||
FILE *f;
|
FILE *f;
|
||||||
@ -23,6 +27,8 @@ struct stack_context {
|
|||||||
Elf *elf;
|
Elf *elf;
|
||||||
unsigned n_thread;
|
unsigned n_thread;
|
||||||
unsigned n_frame;
|
unsigned n_frame;
|
||||||
|
JsonVariant **package_metadata;
|
||||||
|
Set **modules;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int frame_callback(Dwfl_Frame *frame, void *userdata) {
|
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;
|
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 = {
|
static const Dwfl_Callbacks callbacks = {
|
||||||
.find_elf = dwfl_build_id_find_elf,
|
.find_elf = dwfl_build_id_find_elf,
|
||||||
|
.section_address = dwfl_offline_section_address,
|
||||||
.find_debuginfo = dwfl_standard_find_debuginfo,
|
.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;
|
char *buf = NULL;
|
||||||
size_t sz = 0;
|
size_t sz = 0;
|
||||||
int r;
|
int r;
|
||||||
@ -157,6 +376,11 @@ static int make_stack_trace(int fd, const char *executable, char **ret) {
|
|||||||
goto finish;
|
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) {
|
if (dwfl_core_file_attach(c.dwfl, c.elf) < 0) {
|
||||||
r = -EINVAL;
|
r = -EINVAL;
|
||||||
goto finish;
|
goto finish;
|
||||||
@ -170,6 +394,8 @@ static int make_stack_trace(int fd, const char *executable, char **ret) {
|
|||||||
c.f = safe_fclose(c.f);
|
c.f = safe_fclose(c.f);
|
||||||
|
|
||||||
*ret = TAKE_PTR(buf);
|
*ret = TAKE_PTR(buf);
|
||||||
|
if (ret_package_metadata)
|
||||||
|
*ret_package_metadata = TAKE_PTR(package_metadata);
|
||||||
|
|
||||||
r = 0;
|
r = 0;
|
||||||
|
|
||||||
@ -187,10 +413,10 @@ finish:
|
|||||||
return r;
|
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;
|
int r;
|
||||||
|
|
||||||
r = make_stack_trace(fd, executable, ret);
|
r = parse_core(fd, executable, ret, ret_package_metadata);
|
||||||
if (r == -EINVAL)
|
if (r == -EINVAL)
|
||||||
log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno()));
|
log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno()));
|
||||||
else if (r < 0)
|
else if (r < 0)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
#pragma once
|
#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);
|
||||||
|
@ -160,7 +160,8 @@ tests += [
|
|||||||
|
|
||||||
[['src/test/test-random-util.c'],
|
[['src/test/test-random-util.c'],
|
||||||
[],
|
[],
|
||||||
[libm]],
|
[libm],
|
||||||
|
[], '', 'timeout=120'],
|
||||||
|
|
||||||
[['src/test/test-format-table.c']],
|
[['src/test/test-format-table.c']],
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user