From e2de54ccf2bf02dc869e83645a980db158509ae2 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 10 Oct 2024 17:07:21 -0700 Subject: [PATCH 07/18] libsysprof: add debuginfod symbolizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is based on a debuginfod_client provided by Barnabás Pőcze in !73. It extends it to use the new task infrastructure to elevate the download process to the user while loading a capture file. --- config.h.meson | 2 + meson.build | 2 + meson_options.txt | 2 + src/libsysprof/meson.build | 8 + .../sysprof-debuginfod-symbolizer.c | 229 ++++++++++++++++++ .../sysprof-debuginfod-symbolizer.h | 42 ++++ .../sysprof-debuginfod-task-private.h | 42 ++++ src/libsysprof/sysprof-debuginfod-task.c | 131 ++++++++++ src/libsysprof/sysprof-document-loader.c | 15 +- 9 files changed, 468 insertions(+), 5 deletions(-) create mode 100644 src/libsysprof/sysprof-debuginfod-symbolizer.c create mode 100644 src/libsysprof/sysprof-debuginfod-symbolizer.h create mode 100644 src/libsysprof/sysprof-debuginfod-task-private.h create mode 100644 src/libsysprof/sysprof-debuginfod-task.c diff --git a/config.h.meson b/config.h.meson index d2f7589a..48b3c2c2 100644 --- a/config.h.meson +++ b/config.h.meson @@ -16,6 +16,8 @@ #mesondefine HAVE_LIBSYSTEMD +#mesondefine HAVE_DEBUGINFOD + #mesondefine HAVE_PERF_CLOCKID #mesondefine HAVE_POLKIT diff --git a/meson.build b/meson.build index dda0ca4e..81e97072 100644 --- a/meson.build +++ b/meson.build @@ -63,6 +63,7 @@ gio_unix_dep = dependency('gio-unix-2.0', version: glib_req_version, required: need_glib and host_machine.system() != 'windows') gtk_dep = dependency('gtk4', version: gtk_req_version, required: need_gtk) libsystemd_dep = dependency('libsystemd', required: false) +debuginfod_dep = dependency('libdebuginfod', required: get_option('debuginfod')) config_h = configuration_data() config_h.set_quoted('API_VERSION_S', libsysprof_api_version.to_string()) @@ -99,6 +100,7 @@ config_h.set10('ENABLE_NLS', true) config_h.set_quoted('GETTEXT_PACKAGE', 'sysprof') config_h.set_quoted('PACKAGE_LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) config_h.set10('HAVE_LIBSYSTEMD', libsystemd_dep.found()) +config_h.set10('HAVE_DEBUGINFOD', debuginfod_dep.found()) polkit_agent_dep = dependency('polkit-agent-1', required: get_option('polkit-agent')) config_h.set10('HAVE_POLKIT_AGENT', polkit_agent_dep.found()) diff --git a/meson_options.txt b/meson_options.txt index 2f78fc3b..02bb6981 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -49,3 +49,5 @@ option('tests', type: 'boolean') # Optionally disable the examples (this is mostly only useful for building only # libsysprof-capture as a subproject) option('examples', type: 'boolean') + +option('debuginfod', type: 'feature') diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 697e9665..b4e58078 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -153,6 +153,13 @@ libsysprof_private_sources = [ 'timsort/gtktimsort.c', ] +if debuginfod_dep.found() and get_option('debuginfod').enabled() + libsysprof_private_sources += [ + 'sysprof-debuginfod-symbolizer.c', + 'sysprof-debuginfod-task.c' + ] +endif + if polkit_dep.found() libsysprof_private_sources += ['sysprof-polkit.c'] endif @@ -192,6 +199,7 @@ libsysprof_deps = [ libsystemd_dep, polkit_dep, + debuginfod_dep, libeggbitset_static_dep, libelfparser_static_dep, diff --git a/src/libsysprof/sysprof-debuginfod-symbolizer.c b/src/libsysprof/sysprof-debuginfod-symbolizer.c new file mode 100644 index 00000000..8dd60d19 --- /dev/null +++ b/src/libsysprof/sysprof-debuginfod-symbolizer.c @@ -0,0 +1,229 @@ +/* sysprof-debuginfod-symbolizer.c + * + * Copyright 2024 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "sysprof-symbolizer-private.h" +#include "sysprof-debuginfod-symbolizer.h" +#include "sysprof-debuginfod-task-private.h" +#include "sysprof-elf-loader-private.h" +#include "sysprof-symbol-private.h" + +struct _SysprofDebuginfodSymbolizer +{ + SysprofSymbolizer parent_instance; + + GWeakRef loader_wr; + + debuginfod_client *client; + SysprofElfLoader *loader; + GHashTable *cache; + GHashTable *failed; +}; + +struct _SysprofDebuginfodSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofDebuginfodSymbolizer, sysprof_debuginfod_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +static SysprofSymbol * +sysprof_debuginfod_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofDebuginfodSymbolizer *self = SYSPROF_DEBUGINFOD_SYMBOLIZER (symbolizer); + g_autoptr(SysprofElf) elf = NULL; + g_autofree char *name = NULL; + SysprofSymbol *sym = NULL; + SysprofDocumentMmap *map; + const char *build_id; + const char *path; + guint64 relative_address; + guint64 begin_address; + guint64 end_address; + guint64 file_offset; + guint64 map_begin; + guint64 map_end; + + if (process_info == NULL || + process_info->address_layout == NULL || + process_info->mount_namespace == NULL || + (context != SYSPROF_ADDRESS_CONTEXT_NONE && context != SYSPROF_ADDRESS_CONTEXT_USER) || + !(map = sysprof_address_layout_lookup (process_info->address_layout, address))) + return NULL; + + map_begin = sysprof_document_mmap_get_start_address (map); + map_end = sysprof_document_mmap_get_end_address (map); + + g_assert (address < map_end); + g_assert (address >= map_begin); + + file_offset = sysprof_document_mmap_get_file_offset (map); + path = sysprof_document_mmap_get_file (map); + + if (g_hash_table_contains (self->failed, path)) + return NULL; + + elf = sysprof_elf_loader_load (self->loader, + process_info->mount_namespace, + path, + sysprof_document_mmap_get_build_id (map), + sysprof_document_mmap_get_file_inode (map), + NULL); + if (elf == NULL) + return NULL; + + if (!(build_id = sysprof_elf_get_build_id (elf))) + return NULL; + + if (!g_hash_table_contains (self->cache, elf)) + { + g_autoptr(SysprofDebuginfodTask) task = sysprof_debuginfod_task_new (); + g_autoptr(SysprofDocumentLoader) loader = g_weak_ref_get (&self->loader_wr); + g_autoptr(SysprofDocumentTaskScope) scope = _sysprof_document_task_register (SYSPROF_DOCUMENT_TASK (task), loader); + g_autoptr(SysprofElf) debuginfo_elf = NULL; + + if (!(debuginfo_elf = sysprof_debuginfod_task_find_debuginfo (task, self->client, path, build_id, NULL))) + { + g_hash_table_insert (self->failed, g_strdup (path), NULL); + return NULL; + } + + sysprof_elf_set_debug_link_elf (elf, debuginfo_elf); + + g_hash_table_insert (self->cache, g_object_ref (elf), NULL); + } + + relative_address = address; + relative_address -= map_begin; + relative_address += file_offset; + + name = sysprof_elf_get_symbol_at_address (elf, + relative_address, + &begin_address, + &end_address); + if (!name) + return NULL; + + begin_address = CLAMP (begin_address, file_offset, file_offset + (map_end - map_begin)); + end_address = CLAMP (end_address, file_offset, file_offset + (map_end - map_begin)); + if (end_address == begin_address) + end_address++; + + sym = _sysprof_symbol_new (sysprof_strings_get (strings, name), + sysprof_strings_get (strings, path), + sysprof_strings_get (strings, sysprof_elf_get_nick (elf)), + map_begin + (begin_address - file_offset), + map_begin + (end_address - file_offset), + SYSPROF_SYMBOL_KIND_USER); + + return sym; +} + +static void +sysprof_debuginfod_symbolizer_setup (SysprofSymbolizer *symbolizer, + SysprofDocumentLoader *loader) +{ + SysprofDebuginfodSymbolizer *self = SYSPROF_DEBUGINFOD_SYMBOLIZER (symbolizer); + + g_weak_ref_set (&self->loader_wr, loader); +} + +static void +sysprof_debuginfod_symbolizer_dispose (GObject *object) +{ + SysprofDebuginfodSymbolizer *self = SYSPROF_DEBUGINFOD_SYMBOLIZER (object); + + g_hash_table_remove_all (self->cache); + + g_weak_ref_set (&self->loader_wr, NULL); + + G_OBJECT_CLASS (sysprof_debuginfod_symbolizer_parent_class)->dispose (object); +} + +static void +sysprof_debuginfod_symbolizer_finalize (GObject *object) +{ + SysprofDebuginfodSymbolizer *self = SYSPROF_DEBUGINFOD_SYMBOLIZER (object); + + g_clear_object (&self->loader); + + g_clear_pointer (&self->cache, g_hash_table_unref); + g_clear_pointer (&self->failed, g_hash_table_unref); + g_clear_pointer (&self->client, debuginfod_end); + + g_weak_ref_clear (&self->loader_wr); + + G_OBJECT_CLASS (sysprof_debuginfod_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_debuginfod_symbolizer_class_init (SysprofDebuginfodSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->dispose = sysprof_debuginfod_symbolizer_dispose; + object_class->finalize = sysprof_debuginfod_symbolizer_finalize; + + symbolizer_class->setup = sysprof_debuginfod_symbolizer_setup; + symbolizer_class->symbolize = sysprof_debuginfod_symbolizer_symbolize; +} + +static void +sysprof_debuginfod_symbolizer_init (SysprofDebuginfodSymbolizer *self) +{ + g_weak_ref_init (&self->loader_wr, NULL); +} + +SysprofSymbolizer * +sysprof_debuginfod_symbolizer_new (GError **error) +{ + g_autoptr(SysprofDebuginfodSymbolizer) self = NULL; + + self = g_object_new (SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, NULL); + self->client = debuginfod_begin (); + + if (self->client == NULL) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return NULL; + } + + self->loader = sysprof_elf_loader_new (); + self->cache = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL); + self->failed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + return SYSPROF_SYMBOLIZER (g_steal_pointer (&self)); +} diff --git a/src/libsysprof/sysprof-debuginfod-symbolizer.h b/src/libsysprof/sysprof-debuginfod-symbolizer.h new file mode 100644 index 00000000..32dd347e --- /dev/null +++ b/src/libsysprof/sysprof-debuginfod-symbolizer.h @@ -0,0 +1,42 @@ +/* sysprof-debuginfod-symbolizer.h + * + * Copyright 2024 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER (sysprof_debuginfod_symbolizer_get_type()) +#define SYSPROF_IS_DEBUGINFOD_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER) +#define SYSPROF_DEBUGINFOD_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, SysprofDebuginfodSymbolizer) +#define SYSPROF_DEBUGINFOD_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, SysprofDebuginfodSymbolizerClass) + +typedef struct _SysprofDebuginfodSymbolizer SysprofDebuginfodSymbolizer; +typedef struct _SysprofDebuginfodSymbolizerClass SysprofDebuginfodSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_debuginfod_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_debuginfod_symbolizer_new (GError **error); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDebuginfodSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-debuginfod-task-private.h b/src/libsysprof/sysprof-debuginfod-task-private.h new file mode 100644 index 00000000..33e595ec --- /dev/null +++ b/src/libsysprof/sysprof-debuginfod-task-private.h @@ -0,0 +1,42 @@ +/* + * sysprof-debuginfod-task-private.h + * + * Copyright 2024 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-task-private.h" +#include "sysprof-elf-private.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DEBUGINFOD_TASK (sysprof_debuginfod_task_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofDebuginfodTask, sysprof_debuginfod_task, SYSPROF, DEBUGINFOD_TASK, SysprofDocumentTask) + +SysprofDebuginfodTask *sysprof_debuginfod_task_new (void); +SysprofElf *sysprof_debuginfod_task_find_debuginfo (SysprofDebuginfodTask *self, + debuginfod_client *client, + const char *path, + const char *build_id_string, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-debuginfod-task.c b/src/libsysprof/sysprof-debuginfod-task.c new file mode 100644 index 00000000..0f996d09 --- /dev/null +++ b/src/libsysprof/sysprof-debuginfod-task.c @@ -0,0 +1,131 @@ +/* + * sysprof-debuginfod-task.c + * + * Copyright 2024 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include + +#include "sysprof-debuginfod-task-private.h" + +struct _SysprofDebuginfodTask +{ + SysprofDocumentTask parent_instance; +}; + +G_DEFINE_FINAL_TYPE (SysprofDebuginfodTask, sysprof_debuginfod_task, SYSPROF_TYPE_DOCUMENT_TASK) + +static void +sysprof_debuginfod_task_class_init (SysprofDebuginfodTaskClass *klass) +{ +} + +static void +sysprof_debuginfod_task_init (SysprofDebuginfodTask *self) +{ +} + +SysprofDebuginfodTask * +sysprof_debuginfod_task_new (void) +{ + return g_object_new (SYSPROF_TYPE_DEBUGINFOD_TASK, NULL); +} + +static int +sysprof_debuginfod_task_progress_cb (debuginfod_client *client, + long a, + long b) +{ + SysprofDocumentTask *task = debuginfod_get_user_data (client); + double progress; + + g_assert (client != NULL); + g_assert (SYSPROF_IS_DEBUGINFOD_TASK (task)); + + if (b > 0) + progress = (double)a / (double)b; + else + progress = 0; + + _sysprof_document_task_set_progress (task, progress); + + if (sysprof_document_task_is_cancelled (task)) + return -1; + + return 0; +} + +SysprofElf * +sysprof_debuginfod_task_find_debuginfo (SysprofDebuginfodTask *self, + debuginfod_client *client, + const char *path, + const char *build_id_string, + GError **error) +{ + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(SysprofElf) debuginfo_elf = NULL; + g_autofd int fd = -1; + char *debuginfo_path = NULL; + + g_return_val_if_fail (SYSPROF_IS_DEBUGINFOD_TASK (self), NULL); + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (build_id_string != NULL, NULL); + + debuginfod_set_user_data (client, self); + debuginfod_set_progressfn (client, sysprof_debuginfod_task_progress_cb); + + _sysprof_document_task_set_title (SYSPROF_DOCUMENT_TASK (self), _("Downloading Symbols…")); + _sysprof_document_task_take_message (SYSPROF_DOCUMENT_TASK (self), g_strdup (path)); + + fd = debuginfod_find_debuginfo (client, + (const unsigned char *)build_id_string, 0, + &debuginfo_path); + + if (fd < 0) + { + if (error != NULL) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + } + + goto failure; + } + + if (!(mapped_file = g_mapped_file_new_from_fd (fd, FALSE, error))) + goto failure; + + if (!(debuginfo_elf = sysprof_elf_new (debuginfo_path, g_steal_pointer (&mapped_file), 0, error))) + goto failure; + +failure: + free (debuginfo_path); + + debuginfod_set_user_data (client, NULL); + debuginfod_set_progressfn (client, NULL); + + return g_steal_pointer (&debuginfo_elf); +} diff --git a/src/libsysprof/sysprof-document-loader.c b/src/libsysprof/sysprof-document-loader.c index 3cd408c4..00b525df 100644 --- a/src/libsysprof/sysprof-document-loader.c +++ b/src/libsysprof/sysprof-document-loader.c @@ -196,7 +196,6 @@ static void set_default_symbolizer (SysprofDocumentLoader *self) { g_autoptr(SysprofMultiSymbolizer) multi = NULL; - g_autoptr(SysprofSymbolizer) debuginfod = NULL; g_autoptr(GError) error = NULL; g_assert (SYSPROF_IS_DOCUMENT_LOADER (self)); @@ -209,10 +208,16 @@ set_default_symbolizer (SysprofDocumentLoader *self) sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); - if (!(debuginfod = sysprof_debuginfod_symbolizer_new (&error))) - g_warning ("Failed to create debuginfod symbolizer: %s", error->message); - else - sysprof_multi_symbolizer_take (multi, g_steal_pointer (&debuginfod)); +#if HAVE_DEBUGINFOD + { + g_autoptr(SysprofSymbolizer) debuginfod = NULL; + + if (!(debuginfod = sysprof_debuginfod_symbolizer_new (&error))) + g_warning ("Failed to create debuginfod symbolizer: %s", error->message); + else + sysprof_multi_symbolizer_take (multi, g_steal_pointer (&debuginfod)); + } +#endif self->symbolizer = SYSPROF_SYMBOLIZER (g_steal_pointer (&multi)); } -- 2.45.2