| /* 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"); |
| } |