mirror of
https://git.proxmox.com/git/systemd
synced 2025-10-05 11:37:19 +00:00
414 lines
16 KiB
C
414 lines
16 KiB
C
/* 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>
|
|
|
|
#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"
|
|
#include "util.h"
|
|
|
|
#define FRAMES_MAX 64
|
|
#define THREADS_MAX 64
|
|
#define ELF_PACKAGE_METADATA_ID 0xcafe1a7e
|
|
|
|
struct stack_context {
|
|
FILE *f;
|
|
Dwfl *dwfl;
|
|
Elf *elf;
|
|
unsigned n_thread;
|
|
unsigned n_frame;
|
|
JsonVariant **package_metadata;
|
|
Set **modules;
|
|
};
|
|
|
|
static int frame_callback(Dwfl_Frame *frame, void *userdata) {
|
|
struct stack_context *c = userdata;
|
|
Dwarf_Addr pc, pc_adjusted, bias = 0;
|
|
_cleanup_free_ Dwarf_Die *scopes = NULL;
|
|
const char *fname = NULL, *symbol = NULL;
|
|
Dwfl_Module *module;
|
|
bool is_activation;
|
|
uint64_t module_offset = 0;
|
|
|
|
assert(frame);
|
|
assert(c);
|
|
|
|
if (c->n_frame >= FRAMES_MAX)
|
|
return DWARF_CB_ABORT;
|
|
|
|
if (!dwfl_frame_pc(frame, &pc, &is_activation))
|
|
return DWARF_CB_ABORT;
|
|
|
|
pc_adjusted = pc - (is_activation ? 0 : 1);
|
|
|
|
module = dwfl_addrmodule(c->dwfl, pc_adjusted);
|
|
if (module) {
|
|
Dwarf_Die *s, *cudie;
|
|
int n;
|
|
Dwarf_Addr start;
|
|
|
|
cudie = dwfl_module_addrdie(module, pc_adjusted, &bias);
|
|
if (cudie) {
|
|
n = dwarf_getscopes(cudie, pc_adjusted - bias, &scopes);
|
|
for (s = scopes; s < scopes + n; s++) {
|
|
if (IN_SET(dwarf_tag(s), DW_TAG_subprogram, DW_TAG_inlined_subroutine, DW_TAG_entry_point)) {
|
|
Dwarf_Attribute *a, space;
|
|
|
|
a = dwarf_attr_integrate(s, DW_AT_MIPS_linkage_name, &space);
|
|
if (!a)
|
|
a = dwarf_attr_integrate(s, DW_AT_linkage_name, &space);
|
|
if (a)
|
|
symbol = dwarf_formstring(a);
|
|
if (!symbol)
|
|
symbol = dwarf_diename(s);
|
|
|
|
if (symbol)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!symbol)
|
|
symbol = dwfl_module_addrname(module, pc_adjusted);
|
|
|
|
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 + 0x%" PRIx64 ")\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname), module_offset);
|
|
c->n_frame++;
|
|
|
|
return DWARF_CB_OK;
|
|
}
|
|
|
|
static int thread_callback(Dwfl_Thread *thread, void *userdata) {
|
|
struct stack_context *c = userdata;
|
|
pid_t tid;
|
|
|
|
assert(thread);
|
|
assert(c);
|
|
|
|
if (c->n_thread >= THREADS_MAX)
|
|
return DWARF_CB_ABORT;
|
|
|
|
if (c->n_thread != 0)
|
|
fputc('\n', c->f);
|
|
|
|
c->n_frame = 0;
|
|
|
|
tid = dwfl_thread_tid(thread);
|
|
fprintf(c->f, "Stack trace of thread " PID_FMT ":\n", tid);
|
|
|
|
if (dwfl_thread_getframes(thread, frame_callback, c) < 0)
|
|
return DWARF_CB_ABORT;
|
|
|
|
c->n_thread++;
|
|
|
|
return DWARF_CB_OK;
|
|
}
|
|
|
|
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, ¬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;
|
|
|
|
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);
|
|
if (!data)
|
|
continue;
|
|
|
|
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,
|
|
};
|
|
|
|
_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;
|
|
|
|
assert(fd >= 0);
|
|
assert(ret);
|
|
|
|
if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
|
|
return -errno;
|
|
|
|
c.f = open_memstream_unlocked(&buf, &sz);
|
|
if (!c.f)
|
|
return -ENOMEM;
|
|
|
|
elf_version(EV_CURRENT);
|
|
|
|
c.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
|
|
if (!c.elf) {
|
|
r = -EINVAL;
|
|
goto finish;
|
|
}
|
|
|
|
c.dwfl = dwfl_begin(&callbacks);
|
|
if (!c.dwfl) {
|
|
r = -EINVAL;
|
|
goto finish;
|
|
}
|
|
|
|
if (dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) {
|
|
r = -EINVAL;
|
|
goto finish;
|
|
}
|
|
|
|
if (dwfl_report_end(c.dwfl, NULL, NULL) != 0) {
|
|
r = -EINVAL;
|
|
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;
|
|
}
|
|
|
|
if (dwfl_getthreads(c.dwfl, thread_callback, &c) < 0) {
|
|
r = -EINVAL;
|
|
goto finish;
|
|
}
|
|
|
|
c.f = safe_fclose(c.f);
|
|
|
|
*ret = TAKE_PTR(buf);
|
|
if (ret_package_metadata)
|
|
*ret_package_metadata = TAKE_PTR(package_metadata);
|
|
|
|
r = 0;
|
|
|
|
finish:
|
|
if (c.dwfl)
|
|
dwfl_end(c.dwfl);
|
|
|
|
if (c.elf)
|
|
elf_end(c.elf);
|
|
|
|
safe_fclose(c.f);
|
|
|
|
free(buf);
|
|
|
|
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");
|
|
}
|