]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/coredump/stacktrace.c
Merge pull request #19231 from bluca/coredump_decode
[thirdparty/systemd.git] / src / coredump / stacktrace.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <dwarf.h>
4 #include <elfutils/libdwelf.h>
5 #include <elfutils/libdwfl.h>
6 #include <libelf.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "fileio.h"
12 #include "fd-util.h"
13 #include "format-util.h"
14 #include "hexdecoct.h"
15 #include "macro.h"
16 #include "stacktrace.h"
17 #include "string-util.h"
18 #include "util.h"
19
20 #define FRAMES_MAX 64
21 #define THREADS_MAX 64
22 #define ELF_PACKAGE_METADATA_ID 0xcafe1a7e
23
24 struct stack_context {
25 FILE *f;
26 Dwfl *dwfl;
27 Elf *elf;
28 unsigned n_thread;
29 unsigned n_frame;
30 JsonVariant **package_metadata;
31 Set **modules;
32 };
33
34 static int frame_callback(Dwfl_Frame *frame, void *userdata) {
35 struct stack_context *c = userdata;
36 Dwarf_Addr pc, pc_adjusted, bias = 0;
37 _cleanup_free_ Dwarf_Die *scopes = NULL;
38 const char *fname = NULL, *symbol = NULL;
39 Dwfl_Module *module;
40 bool is_activation;
41 uint64_t module_offset = 0;
42
43 assert(frame);
44 assert(c);
45
46 if (c->n_frame >= FRAMES_MAX)
47 return DWARF_CB_ABORT;
48
49 if (!dwfl_frame_pc(frame, &pc, &is_activation))
50 return DWARF_CB_ABORT;
51
52 pc_adjusted = pc - (is_activation ? 0 : 1);
53
54 module = dwfl_addrmodule(c->dwfl, pc_adjusted);
55 if (module) {
56 Dwarf_Die *s, *cudie;
57 int n;
58 Dwarf_Addr start;
59
60 cudie = dwfl_module_addrdie(module, pc_adjusted, &bias);
61 if (cudie) {
62 n = dwarf_getscopes(cudie, pc_adjusted - bias, &scopes);
63 for (s = scopes; s < scopes + n; s++) {
64 if (IN_SET(dwarf_tag(s), DW_TAG_subprogram, DW_TAG_inlined_subroutine, DW_TAG_entry_point)) {
65 Dwarf_Attribute *a, space;
66
67 a = dwarf_attr_integrate(s, DW_AT_MIPS_linkage_name, &space);
68 if (!a)
69 a = dwarf_attr_integrate(s, DW_AT_linkage_name, &space);
70 if (a)
71 symbol = dwarf_formstring(a);
72 if (!symbol)
73 symbol = dwarf_diename(s);
74
75 if (symbol)
76 break;
77 }
78 }
79 }
80
81 if (!symbol)
82 symbol = dwfl_module_addrname(module, pc_adjusted);
83
84 fname = dwfl_module_info(module, NULL, &start, NULL, NULL, NULL, NULL, NULL);
85 module_offset = pc - start;
86 }
87
88 fprintf(c->f, "#%-2u 0x%016" PRIx64 " %s (%s + 0x%" PRIx64 ")\n", c->n_frame, (uint64_t) pc, strna(symbol), strna(fname), module_offset);
89 c->n_frame++;
90
91 return DWARF_CB_OK;
92 }
93
94 static int thread_callback(Dwfl_Thread *thread, void *userdata) {
95 struct stack_context *c = userdata;
96 pid_t tid;
97
98 assert(thread);
99 assert(c);
100
101 if (c->n_thread >= THREADS_MAX)
102 return DWARF_CB_ABORT;
103
104 if (c->n_thread != 0)
105 fputc('\n', c->f);
106
107 c->n_frame = 0;
108
109 tid = dwfl_thread_tid(thread);
110 fprintf(c->f, "Stack trace of thread " PID_FMT ":\n", tid);
111
112 if (dwfl_thread_getframes(thread, frame_callback, c) < 0)
113 return DWARF_CB_ABORT;
114
115 c->n_thread++;
116
117 return DWARF_CB_OK;
118 }
119
120 static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *elf, struct stack_context *c) {
121 size_t n_program_headers;
122 int r;
123
124 assert(name);
125 assert(elf);
126 assert(c);
127
128 /* When iterating over PT_LOAD we will visit modules more than once */
129 if (set_contains(*c->modules, name))
130 return DWARF_CB_OK;
131
132 r = elf_getphdrnum(elf, &n_program_headers);
133 if (r < 0) /* Not the handle we are looking for - that's ok, skip it */
134 return DWARF_CB_OK;
135
136 /* Iterate over all program headers in that ELF object. These will have been copied by
137 * the kernel verbatim when the core file is generated. */
138 for (size_t i = 0; i < n_program_headers; ++i) {
139 size_t note_offset = 0, name_offset, desc_offset;
140 GElf_Phdr mem, *program_header;
141 GElf_Nhdr note_header;
142 Elf_Data *data;
143
144 /* Package metadata is in PT_NOTE headers. */
145 program_header = gelf_getphdr(elf, i, &mem);
146 if (!program_header || program_header->p_type != PT_NOTE)
147 continue;
148
149 /* Fortunately there is an iterator we can use to walk over the
150 * elements of a PT_NOTE program header. We are interested in the
151 * note with type. */
152 data = elf_getdata_rawchunk(elf,
153 program_header->p_offset,
154 program_header->p_filesz,
155 ELF_T_NHDR);
156
157 while (note_offset < data->d_size &&
158 (note_offset = gelf_getnote(data, note_offset, &note_header, &name_offset, &desc_offset)) > 0) {
159 const char *note_name = (const char *)data->d_buf + name_offset;
160 const char *payload = (const char *)data->d_buf + desc_offset;
161
162 if (note_header.n_namesz == 0 || note_header.n_descsz == 0)
163 continue;
164
165 /* Package metadata might have different owners, but the
166 * magic ID is always the same. */
167 if (note_header.n_type == ELF_PACKAGE_METADATA_ID) {
168 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
169
170 r = json_parse(payload, 0, &v, NULL, NULL);
171 if (r < 0) {
172 log_error_errno(r, "json_parse on %s failed: %m", payload);
173 return DWARF_CB_ABORT;
174 }
175
176 /* First pretty-print to the buffer, so that the metadata goes as
177 * plaintext in the journal. */
178 fprintf(c->f, "Metadata for module %s owned by %s found: ",
179 name, note_name);
180 json_variant_dump(v, JSON_FORMAT_NEWLINE|JSON_FORMAT_PRETTY, c->f, NULL);
181 fputc('\n', c->f);
182
183 /* Secondly, if we have a build-id, merge it in the same JSON object
184 * so that it appears all nicely together in the logs/metadata. */
185 if (id_json) {
186 r = json_variant_merge(&v, id_json);
187 if (r < 0) {
188 log_error_errno(r, "json_variant_merge of package meta with buildid failed: %m");
189 return DWARF_CB_ABORT;
190 }
191 }
192
193 /* Then we build a new object using the module name as the key, and merge it
194 * with the previous parses, so that in the end it all fits together in a single
195 * JSON blob. */
196 r = json_build(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR(name, JSON_BUILD_VARIANT(v))));
197 if (r < 0) {
198 log_error_errno(r, "Failed to build JSON object: %m");
199 return DWARF_CB_ABORT;
200 }
201 r = json_variant_merge(c->package_metadata, w);
202 if (r < 0) {
203 log_error_errno(r, "json_variant_merge of package meta with buildid failed: %m");
204 return DWARF_CB_ABORT;
205 }
206
207 /* Finally stash the name, so we avoid double visits. */
208 r = set_put_strdup(c->modules, name);
209 if (r < 0) {
210 log_error_errno(r, "set_put_strdup failed: %m");
211 return DWARF_CB_ABORT;
212 }
213
214 return DWARF_CB_OK;
215 }
216 }
217 }
218
219 /* Didn't find package metadata for this module - that's ok, just go to the next. */
220 return DWARF_CB_OK;
221 }
222
223 static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, Dwarf_Addr start, void *arg) {
224 _cleanup_(json_variant_unrefp) JsonVariant *id_json = NULL;
225 struct stack_context *c = arg;
226 size_t n_program_headers;
227 GElf_Addr id_vaddr, bias;
228 const unsigned char *id;
229 int id_len, r;
230 Elf *elf;
231
232 assert(mod);
233 assert(c);
234
235 if (!name)
236 name = "(unnamed)"; /* For logging purposes */
237
238 /* We are iterating on each "module", which is what dwfl calls ELF objects contained in the
239 * core file, and extracting the build-id first and then the package metadata.
240 * We proceed in a best-effort fashion - not all ELF objects might contain both or either.
241 * The build-id is easy, as libdwfl parses it during the dwfl_core_file_report() call and
242 * stores it separately in an internal library struct. */
243 id_len = dwfl_module_build_id(mod, &id, &id_vaddr);
244 if (id_len <= 0)
245 /* If we don't find a build-id, note it in the journal message, and try
246 * anyway to find the package metadata. It's unlikely to have the latter
247 * without the former, but there's no hard rule. */
248 fprintf(c->f, "Found module %s without build-id.\n", name);
249 else {
250 JsonVariant *build_id;
251
252 /* We will later parse package metadata json and pass it to our caller. Prepare the
253 * build-id in json format too, so that it can be appended and parsed cleanly. It
254 * will then be added as metadata to the journal message with the stack trace. */
255 r = json_build(&id_json, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("buildId", JSON_BUILD_HEX(id, id_len))));
256 if (r < 0) {
257 log_error_errno(r, "json_build on build-id failed: %m");
258 return DWARF_CB_ABORT;
259 }
260
261 build_id = json_variant_by_key(id_json, "buildId");
262 assert_se(build_id);
263 fprintf(c->f, "Found module %s with build-id: %s\n", name, json_variant_string(build_id));
264 }
265
266 /* The .note.package metadata is more difficult. From the module, we need to get a reference
267 * to the ELF object first. We might be lucky and just get it from elfutils. */
268 elf = dwfl_module_getelf(mod, &bias);
269 if (elf)
270 return parse_package_metadata(name, id_json, elf, c);
271
272 /* We did not get the ELF object. That is likely because we didn't get direct
273 * access to the executable, and the version of elfutils does not yet support
274 * parsing it out of the core file directly.
275 * So fallback to manual extraction - get the PT_LOAD section from the core,
276 * and if it's the right one we can interpret it as an Elf object, and parse
277 * its notes manually. */
278
279 r = elf_getphdrnum(c->elf, &n_program_headers);
280 if (r < 0) {
281 log_warning("Could not parse number of program headers from core file: %s",
282 elf_errmsg(-1)); /* -1 retrieves the most recent error */
283 return DWARF_CB_OK;
284 }
285
286 for (size_t i = 0; i < n_program_headers; ++i) {
287 GElf_Phdr mem, *program_header;
288 Elf_Data *data;
289
290 /* The core file stores the ELF files in the PT_LOAD segment .*/
291 program_header = gelf_getphdr(c->elf, i, &mem);
292 if (!program_header || program_header->p_type != PT_LOAD)
293 continue;
294
295 /* Now get a usable Elf reference, and parse the notes from it. */
296 data = elf_getdata_rawchunk(c->elf,
297 program_header->p_offset,
298 program_header->p_filesz,
299 ELF_T_NHDR);
300
301 Elf *memelf = elf_memory(data->d_buf, data->d_size);
302 if (!memelf)
303 continue;
304 r = parse_package_metadata(name, id_json, memelf, c);
305 if (r != DWARF_CB_OK)
306 return r;
307 }
308
309 return DWARF_CB_OK;
310 }
311
312 static int parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) {
313
314 static const Dwfl_Callbacks callbacks = {
315 .find_elf = dwfl_build_id_find_elf,
316 .section_address = dwfl_offline_section_address,
317 .find_debuginfo = dwfl_standard_find_debuginfo,
318 };
319
320 _cleanup_(json_variant_unrefp) JsonVariant *package_metadata = NULL;
321 _cleanup_(set_freep) Set *modules = NULL;
322 struct stack_context c = {
323 .package_metadata = &package_metadata,
324 .modules = &modules,
325 };
326 char *buf = NULL;
327 size_t sz = 0;
328 int r;
329
330 assert(fd >= 0);
331 assert(ret);
332
333 if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
334 return -errno;
335
336 c.f = open_memstream_unlocked(&buf, &sz);
337 if (!c.f)
338 return -ENOMEM;
339
340 elf_version(EV_CURRENT);
341
342 c.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
343 if (!c.elf) {
344 r = -EINVAL;
345 goto finish;
346 }
347
348 c.dwfl = dwfl_begin(&callbacks);
349 if (!c.dwfl) {
350 r = -EINVAL;
351 goto finish;
352 }
353
354 if (dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) {
355 r = -EINVAL;
356 goto finish;
357 }
358
359 if (dwfl_report_end(c.dwfl, NULL, NULL) != 0) {
360 r = -EINVAL;
361 goto finish;
362 }
363
364 if (dwfl_getmodules(c.dwfl, &module_callback, &c, 0) < 0) {
365 r = -EINVAL;
366 goto finish;
367 }
368
369 if (dwfl_core_file_attach(c.dwfl, c.elf) < 0) {
370 r = -EINVAL;
371 goto finish;
372 }
373
374 if (dwfl_getthreads(c.dwfl, thread_callback, &c) < 0) {
375 r = -EINVAL;
376 goto finish;
377 }
378
379 c.f = safe_fclose(c.f);
380
381 *ret = TAKE_PTR(buf);
382 if (ret_package_metadata)
383 *ret_package_metadata = TAKE_PTR(package_metadata);
384
385 r = 0;
386
387 finish:
388 if (c.dwfl)
389 dwfl_end(c.dwfl);
390
391 if (c.elf)
392 elf_end(c.elf);
393
394 safe_fclose(c.f);
395
396 free(buf);
397
398 return r;
399 }
400
401 void coredump_parse_core(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) {
402 int r;
403
404 r = parse_core(fd, executable, ret, ret_package_metadata);
405 if (r == -EINVAL)
406 log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno()));
407 else if (r < 0)
408 log_warning_errno(r, "Failed to generate stack trace: %m");
409 }