diff --git a/0001-sysprof-update-to-AdwSpinner.patch b/0001-sysprof-update-to-AdwSpinner.patch new file mode 100644 index 0000000..f61a0c6 --- /dev/null +++ b/0001-sysprof-update-to-AdwSpinner.patch @@ -0,0 +1,53 @@ +From 1804382418ba2b98d97ca47cbdc2a79cca7061e6 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 9 Oct 2024 16:39:13 -0700 +Subject: [PATCH 01/18] sysprof: update to AdwSpinner + +--- + src/sysprof/meson.build | 2 +- + src/sysprof/sysprof-window.ui | 9 +-------- + 2 files changed, 2 insertions(+), 9 deletions(-) + +diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build +index ccd698ea..989bbd11 100644 +--- a/src/sysprof/meson.build ++++ b/src/sysprof/meson.build +@@ -85,7 +85,7 @@ sysprof_resources = gnome.compile_resources('sysprof-resources', 'sysprof.gresou + sysprof_deps = [ + cc.find_library('m', required: false), + dependency('gtk4', version: gtk_req_version), +- dependency('libadwaita-1', version: '>= 1.6.alpha'), ++ dependency('libadwaita-1', version: '>= 1.6.0'), + dependency('libpanel-1', version: '>= 1.7.0'), + + libsysprof_static_dep, +diff --git a/src/sysprof/sysprof-window.ui b/src/sysprof/sysprof-window.ui +index b33cd613..1b361e37 100644 +--- a/src/sysprof/sysprof-window.ui ++++ b/src/sysprof/sysprof-window.ui +@@ -109,7 +109,7 @@ + + + +- ++ + + + +@@ -117,13 +117,6 @@ + + + +- +- +- +- SysprofWindow +- +- +- + + + +-- +2.45.2 + diff --git a/0002-libsysprof-elf-do-not-allow-setting-self-as-debug-li.patch b/0002-libsysprof-elf-do-not-allow-setting-self-as-debug-li.patch new file mode 100644 index 0000000..b4c3486 --- /dev/null +++ b/0002-libsysprof-elf-do-not-allow-setting-self-as-debug-li.patch @@ -0,0 +1,28 @@ +From 56dfb1d6422cb0b687751b79528e56b6f9e3dfe6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= +Date: Wed, 9 Oct 2024 15:29:59 +0200 +Subject: [PATCH 02/18] libsysprof: elf: do not allow setting self as debug + link + +That will cause infinite recursion in `sysprof_elf_get_symbol_at_address_internal()`. +Also note that loops are still possible, this change +only prevents one way of creating loops. +--- + src/libsysprof/sysprof-elf.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/libsysprof/sysprof-elf.c b/src/libsysprof/sysprof-elf.c +index f4e5f914..0534eccb 100644 +--- a/src/libsysprof/sysprof-elf.c ++++ b/src/libsysprof/sysprof-elf.c +@@ -502,6 +502,7 @@ sysprof_elf_set_debug_link_elf (SysprofElf *self, + { + g_return_if_fail (SYSPROF_IS_ELF (self)); + g_return_if_fail (!debug_link_elf || SYSPROF_IS_ELF (debug_link_elf)); ++ g_return_if_fail (debug_link_elf != self); + + if (g_set_object (&self->debug_link_elf, debug_link_elf)) + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUG_LINK_ELF]); +-- +2.45.2 + diff --git a/0003-libsysprof-elf-do-not-generate-fallback-names.patch b/0003-libsysprof-elf-do-not-generate-fallback-names.patch new file mode 100644 index 0000000..1acd884 --- /dev/null +++ b/0003-libsysprof-elf-do-not-generate-fallback-names.patch @@ -0,0 +1,140 @@ +From 5792f34efabc3d2c2a5292d3b30a597d5c53374b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= +Date: Wed, 9 Oct 2024 15:28:14 +0200 +Subject: [PATCH 03/18] libsysprof: elf: do not generate fallback names + +Fallback names are only used in `SysprofElfSymbolizer` at the +moment, but it also has code to generate fallback symbols. +--- + src/libsysprof/sysprof-elf-private.h | 3 +-- + src/libsysprof/sysprof-elf-symbolizer.c | 12 ++++++------ + src/libsysprof/sysprof-elf.c | 17 +++++------------ + 3 files changed, 12 insertions(+), 20 deletions(-) + +diff --git a/src/libsysprof/sysprof-elf-private.h b/src/libsysprof/sysprof-elf-private.h +index b4d0f737..146bc2de 100644 +--- a/src/libsysprof/sysprof-elf-private.h ++++ b/src/libsysprof/sysprof-elf-private.h +@@ -42,8 +42,7 @@ const char *sysprof_elf_get_debug_link (SysprofElf *self); + char *sysprof_elf_get_symbol_at_address (SysprofElf *self, + guint64 address, + guint64 *begin_address, +- guint64 *end_address, +- gboolean *is_fallback); ++ guint64 *end_address); + SysprofElf *sysprof_elf_get_debug_link_elf (SysprofElf *self); + void sysprof_elf_set_debug_link_elf (SysprofElf *self, + SysprofElf *debug_link_elf); +diff --git a/src/libsysprof/sysprof-elf-symbolizer.c b/src/libsysprof/sysprof-elf-symbolizer.c +index 8fdf204f..f7aabef7 100644 +--- a/src/libsysprof/sysprof-elf-symbolizer.c ++++ b/src/libsysprof/sysprof-elf-symbolizer.c +@@ -61,9 +61,9 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + g_autoptr(SysprofElf) elf = NULL; + SysprofDocumentMmap *map; + g_autofree char *name = NULL; ++ const char *nick = NULL; + const char *path; + const char *build_id; +- gboolean is_fallback = FALSE; + guint64 map_begin; + guint64 map_end; + guint64 relative_address; +@@ -115,14 +115,15 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + NULL))) + goto fallback; + ++ nick = sysprof_elf_get_nick (elf); ++ + /* Try to get the symbol name at the address and the begin/end address + * so that it can be inserted into our symbol cache. + */ + if (!(name = sysprof_elf_get_symbol_at_address (elf, + relative_address, + &begin_address, +- &end_address, +- &is_fallback))) ++ &end_address))) + goto fallback; + + /* Sanitize address ranges if we have to. Sometimes that can happen +@@ -135,11 +136,10 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + sysprof_strings_get (strings, path), +- sysprof_strings_get (strings, sysprof_elf_get_nick (elf)), ++ sysprof_strings_get (strings, nick), + map_begin + (begin_address - file_offset), + map_begin + (end_address - file_offset), + SYSPROF_SYMBOL_KIND_USER); +- ret->is_fallback = is_fallback; + + return ret; + +@@ -156,7 +156,7 @@ fallback: + + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + sysprof_strings_get (strings, path), +- NULL, ++ sysprof_strings_get (strings, nick), + begin_address, end_address, + SYSPROF_SYMBOL_KIND_USER); + ret->is_fallback = TRUE; +diff --git a/src/libsysprof/sysprof-elf.c b/src/libsysprof/sysprof-elf.c +index 0534eccb..df73ce6d 100644 +--- a/src/libsysprof/sysprof-elf.c ++++ b/src/libsysprof/sysprof-elf.c +@@ -409,8 +409,7 @@ sysprof_elf_get_symbol_at_address_internal (SysprofElf *self, + guint64 address, + guint64 *begin_address, + guint64 *end_address, +- guint64 text_offset, +- gboolean *is_fallback) ++ guint64 text_offset) + { + const ElfSym *symbol; + char *ret = NULL; +@@ -421,7 +420,7 @@ sysprof_elf_get_symbol_at_address_internal (SysprofElf *self, + + if (self->debug_link_elf != NULL) + { +- ret = sysprof_elf_get_symbol_at_address_internal (self->debug_link_elf, filename, address, begin_address, end_address, text_offset, is_fallback); ++ ret = sysprof_elf_get_symbol_at_address_internal (self->debug_link_elf, filename, address, begin_address, end_address, text_offset); + + if (ret != NULL) + return ret; +@@ -447,11 +446,7 @@ sysprof_elf_get_symbol_at_address_internal (SysprofElf *self, + } + else + { +- begin = address; +- end = address + 1; +- ret = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", filename, address); +- if (is_fallback) +- *is_fallback = TRUE; ++ return NULL; + } + + if (begin_address) +@@ -467,16 +462,14 @@ char * + sysprof_elf_get_symbol_at_address (SysprofElf *self, + guint64 address, + guint64 *begin_address, +- guint64 *end_address, +- gboolean *is_fallback) ++ guint64 *end_address) + { + return sysprof_elf_get_symbol_at_address_internal (self, + self->file, + address, + begin_address, + end_address, +- self->text_offset, +- is_fallback); ++ self->text_offset); + } + + /** +-- +2.45.2 + diff --git a/0004-sysprof-add-SysprofDocumentTask-abstraction.patch b/0004-sysprof-add-SysprofDocumentTask-abstraction.patch new file mode 100644 index 0000000..7094106 --- /dev/null +++ b/0004-sysprof-add-SysprofDocumentTask-abstraction.patch @@ -0,0 +1,1277 @@ +From f1ce6229182005afe07896381a884eacda229b5c Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Thu, 10 Oct 2024 17:02:06 -0700 +Subject: [PATCH 04/18] sysprof: add SysprofDocumentTask abstraction + +This provides a task abstraction to SysprofDocumentLoader so that we can +elevate information about tasks to the user interface. It also moves the +spinner to a menu button w/ popover to display those tasks. +--- + src/libsysprof/meson.build | 2 + + .../sysprof-document-loader-private.h | 34 ++ + src/libsysprof/sysprof-document-loader.c | 129 ++++++- + src/libsysprof/sysprof-document-loader.h | 2 + + .../sysprof-document-task-private.h | 53 +++ + src/libsysprof/sysprof-document-task.c | 327 ++++++++++++++++++ + src/libsysprof/sysprof-document-task.h | 46 +++ + src/libsysprof/sysprof.h | 1 + + src/sysprof/meson.build | 1 + + src/sysprof/style.css | 9 + + src/sysprof/sysprof-task-row.c | 139 ++++++++ + src/sysprof/sysprof-task-row.h | 32 ++ + src/sysprof/sysprof-task-row.ui | 71 ++++ + src/sysprof/sysprof-window.c | 36 +- + src/sysprof/sysprof-window.ui | 75 +++- + src/sysprof/sysprof.gresource.xml | 1 + + 16 files changed, 937 insertions(+), 21 deletions(-) + create mode 100644 src/libsysprof/sysprof-document-loader-private.h + create mode 100644 src/libsysprof/sysprof-document-task-private.h + create mode 100644 src/libsysprof/sysprof-document-task.c + create mode 100644 src/libsysprof/sysprof-document-task.h + create mode 100644 src/sysprof/sysprof-task-row.c + create mode 100644 src/sysprof/sysprof-task-row.h + create mode 100644 src/sysprof/sysprof-task-row.ui + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index fd53ee11..697e9665 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -32,6 +32,7 @@ libsysprof_public_sources = [ + 'sysprof-document-overlay.c', + 'sysprof-document-process.c', + 'sysprof-document-sample.c', ++ 'sysprof-document-task.c', + 'sysprof-document-traceable.c', + 'sysprof-document.c', + 'sysprof-elf-symbolizer.c', +@@ -96,6 +97,7 @@ libsysprof_public_headers = [ + 'sysprof-document-overlay.h', + 'sysprof-document-process.h', + 'sysprof-document-sample.h', ++ 'sysprof-document-task.h', + 'sysprof-document-traceable.h', + 'sysprof-document.h', + 'sysprof-elf-symbolizer.h', +diff --git a/src/libsysprof/sysprof-document-loader-private.h b/src/libsysprof/sysprof-document-loader-private.h +new file mode 100644 +index 00000000..f7982489 +--- /dev/null ++++ b/src/libsysprof/sysprof-document-loader-private.h +@@ -0,0 +1,34 @@ ++/* ++ * sysprof-document-loader-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 "sysprof-document-loader.h" ++#include "sysprof-document-task.h" ++ ++G_BEGIN_DECLS ++ ++void _sysprof_document_loader_add_task (SysprofDocumentLoader *self, ++ SysprofDocumentTask *task); ++void _sysprof_document_loader_remove_task (SysprofDocumentLoader *self, ++ SysprofDocumentTask *task); ++ ++G_END_DECLS +diff --git a/src/libsysprof/sysprof-document-loader.c b/src/libsysprof/sysprof-document-loader.c +index 6622f325..3cd408c4 100644 +--- a/src/libsysprof/sysprof-document-loader.c ++++ b/src/libsysprof/sysprof-document-loader.c +@@ -27,18 +27,21 @@ + #include + + #include "sysprof-bundled-symbolizer.h" ++#include "sysprof-debuginfod-symbolizer.h" + #include "sysprof-document-bitset-index-private.h" +-#include "sysprof-document-loader.h" ++#include "sysprof-document-loader-private.h" + #include "sysprof-document-private.h" + #include "sysprof-elf-symbolizer.h" + #include "sysprof-jitmap-symbolizer.h" + #include "sysprof-kallsyms-symbolizer.h" + #include "sysprof-multi-symbolizer.h" ++#include "sysprof-symbolizer-private.h" + + struct _SysprofDocumentLoader + { + GObject parent_instance; + GMutex mutex; ++ GListStore *tasks; + SysprofSymbolizer *symbolizer; + char *filename; + char *message; +@@ -53,6 +56,7 @@ enum { + PROP_FRACTION, + PROP_MESSAGE, + PROP_SYMBOLIZER, ++ PROP_TASKS, + N_PROPS + }; + +@@ -192,6 +196,8 @@ 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)); + +@@ -202,9 +208,25 @@ set_default_symbolizer (SysprofDocumentLoader *self) + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); + 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)); ++ + self->symbolizer = SYSPROF_SYMBOLIZER (g_steal_pointer (&multi)); + } + ++static void ++sysprof_document_loader_dispose (GObject *object) ++{ ++ SysprofDocumentLoader *self = (SysprofDocumentLoader *)object; ++ ++ g_list_store_remove_all (self->tasks); ++ ++ G_OBJECT_CLASS (sysprof_document_loader_parent_class)->dispose (object); ++} ++ + static void + sysprof_document_loader_finalize (GObject *object) + { +@@ -212,6 +234,7 @@ sysprof_document_loader_finalize (GObject *object) + + g_clear_handle_id (&self->notify_source, g_source_remove); + g_clear_object (&self->symbolizer); ++ g_clear_object (&self->tasks); + g_clear_pointer (&self->filename, g_free); + g_clear_pointer (&self->message, g_free); + g_clear_fd (&self->fd, NULL); +@@ -242,6 +265,10 @@ sysprof_document_loader_get_property (GObject *object, + g_value_set_object (value, sysprof_document_loader_get_symbolizer (self)); + break; + ++ case PROP_TASKS: ++ g_value_take_object (value, sysprof_document_loader_list_tasks (self)); ++ break; ++ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +@@ -271,6 +298,7 @@ sysprof_document_loader_class_init (SysprofDocumentLoaderClass *klass) + { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ++ object_class->dispose = sysprof_document_loader_dispose; + object_class->finalize = sysprof_document_loader_finalize; + object_class->get_property = sysprof_document_loader_get_property; + object_class->set_property = sysprof_document_loader_set_property; +@@ -290,6 +318,11 @@ sysprof_document_loader_class_init (SysprofDocumentLoaderClass *klass) + SYSPROF_TYPE_SYMBOLIZER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + ++ properties[PROP_TASKS] = ++ g_param_spec_object ("tasks", NULL, NULL, ++ G_TYPE_LIST_MODEL, ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ + g_object_class_install_properties (object_class, N_PROPS, properties); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT); +@@ -302,6 +335,7 @@ sysprof_document_loader_init (SysprofDocumentLoader *self) + g_mutex_init (&self->mutex); + + self->fd = -1; ++ self->tasks = g_list_store_new (SYSPROF_TYPE_DOCUMENT_TASK); + + set_default_symbolizer (self); + } +@@ -546,6 +580,8 @@ sysprof_document_loader_load_async (SysprofDocumentLoader *self, + + set_progress (0., _("Loading document"), self); + ++ _sysprof_symbolizer_setup (self->symbolizer, self); ++ + if (self->fd != -1) + mapped_file_new_from_fd_async (self->fd, + cancellable, +@@ -657,3 +693,94 @@ sysprof_document_loader_load (SysprofDocumentLoader *self, + + return state.document; + } ++ ++typedef struct _TaskOp ++{ ++ SysprofDocumentLoader *loader; ++ SysprofDocumentTask *task; ++ guint remove : 1; ++} TaskOp; ++ ++static void ++_g_list_store_remove (GListStore *store, ++ gpointer instance) ++{ ++ GListModel *model = G_LIST_MODEL (store); ++ guint n_items = g_list_model_get_n_items (model); ++ ++ for (guint i = 0; i < n_items; i++) ++ { ++ g_autoptr(GObject) element = g_list_model_get_item (model, i); ++ ++ if (element == instance) ++ { ++ g_list_store_remove (store, i); ++ return; ++ } ++ } ++} ++ ++static gboolean ++task_op_run (gpointer data) ++{ ++ TaskOp *op = data; ++ ++ if (op->remove) ++ _g_list_store_remove (op->loader->tasks, op->task); ++ else ++ g_list_store_append (op->loader->tasks, op->task); ++ ++ g_clear_object (&op->loader); ++ g_clear_object (&op->task); ++ g_free (op); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static TaskOp * ++task_op_new (SysprofDocumentLoader *loader, ++ SysprofDocumentTask *task, ++ gboolean remove) ++{ ++ TaskOp op = { ++ g_object_ref (loader), ++ g_object_ref (task), ++ !!remove ++ }; ++ ++ return g_memdup2 (&op, sizeof op); ++} ++ ++void ++_sysprof_document_loader_add_task (SysprofDocumentLoader *self, ++ SysprofDocumentTask *task) ++{ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self)); ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (task)); ++ ++ g_idle_add (task_op_run, task_op_new (self, task, FALSE)); ++} ++ ++void ++_sysprof_document_loader_remove_task (SysprofDocumentLoader *self, ++ SysprofDocumentTask *task) ++{ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self)); ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (task)); ++ ++ g_idle_add (task_op_run, task_op_new (self, task, TRUE)); ++} ++ ++/** ++ * sysprof_document_loader_list_tasks: ++ * @self: a #SysprofDocumentLoader ++ * ++ * Returns: (transfer full): a #GListModel of #SysprofDocumentTask. ++ */ ++GListModel * ++sysprof_document_loader_list_tasks (SysprofDocumentLoader *self) ++{ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), NULL); ++ ++ return g_object_ref (G_LIST_MODEL (self->tasks)); ++} +diff --git a/src/libsysprof/sysprof-document-loader.h b/src/libsysprof/sysprof-document-loader.h +index 289dae30..1a1ea7da 100644 +--- a/src/libsysprof/sysprof-document-loader.h ++++ b/src/libsysprof/sysprof-document-loader.h +@@ -48,6 +48,8 @@ SYSPROF_AVAILABLE_IN_ALL + double sysprof_document_loader_get_fraction (SysprofDocumentLoader *self); + SYSPROF_AVAILABLE_IN_ALL + const char *sysprof_document_loader_get_message (SysprofDocumentLoader *self); ++SYSPROF_AVAILABLE_IN_ALL ++GListModel *sysprof_document_loader_list_tasks (SysprofDocumentLoader *self); + SYSPROF_AVAILABLE_IN_ALL + SysprofDocument *sysprof_document_loader_load (SysprofDocumentLoader *self, + GCancellable *cancellable, +diff --git a/src/libsysprof/sysprof-document-task-private.h b/src/libsysprof/sysprof-document-task-private.h +new file mode 100644 +index 00000000..3590b234 +--- /dev/null ++++ b/src/libsysprof/sysprof-document-task-private.h +@@ -0,0 +1,53 @@ ++/* ++ * sysprof-document-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-loader.h" ++#include "sysprof-document-task.h" ++ ++G_BEGIN_DECLS ++ ++typedef struct _SysprofDocumentTask SysprofDocumentTaskScope; ++ ++struct _SysprofDocumentTaskClass ++{ ++ GObjectClass parent_class; ++ ++ void (*cancel) (SysprofDocumentTask *self); ++}; ++ ++GCancellable *_sysprof_document_task_get_cancellable (SysprofDocumentTask *self); ++void _sysprof_document_task_set_title (SysprofDocumentTask *self, ++ const char *title); ++void _sysprof_document_task_take_message (SysprofDocumentTask *self, ++ char *message); ++void _sysprof_document_task_set_progress (SysprofDocumentTask *self, ++ double progress); ++SysprofDocumentTaskScope *_sysprof_document_task_register (SysprofDocumentTask *self, ++ SysprofDocumentLoader *loader); ++void _sysprof_document_task_unregister (SysprofDocumentTask *self); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentTaskScope, _sysprof_document_task_unregister) ++ ++G_END_DECLS +diff --git a/src/libsysprof/sysprof-document-task.c b/src/libsysprof/sysprof-document-task.c +new file mode 100644 +index 00000000..a8bfb30a +--- /dev/null ++++ b/src/libsysprof/sysprof-document-task.c +@@ -0,0 +1,327 @@ ++/* ++ * sysprof-document-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 "sysprof-document-loader-private.h" ++#include "sysprof-document-task-private.h" ++ ++typedef struct ++{ ++ GMutex mutex; ++ char *message; ++ char *title; ++ double progress; ++ GCancellable *cancellable; ++ guint notify_source; ++ GWeakRef loader_wr; ++} SysprofDocumentTaskPrivate; ++ ++enum { ++ PROP_0, ++ PROP_CANCELLED, ++ PROP_MESSAGE, ++ PROP_PROGRESS, ++ PROP_TITLE, ++ N_PROPS ++}; ++ ++G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SysprofDocumentTask, sysprof_document_task, G_TYPE_OBJECT) ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++sysprof_document_task_finalize (GObject *object) ++{ ++ SysprofDocumentTask *self = (SysprofDocumentTask *)object; ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_mutex_clear (&priv->mutex); ++ g_clear_handle_id (&priv->notify_source, g_source_remove); ++ g_clear_pointer (&priv->message, g_free); ++ g_clear_pointer (&priv->title, g_free); ++ g_clear_object (&priv->cancellable); ++ g_weak_ref_clear (&priv->loader_wr); ++ ++ G_OBJECT_CLASS (sysprof_document_task_parent_class)->finalize (object); ++} ++ ++static void ++sysprof_document_task_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ SysprofDocumentTask *self = SYSPROF_DOCUMENT_TASK (object); ++ ++ switch (prop_id) ++ { ++ case PROP_CANCELLED: ++ g_value_set_boolean (value, sysprof_document_task_is_cancelled (self)); ++ break; ++ ++ case PROP_MESSAGE: ++ g_value_take_string (value, sysprof_document_task_dup_message (self)); ++ break; ++ ++ case PROP_PROGRESS: ++ g_value_set_double (value, sysprof_document_task_get_progress (self)); ++ break; ++ ++ case PROP_TITLE: ++ g_value_take_string (value, sysprof_document_task_dup_title (self)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++sysprof_document_task_class_init (SysprofDocumentTaskClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = sysprof_document_task_finalize; ++ object_class->get_property = sysprof_document_task_get_property; ++ ++ properties[PROP_CANCELLED] = ++ g_param_spec_boolean ("cancelled", NULL, NULL, ++ FALSE, ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ ++ properties[PROP_MESSAGE] = ++ g_param_spec_string ("message", NULL, NULL, ++ NULL, ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ ++ properties[PROP_PROGRESS] = ++ g_param_spec_double ("progress", NULL, NULL, ++ 0., 1., 0., ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ ++ properties[PROP_TITLE] = ++ g_param_spec_string ("title", NULL, NULL, ++ NULL, ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++} ++ ++static void ++sysprof_document_task_init (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_weak_ref_init (&priv->loader_wr, NULL); ++ ++ priv->cancellable = g_cancellable_new (); ++} ++ ++static gboolean ++notify_in_idle_cb (gpointer data) ++{ ++ SysprofDocumentTask *self = data; ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_assert (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ g_mutex_lock (&priv->mutex); ++ priv->notify_source = 0; ++ g_mutex_unlock (&priv->mutex); ++ ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CANCELLED]); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MESSAGE]); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PROGRESS]); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++notify_in_idle_locked (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_assert (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ if (priv->notify_source == 0) ++ priv->notify_source = g_idle_add_full (G_PRIORITY_LOW, ++ notify_in_idle_cb, ++ g_object_ref (self), ++ g_object_unref); ++} ++ ++char * ++sysprof_document_task_dup_message (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ char *ret; ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), NULL); ++ ++ g_mutex_lock (&priv->mutex); ++ ret = g_strdup (priv->message); ++ g_mutex_unlock (&priv->mutex); ++ ++ return ret; ++} ++ ++char * ++sysprof_document_task_dup_title (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ char *ret; ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), NULL); ++ ++ g_mutex_lock (&priv->mutex); ++ ret = g_strdup (priv->title); ++ g_mutex_unlock (&priv->mutex); ++ ++ return ret; ++} ++ ++void ++_sysprof_document_task_take_message (SysprofDocumentTask *self, ++ char *message) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ g_mutex_lock (&priv->mutex); ++ g_free (priv->message); ++ priv->message = message; ++ notify_in_idle_locked (self); ++ g_mutex_unlock (&priv->mutex); ++} ++ ++double ++sysprof_document_task_get_progress (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ double ret; ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), 0); ++ ++ g_mutex_lock (&priv->mutex); ++ ret = priv->progress; ++ g_mutex_unlock (&priv->mutex); ++ ++ return ret; ++} ++ ++void ++_sysprof_document_task_set_progress (SysprofDocumentTask *self, ++ double progress) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ progress = CLAMP (progress, 0., 1.); ++ ++ g_mutex_lock (&priv->mutex); ++ priv->progress = progress; ++ notify_in_idle_locked (self); ++ g_mutex_unlock (&priv->mutex); ++} ++ ++void ++_sysprof_document_task_set_title (SysprofDocumentTask *self, ++ const char *title) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ g_mutex_lock (&priv->mutex); ++ if (g_set_str (&priv->title, title)) ++ notify_in_idle_locked (self); ++ g_mutex_unlock (&priv->mutex); ++} ++ ++gboolean ++sysprof_document_task_is_cancelled (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ gboolean ret; ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), FALSE); ++ ++ g_mutex_lock (&priv->mutex); ++ ret = g_cancellable_is_cancelled (priv->cancellable); ++ g_mutex_unlock (&priv->mutex); ++ ++ return ret; ++} ++ ++GCancellable * ++_sysprof_document_task_get_cancellable (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), NULL); ++ ++ return priv->cancellable; ++} ++ ++SysprofDocumentTaskScope * ++_sysprof_document_task_register (SysprofDocumentTask *self, ++ SysprofDocumentLoader *loader) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TASK (self), NULL); ++ g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (loader), NULL); ++ ++ g_weak_ref_set (&priv->loader_wr, loader); ++ ++ _sysprof_document_loader_add_task (loader, self); ++ ++ return self; ++} ++ ++void ++_sysprof_document_task_unregister (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ g_autoptr(SysprofDocumentLoader) loader = NULL; ++ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ if ((loader = g_weak_ref_get (&priv->loader_wr))) ++ _sysprof_document_loader_remove_task (loader, self); ++} ++ ++void ++sysprof_document_task_cancel (SysprofDocumentTask *self) ++{ ++ SysprofDocumentTaskPrivate *priv = sysprof_document_task_get_instance_private (self); ++ ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_TASK (self)); ++ ++ g_cancellable_cancel (priv->cancellable); ++ ++ if (SYSPROF_DOCUMENT_TASK_GET_CLASS (self)->cancel) ++ SYSPROF_DOCUMENT_TASK_GET_CLASS (self)->cancel (self); ++} +diff --git a/src/libsysprof/sysprof-document-task.h b/src/libsysprof/sysprof-document-task.h +new file mode 100644 +index 00000000..1f0be928 +--- /dev/null ++++ b/src/libsysprof/sysprof-document-task.h +@@ -0,0 +1,46 @@ ++/* ++ * sysprof-document-task.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-version-macros.h" ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_DOCUMENT_TASK (sysprof_document_task_get_type()) ++ ++SYSPROF_AVAILABLE_IN_ALL ++G_DECLARE_DERIVABLE_TYPE (SysprofDocumentTask, sysprof_document_task, SYSPROF, DOCUMENT_TASK, GObject) ++ ++SYSPROF_AVAILABLE_IN_ALL ++double sysprof_document_task_get_progress (SysprofDocumentTask *self); ++SYSPROF_AVAILABLE_IN_ALL ++char *sysprof_document_task_dup_message (SysprofDocumentTask *self); ++SYSPROF_AVAILABLE_IN_ALL ++char *sysprof_document_task_dup_title (SysprofDocumentTask *self); ++SYSPROF_AVAILABLE_IN_ALL ++gboolean sysprof_document_task_is_cancelled (SysprofDocumentTask *self); ++SYSPROF_AVAILABLE_IN_ALL ++void sysprof_document_task_cancel (SysprofDocumentTask *self); ++ ++G_END_DECLS +diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h +index 529386c8..c2176619 100644 +--- a/src/libsysprof/sysprof.h ++++ b/src/libsysprof/sysprof.h +@@ -56,6 +56,7 @@ G_BEGIN_DECLS + # include "sysprof-document-overlay.h" + # include "sysprof-document-process.h" + # include "sysprof-document-sample.h" ++# include "sysprof-document-task.h" + # include "sysprof-document-traceable.h" + # include "sysprof-document.h" + # include "sysprof-elf-symbolizer.h" +diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build +index 989bbd11..ca42ead3 100644 +--- a/src/sysprof/meson.build ++++ b/src/sysprof/meson.build +@@ -59,6 +59,7 @@ sysprof_sources = [ + 'sysprof-split-layer.c', + 'sysprof-storage-section.c', + 'sysprof-symbol-label.c', ++ 'sysprof-task-row.c', + 'sysprof-time-filter-model.c', + 'sysprof-time-label.c', + 'sysprof-time-ruler.c', +diff --git a/src/sysprof/style.css b/src/sysprof/style.css +index 946ab248..d5d7b5e2 100644 +--- a/src/sysprof/style.css ++++ b/src/sysprof/style.css +@@ -87,3 +87,12 @@ flamegraph { + timespanlayer { + font-size: 10px; + } ++ ++popover.tasks listview row { ++ background: transparent; ++ padding: 6px; ++} ++ ++popover.tasks listview row label.heading { ++ padding-bottom: 3px; ++} +diff --git a/src/sysprof/sysprof-task-row.c b/src/sysprof/sysprof-task-row.c +new file mode 100644 +index 00000000..a8e51754 +--- /dev/null ++++ b/src/sysprof/sysprof-task-row.c +@@ -0,0 +1,139 @@ ++/* ++ * sysprof-task-row.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 "sysprof-task-row.h" ++ ++struct _SysprofTaskRow ++{ ++ GtkWidget parent_instance; ++ SysprofDocumentTask *task; ++}; ++ ++enum { ++ PROP_0, ++ PROP_TASK, ++ N_PROPS ++}; ++ ++G_DEFINE_FINAL_TYPE (SysprofTaskRow, sysprof_task_row, GTK_TYPE_WIDGET) ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++sysprof_task_row_cancel (GtkWidget *widget, ++ const char *action, ++ GVariant *param) ++{ ++ SysprofTaskRow *self = SYSPROF_TASK_ROW (widget); ++ ++ if (self->task) ++ sysprof_document_task_cancel (self->task); ++} ++ ++static void ++sysprof_task_row_dispose (GObject *object) ++{ ++ SysprofTaskRow *self = (SysprofTaskRow *)object; ++ GtkWidget *child; ++ ++ gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_TASK_ROW); ++ ++ while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) ++ gtk_widget_unparent (child); ++ ++ g_clear_object (&self->task); ++ ++ G_OBJECT_CLASS (sysprof_task_row_parent_class)->dispose (object); ++} ++ ++static void ++sysprof_task_row_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ SysprofTaskRow *self = SYSPROF_TASK_ROW (object); ++ ++ switch (prop_id) ++ { ++ case PROP_TASK: ++ g_value_set_object (value, self->task); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++sysprof_task_row_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ SysprofTaskRow *self = SYSPROF_TASK_ROW (object); ++ ++ switch (prop_id) ++ { ++ case PROP_TASK: ++ if (g_set_object (&self->task, g_value_get_object (value))) ++ g_object_notify_by_pspec (object, pspec); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++sysprof_task_row_class_init (SysprofTaskRowClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); ++ ++ object_class->dispose = sysprof_task_row_dispose; ++ object_class->get_property = sysprof_task_row_get_property; ++ object_class->set_property = sysprof_task_row_set_property; ++ ++ properties[PROP_TASK] = ++ g_param_spec_object ("task", NULL, NULL, ++ SYSPROF_TYPE_DOCUMENT_TASK, ++ (G_PARAM_READWRITE | ++ G_PARAM_EXPLICIT_NOTIFY | ++ G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++ ++ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-task-row.ui"); ++ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); ++ ++ gtk_widget_class_install_action (widget_class, "task.cancel", NULL, sysprof_task_row_cancel); ++} ++ ++static void ++sysprof_task_row_init (SysprofTaskRow *self) ++{ ++ gtk_widget_init_template (GTK_WIDGET (self)); ++} +diff --git a/src/sysprof/sysprof-task-row.h b/src/sysprof/sysprof-task-row.h +new file mode 100644 +index 00000000..39bc3606 +--- /dev/null ++++ b/src/sysprof/sysprof-task-row.h +@@ -0,0 +1,32 @@ ++/* ++ * sysprof-task-row.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 ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_TASK_ROW (sysprof_task_row_get_type()) ++ ++G_DECLARE_FINAL_TYPE (SysprofTaskRow, sysprof_task_row, SYSPROF, TASK_ROW, GtkWidget) ++ ++G_END_DECLS +diff --git a/src/sysprof/sysprof-task-row.ui b/src/sysprof/sysprof-task-row.ui +new file mode 100644 +index 00000000..ac91d79f +--- /dev/null ++++ b/src/sysprof/sysprof-task-row.ui +@@ -0,0 +1,71 @@ ++ ++ ++ ++ +diff --git a/src/sysprof/sysprof-window.c b/src/sysprof/sysprof-window.c +index 869d6916..5fd6e9a1 100644 +--- a/src/sysprof/sysprof-window.c ++++ b/src/sysprof/sysprof-window.c +@@ -38,29 +38,32 @@ + #include "sysprof-samples-section.h" + #include "sysprof-sidebar.h" + #include "sysprof-storage-section.h" ++#include "sysprof-task-row.h" + #include "sysprof-util.h" + #include "sysprof-window.h" + + struct _SysprofWindow + { +- AdwApplicationWindow parent_instance; ++ AdwApplicationWindow parent_instance; + +- SysprofDocument *document; +- SysprofSession *session; ++ SysprofDocument *document; ++ SysprofDocumentLoader *loader; ++ SysprofSession *session; + +- GtkToggleButton *show_right_sidebar; +- GtkWidget *left_split_overlay; +- GtkWidget *right_split_overlay; +- GtkProgressBar *progress_bar; +- AdwWindowTitle *stack_title; ++ GtkToggleButton *show_right_sidebar; ++ GtkWidget *left_split_overlay; ++ GtkWidget *right_split_overlay; ++ GtkProgressBar *progress_bar; ++ AdwWindowTitle *stack_title; + +- guint disposed : 1; ++ guint disposed : 1; + }; + + enum { + PROP_0, + PROP_DOCUMENT, + PROP_IS_LOADED, ++ PROP_LOADER, + PROP_SESSION, + N_PROPS + }; +@@ -513,6 +516,10 @@ sysprof_window_get_property (GObject *object, + g_value_set_object (value, sysprof_window_get_document (self)); + break; + ++ case PROP_LOADER: ++ g_value_set_object (value, self->loader); ++ break; ++ + case PROP_IS_LOADED: + g_value_set_boolean (value, !!sysprof_window_get_document (self)); + break; +@@ -560,6 +567,11 @@ sysprof_window_class_init (SysprofWindowClass *klass) + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + ++ properties[PROP_LOADER] = ++ g_param_spec_object ("loader", NULL, NULL, ++ SYSPROF_TYPE_DOCUMENT_LOADER, ++ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); ++ + properties [PROP_IS_LOADED] = + g_param_spec_boolean ("is-loaded", NULL, NULL, + FALSE, +@@ -617,6 +629,7 @@ sysprof_window_class_init (SysprofWindowClass *klass) + g_type_ensure (SYSPROF_TYPE_SESSION); + g_type_ensure (SYSPROF_TYPE_SYMBOL); + g_type_ensure (SYSPROF_TYPE_SIDEBAR); ++ g_type_ensure (SYSPROF_TYPE_TASK_ROW); + } + + static void +@@ -711,6 +724,9 @@ sysprof_window_load_cb (GObject *object, + g_binding_unbind (g_object_get_data (G_OBJECT (loader), "message-binding")); + g_object_set_data (G_OBJECT (loader), "message-binding", NULL); + ++ if (g_set_object (&self->loader, NULL)) ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADER]); ++ + if (!(document = sysprof_document_loader_load_finish (loader, result, &error))) + { + GtkWidget *dialog; +@@ -750,6 +766,8 @@ sysprof_window_create (SysprofApplication *app, + self = g_object_new (SYSPROF_TYPE_WINDOW, + "application", app, + NULL); ++ self->loader = g_object_ref (loader); ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADER]); + g_object_bind_property (loader, "fraction", + self->progress_bar, "fraction", + G_BINDING_SYNC_CREATE); +diff --git a/src/sysprof/sysprof-window.ui b/src/sysprof/sysprof-window.ui +index 1b361e37..f4e1e7cb 100644 +--- a/src/sysprof/sysprof-window.ui ++++ b/src/sysprof/sysprof-window.ui +@@ -108,17 +108,6 @@ + + + +- +- +- +- +- +- SysprofWindow +- +- +- +- +- + + + +@@ -215,6 +204,70 @@ + Toggle Right Panel + + ++ ++ ++ true ++ false ++ ++ ++ ++ ++ ++ ++ ++ SysprofWindow ++ ++ ++ ++ ++ ++ ++ 300 ++ ++ ++ never ++ true ++ 600 ++ ++ ++ ++ ++ ++ ++ SysprofWindow ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++]]> ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/sysprof/sysprof.gresource.xml b/src/sysprof/sysprof.gresource.xml +index aefb57a9..70040947 100644 +--- a/src/sysprof/sysprof.gresource.xml ++++ b/src/sysprof/sysprof.gresource.xml +@@ -53,6 +53,7 @@ + sysprof-samples-section.ui + sysprof-sidebar.ui + sysprof-storage-section.ui ++ sysprof-task-row.ui + sysprof-time-scrubber.ui + sysprof-traceables-utility.ui + sysprof-weighted-callgraph-view.ui +-- +2.45.2 + diff --git a/0005-libsysprof-add-setup-hooks-for-symbolizers.patch b/0005-libsysprof-add-setup-hooks-for-symbolizers.patch new file mode 100644 index 0000000..dbe33fc --- /dev/null +++ b/0005-libsysprof-add-setup-hooks-for-symbolizers.patch @@ -0,0 +1,120 @@ +From a0ede5e2b3decc0002f452f0d44bd1a3e7e23870 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Thu, 10 Oct 2024 17:03:30 -0700 +Subject: [PATCH 05/18] libsysprof: add setup hooks for symbolizers + +This gives the symbolizer access to the loader so we can propagate tasks +back to it. +--- + src/libsysprof/sysprof-multi-symbolizer.c | 22 +++++++++++++++++++++ + src/libsysprof/sysprof-symbolizer-private.h | 6 +++++- + src/libsysprof/sysprof-symbolizer.c | 11 +++++++++++ + 3 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/src/libsysprof/sysprof-multi-symbolizer.c b/src/libsysprof/sysprof-multi-symbolizer.c +index 483cdde5..e1ad90b0 100644 +--- a/src/libsysprof/sysprof-multi-symbolizer.c ++++ b/src/libsysprof/sysprof-multi-symbolizer.c +@@ -27,6 +27,7 @@ struct _SysprofMultiSymbolizer + { + SysprofSymbolizer parent_instance; + GPtrArray *symbolizers; ++ guint frozen : 1; + }; + + struct _SysprofMultiSymbolizerClass +@@ -138,6 +139,25 @@ sysprof_multi_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + return NULL; + } + ++static void ++sysprof_multi_symbolizer_setup (SysprofSymbolizer *symbolizer, ++ SysprofDocumentLoader *loader) ++{ ++ SysprofMultiSymbolizer *self = (SysprofMultiSymbolizer *)symbolizer; ++ ++ g_assert (SYSPROF_IS_MULTI_SYMBOLIZER (self)); ++ g_assert (SYSPROF_IS_DOCUMENT_LOADER (loader)); ++ ++ self->frozen = TRUE; ++ ++ for (guint i = 0; i < self->symbolizers->len; i++) ++ { ++ SysprofSymbolizer *child = g_ptr_array_index (self->symbolizers, i); ++ ++ _sysprof_symbolizer_setup (child, loader); ++ } ++} ++ + static void + sysprof_multi_symbolizer_finalize (GObject *object) + { +@@ -159,6 +179,7 @@ sysprof_multi_symbolizer_class_init (SysprofMultiSymbolizerClass *klass) + symbolizer_class->prepare_async = sysprof_multi_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_multi_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_multi_symbolizer_symbolize; ++ symbolizer_class->setup = sysprof_multi_symbolizer_setup; + } + + static void +@@ -188,6 +209,7 @@ sysprof_multi_symbolizer_take (SysprofMultiSymbolizer *self, + g_return_if_fail (SYSPROF_IS_MULTI_SYMBOLIZER (self)); + g_return_if_fail (SYSPROF_IS_SYMBOLIZER (symbolizer)); + g_return_if_fail ((gpointer)self != (gpointer)symbolizer); ++ g_return_if_fail (self->frozen == FALSE); + + g_ptr_array_add (self->symbolizers, symbolizer); + } +diff --git a/src/libsysprof/sysprof-symbolizer-private.h b/src/libsysprof/sysprof-symbolizer-private.h +index dd917e44..3d68c52b 100644 +--- a/src/libsysprof/sysprof-symbolizer-private.h ++++ b/src/libsysprof/sysprof-symbolizer-private.h +@@ -22,6 +22,7 @@ + + #include "sysprof-address-layout-private.h" + #include "sysprof-document.h" ++#include "sysprof-document-loader.h" + #include "sysprof-mount-namespace-private.h" + #include "sysprof-process-info-private.h" + #include "sysprof-strings-private.h" +@@ -41,6 +42,8 @@ struct _SysprofSymbolizerClass + { + GObjectClass parent_class; + ++ void (*setup) (SysprofSymbolizer *self, ++ SysprofDocumentLoader *loader); + void (*prepare_async) (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, +@@ -56,7 +59,8 @@ struct _SysprofSymbolizerClass + SysprofAddress address); + }; + +- ++void _sysprof_symbolizer_setup (SysprofSymbolizer *self, ++ SysprofDocumentLoader *loader); + void _sysprof_symbolizer_prepare_async (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, +diff --git a/src/libsysprof/sysprof-symbolizer.c b/src/libsysprof/sysprof-symbolizer.c +index 9ad17ca2..47d6021a 100644 +--- a/src/libsysprof/sysprof-symbolizer.c ++++ b/src/libsysprof/sysprof-symbolizer.c +@@ -99,3 +99,14 @@ _sysprof_symbolizer_symbolize (SysprofSymbolizer *self, + { + return SYSPROF_SYMBOLIZER_GET_CLASS (self)->symbolize (self, strings, process_info, context, address); + } ++ ++void ++_sysprof_symbolizer_setup (SysprofSymbolizer *self, ++ SysprofDocumentLoader *loader) ++{ ++ g_return_if_fail (SYSPROF_IS_SYMBOLIZER (self)); ++ g_return_if_fail (SYSPROF_IS_DOCUMENT_LOADER (loader)); ++ ++ if (SYSPROF_SYMBOLIZER_GET_CLASS (self)->setup) ++ SYSPROF_SYMBOLIZER_GET_CLASS (self)->setup (self, loader); ++} +-- +2.45.2 + diff --git a/0006-libsysprof-hoist-fallback-symbol-creation.patch b/0006-libsysprof-hoist-fallback-symbol-creation.patch new file mode 100644 index 0000000..7d70993 --- /dev/null +++ b/0006-libsysprof-hoist-fallback-symbol-creation.patch @@ -0,0 +1,159 @@ +From 045589a30f5436d5a2887136952e8e0a28e8f2dc Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Thu, 10 Oct 2024 17:04:19 -0700 +Subject: [PATCH 06/18] libsysprof: hoist fallback symbol creation + +This makes sure that we get that even when not using the Elf symbolizer +but also means we can fallback through the Elf symbolizer into the +debuginfod symbolizer. +--- + src/libsysprof/sysprof-document-symbols.c | 69 ++++++++++++++++++++++- + src/libsysprof/sysprof-elf-symbolizer.c | 24 +------- + 2 files changed, 69 insertions(+), 24 deletions(-) + +diff --git a/src/libsysprof/sysprof-document-symbols.c b/src/libsysprof/sysprof-document-symbols.c +index 2f8d1849..828c7fb6 100644 +--- a/src/libsysprof/sysprof-document-symbols.c ++++ b/src/libsysprof/sysprof-document-symbols.c +@@ -85,6 +85,71 @@ symbolize_free (Symbolize *state) + g_free (state); + } + ++static SysprofSymbol * ++do_symbolize (SysprofSymbolizer *symbolizer, ++ SysprofStrings *strings, ++ SysprofProcessInfo *process_info, ++ SysprofAddressContext last_context, ++ SysprofAddress address) ++{ ++ SysprofDocumentMmap *map; ++ g_autofree char *name = NULL; ++ SysprofSymbol *ret; ++ const char *nick = NULL; ++ const char *path; ++ guint64 map_begin; ++ guint64 map_end; ++ guint64 relative_address; ++ guint64 begin_address; ++ guint64 end_address; ++ guint64 file_offset; ++ ++ if ((ret = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) ++ return ret; ++ ++ /* Fallback, we failed to locate the symbol within a file we can ++ * access, so tell the user about what file contained the symbol ++ * and where (relative to that file) the IP was. ++ */ ++ ++ if (!(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_begin); ++ g_assert (address < map_end); ++ ++ file_offset = sysprof_document_mmap_get_file_offset (map); ++ ++ relative_address = address; ++ relative_address -= map_begin; ++ relative_address += file_offset; ++ ++ path = sysprof_document_mmap_get_file (map); ++ ++ 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++; ++ ++ name = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", ++ sysprof_document_mmap_get_file (map), ++ relative_address); ++ begin_address = address; ++ end_address = address + 1; ++ ++ ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), ++ sysprof_strings_get (strings, path), ++ sysprof_strings_get (strings, nick), ++ begin_address, end_address, ++ SYSPROF_SYMBOL_KIND_USER); ++ ret->is_fallback = TRUE; ++ ++ return ret; ++} ++ + static void + add_traceable (SysprofDocumentSymbols *self, + SysprofStrings *strings, +@@ -123,7 +188,7 @@ add_traceable (SysprofDocumentSymbols *self, + if (sysprof_symbol_cache_lookup (self->kernel_symbols, address) != NULL) + continue; + +- if ((symbol = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) ++ if ((symbol = do_symbolize (symbolizer, strings, process_info, last_context, address))) + sysprof_symbol_cache_take (self->kernel_symbols, g_steal_pointer (&symbol)); + } + else +@@ -134,7 +199,7 @@ add_traceable (SysprofDocumentSymbols *self, + sysprof_symbol_cache_lookup (process_info->symbol_cache, address) != NULL) + continue; + +- if ((symbol = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) ++ if ((symbol = do_symbolize (symbolizer, strings, process_info, last_context, address))) + sysprof_symbol_cache_take (process_info->symbol_cache, g_steal_pointer (&symbol)); + } + } +diff --git a/src/libsysprof/sysprof-elf-symbolizer.c b/src/libsysprof/sysprof-elf-symbolizer.c +index f7aabef7..05bd1d6c 100644 +--- a/src/libsysprof/sysprof-elf-symbolizer.c ++++ b/src/libsysprof/sysprof-elf-symbolizer.c +@@ -113,7 +113,7 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + build_id, + file_inode, + NULL))) +- goto fallback; ++ return NULL; + + nick = sysprof_elf_get_nick (elf); + +@@ -124,7 +124,7 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + relative_address, + &begin_address, + &end_address))) +- goto fallback; ++ return NULL; + + /* Sanitize address ranges if we have to. Sometimes that can happen + * for us, but it seems to be limited to glibc. +@@ -142,26 +142,6 @@ sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SYSPROF_SYMBOL_KIND_USER); + + return ret; +- +-fallback: +- /* Fallback, we failed to locate the symbol within a file we can +- * access, so tell the user about what file contained the symbol +- * and where (relative to that file) the IP was. +- */ +- name = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", +- sysprof_document_mmap_get_file (map), +- relative_address); +- begin_address = address; +- end_address = address + 1; +- +- ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), +- sysprof_strings_get (strings, path), +- sysprof_strings_get (strings, nick), +- begin_address, end_address, +- SYSPROF_SYMBOL_KIND_USER); +- ret->is_fallback = TRUE; +- +- return ret; + } + + static void +-- +2.45.2 + diff --git a/0007-libsysprof-add-debuginfod-symbolizer.patch b/0007-libsysprof-add-debuginfod-symbolizer.patch new file mode 100644 index 0000000..a46650e --- /dev/null +++ b/0007-libsysprof-add-debuginfod-symbolizer.patch @@ -0,0 +1,600 @@ +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 + diff --git a/0008-libsysprof-ensure-access-to-process-info.patch b/0008-libsysprof-ensure-access-to-process-info.patch new file mode 100644 index 0000000..e593d7c --- /dev/null +++ b/0008-libsysprof-ensure-access-to-process-info.patch @@ -0,0 +1,26 @@ +From 27e5392cbf4ce792a26acc0945d36a243d9826cf Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Fri, 11 Oct 2024 11:08:52 -0700 +Subject: [PATCH 08/18] libsysprof: ensure access to process info + +--- + src/libsysprof/sysprof-document-symbols.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/libsysprof/sysprof-document-symbols.c b/src/libsysprof/sysprof-document-symbols.c +index 828c7fb6..e5584a69 100644 +--- a/src/libsysprof/sysprof-document-symbols.c ++++ b/src/libsysprof/sysprof-document-symbols.c +@@ -112,6 +112,9 @@ do_symbolize (SysprofSymbolizer *symbolizer, + * and where (relative to that file) the IP was. + */ + ++ if (process_info == NULL || process_info->address_layout == NULL) ++ return NULL; ++ + if (!(map = sysprof_address_layout_lookup (process_info->address_layout, address))) + return NULL; + +-- +2.45.2 + diff --git a/0009-libsysprof-fix-building-with-Ddebuginfod-auto.patch b/0009-libsysprof-fix-building-with-Ddebuginfod-auto.patch new file mode 100644 index 0000000..9675f2d --- /dev/null +++ b/0009-libsysprof-fix-building-with-Ddebuginfod-auto.patch @@ -0,0 +1,25 @@ +From 970acbbcf5a25983b593ef781885c81f8d55cee2 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Fri, 11 Oct 2024 11:26:13 -0700 +Subject: [PATCH 09/18] libsysprof: fix building with -Ddebuginfod=auto + +--- + src/libsysprof/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index b4e58078..ce6e37f2 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -153,7 +153,7 @@ libsysprof_private_sources = [ + 'timsort/gtktimsort.c', + ] + +-if debuginfod_dep.found() and get_option('debuginfod').enabled() ++if debuginfod_dep.found() + libsysprof_private_sources += [ + 'sysprof-debuginfod-symbolizer.c', + 'sysprof-debuginfod-task.c' +-- +2.45.2 + diff --git a/0010-libsysprof-return-NULL-instance-unless-debuginfod-wo.patch b/0010-libsysprof-return-NULL-instance-unless-debuginfod-wo.patch new file mode 100644 index 0000000..471e605 --- /dev/null +++ b/0010-libsysprof-return-NULL-instance-unless-debuginfod-wo.patch @@ -0,0 +1,158 @@ +From f9f78bfbd727b0b95dff4fad5325c72731e0e970 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Fri, 11 Oct 2024 11:45:41 -0700 +Subject: [PATCH 10/18] libsysprof: return NULL instance unless debuginfod + works + +If we are configured with support for debuginfod and it fails to initialize +or we are not configured to use debuginfod, always ensure g_object_new() +will return a NULL instance back. + +This helps prevent against misconfigured instances so we do not need to +do so many checks in vfuncs as well as allowing us to have the GType be +available always even when not built with debuginfod. +--- + .../sysprof-debuginfod-symbolizer.c | 66 +++++++++++++++---- + 1 file changed, 53 insertions(+), 13 deletions(-) + +diff --git a/src/libsysprof/sysprof-debuginfod-symbolizer.c b/src/libsysprof/sysprof-debuginfod-symbolizer.c +index 8dd60d19..bcad9a5c 100644 +--- a/src/libsysprof/sysprof-debuginfod-symbolizer.c ++++ b/src/libsysprof/sysprof-debuginfod-symbolizer.c +@@ -20,18 +20,22 @@ + + #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" + ++#if HAVE_DEBUGINFOD ++# include ++# include "sysprof-debuginfod-task-private.h" ++#else ++typedef struct _debuginfod_client debuginfod_client; ++#endif ++ + struct _SysprofDebuginfodSymbolizer + { + SysprofSymbolizer parent_instance; +@@ -58,6 +62,7 @@ sysprof_debuginfod_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofAddressContext context, + SysprofAddress address) + { ++#if HAVE_DEBUGINFOD + SysprofDebuginfodSymbolizer *self = SYSPROF_DEBUGINFOD_SYMBOLIZER (symbolizer); + g_autoptr(SysprofElf) elf = NULL; + g_autofree char *name = NULL; +@@ -145,6 +150,9 @@ sysprof_debuginfod_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SYSPROF_SYMBOL_KIND_USER); + + return sym; ++#else ++ return NULL; ++#endif + } + + static void +@@ -177,19 +185,54 @@ sysprof_debuginfod_symbolizer_finalize (GObject *object) + + g_clear_pointer (&self->cache, g_hash_table_unref); + g_clear_pointer (&self->failed, g_hash_table_unref); ++ ++#if HAVE_DEBUGINFOD + g_clear_pointer (&self->client, debuginfod_end); ++#endif + + g_weak_ref_clear (&self->loader_wr); + + G_OBJECT_CLASS (sysprof_debuginfod_symbolizer_parent_class)->finalize (object); + } + ++static GObject * ++sysprof_debuginfod_symbolizer_constructor (GType type, ++ guint n_construct_params, ++ GObjectConstructParam *construct_properties) ++{ ++#if HAVE_DEBUGINFOD ++ debuginfod_client *client; ++ GObject *object; ++ ++ g_assert (type == SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER); ++ ++ /* Don't even allow creating a SysprofDebuginfodSymbolizer instance unless we ++ * can create a new debuginfod_client. This ensures that even if an application ++ * does `g_object_new(SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, NULL)` they will get ++ * `NULL` back instead of a misconfigured instance. ++ */ ++ if (!(client = debuginfod_begin ())) ++ return NULL; ++ ++ object = G_OBJECT_CLASS (sysprof_debuginfod_symbolizer_parent_class) ++ ->constructor (type, n_construct_params, construct_properties); ++ ++ SYSPROF_DEBUGINFOD_SYMBOLIZER (object)->client = client; ++ ++ return object; ++#else ++ errno = ENOTSUP; ++ return NULL; ++#endif ++} ++ + 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->constructor = sysprof_debuginfod_symbolizer_constructor; + object_class->dispose = sysprof_debuginfod_symbolizer_dispose; + object_class->finalize = sysprof_debuginfod_symbolizer_finalize; + +@@ -201,17 +244,18 @@ static void + sysprof_debuginfod_symbolizer_init (SysprofDebuginfodSymbolizer *self) + { + g_weak_ref_init (&self->loader_wr, 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); + } + + SysprofSymbolizer * + sysprof_debuginfod_symbolizer_new (GError **error) + { +- g_autoptr(SysprofDebuginfodSymbolizer) self = NULL; ++ SysprofSymbolizer *self; + +- self = g_object_new (SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, NULL); +- self->client = debuginfod_begin (); +- +- if (self->client == NULL) ++ if (!(self = g_object_new (SYSPROF_TYPE_DEBUGINFOD_SYMBOLIZER, NULL))) + { + int errsv = errno; + g_set_error_literal (error, +@@ -221,9 +265,5 @@ sysprof_debuginfod_symbolizer_new (GError **error) + 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)); ++ return self; + } +-- +2.45.2 + diff --git a/0011-build-always-build-debuginfod-symbolizer.patch b/0011-build-always-build-debuginfod-symbolizer.patch new file mode 100644 index 0000000..d1a6800 --- /dev/null +++ b/0011-build-always-build-debuginfod-symbolizer.patch @@ -0,0 +1,56 @@ +From 7d2fccd26931a2ffb5006511a6ecaf529016d7ae Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Fri, 11 Oct 2024 11:46:26 -0700 +Subject: [PATCH 11/18] build: always build debuginfod symbolizer + +Even if it is disabled, we want the GType enabled and part of our ABI. We +will return NULL if one is created and debuginfod is not supported or if +we failed to create a client. +--- + src/libsysprof/meson.build | 3 ++- + src/libsysprof/sysprof.h | 1 + + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index ce6e37f2..549042de 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -10,6 +10,7 @@ libsysprof_public_sources = [ + 'sysprof-cpu-info.c', + 'sysprof-cpu-usage.c', + 'sysprof-dbus-monitor.c', ++ 'sysprof-debuginfod-symbolizer.c', + 'sysprof-diagnostic.c', + 'sysprof-disk-usage.c', + 'sysprof-document-allocation.c', +@@ -75,6 +76,7 @@ libsysprof_public_headers = [ + 'sysprof-cpu-info.h', + 'sysprof-cpu-usage.h', + 'sysprof-dbus-monitor.h', ++ 'sysprof-debuginfod-symbolizer.h', + 'sysprof-diagnostic.h', + 'sysprof-disk-usage.h', + 'sysprof-document-allocation.h', +@@ -155,7 +157,6 @@ libsysprof_private_sources = [ + + if debuginfod_dep.found() + libsysprof_private_sources += [ +- 'sysprof-debuginfod-symbolizer.c', + 'sysprof-debuginfod-task.c' + ] + endif +diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h +index c2176619..d30f9fd4 100644 +--- a/src/libsysprof/sysprof.h ++++ b/src/libsysprof/sysprof.h +@@ -34,6 +34,7 @@ G_BEGIN_DECLS + # include "sysprof-cpu-info.h" + # include "sysprof-cpu-usage.h" + # include "sysprof-dbus-monitor.h" ++# include "sysprof-debuginfod-symbolizer.h" + # include "sysprof-diagnostic.h" + # include "sysprof-disk-usage.h" + # include "sysprof-document-allocation.h" +-- +2.45.2 + diff --git a/0012-libsysprof-remove-unnecessary-address-calculation.patch b/0012-libsysprof-remove-unnecessary-address-calculation.patch new file mode 100644 index 0000000..3355b2d --- /dev/null +++ b/0012-libsysprof-remove-unnecessary-address-calculation.patch @@ -0,0 +1,49 @@ +From d19dd1b167de2c49f33c86908b7715790481a3de Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 23 Oct 2024 11:54:33 -0700 +Subject: [PATCH 12/18] libsysprof: remove unnecessary address calculation + +We are only fallback symbols here, which is 1 address-wide. +--- + src/libsysprof/sysprof-document-symbols.c | 11 +---------- + 1 file changed, 1 insertion(+), 10 deletions(-) + +diff --git a/src/libsysprof/sysprof-document-symbols.c b/src/libsysprof/sysprof-document-symbols.c +index e5584a69..642f702b 100644 +--- a/src/libsysprof/sysprof-document-symbols.c ++++ b/src/libsysprof/sysprof-document-symbols.c +@@ -100,8 +100,6 @@ do_symbolize (SysprofSymbolizer *symbolizer, + guint64 map_begin; + guint64 map_end; + guint64 relative_address; +- guint64 begin_address; +- guint64 end_address; + guint64 file_offset; + + if ((ret = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) +@@ -132,21 +130,14 @@ do_symbolize (SysprofSymbolizer *symbolizer, + + path = sysprof_document_mmap_get_file (map); + +- 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++; +- + name = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", + sysprof_document_mmap_get_file (map), + relative_address); +- begin_address = address; +- end_address = address + 1; + + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + sysprof_strings_get (strings, path), + sysprof_strings_get (strings, nick), +- begin_address, end_address, ++ address, address + 1, + SYSPROF_SYMBOL_KIND_USER); + ret->is_fallback = TRUE; + +-- +2.45.2 + diff --git a/0013-libsysprof-add-muxer-GSource.patch b/0013-libsysprof-add-muxer-GSource.patch new file mode 100644 index 0000000..bfd0af7 --- /dev/null +++ b/0013-libsysprof-add-muxer-GSource.patch @@ -0,0 +1,251 @@ +From 37b1710bcb04bcee2f9c05293ac535c514cd392b Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Fri, 25 Oct 2024 10:49:15 -0700 +Subject: [PATCH 13/18] libsysprof: add muxer GSource + +This allows copying events from a capture stream transparently into the +destination. No processing of the stream is performed, but that may change +in the future to accomidate JIT/Counter translations. + +Internal only as support for upcoming live unwinding via external process. +--- + src/libsysprof/meson.build | 1 + + src/libsysprof/sysprof-muxer-source.c | 173 ++++++++++++++++++++++++++ + src/libsysprof/sysprof-muxer-source.h | 33 +++++ + 3 files changed, 207 insertions(+) + create mode 100644 src/libsysprof/sysprof-muxer-source.c + create mode 100644 src/libsysprof/sysprof-muxer-source.h + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index 549042de..f3fa0850 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -147,6 +147,7 @@ libsysprof_private_sources = [ + 'sysprof-mount-device.c', + 'sysprof-mount-namespace.c', + 'sysprof-mount.c', ++ 'sysprof-muxer-source.c', + 'sysprof-perf-event-stream.c', + 'sysprof-podman.c', + 'sysprof-process-info.c', +diff --git a/src/libsysprof/sysprof-muxer-source.c b/src/libsysprof/sysprof-muxer-source.c +new file mode 100644 +index 00000000..c037a38f +--- /dev/null ++++ b/src/libsysprof/sysprof-muxer-source.c +@@ -0,0 +1,173 @@ ++/* ++ * sysprof-muxer-source.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 "sysprof-muxer-source.h" ++ ++#define DEFAULT_BUFFER_SIZE (4096*16) ++ ++typedef struct _SysprofMuxerSource ++{ ++ GSource gsource; ++ int capture_fd; ++ SysprofCaptureWriter *writer; ++ struct { ++ guint8 *allocation; ++ guint8 *begin; ++ guint8 *end; ++ guint8 *capacity; ++ gsize to_skip; ++ } buffer; ++} SysprofMuxerSource; ++ ++static gboolean ++sysprof_muxer_source_size (SysprofMuxerSource *source) ++{ ++ return source->buffer.end - source->buffer.begin; ++} ++ ++static gboolean ++sysprof_muxer_source_dispatch (GSource *gsource, ++ GSourceFunc callback, ++ gpointer user_data) ++{ ++ SysprofMuxerSource *source = (SysprofMuxerSource *)gsource; ++ gssize n_read; ++ ++ g_assert (source != NULL); ++ g_assert (source->writer != NULL); ++ ++ /* Try to read the next chunk */ ++ n_read = read (source->capture_fd, source->buffer.end, source->buffer.capacity - source->buffer.end); ++ ++ if (n_read > 0) ++ { ++ const SysprofCaptureFrame *frame; ++ ++ /* Advance tail to what was filled */ ++ source->buffer.end += n_read; ++ ++ /* Get to next alignment */ ++ if (source->buffer.to_skip) ++ { ++ gsize amount = MIN (source->buffer.to_skip, source->buffer.end - source->buffer.begin); ++ source->buffer.begin += amount; ++ source->buffer.to_skip -= amount; ++ } ++ ++ /* If there is enough to read the frame header, try to read and dispatch ++ * it in raw form. We assume we're the same endianness here because this ++ * is coming from the same host (live-unwinder currently). ++ */ ++ while (sysprof_muxer_source_size (source) >= sizeof *frame) ++ { ++ frame = (const SysprofCaptureFrame *)source->buffer.begin; ++ ++ if (frame->len <= sysprof_muxer_source_size (source)) ++ { ++ source->buffer.begin += frame->len; ++ ++ if (frame->len % sizeof (guint64) != 0) ++ source->buffer.to_skip = sizeof (guint64) - (frame->len % sizeof (guint64)); ++ ++ /* TODO: Technically for counters/JIT map we need to translate them. */ ++ ++ _sysprof_capture_writer_add_raw (source->writer, frame); ++ } ++ ++ if (source->buffer.to_skip > 0 && ++ source->buffer.to_skip <= sysprof_muxer_source_size (source)) ++ { ++ source->buffer.begin += source->buffer.to_skip; ++ source->buffer.to_skip = 0; ++ continue; ++ } ++ ++ break; ++ } ++ ++ /* Move anything left to the head of the buffer so we can ++ * fill in the entire next frame of data. ++ */ ++ if (source->buffer.begin < source->buffer.end) ++ { ++ /* TODO: Should we adjust for alignment here? */ ++ ++ memmove (source->buffer.allocation, ++ source->buffer.begin, ++ source->buffer.end - source->buffer.begin); ++ source->buffer.end = source->buffer.allocation + (source->buffer.end - source->buffer.begin); ++ source->buffer.begin = source->buffer.allocation; ++ } ++ else ++ { ++ source->buffer.end = source->buffer.allocation; ++ source->buffer.begin = source->buffer.allocation; ++ } ++ } ++ ++ ++ return G_SOURCE_CONTINUE; ++} ++ ++static void ++sysprof_muxer_source_finalize (GSource *gsource) ++{ ++ SysprofMuxerSource *source = (SysprofMuxerSource *)gsource; ++ ++ g_clear_fd (&source->capture_fd, NULL); ++ g_clear_pointer (&source->writer, sysprof_capture_writer_unref); ++ g_clear_pointer (&source->buffer.begin, g_free); ++} ++ ++static const GSourceFuncs source_funcs = { ++ .dispatch = sysprof_muxer_source_dispatch, ++ .finalize = sysprof_muxer_source_finalize, ++}; ++ ++GSource * ++sysprof_muxer_source_new (int capture_fd, ++ SysprofCaptureWriter *writer) ++{ ++ SysprofMuxerSource *source; ++ ++ g_return_val_if_fail (capture_fd > -1, NULL); ++ g_return_val_if_fail (writer != NULL, NULL); ++ ++ source = (SysprofMuxerSource *)g_source_new ((GSourceFuncs *)&source_funcs, sizeof (SysprofMuxerSource)); ++ source->capture_fd = capture_fd; ++ source->writer = sysprof_capture_writer_ref (writer); ++ source->buffer.allocation = g_malloc (DEFAULT_BUFFER_SIZE); ++ source->buffer.begin = source->buffer.allocation; ++ source->buffer.end = source->buffer.allocation; ++ source->buffer.capacity = source->buffer.allocation + DEFAULT_BUFFER_SIZE; ++ source->buffer.to_skip = sizeof (SysprofCaptureFileHeader); ++ ++ g_unix_set_fd_nonblocking (capture_fd, TRUE, NULL); ++ ++ g_source_add_unix_fd ((GSource *)source, capture_fd, G_IO_IN); ++ ++ return (GSource *)source; ++} +diff --git a/src/libsysprof/sysprof-muxer-source.h b/src/libsysprof/sysprof-muxer-source.h +new file mode 100644 +index 00000000..9b9a94e1 +--- /dev/null ++++ b/src/libsysprof/sysprof-muxer-source.h +@@ -0,0 +1,33 @@ ++/* ++ * sysprof-muxer-source.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 ++ ++G_BEGIN_DECLS ++ ++GSource *sysprof_muxer_source_new (int capture_fd, ++ SysprofCaptureWriter *writer); ++ ++G_END_DECLS +-- +2.45.2 + diff --git a/0014-libsysprof-add-support-for-stack-regs-options-in-att.patch b/0014-libsysprof-add-support-for-stack-regs-options-in-att.patch new file mode 100644 index 0000000..b36c101 --- /dev/null +++ b/0014-libsysprof-add-support-for-stack-regs-options-in-att.patch @@ -0,0 +1,138 @@ +From 38646818c06b45417b693695819d05198fb8e53b Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Sun, 3 Nov 2024 10:39:23 -0800 +Subject: [PATCH 14/18] libsysprof: add support for stack/regs options in attr + +This requires a coordinating sysprofd that knows how to handle reading the +new attributes. Setting these fields will allow snapshotting the contents +of the stack and registers to do offline unwinding. + +Also make the conversion to GVariant available outside the module so that +we can consume it for live unwinding. +--- + .../sysprof-perf-event-stream-private.h | 25 ++++++++++--------- + src/libsysprof/sysprof-perf-event-stream.c | 10 +++++--- + src/sysprofd/helpers.c | 16 ++++++++++++ + 3 files changed, 36 insertions(+), 15 deletions(-) + +diff --git a/src/libsysprof/sysprof-perf-event-stream-private.h b/src/libsysprof/sysprof-perf-event-stream-private.h +index 4ac0fd1e..7e3d9deb 100644 +--- a/src/libsysprof/sysprof-perf-event-stream-private.h ++++ b/src/libsysprof/sysprof-perf-event-stream-private.h +@@ -165,17 +165,18 @@ typedef void (*SysprofPerfEventCallback) (const SysprofPerfEvent *event, + + G_DECLARE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, SYSPROF, PERF_EVENT_STREAM, GObject) + +-DexFuture *sysprof_perf_event_stream_new (GDBusConnection *connection, +- struct perf_event_attr *attr, +- int cpu, +- int group_fd, +- guint64 flags, +- SysprofPerfEventCallback callback, +- gpointer callback_data, +- GDestroyNotify callback_data_destroy); +-gboolean sysprof_perf_event_stream_enable (SysprofPerfEventStream *self, +- GError **error); +-gboolean sysprof_perf_event_stream_disable (SysprofPerfEventStream *self, +- GError **error); ++DexFuture *sysprof_perf_event_stream_new (GDBusConnection *connection, ++ struct perf_event_attr *attr, ++ int cpu, ++ int group_fd, ++ guint64 flags, ++ SysprofPerfEventCallback callback, ++ gpointer callback_data, ++ GDestroyNotify callback_data_destroy); ++gboolean sysprof_perf_event_stream_enable (SysprofPerfEventStream *self, ++ GError **error); ++gboolean sysprof_perf_event_stream_disable (SysprofPerfEventStream *self, ++ GError **error); ++GVariant *_sysprof_perf_event_attr_to_variant (const struct perf_event_attr *attr); + + G_END_DECLS +diff --git a/src/libsysprof/sysprof-perf-event-stream.c b/src/libsysprof/sysprof-perf-event-stream.c +index a7bf8d88..c40182ad 100644 +--- a/src/libsysprof/sysprof-perf-event-stream.c ++++ b/src/libsysprof/sysprof-perf-event-stream.c +@@ -109,8 +109,8 @@ G_DEFINE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, G_TYPE_O + + static GParamSpec *properties [N_PROPS]; + +-static GVariant * +-build_options_dict (const struct perf_event_attr *attr) ++GVariant * ++_sysprof_perf_event_attr_to_variant (const struct perf_event_attr *attr) + { + return g_variant_take_ref ( + g_variant_new_parsed ("[" +@@ -130,6 +130,8 @@ build_options_dict (const struct perf_event_attr *attr) + "{'sample_period', <%t>}," + "{'sample_type', <%t>}," + "{'task', <%b>}," ++ "{'sample_stack_user', <%u>}," ++ "{'sample_regs_user', <%t>}," + "{'type', <%u>}" + "]", + (gboolean)!!attr->comm, +@@ -148,6 +150,8 @@ build_options_dict (const struct perf_event_attr *attr) + (guint64)attr->sample_period, + (guint64)attr->sample_type, + (gboolean)!!attr->task, ++ (guint32)attr->sample_stack_user, ++ (guint64)attr->sample_regs_user, + (guint32)attr->type)); + } + +@@ -513,7 +517,7 @@ sysprof_perf_event_stream_new (GDBusConnection *connection, + group_fd_handle = g_unix_fd_list_append (fd_list, group_fd, NULL); + } + +- options = build_options_dict (attr); ++ options = _sysprof_perf_event_attr_to_variant (attr); + + g_dbus_connection_call_with_unix_fd_list (connection, + "org.gnome.Sysprof3", +diff --git a/src/sysprofd/helpers.c b/src/sysprofd/helpers.c +index 2aebc417..7e5df34a 100644 +--- a/src/sysprofd/helpers.c ++++ b/src/sysprofd/helpers.c +@@ -127,6 +127,8 @@ helpers_perf_event_open (GVariant *options, + guint64 sample_period = 0; + guint64 sample_type = 0; + guint64 config = 0; ++ guint64 sample_regs_user = 0; ++ guint sample_stack_user = 0; + int clockid = CLOCK_MONOTONIC; + int comm = 0; + int mmap_ = 0; +@@ -236,6 +238,18 @@ helpers_perf_event_open (GVariant *options, + goto bad_arg; + use_clockid = g_variant_get_boolean (value); + } ++ else if (strcmp (key, "sample_stack_user") == 0) ++ { ++ if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) ++ goto bad_arg; ++ sample_stack_user = g_variant_get_uint32 (value); ++ } ++ else if (strcmp (key, "sample_regs_user") == 0) ++ { ++ if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64)) ++ goto bad_arg; ++ sample_regs_user = g_variant_get_uint64 (value); ++ } + + continue; + +@@ -257,6 +271,8 @@ helpers_perf_event_open (GVariant *options, + attr.task = !!task; + attr.type = type; + attr.wakeup_events = wakeup_events; ++ attr.sample_regs_user = sample_regs_user; ++ attr.sample_stack_user = sample_stack_user; + + #ifdef HAVE_PERF_CLOCKID + if (!use_clockid || clockid < 0) +-- +2.45.2 + diff --git a/0015-sysprofd-add-support-for-unwinding-without-frame-poi.patch b/0015-sysprofd-add-support-for-unwinding-without-frame-poi.patch new file mode 100644 index 0000000..78942dd --- /dev/null +++ b/0015-sysprofd-add-support-for-unwinding-without-frame-poi.patch @@ -0,0 +1,2787 @@ +From 565ff01f20c66e35c2977a55519bd5e799a39eb3 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Sun, 3 Nov 2024 10:41:44 -0800 +Subject: [PATCH 15/18] sysprofd: add support for unwinding without frame + pointers + +This provides a new sysprof-live-unwinder subprocess that runs as root to +allow accessing all processes on the system via /proc/$pid/. It is spawned +by sysprofd with various perf event FDs and a FD to write captures to. + +Ideally the capture_fd is something that will naturally error if the client +application crashes (such as a socketpair() having the peer close). This +is not enforced but encouraged. Additionally, an event_fd is used to allow +the client application to signal the live-unwinder to exit. + +Unwinding is performed by looking at the modules loaded into the target +pid and using libdwfl to access DWARF/CFI/etc state machinery. Stack data +does not touch the disk as it exists in a mmap buffer from perf and is +then translated into a callchain and sent to the Sysprof client. + +Unwinding occurs as normal post-mortem though is improved through the use +of debuginfod to locate the appropriate symbols. +--- + meson.build | 3 +- + src/meson.build | 3 + + src/sysprof-live-unwinder/main.c | 752 ++++++++++++++++++ + src/sysprof-live-unwinder/meson.build | 19 + + .../sysprof-live-process.c | 505 ++++++++++++ + .../sysprof-live-process.h | 50 ++ + .../sysprof-live-unwinder.c | 427 ++++++++++ + .../sysprof-live-unwinder.h | 79 ++ + src/sysprof-live-unwinder/tests/meson.build | 35 + + .../tests/test-live-unwinder.c | 391 +++++++++ + src/sysprofd/ipc-unwinder-impl.c | 247 ++++++ + src/sysprofd/ipc-unwinder-impl.h | 34 + + src/sysprofd/meson.build | 10 +- + src/sysprofd/org.gnome.Sysprof3.Unwinder.xml | 23 + + src/sysprofd/sysprofd.c | 6 +- + 15 files changed, 2580 insertions(+), 4 deletions(-) + create mode 100644 src/sysprof-live-unwinder/main.c + create mode 100644 src/sysprof-live-unwinder/meson.build + create mode 100644 src/sysprof-live-unwinder/sysprof-live-process.c + create mode 100644 src/sysprof-live-unwinder/sysprof-live-process.h + create mode 100644 src/sysprof-live-unwinder/sysprof-live-unwinder.c + create mode 100644 src/sysprof-live-unwinder/sysprof-live-unwinder.h + create mode 100644 src/sysprof-live-unwinder/tests/meson.build + create mode 100644 src/sysprof-live-unwinder/tests/test-live-unwinder.c + create mode 100644 src/sysprofd/ipc-unwinder-impl.c + create mode 100644 src/sysprofd/ipc-unwinder-impl.h + create mode 100644 src/sysprofd/org.gnome.Sysprof3.Unwinder.xml + +diff --git a/meson.build b/meson.build +index 81e97072..9c37ebc4 100644 +--- a/meson.build ++++ b/meson.build +@@ -39,12 +39,13 @@ need_glib = (need_gtk or + get_option('tools') or + get_option('tests')) + need_libsysprof = (need_gtk or ++ get_option('sysprofd') == 'bundled' or + get_option('libsysprof') or + get_option('examples') or + get_option('tools') or + get_option('tests')) + +-dex_req = '0.6' ++dex_req = '0.9' + glib_req = '2.76.0' + gtk_req = '4.15' + polkit_req = '0.105' +diff --git a/src/meson.build b/src/meson.build +index f3e0c61d..9432755e 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -11,6 +11,8 @@ sysprof_version_conf.set('MINOR_VERSION', sysprof_version[1]) + sysprof_version_conf.set('MICRO_VERSION', 0) + sysprof_version_conf.set('VERSION', meson.project_version()) + ++pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir')) ++ + subdir('libsysprof-capture') + + if need_libsysprof +@@ -20,6 +22,7 @@ endif + + if get_option('sysprofd') == 'bundled' + subdir('sysprofd') ++ subdir('sysprof-live-unwinder') + endif + + if get_option('gtk') +diff --git a/src/sysprof-live-unwinder/main.c b/src/sysprof-live-unwinder/main.c +new file mode 100644 +index 00000000..9e2733ae +--- /dev/null ++++ b/src/sysprof-live-unwinder/main.c +@@ -0,0 +1,752 @@ ++/* ++ * main.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 ++ ++#include ++ ++#include "sysprof-live-unwinder.h" ++#include "sysprof-perf-event-stream-private.h" ++ ++#define CAPTURE_BUFFER_SIZE (4096*16) ++#define N_PAGES 32 ++ ++#define DUMP_BYTES(_n, _b, _l) \ ++ G_STMT_START { \ ++ GString *str, *astr; \ ++ gsize _i; \ ++ g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \ ++ " %s = %p [%d]", #_n, _b, (gint)_l); \ ++ str = g_string_sized_new (80); \ ++ astr = g_string_sized_new (16); \ ++ for (_i = 0; _i < _l; _i++) \ ++ { \ ++ if ((_i % 16) == 0) \ ++ g_string_append_printf (str, "%06x: ", (guint)_i); \ ++ g_string_append_printf (str, " %02x", _b[_i]); \ ++ \ ++ if (g_ascii_isprint(_b[_i])) \ ++ g_string_append_printf (astr, " %c", _b[_i]); \ ++ else \ ++ g_string_append (astr, " ."); \ ++ \ ++ if ((_i % 16) == 15) \ ++ { \ ++ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \ ++ "%s %s", str->str, astr->str); \ ++ str->str[0] = str->len = 0; \ ++ astr->str[0] = astr->len = 0; \ ++ } \ ++ else if ((_i % 16) == 7) \ ++ { \ ++ g_string_append (str, " "); \ ++ g_string_append (astr, " "); \ ++ } \ ++ } \ ++ \ ++ if (_i != 16) \ ++ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \ ++ "%-56s %s", str->str, astr->str); \ ++ \ ++ g_string_free (str, TRUE); \ ++ g_string_free (astr, TRUE); \ ++ } G_STMT_END ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) ++ ++typedef struct _PerfSource ++{ ++ GSource gsource; ++ ++ SysprofLiveUnwinder *unwinder; ++ SysprofCaptureWriter *writer; ++ ++ guint64 map_size; ++ struct perf_event_mmap_page *map; ++ guint8 *map_data; ++ const guint8 *map_data_end; ++ guint64 map_data_size; ++ guint64 tail; ++ ++ guint8 *buffer; ++ ++ int cpu; ++ GPid self_pid; ++ ++ guint stack_size; ++} PerfSource; ++ ++typedef struct _PerfFDArg ++{ ++ int fd; ++ int cpu; ++} PerfFDArg; ++ ++SYSPROF_ALIGNED_BEGIN(8); ++typedef struct _StackRegs ++{ ++ guint64 abi; ++ guint64 registers[0]; ++} StackRegs ++SYSPROF_ALIGNED_END(8); ++ ++SYSPROF_ALIGNED_BEGIN(8); ++typedef struct _StackUser ++{ ++ guint64 size; ++ guint8 data[0]; ++} StackUser ++SYSPROF_ALIGNED_END(8); ++ ++static GArray *all_perf_fds; ++ ++static inline void ++realign (gsize *pos, ++ gsize align) ++{ ++ *pos = (*pos + align - 1) & ~(align - 1); ++} ++ ++static void ++handle_event (PerfSource *source, ++ const SysprofPerfEvent *event) ++{ ++ gsize offset; ++ gint64 time; ++ ++ g_assert (source != NULL); ++ g_assert (event != NULL); ++ ++ switch (event->header.type) ++ { ++ case PERF_RECORD_COMM: ++ offset = strlen (event->comm.comm) + 1; ++ realign (&offset, sizeof (guint64)); ++ offset += sizeof (GPid) + sizeof (GPid); ++ memcpy (&time, event->comm.comm + offset, sizeof time); ++ ++ if (event->comm.pid == event->comm.tid) ++ { ++ sysprof_live_unwinder_seen_process (source->unwinder, ++ time, ++ source->cpu, ++ event->comm.pid, ++ event->comm.comm); ++ } ++ ++ break; ++ ++ case PERF_RECORD_EXIT: ++ /* Ignore fork exits for now */ ++ if (event->exit.tid != event->exit.pid) ++ break; ++ ++ sysprof_live_unwinder_process_exited (source->unwinder, ++ event->exit.time, ++ source->cpu, ++ event->exit.pid); ++ ++ break; ++ ++ case PERF_RECORD_FORK: ++ sysprof_live_unwinder_process_forked (source->unwinder, ++ event->fork.time, ++ source->cpu, ++ event->fork.ptid, ++ event->fork.tid); ++ break; ++ ++ case PERF_RECORD_LOST: ++ { ++ char message[64]; ++ g_snprintf (message, sizeof message, ++ "Lost %"G_GUINT64_FORMAT" samples", ++ event->lost.lost); ++ sysprof_capture_writer_add_log (source->writer, ++ SYSPROF_CAPTURE_CURRENT_TIME, ++ source->cpu, ++ -1, ++ G_LOG_LEVEL_CRITICAL, "Sampler", message); ++ break; ++ } ++ ++ case PERF_RECORD_MMAP: ++ offset = strlen (event->mmap.filename) + 1; ++ realign (&offset, sizeof (guint64)); ++ offset += sizeof (GPid) + sizeof (GPid); ++ memcpy (&time, event->mmap.filename + offset, sizeof time); ++ ++ sysprof_live_unwinder_track_mmap (source->unwinder, ++ time, ++ source->cpu, ++ event->mmap.pid, ++ event->mmap.addr, ++ event->mmap.addr + event->mmap.len, ++ event->mmap.pgoff, ++ 0, ++ event->mmap.filename, ++ NULL); ++ ++ break; ++ ++ case PERF_RECORD_MMAP2: ++ offset = strlen (event->mmap2.filename) + 1; ++ realign (&offset, sizeof (guint64)); ++ offset += sizeof (GPid) + sizeof (GPid); ++ memcpy (&time, event->mmap2.filename + offset, sizeof time); ++ ++ if ((event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) != 0) ++ { ++ char build_id[G_N_ELEMENTS (event->mmap2.build_id) * 2 + 1]; ++ guint len = MIN (G_N_ELEMENTS (event->mmap2.build_id), event->mmap2.build_id_size); ++ ++ for (guint i = 0; i < len; i++) ++ g_snprintf (&build_id[len*2], 3, "%02x", event->mmap2.build_id[i]); ++ build_id[len*2] = 0; ++ ++ sysprof_live_unwinder_track_mmap (source->unwinder, ++ time, ++ source->cpu, ++ event->mmap2.pid, ++ event->mmap2.addr, ++ event->mmap2.addr + event->mmap2.len, ++ event->mmap2.pgoff, ++ event->mmap2.ino, ++ event->mmap2.filename, ++ build_id); ++ } ++ else ++ { ++ sysprof_live_unwinder_track_mmap (source->unwinder, ++ time, ++ source->cpu, ++ event->mmap2.pid, ++ event->mmap2.addr, ++ event->mmap2.addr + event->mmap2.len, ++ event->mmap2.pgoff, ++ event->mmap2.ino, ++ event->mmap2.filename, ++ NULL); ++ } ++ break; ++ ++ case PERF_RECORD_READ: ++ break; ++ ++ case PERF_RECORD_SAMPLE: ++ { ++ const guint8 *endptr = (const guint8 *)event + event->header.size; ++ const guint64 *ips = event->callchain.ips; ++ int n_ips = event->callchain.n_ips; ++ guint64 trace[3]; ++ ++ /* We always expect PERF_RECORD_SAMPLE to contain a callchain because ++ * we need that even if we sample the stack for user-space unwinding. ++ * Otherwise we lose the blended stack trace. ++ */ ++ if (n_ips == 0) ++ { ++ if (event->callchain.header.misc & PERF_RECORD_MISC_KERNEL) ++ { ++ trace[0] = PERF_CONTEXT_KERNEL; ++ trace[1] = event->callchain.ip; ++ trace[2] = PERF_CONTEXT_USER; ++ ++ ips = trace; ++ n_ips = 3; ++ } ++ else ++ { ++ trace[0] = PERF_CONTEXT_USER; ++ trace[1] = event->callchain.ip; ++ ++ ips = trace; ++ n_ips = 2; ++ } ++ } ++ ++ if (source->stack_size && source->stack_size < event->header.size) ++ { ++ guint64 dyn_size = *((const guint64 *)endptr - 1); ++ const StackUser *stack_user = (const StackUser *)(endptr - sizeof (guint64) - source->stack_size - sizeof (StackUser)); ++ const StackRegs *stack_regs = (const StackRegs *)&event->callchain.ips[event->callchain.n_ips]; ++ guint n_regs = ((const guint8 *)stack_user - (const guint8 *)stack_regs->registers) / sizeof (guint64); ++ ++#if 0 ++ g_print ("n_ips=%u stack_size=%ld dyn_size=%ld abi=%ld n_regs=%d\n", ++ n_ips, stack_user->size, dyn_size, stack_regs->abi, n_regs); ++#endif ++ ++ sysprof_live_unwinder_process_sampled_with_stack (source->unwinder, ++ event->callchain.time, ++ source->cpu, ++ event->callchain.pid, ++ event->callchain.tid, ++ ips, ++ n_ips, ++ stack_user->data, ++ stack_user->size, ++ dyn_size, ++ stack_regs->abi, ++ stack_regs->registers, ++ n_regs); ++ ++ break; ++ } ++ ++ sysprof_live_unwinder_process_sampled (source->unwinder, ++ event->callchain.time, ++ source->cpu, ++ event->callchain.pid, ++ event->callchain.tid, ++ ips, ++ n_ips); ++ ++ break; ++ } ++ ++ case PERF_RECORD_THROTTLE: ++ case PERF_RECORD_UNTHROTTLE: ++ default: ++ break; ++ } ++} ++ ++static gboolean ++perf_source_prepare (GSource *gsource, ++ int *timeout) ++{ ++ *timeout = 50; ++ return FALSE; ++} ++ ++static gboolean ++perf_source_check (GSource *gsource) ++{ ++ PerfSource *source = (PerfSource *)gsource; ++ guint64 head; ++ guint64 tail; ++ ++ atomic_thread_fence (memory_order_acquire); ++ ++ tail = source->tail; ++ head = source->map->data_head; ++ ++ if (head < tail) ++ tail = head; ++ ++ return (head - tail) >= sizeof (struct perf_event_header); ++} ++ ++static gboolean ++perf_source_dispatch (GSource *gsource, ++ GSourceFunc callback, ++ gpointer user_data) ++{ ++ PerfSource *source = (PerfSource *)gsource; ++ guint64 n_bytes = source->map_data_size; ++ guint64 mask = n_bytes - 1; ++ guint64 head; ++ guint64 tail; ++ guint us = 0; ++ guint them = 0; ++ ++ g_assert (source != NULL); ++ ++ tail = source->tail; ++ head = source->map->data_head; ++ ++ atomic_thread_fence (memory_order_acquire); ++ ++ if (head < tail) ++ tail = head; ++ ++ while ((head - tail) >= sizeof (struct perf_event_header)) ++ { ++ const SysprofPerfEvent *event; ++ struct perf_event_header *header; ++ gboolean is_self = FALSE; ++ ++ /* Note that: ++ * ++ * - perf events are a multiple of 64 bits ++ * - the perf event header is 64 bits ++ * - the data area is a multiple of 64 bits ++ * ++ * which means there will always be space for one header, which means we ++ * can safely dereference the size field. ++ */ ++ header = (struct perf_event_header *)(gpointer)(source->map_data + (tail & mask)); ++ ++ if (header->size > head - tail) ++ { ++ /* The kernel did not generate a complete event. ++ * I don't think that can happen, but we may as well ++ * be paranoid. ++ */ ++ g_warn_if_reached (); ++ break; ++ } ++ ++ if (source->map_data + (tail & mask) + header->size > source->map_data_end) ++ { ++ guint8 *b = source->buffer; ++ gint n_before; ++ gint n_after; ++ ++ n_after = (tail & mask) + header->size - n_bytes; ++ n_before = header->size - n_after; ++ ++ memcpy (b, source->map_data + (tail & mask), n_before); ++ memcpy (b + n_before, source->map_data, n_after); ++ ++ header = (struct perf_event_header *)(gpointer)b; ++ } ++ ++ event = (SysprofPerfEvent *)header; ++ ++ switch (event->header.type) ++ { ++ default: ++ case PERF_RECORD_COMM: ++ case PERF_RECORD_EXIT: ++ case PERF_RECORD_FORK: ++ case PERF_RECORD_MMAP: ++ case PERF_RECORD_MMAP2: ++ break; ++ ++ case PERF_RECORD_SAMPLE: ++ is_self = event->callchain.pid == source->self_pid; ++ break; ++ ++ case PERF_RECORD_READ: ++ case PERF_RECORD_THROTTLE: ++ case PERF_RECORD_UNTHROTTLE: ++ goto skip_callback; ++ ++ case PERF_RECORD_LOST: ++ break; ++ } ++ ++ handle_event (source, event); ++ ++ us += is_self; ++ them += !is_self; ++ ++ skip_callback: ++ tail += header->size; ++ } ++ ++ source->tail = tail; ++ ++ atomic_thread_fence (memory_order_seq_cst); ++ ++ source->map->data_tail = tail; ++ ++ sysprof_capture_writer_flush (source->writer); ++ ++ return G_SOURCE_CONTINUE; ++} ++ ++static void ++perf_source_finalize (GSource *gsource) ++{ ++ PerfSource *source = (PerfSource *)gsource; ++ ++ if (source->map != NULL && ++ (gpointer)source->map != MAP_FAILED) ++ munmap ((gpointer)source->map, source->map_size); ++ ++ g_clear_pointer (&source->buffer, g_free); ++ g_clear_pointer (&source->writer, sysprof_capture_writer_unref); ++ g_clear_object (&source->unwinder); ++ ++ source->map = NULL; ++ source->map_data = NULL; ++ source->map_data_end = NULL; ++ source->map_data_size = 0; ++ source->map_size = 0; ++ source->tail = 0; ++} ++ ++static const GSourceFuncs source_funcs = { ++ .prepare = perf_source_prepare, ++ .check = perf_source_check, ++ .dispatch = perf_source_dispatch, ++ .finalize = perf_source_finalize, ++}; ++ ++static gboolean ++perf_source_init (PerfSource *source, ++ int fd, ++ SysprofLiveUnwinder *unwinder, ++ SysprofCaptureWriter *writer, ++ int cpu, ++ int stack_size, ++ GError **error) ++{ ++ gsize map_size; ++ guint8 *map; ++ ++ g_assert (source != NULL); ++ g_assert (writer != NULL); ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (unwinder)); ++ g_assert (fd > STDERR_FILENO); ++ ++ map_size = N_PAGES * sysprof_getpagesize () + sysprof_getpagesize (); ++ map = mmap (NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); ++ ++ if ((gpointer)map == MAP_FAILED) ++ { ++ int errsv = errno; ++ g_set_error_literal (error, ++ G_FILE_ERROR, ++ g_file_error_from_errno (errsv), ++ g_strerror (errsv)); ++ return FALSE; ++ } ++ ++ source->writer = sysprof_capture_writer_ref (writer); ++ source->unwinder = g_object_ref (unwinder); ++ source->buffer = g_malloc (map_size); ++ source->map_size = map_size; ++ source->map = (gpointer)map; ++ source->map_data = map + sysprof_getpagesize (); ++ source->map_data_size = N_PAGES * sysprof_getpagesize (); ++ source->map_data_end = source->map_data + source->map_data_size; ++ source->tail = 0; ++ source->self_pid = getpid (); ++ source->cpu = cpu; ++ source->stack_size = stack_size; ++ ++ g_source_add_unix_fd ((GSource *)source, fd, G_IO_IN); ++ ++ return TRUE; ++} ++ ++static GSource * ++perf_source_new (int perf_fd, ++ SysprofLiveUnwinder *unwinder, ++ SysprofCaptureWriter *writer, ++ int cpu, ++ int stack_size, ++ GError **error) ++{ ++ GSource *source; ++ ++ if (perf_fd <= STDERR_FILENO) ++ { ++ g_set_error (error, ++ G_FILE_ERROR, ++ G_FILE_ERROR_BADF, ++ "Invalid file-descriptor for perf event stream"); ++ return NULL; ++ } ++ ++ source = g_source_new ((GSourceFuncs *)&source_funcs, sizeof (PerfSource)); ++ g_source_set_static_name (source, "[perf-event-stream]"); ++ g_source_set_priority (source, G_PRIORITY_HIGH); ++ ++ if (!perf_source_init ((PerfSource *)source, perf_fd, unwinder, writer, cpu, stack_size, error)) ++ g_clear_pointer (&source, g_source_unref); ++ ++ return source; ++} ++ ++static void ++clear_perf_fd (gpointer data) ++{ ++ PerfFDArg *arg = data; ++ ++ if (arg->fd > STDERR_FILENO) ++ { ++ close (arg->fd); ++ arg->fd = -1; ++ } ++} ++ ++static gboolean ++perf_fd_callback (const char *option_name, ++ const char *option_value, ++ gpointer data, ++ GError **error) ++{ ++ PerfFDArg arg = {-1, -1}; ++ ++ if (sscanf (option_value, "%d:%d", &arg.fd, &arg.cpu) < 1) ++ { ++ g_set_error (error, ++ G_FILE_ERROR, ++ G_FILE_ERROR_BADF, ++ "--perf-fd must be in the format FD or FD:CPU_NUMBER"); ++ return FALSE; ++ } ++ ++ if (arg.fd <= STDERR_FILENO) ++ { ++ g_set_error (error, ++ G_FILE_ERROR, ++ G_FILE_ERROR_BADF, ++ "--perf-fd must be >= %d", ++ STDERR_FILENO); ++ return FALSE; ++ } ++ ++ g_array_append_val (all_perf_fds, arg); ++ ++ return TRUE; ++} ++ ++static void ++bump_to_max_fd_limit (void) ++{ ++ struct rlimit limit; ++ ++ if (getrlimit (RLIMIT_NOFILE, &limit) == 0) ++ { ++ limit.rlim_cur = limit.rlim_max; ++ ++ if (setrlimit (RLIMIT_NOFILE, &limit) != 0) ++ g_warning ("Failed to set FD limit to %"G_GSSIZE_FORMAT"", ++ (gssize)limit.rlim_max); ++ else ++ g_debug ("Set RLIMIT_NOFILE to %"G_GSSIZE_FORMAT"", ++ (gssize)limit.rlim_max); ++ } ++} ++ ++static gboolean ++exit_callback (gpointer user_data) ++{ ++ g_main_loop_quit (user_data); ++ return G_SOURCE_REMOVE; ++} ++ ++int ++main (int argc, ++ char *argv[]) ++{ ++ g_autoptr(SysprofCaptureWriter) writer = NULL; ++ g_autoptr(SysprofLiveUnwinder) unwinder = NULL; ++ g_autoptr(GOptionContext) context = NULL; ++ g_autoptr(GMainLoop) main_loop = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autofd int capture_fd = -1; ++ g_autofd int kallsyms_fd = -1; ++ g_autofd int event_fd = -1; ++ int stack_size = 0; ++ const GOptionEntry entries[] = { ++ { "perf-fd", 0, 0, G_OPTION_ARG_CALLBACK, perf_fd_callback, "A file-descriptor to the perf event stream", "FD[:CPU]" }, ++ { "capture-fd", 0, 0, G_OPTION_ARG_INT, &capture_fd, "A file-descriptor to the sysprof capture", "FD" }, ++ { "event-fd", 0, 0, G_OPTION_ARG_INT, &event_fd, "A file-descriptor to an event-fd used to notify unwinder should exit", "FD" }, ++ { "kallsyms", 'k', 0, G_OPTION_ARG_INT, &kallsyms_fd, "Bundle kallsyms provided from passed FD", "FD" }, ++ { "stack-size", 's', 0, G_OPTION_ARG_INT, &stack_size, "Size of stacks being recorded", "STACK_SIZE" }, ++ { 0 } ++ }; ++ ++ main_loop = g_main_loop_new (NULL, FALSE); ++ ++ all_perf_fds = g_array_new (FALSE, FALSE, sizeof (PerfFDArg)); ++ g_array_set_clear_func (all_perf_fds, clear_perf_fd); ++ ++ context = g_option_context_new ("- translate perf event stream to sysprof"); ++ g_option_context_add_main_entries (context, entries, NULL); ++ g_option_context_set_summary (context, "\ ++ This tool is used by sysprofd to process incoming perf events that\n\ ++ include a copy of the stack and register state to the Sysprof capture\n\ ++ format.\n\ ++\n\ ++ It should be provided two file-descriptors. One is for the perf-event\n\ ++ stream and one is for the Sysprof capture writer.\n\ ++\n\ ++ Events that are not related to stack traces will also be passed along to\n\ ++ to the capture in the standard Sysprof capture format. That includes mmap\n\ ++ events, process information, and more.\n\ ++\n\ ++Examples:\n\ ++\n\ ++ # FD 3 contains perf_event stream for CPU 1\n\ ++ sysprof-translate --perf-fd=3:1 --capture-fd=4"); ++ ++ if (!g_option_context_parse (context, &argc, &argv, &error)) ++ { ++ g_printerr ("%s\n", error->message); ++ return EXIT_FAILURE; ++ } ++ ++ if (capture_fd <= STDERR_FILENO) ++ { ++ g_printerr ("--capture-fd must be > %d\n", STDERR_FILENO); ++ return EXIT_FAILURE; ++ } ++ ++ writer = sysprof_capture_writer_new_from_fd (g_steal_fd (&capture_fd), CAPTURE_BUFFER_SIZE); ++ ++ if (all_perf_fds->len == 0) ++ { ++ g_printerr ("You must secify at least one --perf-fd\n"); ++ return EXIT_FAILURE; ++ } ++ ++ bump_to_max_fd_limit (); ++ ++ unwinder = sysprof_live_unwinder_new (writer, g_steal_fd (&kallsyms_fd)); ++ ++ for (guint i = 0; i < all_perf_fds->len; i++) ++ { ++ const PerfFDArg *arg = &g_array_index (all_perf_fds, PerfFDArg, i); ++ g_autoptr(GSource) perf_source = NULL; ++ ++ if (!(perf_source = perf_source_new (arg->fd, unwinder, writer, arg->cpu, stack_size, &error))) ++ { ++ g_printerr ("Failed to initialize perf event stream: %s\n", ++ error->message); ++ return EXIT_FAILURE; ++ } ++ ++ g_source_attach (perf_source, NULL); ++ } ++ ++ if (event_fd != -1) ++ { ++ g_autoptr(GSource) exit_source = g_unix_fd_source_new (event_fd, G_IO_IN); ++ ++ g_source_set_callback (exit_source, ++ exit_callback, ++ g_main_loop_ref (main_loop), ++ (GDestroyNotify) g_main_loop_unref); ++ g_source_attach (exit_source, NULL); ++ } ++ ++ g_main_loop_run (main_loop); ++ ++ g_clear_pointer (&all_perf_fds, g_array_unref); ++ ++ return EXIT_SUCCESS; ++} +diff --git a/src/sysprof-live-unwinder/meson.build b/src/sysprof-live-unwinder/meson.build +new file mode 100644 +index 00000000..8cef7106 +--- /dev/null ++++ b/src/sysprof-live-unwinder/meson.build +@@ -0,0 +1,19 @@ ++sysprof_live_unwinder_deps = [ ++ libsysprof_static_dep, ++ dependency('libdw'), ++] ++ ++sysprof_live_unwinder_sources = [ ++ 'sysprof-live-process.c', ++ 'sysprof-live-unwinder.c', ++ 'main.c', ++] ++ ++sysprof_live_unwinder = executable('sysprof-live-unwinder', sysprof_live_unwinder_sources, ++ dependencies: sysprof_live_unwinder_deps, ++ c_args: release_flags, ++ install: true, ++ install_dir: pkglibexecdir, ++) ++ ++subdir('tests') +diff --git a/src/sysprof-live-unwinder/sysprof-live-process.c b/src/sysprof-live-unwinder/sysprof-live-process.c +new file mode 100644 +index 00000000..7932048b +--- /dev/null ++++ b/src/sysprof-live-unwinder/sysprof-live-process.c +@@ -0,0 +1,505 @@ ++/* ++ * sysprof-live-pid.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 ++ ++/* Workaround for linux/fcntl.h also including the ++ * flock definitions in addition to libc. ++ */ ++#ifndef _LINUX_FCNTL_H ++# define _LINUX_FCNTL_H ++# include ++# undef _LINUX_FCNTL_H ++#else ++# include ++#endif ++ ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "sysprof-live-process.h" ++ ++typedef struct _SysprofLiveProcess ++{ ++ Dwfl_Callbacks callbacks; ++ Dwfl *dwfl; ++ Elf *elf; ++ char *root; ++ GPid pid; ++ int fd; ++} SysprofLiveProcess; ++ ++typedef struct _SysprofUnwinder ++{ ++ SysprofLiveProcess *process; ++ const guint8 *stack; ++ gsize stack_len; ++ const guint64 *registers; ++ guint n_registers; ++ guint64 *addresses; ++ guint addresses_capacity; ++ guint addresses_len; ++ guint64 abi; ++ guint64 sp; ++ guint64 pc; ++ guint64 base_address; ++ GPid pid; ++ GPid tid; ++} SysprofUnwinder; ++ ++static SysprofUnwinder *current_unwinder; ++ ++static inline GPid ++sysprof_unwinder_next_thread (Dwfl *dwfl, ++ void *user_data, ++ void **thread_argp) ++{ ++ SysprofUnwinder *unwinder = current_unwinder; ++ ++ if (*thread_argp == NULL) ++ { ++ *thread_argp = unwinder; ++ return unwinder->tid; ++ } ++ ++ return 0; ++} ++ ++static inline bool ++sysprof_unwinder_get_thread (Dwfl *dwfl, ++ pid_t tid, ++ void *user_data, ++ void **thread_argp) ++{ ++ SysprofUnwinder *unwinder = current_unwinder; ++ ++ if (unwinder->tid == tid) ++ { ++ *thread_argp = unwinder; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static inline bool ++copy_word (const guint8 *data, ++ Dwarf_Word *result, ++ guint64 abi) ++{ ++ if (abi == PERF_SAMPLE_REGS_ABI_64) ++ memcpy (result, data, sizeof (guint64)); ++ else if (abi == PERF_SAMPLE_REGS_ABI_32) ++ memcpy (result, data, sizeof (guint32)); ++ else ++ g_assert_not_reached (); ++ ++ return TRUE; ++} ++ ++static inline bool ++sysprof_unwinder_memory_read (Dwfl *dwfl, ++ Dwarf_Addr addr, ++ Dwarf_Word *result, ++ void *user_data) ++{ ++ SysprofUnwinder *unwinder = current_unwinder; ++ ++ if (addr < unwinder->base_address || addr - unwinder->base_address >= unwinder->stack_len) ++ { ++ Dwfl_Module *module = NULL; ++ Elf_Data *data = NULL; ++ Elf_Scn *section = NULL; ++ Dwarf_Addr bias; ++ ++ if (!(module = dwfl_addrmodule (dwfl, addr)) || ++ !(section = dwfl_module_address_section (module, &addr, &bias)) || ++ !(data = elf_getdata (section, NULL))) ++ return FALSE; ++ ++ if (data->d_buf && data->d_size > addr) ++ return copy_word ((guint8 *)data->d_buf + addr, result, unwinder->abi); ++ ++ return FALSE; ++ } ++ ++ return copy_word (&unwinder->stack[addr - unwinder->base_address], result, unwinder->abi); ++} ++ ++static inline bool ++sysprof_unwinder_set_initial_registers (Dwfl_Thread *thread, ++ void *user_data) ++{ ++ SysprofUnwinder *unwinder = current_unwinder; ++ ++ dwfl_thread_state_register_pc (thread, unwinder->pc); ++ ++ if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64) ++ { ++ static const int regs_x86_64[] = {0, 3, 2, 1, 4, 5, 6, 7/*sp*/, 9, 10, 11, 12, 13, 14, 15, 16, 8/*ip*/}; ++ ++ for (int i = 0; i < G_N_ELEMENTS (regs_x86_64); i++) ++ { ++ int j = regs_x86_64[i]; ++ ++ dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]); ++ } ++ } ++ else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32) ++ { ++ static const int regs_i386[] = {0, 2, 3, 1, 7/*sp*/, 6, 4, 5, 8/*ip*/}; ++ ++ for (int i = 0; i < G_N_ELEMENTS (regs_i386); i++) ++ { ++ int j = regs_i386[i]; ++ ++ dwfl_thread_state_registers (thread, i, 1, &unwinder->registers[j]); ++ } ++ } ++ ++ return TRUE; ++} ++ ++static const Dwfl_Thread_Callbacks thread_callbacks = { ++ sysprof_unwinder_next_thread, ++ sysprof_unwinder_get_thread, ++ sysprof_unwinder_memory_read, ++ sysprof_unwinder_set_initial_registers, ++ NULL, /* detach */ ++ NULL, /* thread_detach */ ++}; ++ ++static inline int ++sysprof_unwinder_frame_cb (Dwfl_Frame *frame, ++ void *user_data) ++{ ++ SysprofUnwinder *unwinder = current_unwinder; ++ Dwarf_Addr pc; ++ Dwarf_Addr sp; ++ bool is_activation; ++ guint8 sp_register_id; ++ ++ if (unwinder->addresses_len == unwinder->addresses_capacity) ++ return DWARF_CB_ABORT; ++ ++ if (!dwfl_frame_pc (frame, &pc, &is_activation)) ++ return DWARF_CB_ABORT; ++ ++ if (unwinder->abi == PERF_SAMPLE_REGS_ABI_64) ++ sp_register_id = 7; ++ else if (unwinder->abi == PERF_SAMPLE_REGS_ABI_32) ++ sp_register_id = 4; ++ else ++ return DWARF_CB_ABORT; ++ ++ if (dwfl_frame_reg (frame, sp_register_id, &sp) < 0) ++ return DWARF_CB_ABORT; ++ ++ unwinder->addresses[unwinder->addresses_len++] = pc; ++ ++ return DWARF_CB_OK; ++} ++ ++static inline guint ++sysprof_unwind (SysprofLiveProcess *self, ++ Dwfl *dwfl, ++ Elf *elf, ++ GPid pid, ++ GPid tid, ++ guint64 abi, ++ const guint64 *registers, ++ guint n_registers, ++ const guint8 *stack, ++ gsize stack_len, ++ guint64 *addresses, ++ guint n_addresses) ++{ ++#if defined(__x86_64__) || defined(__i386__) ++ SysprofUnwinder unwinder; ++ ++ g_assert (dwfl != NULL); ++ g_assert (elf != NULL); ++ ++ /* Ignore anything byt 32/64 defined abi */ ++ if (!(abi == PERF_SAMPLE_REGS_ABI_32 || abi == PERF_SAMPLE_REGS_ABI_64)) ++ return 0; ++ ++ /* Make sure we have registers/stack to work with */ ++ if (registers == NULL || stack == NULL || stack_len == 0) ++ return 0; ++ ++ /* 9 registers on 32-bit x86, 17 on 64-bit x86_64 */ ++ if (!((abi == PERF_SAMPLE_REGS_ABI_32 && n_registers == 9) || ++ (abi == PERF_SAMPLE_REGS_ABI_64 && n_registers == 17))) ++ return 0; ++ ++ unwinder.process = self; ++ unwinder.sp = registers[7]; ++ unwinder.pc = registers[8]; ++ unwinder.base_address = unwinder.sp; ++ unwinder.addresses = addresses; ++ unwinder.addresses_capacity = n_addresses; ++ unwinder.addresses_len = 0; ++ unwinder.pid = pid; ++ unwinder.tid = tid; ++ unwinder.stack = stack; ++ unwinder.stack_len = stack_len; ++ unwinder.abi = abi; ++ unwinder.registers = registers; ++ unwinder.n_registers = n_registers; ++ ++ current_unwinder = &unwinder; ++ dwfl_getthread_frames (dwfl, tid, sysprof_unwinder_frame_cb, NULL); ++ current_unwinder = NULL; ++ ++ return unwinder.addresses_len; ++#else ++ return 0; ++#endif ++} ++ ++G_GNUC_NO_INLINE static int ++_pidfd_open (int pid, ++ unsigned flags) ++{ ++ int pidfd = syscall (SYS_pidfd_open, pid, flags); ++ ++ if (pidfd != -1) ++ { ++ int old_flags = fcntl (pidfd, F_GETFD); ++ ++ if (old_flags != -1) ++ fcntl (pidfd, F_SETFD, old_flags | FD_CLOEXEC); ++ } ++ ++ return pidfd; ++} ++ ++static int ++sysprof_live_process_find_elf (Dwfl_Module *module, ++ void **user_data, ++ const char *module_name, ++ Dwarf_Addr base_addr, ++ char **filename, ++ Elf **elf) ++{ ++ g_assert (current_unwinder != NULL); ++ g_assert (current_unwinder->process != NULL); ++ ++ *filename = NULL; ++ *elf = NULL; ++ ++ if (module_name[0] == '/') ++ { ++ g_autofree char *path = g_strdup_printf ("/proc/%u/root/%s", current_unwinder->pid, module_name); ++ g_autofd int fd = open (path, O_RDONLY | O_CLOEXEC); ++ ++ if (fd != -1) ++ { ++ *elf = dwelf_elf_begin (fd); ++ return g_steal_fd (&fd); ++ } ++ } ++ ++ return dwfl_linux_proc_find_elf (module, user_data, module_name, base_addr, filename, elf); ++} ++ ++static int ++sysprof_live_process_find_debuginfo (Dwfl_Module *module, ++ void **user_data, ++ const char *module_name, ++ Dwarf_Addr base_addr, ++ const char *file_name, ++ const char *debuglink_file, ++ GElf_Word debuglink_crc, ++ char **debuginfo_file_name) ++{ ++ return -1; ++} ++ ++SysprofLiveProcess * ++sysprof_live_process_new (GPid pid) ++{ ++ SysprofLiveProcess *live_process; ++ ++ live_process = g_atomic_rc_box_new0 (SysprofLiveProcess); ++ live_process->pid = pid; ++ live_process->fd = _pidfd_open (pid, 0); ++ live_process->root = g_strdup_printf ("/proc/%u/root/", pid); ++ live_process->callbacks.find_elf = sysprof_live_process_find_elf; ++ live_process->callbacks.find_debuginfo = sysprof_live_process_find_debuginfo; ++ live_process->callbacks.debuginfo_path = g_new0 (char *, 2); ++ live_process->callbacks.debuginfo_path[0] = g_build_filename (live_process->root, "usr/lib/debug", NULL); ++ ++ return live_process; ++} ++ ++SysprofLiveProcess * ++sysprof_live_process_ref (SysprofLiveProcess *live_process) ++{ ++ g_return_val_if_fail (live_process != NULL, NULL); ++ ++ return g_atomic_rc_box_acquire (live_process); ++} ++ ++static void ++sysprof_live_process_finalize (gpointer data) ++{ ++ SysprofLiveProcess *live_process = data; ++ ++ if (live_process->fd != -1) ++ { ++ close (live_process->fd); ++ live_process->fd = -1; ++ } ++ ++ g_clear_pointer (&live_process->elf, elf_end); ++ g_clear_pointer (&live_process->dwfl, dwfl_end); ++ g_clear_pointer (&live_process->root, g_free); ++ g_clear_pointer (&live_process->callbacks.debuginfo_path, g_free); ++} ++ ++void ++sysprof_live_process_unref (SysprofLiveProcess *live_process) ++{ ++ g_return_if_fail (live_process != NULL); ++ ++ g_atomic_rc_box_release_full (live_process, sysprof_live_process_finalize); ++} ++ ++gboolean ++sysprof_live_process_is_active (SysprofLiveProcess *self) ++{ ++ g_return_val_if_fail (self != NULL, FALSE); ++ ++ return self->fd > -1; ++} ++ ++static Dwfl * ++sysprof_live_process_get_dwfl (SysprofLiveProcess *self) ++{ ++ g_assert (self != NULL); ++ ++ if G_UNLIKELY (self->dwfl == NULL) ++ { ++ self->dwfl = dwfl_begin (&self->callbacks); ++ ++ dwfl_linux_proc_report (self->dwfl, self->pid); ++ dwfl_report_end (self->dwfl, NULL, NULL); ++ ++ if (self->fd > -1) ++ { ++ char path[64]; ++ g_autofd int exe_fd = -1; ++ ++ g_snprintf (path, sizeof path, "/proc/%u/exe", self->pid); ++ exe_fd = open (path, O_RDONLY); ++ ++ if (exe_fd > -1) ++ { ++ self->elf = elf_begin (exe_fd, ELF_C_READ_MMAP, NULL); ++ ++ if (self->elf != NULL) ++ dwfl_attach_state (self->dwfl, self->elf, self->pid, &thread_callbacks, self); ++ } ++ } ++ else ++ g_warning ("Attmpting to load exited process\n"); ++ } ++ ++ return self->dwfl; ++} ++ ++guint ++sysprof_live_process_unwind (SysprofLiveProcess *self, ++ GPid tid, ++ guint64 abi, ++ const guint8 *stack, ++ gsize stack_len, ++ const guint64 *registers, ++ guint n_registers, ++ guint64 *addresses, ++ guint n_addresses) ++{ ++#if defined(__x86_64__) || defined(__i386__) ++ Dwfl *dwfl; ++ ++ g_assert (self != NULL); ++ g_assert (stack != NULL); ++ g_assert (registers != NULL); ++ g_assert (addresses != NULL); ++ ++ if (!sysprof_live_process_is_active (self)) ++ return 0; ++ ++ if (!(dwfl = sysprof_live_process_get_dwfl (self))) ++ return 0; ++ ++ if (self->elf == NULL) ++ return 0; ++ ++ g_assert (self->dwfl != NULL); ++ g_assert (self->elf != NULL); ++ ++ return sysprof_unwind (self, ++ self->dwfl, ++ self->elf, ++ self->pid, ++ tid, ++ abi, ++ registers, ++ n_registers, ++ stack, ++ stack_len, ++ addresses, ++ n_addresses); ++#else ++ return 0; ++#endif ++} ++ ++void ++sysprof_live_process_add_map (SysprofLiveProcess *self, ++ guint64 begin, ++ guint64 end, ++ guint64 offset, ++ guint64 inode, ++ const char *filename) ++{ ++ g_assert (self != NULL); ++ ++ /* We'll reparse VMAs on next use */ ++ g_clear_pointer (&self->dwfl, dwfl_end); ++ g_clear_pointer (&self->elf, elf_end); ++} +diff --git a/src/sysprof-live-unwinder/sysprof-live-process.h b/src/sysprof-live-unwinder/sysprof-live-process.h +new file mode 100644 +index 00000000..e2601a5b +--- /dev/null ++++ b/src/sysprof-live-unwinder/sysprof-live-process.h +@@ -0,0 +1,50 @@ ++/* ++ * sysprof-live-pid.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 ++ ++G_BEGIN_DECLS ++ ++typedef struct _SysprofLiveProcess SysprofLiveProcess; ++ ++SysprofLiveProcess *sysprof_live_process_new (GPid pid); ++SysprofLiveProcess *sysprof_live_process_ref (SysprofLiveProcess *self); ++void sysprof_live_process_unref (SysprofLiveProcess *self); ++gboolean sysprof_live_process_is_active (SysprofLiveProcess *self); ++void sysprof_live_process_add_map (SysprofLiveProcess *self, ++ guint64 begin, ++ guint64 end, ++ guint64 offset, ++ guint64 inode, ++ const char *filename); ++guint sysprof_live_process_unwind (SysprofLiveProcess *self, ++ GPid tid, ++ guint64 abi, ++ const guint8 *stack, ++ gsize stack_len, ++ const guint64 *registers, ++ guint n_registers, ++ guint64 *addresses, ++ guint n_addresses); ++ ++G_END_DECLS +diff --git a/src/sysprof-live-unwinder/sysprof-live-unwinder.c b/src/sysprof-live-unwinder/sysprof-live-unwinder.c +new file mode 100644 +index 00000000..c3b954b2 +--- /dev/null ++++ b/src/sysprof-live-unwinder/sysprof-live-unwinder.c +@@ -0,0 +1,427 @@ ++/* ++ * sysprof-live-unwinder.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-live-process.h" ++#include "sysprof-live-unwinder.h" ++#include "sysprof-maps-parser-private.h" ++ ++struct _SysprofLiveUnwinder ++{ ++ GObject parent_instance; ++ SysprofCaptureWriter *writer; ++ GHashTable *live_pids_by_pid; ++}; ++ ++G_DEFINE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, G_TYPE_OBJECT) ++ ++enum { ++ CLOSED, ++ N_SIGNALS ++}; ++ ++static guint signals[N_SIGNALS]; ++ ++static char * ++sysprof_live_unwinder_read_file (SysprofLiveUnwinder *self, ++ const char *path, ++ gboolean insert_into_capture) ++{ ++ gint64 when = SYSPROF_CAPTURE_CURRENT_TIME; ++ char *contents = NULL; ++ gsize len = 0; ++ gsize offset = 0; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ g_assert (self->writer != NULL); ++ ++ if (!g_file_get_contents (path, &contents, &len, NULL)) ++ return NULL; ++ ++ if (insert_into_capture) ++ { ++ while (len > 0) ++ { ++ gsize this_write = MIN (len, 4096*4); ++ ++ if (!sysprof_capture_writer_add_file (self->writer, ++ when, ++ -1, ++ -1, ++ path, ++ this_write == len, ++ (const guint8 *)&contents[offset], this_write)) ++ break; ++ ++ len -= this_write; ++ offset += this_write; ++ } ++ } ++ ++ return contents; ++} ++ ++static char * ++sysprof_live_unwinder_read_pid_file (SysprofLiveUnwinder *self, ++ GPid pid, ++ const char *path_part, ++ gboolean insert_into_capture) ++{ ++ g_autofree char *path = NULL; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ g_assert (self->writer != NULL); ++ ++ path = g_strdup_printf ("/proc/%d/%s", pid, path_part); ++ ++ return sysprof_live_unwinder_read_file (self, path, insert_into_capture); ++} ++ ++static SysprofLiveProcess * ++sysprof_live_unwinder_find_pid (SysprofLiveUnwinder *self, ++ GPid pid, ++ gboolean send_comm) ++{ ++ SysprofLiveProcess *live_pid; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ if (pid < 0) ++ return NULL; ++ ++ if (!(live_pid = g_hash_table_lookup (self->live_pids_by_pid, GINT_TO_POINTER (pid)))) ++ { ++ gint64 now = SYSPROF_CAPTURE_CURRENT_TIME; ++ ++ live_pid = sysprof_live_process_new (pid); ++ ++ g_hash_table_replace (self->live_pids_by_pid, GINT_TO_POINTER (pid), live_pid); ++ ++ if (send_comm) ++ { ++ g_autofree char *path = g_strdup_printf ("/proc/%d/comm", pid); ++ g_autofree char *comm = NULL; ++ gsize len; ++ ++ if (g_file_get_contents (path, &comm, &len, NULL)) ++ { ++ g_autofree char *tmp = comm; ++ comm = g_strstrip (g_utf8_make_valid (tmp, len)); ++ sysprof_capture_writer_add_process (self->writer, now, -1, pid, comm); ++ } ++ } ++ ++ if (sysprof_live_process_is_active (live_pid)) ++ { ++ g_autofree char *mountinfo = sysprof_live_unwinder_read_pid_file (self, pid, "mountinfo", TRUE); ++ g_autofree char *maps = sysprof_live_unwinder_read_pid_file (self, pid, "maps", FALSE); ++ ++ if (maps != NULL) ++ { ++ SysprofMapsParser maps_parser; ++ guint64 begin, end, offset, inode; ++ char *filename; ++ ++ sysprof_maps_parser_init (&maps_parser, maps, -1); ++ ++ while (sysprof_maps_parser_next (&maps_parser, &begin, &end, &offset, &inode, &filename)) ++ { ++ sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename); ++ sysprof_capture_writer_add_map (self->writer, now, -1, pid, ++ begin, end, offset, ++ inode, filename); ++ g_free (filename); ++ } ++ } ++ } ++ } ++ ++ return live_pid; ++} ++ ++static void ++sysprof_live_unwinder_mine_pids (SysprofLiveUnwinder *self) ++{ ++ g_autoptr(GDir) dir = NULL; ++ const char *name; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ g_assert (self->writer != NULL); ++ ++ if (!(dir = g_dir_open ("/proc", 0, NULL))) ++ return; ++ ++ while ((name = g_dir_read_name (dir))) ++ { ++ GPid pid; ++ ++ if (!g_ascii_isdigit (*name)) ++ continue; ++ ++ if (!(pid = atoi (name))) ++ continue; ++ ++ sysprof_live_unwinder_find_pid (self, pid, TRUE); ++ } ++} ++ ++static void ++sysprof_live_unwinder_finalize (GObject *object) ++{ ++ SysprofLiveUnwinder *self = (SysprofLiveUnwinder *)object; ++ ++ g_clear_pointer (&self->writer, sysprof_capture_writer_unref); ++ g_clear_pointer (&self->live_pids_by_pid, g_hash_table_unref); ++ ++ G_OBJECT_CLASS (sysprof_live_unwinder_parent_class)->finalize (object); ++} ++ ++static void ++sysprof_live_unwinder_class_init (SysprofLiveUnwinderClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = sysprof_live_unwinder_finalize; ++ ++ signals[CLOSED] = ++ g_signal_new ("closed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, ++ NULL, ++ G_TYPE_NONE, 0); ++} ++ ++static void ++sysprof_live_unwinder_init (SysprofLiveUnwinder *self) ++{ ++ self->live_pids_by_pid = g_hash_table_new_full (NULL, ++ NULL, ++ NULL, ++ (GDestroyNotify)sysprof_live_process_unref); ++} ++ ++SysprofLiveUnwinder * ++sysprof_live_unwinder_new (SysprofCaptureWriter *writer, ++ int kallsyms_fd) ++{ ++ SysprofLiveUnwinder *self; ++ g_autofree char *mounts = NULL; ++ ++ g_return_val_if_fail (writer != NULL, NULL); ++ ++ self = g_object_new (SYSPROF_TYPE_LIVE_UNWINDER, NULL); ++ self->writer = sysprof_capture_writer_ref (writer); ++ ++ if (kallsyms_fd != -1) ++ { ++ sysprof_capture_writer_add_file_fd (writer, ++ SYSPROF_CAPTURE_CURRENT_TIME, ++ -1, ++ -1, ++ "/proc/kallsyms", ++ kallsyms_fd); ++ close (kallsyms_fd); ++ } ++ ++ mounts = sysprof_live_unwinder_read_file (self, "/proc/mounts", TRUE); ++ ++ sysprof_live_unwinder_mine_pids (self); ++ ++ return self; ++} ++ ++void ++sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ const char *comm) ++{ ++ G_GNUC_UNUSED SysprofLiveProcess *live_pid; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ live_pid = sysprof_live_unwinder_find_pid (self, pid, FALSE); ++ ++ sysprof_capture_writer_add_process (self->writer, time, cpu, pid, comm); ++} ++ ++void ++sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid) ++{ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ sysprof_capture_writer_add_exit (self->writer, time, cpu, pid); ++} ++ ++void ++sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid parent_tid, ++ GPid tid) ++{ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ sysprof_capture_writer_add_fork (self->writer, time, cpu, parent_tid, tid); ++} ++ ++void ++sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ SysprofCaptureAddress begin, ++ SysprofCaptureAddress end, ++ SysprofCaptureAddress offset, ++ guint64 inode, ++ const char *filename, ++ const char *build_id) ++{ ++ G_GNUC_UNUSED SysprofLiveProcess *live_pid; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE); ++ ++ if (build_id != NULL) ++ sysprof_capture_writer_add_map_with_build_id (self->writer, time, cpu, pid, ++ begin, end, offset, ++ inode, filename, ++ build_id); ++ else ++ sysprof_capture_writer_add_map (self->writer, time, cpu, pid, ++ begin, end, offset, ++ inode, filename); ++ ++ sysprof_live_process_add_map (live_pid, begin, end, offset, inode, filename); ++} ++ ++void ++sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ GPid tid, ++ const SysprofCaptureAddress *addresses, ++ guint n_addresses) ++{ ++ G_GNUC_UNUSED SysprofLiveProcess *live_pid; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ ++ live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE); ++ ++ sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid, ++ addresses, n_addresses); ++} ++ ++void ++sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ GPid tid, ++ const SysprofCaptureAddress *addresses, ++ guint n_addresses, ++ const guint8 *stack, ++ guint64 stack_size, ++ guint64 stack_dyn_size, ++ guint64 abi, ++ const guint64 *registers, ++ guint n_registers) ++{ ++ SysprofLiveProcess *live_pid; ++ SysprofCaptureAddress unwound[256]; ++ gboolean found_user = FALSE; ++ guint pos; ++ ++ g_assert (SYSPROF_IS_LIVE_UNWINDER (self)); ++ g_assert (stack != NULL); ++ g_assert (stack_dyn_size <= stack_size); ++ ++ if (stack_dyn_size == 0 || n_addresses >= G_N_ELEMENTS (unwound)) ++ { ++ sysprof_live_unwinder_process_sampled (self, time, cpu, pid, tid, addresses, n_addresses); ++ return; ++ } ++ ++ live_pid = sysprof_live_unwinder_find_pid (self, pid, TRUE); ++ ++ /* Copy addresses over (which might be kernel, context-switch, etc until ++ * we get to the PERF_CONTEXT_USER. We'll decode the stack right into the ++ * location after that. ++ */ ++ for (pos = 0; pos < n_addresses; pos++) ++ { ++ unwound[pos] = addresses[pos]; ++ ++ if (addresses[pos] == PERF_CONTEXT_USER) ++ { ++ found_user = TRUE; ++ break; ++ } ++ } ++ ++ /* If we didn't find a user context (but we have a stack size) synthesize ++ * the PERF_CONTEXT_USER now. ++ */ ++ if (!found_user && pos < G_N_ELEMENTS (unwound)) ++ unwound[pos++] = PERF_CONTEXT_USER; ++ ++ /* Now request the live process unwind the user-space stack */ ++ if (pos < G_N_ELEMENTS (unwound)) ++ { ++ guint n_unwound; ++ ++ n_unwound = sysprof_live_process_unwind (live_pid, ++ tid, ++ abi, ++ stack, ++ stack_dyn_size, ++ registers, ++ n_registers, ++ &unwound[pos], ++ G_N_ELEMENTS (unwound) - pos); ++ ++ /* Only take DWARF unwind if it was better */ ++ if (pos + n_unwound > n_addresses) ++ { ++ addresses = unwound; ++ n_addresses = pos + n_unwound; ++ } ++ } ++ ++ sysprof_capture_writer_add_sample (self->writer, time, cpu, pid, tid, addresses, n_addresses); ++} +diff --git a/src/sysprof-live-unwinder/sysprof-live-unwinder.h b/src/sysprof-live-unwinder/sysprof-live-unwinder.h +new file mode 100644 +index 00000000..e27ed9a4 +--- /dev/null ++++ b/src/sysprof-live-unwinder/sysprof-live-unwinder.h +@@ -0,0 +1,79 @@ ++/* ++ * sysprof-live-unwinder.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 ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_LIVE_UNWINDER (sysprof_live_unwinder_get_type()) ++ ++G_DECLARE_FINAL_TYPE (SysprofLiveUnwinder, sysprof_live_unwinder, SYSPROF, LIVE_UNWINDER, GObject) ++ ++SysprofLiveUnwinder *sysprof_live_unwinder_new (SysprofCaptureWriter *writer, ++ int kallsyms_fd); ++void sysprof_live_unwinder_seen_process (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ const char *comm); ++void sysprof_live_unwinder_process_exited (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid); ++void sysprof_live_unwinder_process_forked (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid parent_tid, ++ GPid tid); ++void sysprof_live_unwinder_track_mmap (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ SysprofCaptureAddress begin, ++ SysprofCaptureAddress end, ++ SysprofCaptureAddress offset, ++ guint64 inode, ++ const char *filename, ++ const char *build_id); ++void sysprof_live_unwinder_process_sampled (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ GPid tid, ++ const SysprofCaptureAddress *addresses, ++ guint n_addresses); ++void sysprof_live_unwinder_process_sampled_with_stack (SysprofLiveUnwinder *self, ++ gint64 time, ++ int cpu, ++ GPid pid, ++ GPid tid, ++ const SysprofCaptureAddress *addresses, ++ guint n_addresses, ++ const guint8 *stack, ++ guint64 stack_size, ++ guint64 stack_dyn_size, ++ guint64 abi, ++ const guint64 *registers, ++ guint n_registers); ++ ++G_END_DECLS +diff --git a/src/sysprof-live-unwinder/tests/meson.build b/src/sysprof-live-unwinder/tests/meson.build +new file mode 100644 +index 00000000..9e1a945a +--- /dev/null ++++ b/src/sysprof-live-unwinder/tests/meson.build +@@ -0,0 +1,35 @@ ++sysprof_live_unwinder_test_env = [ ++ 'G_DEBUG=gc-friendly', ++ 'GSETTINGS_BACKEND=memory', ++ 'MALLOC_CHECK_=2', ++] ++ ++sysprof_live_unwinder_testsuite_c_args = [ ++ '-DG_ENABLE_DEBUG', ++ '-UG_DISABLE_ASSERT', ++ '-UG_DISABLE_CAST_CHECKS', ++ '-DBUILDDIR="@0@"'.format(meson.current_build_dir()), ++] ++ ++sysprof_live_unwinder_testsuite = { ++ 'test-live-unwinder' : {'skip': true}, ++} ++ ++sysprof_live_unwinder_testsuite_deps = [ ++ libsysprof_static_dep, ++] ++ ++if polkit_agent_dep.found() ++ sysprof_live_unwinder_testsuite_deps += polkit_agent_dep ++endif ++ ++foreach test, params: sysprof_live_unwinder_testsuite ++ test_exe = executable(test, '@0@.c'.format(test), ++ c_args: sysprof_live_unwinder_testsuite_c_args, ++ dependencies: sysprof_live_unwinder_testsuite_deps, ++ ) ++ ++ if not params.get('skip', false) ++ test(test, test_exe, env: sysprof_live_unwinder_test_env) ++ endif ++endforeach +diff --git a/src/sysprof-live-unwinder/tests/test-live-unwinder.c b/src/sysprof-live-unwinder/tests/test-live-unwinder.c +new file mode 100644 +index 00000000..114cc568 +--- /dev/null ++++ b/src/sysprof-live-unwinder/tests/test-live-unwinder.c +@@ -0,0 +1,391 @@ ++/* ++ * test-live-unwinder.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 ++ ++#include ++ ++#include ++ ++#include "sysprof-perf-event-stream-private.h" ++ ++#if HAVE_POLKIT_AGENT ++# define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE ++# include ++# include ++#endif ++ ++#include ++ ++#define N_WAKEUP_EVENTS 149 ++ ++/* The following was provided to Sysprof by Serhei Makarov as part ++ * of the eu-stacktrace prototype work. ++ */ ++#ifdef _ASM_X86_PERF_REGS_H ++/* #define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK -- error on x86_64 due to including segment regs*/ ++#define REG(R) (1ULL << PERF_REG_X86_ ## R) ++#define DWARF_NEEDED_REGS (/* no FLAGS */ REG(IP) | REG(SP) | REG(AX) | REG(CX) | REG(DX) | REG(BX) | REG(SI) | REG(DI) | REG(SP) | REG(BP) | /* no segment regs */ REG(R8) | REG(R9) | REG(R10) | REG(R11) | REG(R12) | REG(R13) | REG(R14) | REG(R15)) ++/* XXX register ordering is defined in linux arch/x86/include/uapi/asm/perf_regs.h; ++ see code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs() ++ and note how registers are added in the same order as the perf_regs.h enum */ ++#define SYSPROF_ARCH_PREFERRED_REGS DWARF_NEEDED_REGS ++/* TODO: add other architectures, imitating the linux tools/perf tree */ ++#else ++# define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK ++#endif /* _ASM_{arch}_PERF_REGS_H */ ++ ++static gboolean sample_stack; ++static char *kallsyms = NULL; ++static int sample_stack_size = 8192; ++ ++static void ++open_perf_stream_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ GDBusConnection *connection = (GDBusConnection *)object; ++ g_autoptr(DexPromise) promise = user_data; ++ g_autoptr(GUnixFDList) fd_list = NULL; ++ g_autoptr(GVariant) ret = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ g_assert (G_IS_DBUS_CONNECTION (connection)); ++ g_assert (G_IS_ASYNC_RESULT (result)); ++ g_assert (DEX_IS_PROMISE (promise)); ++ ++ if ((ret = g_dbus_connection_call_with_unix_fd_list_finish (connection, &fd_list, result, &error))) ++ { ++ int handle; ++ int fd; ++ ++ g_variant_get (ret, "(h)", &handle); ++ ++ if (-1 != (fd = g_unix_fd_list_get (fd_list, handle, &error))) ++ { ++ dex_promise_resolve_fd (promise, g_steal_fd (&fd)); ++ return; ++ } ++ } ++ ++ g_assert (error != NULL); ++ ++ dex_promise_reject (promise, g_steal_pointer (&error)); ++} ++ ++static int ++open_perf_stream (GDBusConnection *bus, ++ int cpu, ++ GError **error) ++{ ++ struct perf_event_attr attr = {0}; ++ gboolean with_mmap2 = TRUE; ++ gboolean use_software = FALSE; ++ ++ g_assert (G_IS_DBUS_CONNECTION (bus)); ++ g_assert (cpu >= 0); ++ g_assert (error != NULL); ++ ++try_again: ++ attr.sample_type = PERF_SAMPLE_IP ++ | PERF_SAMPLE_TID ++ | PERF_SAMPLE_IDENTIFIER ++ | PERF_SAMPLE_CALLCHAIN ++ | PERF_SAMPLE_TIME; ++ ++ if (sample_stack) ++ { ++ attr.sample_type |= (PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER); ++ attr.sample_stack_user = sample_stack_size; ++ attr.sample_regs_user = SYSPROF_ARCH_PREFERRED_REGS; ++ } ++ ++ attr.wakeup_events = N_WAKEUP_EVENTS; ++ attr.disabled = TRUE; ++ attr.mmap = TRUE; ++ attr.mmap2 = with_mmap2; ++ attr.comm = 1; ++ attr.task = 1; ++ attr.exclude_idle = 1; ++ attr.sample_id_all = 1; ++ ++#ifdef HAVE_PERF_CLOCKID ++ attr.clockid = sysprof_clock; ++ attr.use_clockid = 1; ++#endif ++ ++ attr.size = sizeof attr; ++ ++ if (use_software) ++ { ++ attr.type = PERF_TYPE_SOFTWARE; ++ attr.config = PERF_COUNT_SW_CPU_CLOCK; ++ attr.sample_period = 1000000; ++ } ++ else ++ { ++ attr.type = PERF_TYPE_HARDWARE; ++ attr.config = PERF_COUNT_HW_CPU_CYCLES; ++ attr.sample_period = 1200000; ++ } ++ ++ { ++ g_autoptr(GVariant) options = _sysprof_perf_event_attr_to_variant (&attr); ++ g_autoptr(DexPromise) promise = dex_promise_new (); ++ g_autofd int fd = -1; ++ ++ g_dbus_connection_call_with_unix_fd_list (bus, ++ "org.gnome.Sysprof3", ++ "/org/gnome/Sysprof3", ++ "org.gnome.Sysprof3.Service", ++ "PerfEventOpen", ++ g_variant_new ("(@a{sv}iiht)", ++ options, ++ -1, ++ cpu, ++ -1, ++ 0), ++ G_VARIANT_TYPE ("(h)"), ++ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, ++ G_MAXUINT, ++ NULL, ++ dex_promise_get_cancellable (promise), ++ open_perf_stream_cb, ++ dex_ref (promise)); ++ ++ fd = dex_await_fd (dex_ref (promise), error); ++ ++ if (*error == NULL) ++ { ++ g_printerr ("CPU[%d]: opened perf_event stream as FD %d\n", cpu, fd); ++ return g_steal_fd (&fd); ++ } ++ ++ fd = -1; ++ } ++ ++ if (with_mmap2) ++ { ++ g_clear_error (error); ++ with_mmap2 = FALSE; ++ goto try_again; ++ } ++ ++ if (use_software == FALSE) ++ { ++ g_clear_error (error); ++ with_mmap2 = TRUE; ++ use_software = TRUE; ++ goto try_again; ++ } ++ ++ g_assert (*error != NULL); ++ ++ return -1; ++} ++ ++static DexFuture * ++main_fiber (gpointer user_data) ++{ ++ g_autoptr(GSubprocessLauncher) launcher = NULL; ++ g_autoptr(GDBusConnection) bus = NULL; ++ g_autoptr(GSubprocess) subprocess = NULL; ++ g_autoptr(GPtrArray) argv = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autofd int writer_fd = -1; ++ int n_cpu = g_get_num_processors (); ++ int next_target_fd = 3; ++ ++ /* Get our bus we will use for authorization */ ++ if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) ++ return dex_future_new_for_error (g_steal_pointer (&error)); ++ ++ /* Setup our launcher we'll use to map FDs into the translator */ ++ launcher = g_subprocess_launcher_new (0); ++ ++ /* Setup our argv which will notify the child about where to ++ * find the FDs containing perf event streams. ++ */ ++ argv = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (argv, g_build_filename (BUILDDIR, "..", "sysprof-live-unwinder", NULL)); ++ ++ /* Provide kallsyms from the file provided */ ++ if (kallsyms != NULL) ++ { ++ int fd = open (kallsyms, O_RDONLY | O_CLOEXEC); ++ ++ if (fd != -1) ++ { ++ g_subprocess_launcher_take_fd (launcher, fd, next_target_fd); ++ g_ptr_array_add (argv, g_strdup_printf ("--kallsyms=%d", next_target_fd++)); ++ } ++ } ++ ++ if (sample_stack) ++ g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", sample_stack_size)); ++ ++ g_printerr ("sysprof-live-unwinder at %s\n", (const char *)argv->pdata[0]); ++ ++ /* First try to open a perf_event stream for as many CPUs as we ++ * can before we get complaints from the kernel. ++ */ ++ for (int cpu = 0; cpu < n_cpu; cpu++) ++ { ++ g_autoptr(GError) cpu_error = NULL; ++ g_autofd int perf_fd = open_perf_stream (bus, cpu, &cpu_error); ++ ++ if (perf_fd == -1) ++ { ++ g_printerr ("CPU[%d]: %s\n", cpu, cpu_error->message); ++ continue; ++ } ++ ++ if (0 != ioctl (perf_fd, PERF_EVENT_IOC_ENABLE)) ++ { ++ int errsv = errno; ++ g_warning ("Failed to enable perf_fd: %s", g_strerror (errsv)); ++ } ++ ++ g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu)); ++ g_subprocess_launcher_take_fd (launcher, g_steal_fd (&perf_fd), next_target_fd); ++ ++ next_target_fd++; ++ } ++ ++ /* Now create a FD for our destination capture. */ ++ if (-1 == (writer_fd = open ("translated.syscap", O_CREAT|O_RDWR|O_CLOEXEC, 0664)) || ++ ftruncate (writer_fd, 0) != 0) ++ return dex_future_new_for_errno (errno); ++ g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd)); ++ g_subprocess_launcher_take_fd (launcher, g_steal_fd (&writer_fd), next_target_fd); ++ next_target_fd++; ++ ++ /* Null-terminate our argv */ ++ g_ptr_array_add (argv, NULL); ++ ++ /* Spawn our worker process with the perf FDs and writer provided */ ++ if (!(subprocess = g_subprocess_launcher_spawnv (launcher, ++ (const char * const *)argv->pdata, ++ &error))) ++ return dex_future_new_for_error (g_steal_pointer (&error)); ++ ++ /* Now wait for the translation process to complete */ ++ if (!dex_await_boolean (dex_subprocess_wait_check (subprocess), &error)) ++ return dex_future_new_for_error (g_steal_pointer (&error)); ++ ++ return dex_future_new_true (); ++} ++ ++static DexFuture * ++finally_cb (DexFuture *future, ++ gpointer user_data) ++{ ++ g_autoptr(GError) error = NULL; ++ GMainLoop *main_loop = user_data; ++ ++ if (!dex_await (dex_ref (future), &error)) ++ { ++ g_printerr ("Error: %s\n", error->message); ++ exit (EXIT_FAILURE); ++ } ++ ++ g_main_loop_quit (main_loop); ++ ++ return NULL; ++} ++ ++int ++main (int argc, ++ char *argv[]) ++{ ++#if HAVE_POLKIT_AGENT ++ PolkitAgentListener *polkit = NULL; ++ PolkitSubject *subject = NULL; ++#endif ++ g_autoptr(GOptionContext) context = NULL; ++ g_autoptr(GMainLoop) main_loop = NULL; ++ g_autoptr(GError) error = NULL; ++ DexFuture *future; ++ GOptionEntry entries[] = { ++ { "sample-stack", 's', 0, G_OPTION_ARG_NONE, &sample_stack, "If the stack should be sampled for user-space unwinding" }, ++ { "sample-stack-size", 'S', 0, G_OPTION_ARG_INT, &sample_stack_size, "If size of the stack to sample in bytes" }, ++ { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms, "Specify kallsyms for use" }, ++ { NULL } ++ }; ++ ++ sysprof_clock_init (); ++ dex_init (); ++ ++ main_loop = g_main_loop_new (NULL, FALSE); ++ context = g_option_context_new ("- test sysprof-live-unwinder"); ++ g_option_context_add_main_entries (context, entries, NULL); ++ ++ if (!g_option_context_parse (context, &argc, &argv, &error)) ++ { ++ g_printerr ("%s\n", error->message); ++ return EXIT_FAILURE; ++ } ++ ++#if HAVE_POLKIT_AGENT ++ /* Start polkit agent so that we can elevate privileges from a TTY */ ++ if (g_getenv ("DESKTOP_SESSION") == NULL && ++ (subject = polkit_unix_process_new_for_owner (getpid (), 0, -1))) ++ { ++ g_autoptr(GError) pkerror = NULL; ++ ++ polkit = polkit_agent_text_listener_new (NULL, NULL); ++ polkit_agent_listener_register (polkit, ++ POLKIT_AGENT_REGISTER_FLAGS_NONE, ++ subject, ++ NULL, ++ NULL, ++ &pkerror); ++ ++ if (pkerror != NULL) ++ { ++ g_dbus_error_strip_remote_error (pkerror); ++ g_printerr ("Failed to register polkit agent: %s\n", ++ pkerror->message); ++ } ++ } ++#endif ++ ++ future = dex_scheduler_spawn (NULL, 0, main_fiber, NULL, NULL); ++ future = dex_future_finally (future, ++ finally_cb, ++ g_main_loop_ref (main_loop), ++ (GDestroyNotify) g_main_loop_unref); ++ dex_future_disown (future); ++ ++ g_main_loop_run (main_loop); ++ ++ g_clear_pointer (&kallsyms, g_free); ++ ++ return EXIT_SUCCESS; ++} +diff --git a/src/sysprofd/ipc-unwinder-impl.c b/src/sysprofd/ipc-unwinder-impl.c +new file mode 100644 +index 00000000..7f218de6 +--- /dev/null ++++ b/src/sysprofd/ipc-unwinder-impl.c +@@ -0,0 +1,247 @@ ++/* ++ * ipc-unwinder-impl.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 ++ */ ++ ++#define G_LOG_DOMAIN "ipc-unwinder-impl" ++ ++#include "config.h" ++ ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "ipc-unwinder-impl.h" ++ ++struct _IpcUnwinderImpl ++{ ++ IpcUnwinderSkeleton parent_instance; ++}; ++ ++static void ++child_setup (gpointer data) ++{ ++ prctl (PR_SET_PDEATHSIG, SIGKILL); ++} ++ ++static gboolean ++ipc_unwinder_impl_handle_unwind (IpcUnwinder *unwinder, ++ GDBusMethodInvocation *invocation, ++ GUnixFDList *fd_list, ++ guint stack_size, ++ GVariant *arg_perf_fds, ++ GVariant *arg_event_fd) ++{ ++ g_autoptr(GSubprocessLauncher) launcher = NULL; ++ g_autoptr(GSubprocess) subprocess = NULL; ++ g_autoptr(GUnixFDList) out_fd_list = NULL; ++ g_autoptr(GPtrArray) argv = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autofd int our_fd = -1; ++ g_autofd int their_fd = -1; ++ g_autofd int event_fd = -1; ++ GVariantIter iter; ++ int capture_fd_handle; ++ int pair[2]; ++ int next_target_fd = 3; ++ int perf_fd_handle; ++ int cpu; ++ ++ g_assert (IPC_IS_UNWINDER_IMPL (unwinder)); ++ g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); ++ g_assert (!fd_list || G_IS_UNIX_FD_LIST (fd_list)); ++ ++ if (stack_size == 0 || stack_size % sysconf (_SC_PAGESIZE) != 0) ++ { ++ g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation), ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "Stack size must be a multiple of the page size"); ++ return TRUE; ++ } ++ ++ if (fd_list == NULL) ++ { ++ g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation), ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FILE_NOT_FOUND, ++ "Missing perf FDs"); ++ return TRUE; ++ } ++ ++ launcher = g_subprocess_launcher_new (0); ++ argv = g_ptr_array_new_with_free_func (g_free); ++ ++ g_ptr_array_add (argv, g_strdup (PACKAGE_LIBEXECDIR "/sysprof-live-unwinder")); ++ g_ptr_array_add (argv, g_strdup_printf ("--stack-size=%u", stack_size)); ++ ++ if (-1 == (event_fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (arg_event_fd), &error))) ++ { ++ g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); ++ return TRUE; ++ } ++ ++ g_ptr_array_add (argv, g_strdup_printf ("--event-fd=%u", next_target_fd)); ++ g_subprocess_launcher_take_fd (launcher, g_steal_fd (&event_fd), next_target_fd++); ++ ++ g_variant_iter_init (&iter, arg_perf_fds); ++ ++ while (g_variant_iter_loop (&iter, "(hi)", &perf_fd_handle, &cpu)) ++ { ++ g_autofd int perf_fd = g_unix_fd_list_get (fd_list, perf_fd_handle, &error); ++ ++ if (perf_fd < 0) ++ { ++ g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); ++ return TRUE; ++ } ++ ++ g_ptr_array_add (argv, g_strdup_printf ("--perf-fd=%d:%d", next_target_fd, cpu)); ++ g_subprocess_launcher_take_fd (launcher, ++ g_steal_fd (&perf_fd), ++ next_target_fd++); ++ } ++ ++ g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL); ++ ++ if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, pair) < 0) ++ { ++ int errsv = errno; ++ g_dbus_method_invocation_return_error_literal (g_steal_pointer (&invocation), ++ G_IO_ERROR, ++ g_io_error_from_errno (errsv), ++ g_strerror (errsv)); ++ return TRUE; ++ } ++ ++ our_fd = g_steal_fd (&pair[0]); ++ their_fd = g_steal_fd (&pair[1]); ++ ++ out_fd_list = g_unix_fd_list_new (); ++ capture_fd_handle = g_unix_fd_list_append (out_fd_list, their_fd, &error); ++ ++ if (capture_fd_handle < 0) ++ { ++ g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); ++ return TRUE; ++ } ++ ++ g_ptr_array_add (argv, g_strdup_printf ("--capture-fd=%d", next_target_fd)); ++ g_subprocess_launcher_take_fd (launcher, g_steal_fd (&our_fd), next_target_fd++); ++ ++ g_ptr_array_add (argv, NULL); ++ ++ if (!(subprocess = g_subprocess_launcher_spawnv (launcher, (const char * const *)argv->pdata, &error))) ++ { ++ g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); ++ return TRUE; ++ } ++ ++ ipc_unwinder_complete_unwind (unwinder, ++ g_steal_pointer (&invocation), ++ out_fd_list, ++ g_variant_new_handle (capture_fd_handle)); ++ ++ g_subprocess_wait_async (subprocess, NULL, NULL, NULL); ++ ++ return TRUE; ++} ++ ++static void ++unwinder_iface_init (IpcUnwinderIface *iface) ++{ ++ iface->handle_unwind = ipc_unwinder_impl_handle_unwind; ++} ++ ++G_DEFINE_FINAL_TYPE_WITH_CODE (IpcUnwinderImpl, ipc_unwinder_impl, IPC_TYPE_UNWINDER_SKELETON, ++ G_IMPLEMENT_INTERFACE (IPC_TYPE_UNWINDER, unwinder_iface_init)) ++ ++static gboolean ++ipc_unwinder_impl_g_authorize_method (GDBusInterfaceSkeleton *skeleton, ++ GDBusMethodInvocation *invocation) ++{ ++ PolkitAuthorizationResult *res = NULL; ++ PolkitAuthority *authority = NULL; ++ PolkitSubject *subject = NULL; ++ const gchar *peer_name; ++ gboolean ret = TRUE; ++ ++ g_assert (IPC_IS_UNWINDER_IMPL (skeleton)); ++ g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); ++ ++ peer_name = g_dbus_method_invocation_get_sender (invocation); ++ ++ if (!(authority = polkit_authority_get_sync (NULL, NULL)) || ++ !(subject = polkit_system_bus_name_new (peer_name)) || ++ !(res = polkit_authority_check_authorization_sync (authority, ++ POLKIT_SUBJECT (subject), ++ "org.gnome.sysprof3.profile", ++ NULL, ++ POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, ++ NULL, ++ NULL)) || ++ !polkit_authorization_result_get_is_authorized (res)) ++ { ++ g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Not authorized to make request"); ++ ret = FALSE; ++ } ++ ++ g_clear_object (&authority); ++ g_clear_object (&subject); ++ g_clear_object (&res); ++ ++ return ret; ++} ++ ++static void ++ipc_unwinder_impl_finalize (GObject *object) ++{ ++ G_OBJECT_CLASS (ipc_unwinder_impl_parent_class)->finalize (object); ++} ++ ++static void ++ipc_unwinder_impl_class_init (IpcUnwinderImplClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass); ++ ++ object_class->finalize = ipc_unwinder_impl_finalize; ++ ++ skeleton_class->g_authorize_method = ipc_unwinder_impl_g_authorize_method; ++} ++ ++static void ++ipc_unwinder_impl_init (IpcUnwinderImpl *self) ++{ ++} ++ ++IpcUnwinder * ++ipc_unwinder_impl_new (void) ++{ ++ return g_object_new (IPC_TYPE_UNWINDER_IMPL, NULL); ++} +diff --git a/src/sysprofd/ipc-unwinder-impl.h b/src/sysprofd/ipc-unwinder-impl.h +new file mode 100644 +index 00000000..ebe7a2f0 +--- /dev/null ++++ b/src/sysprofd/ipc-unwinder-impl.h +@@ -0,0 +1,34 @@ ++/* ++ * ipc-unwinder-impl.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 "ipc-unwinder.h" ++ ++G_BEGIN_DECLS ++ ++#define IPC_TYPE_UNWINDER_IMPL (ipc_unwinder_impl_get_type()) ++ ++G_DECLARE_FINAL_TYPE (IpcUnwinderImpl, ipc_unwinder_impl, IPC, UNWINDER_IMPL, IpcUnwinderSkeleton) ++ ++IpcUnwinder *ipc_unwinder_impl_new (void); ++ ++G_END_DECLS +diff --git a/src/sysprofd/meson.build b/src/sysprofd/meson.build +index 34901f44..b7e87ebe 100644 +--- a/src/sysprofd/meson.build ++++ b/src/sysprofd/meson.build +@@ -10,18 +10,24 @@ ipc_service_src = gnome.gdbus_codegen('ipc-service', + namespace: 'Ipc', + ) + ++ipc_unwinder_src = gnome.gdbus_codegen('ipc-unwinder', ++ sources: 'org.gnome.Sysprof3.Unwinder.xml', ++ interface_prefix: 'org.gnome.Sysprof3.', ++ namespace: 'Ipc', ++) ++ + sysprofd_sources = [ + 'sysprofd.c', + 'ipc-rapl-profiler.c', + 'ipc-service-impl.c', ++ 'ipc-unwinder-impl.c', + 'sysprof-turbostat.c', + 'helpers.c', + ipc_profiler_src, + ipc_service_src, ++ ipc_unwinder_src, + ] + +-pkglibexecdir = join_paths(get_option('prefix'), get_option('libexecdir')) +- + sysprofd_deps = [ + glib_dep, + gio_dep, +diff --git a/src/sysprofd/org.gnome.Sysprof3.Unwinder.xml b/src/sysprofd/org.gnome.Sysprof3.Unwinder.xml +new file mode 100644 +index 00000000..fb2c7848 +--- /dev/null ++++ b/src/sysprofd/org.gnome.Sysprof3.Unwinder.xml +@@ -0,0 +1,23 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/src/sysprofd/sysprofd.c b/src/sysprofd/sysprofd.c +index e39d59c4..7a7101e8 100644 +--- a/src/sysprofd/sysprofd.c ++++ b/src/sysprofd/sysprofd.c +@@ -30,9 +30,11 @@ + + #include "ipc-rapl-profiler.h" + #include "ipc-service-impl.h" ++#include "ipc-unwinder-impl.h" + + #define V3_PATH "/org/gnome/Sysprof3" + #define RAPL_PATH "/org/gnome/Sysprof3/RAPL" ++#define UNWINDER_PATH "/org/gnome/Sysprof3/Unwinder" + #define NAME_ACQUIRE_DELAY_SECS 3 + #define INACTIVITY_TIMEOUT_SECS 120 + +@@ -126,6 +128,7 @@ main (gint argc, + { + g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new (); + g_autoptr(IpcService) v3_service = ipc_service_impl_new (); ++ g_autoptr(IpcUnwinder) unwinder = ipc_unwinder_impl_new (); + + g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL); + g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL); +@@ -133,7 +136,8 @@ main (gint argc, + activity_cb (NULL, NULL); + + if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) && +- g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error)) ++ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error) && ++ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (unwinder), bus, UNWINDER_PATH, &error)) + { + for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++) + { +-- +2.45.2 + diff --git a/0016-libsysprof-add-SysprofUserSampler-for-live-unwinding.patch b/0016-libsysprof-add-SysprofUserSampler-for-live-unwinding.patch new file mode 100644 index 0000000..bd91672 --- /dev/null +++ b/0016-libsysprof-add-SysprofUserSampler-for-live-unwinding.patch @@ -0,0 +1,694 @@ +From ef191d3f52ad904a9af8908ff5c2a1e44dfd27b0 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Sun, 3 Nov 2024 10:53:29 -0800 +Subject: [PATCH 16/18] libsysprof: add SysprofUserSampler for live unwinding + +This instrument triggers the live unwinder in sysprofd to capture a +pre-configured amount of stack contents and CPU registers. You can use +this instead of SysprofSampler in cases where you do not have frame- +pointers but want a useful trace. + +It does have a moderate amount of CPU overhead compared to just relying +on frame-pointers so keep that in mind. Generally useful on platforms +that do not have frame pointers such as CentOS. +--- + src/libsysprof/meson.build | 7 + + src/libsysprof/sysprof-user-sampler.c | 570 ++++++++++++++++++++++++++ + src/libsysprof/sysprof-user-sampler.h | 43 ++ + src/libsysprof/sysprof.h | 1 + + 4 files changed, 621 insertions(+) + create mode 100644 src/libsysprof/sysprof-user-sampler.c + create mode 100644 src/libsysprof/sysprof-user-sampler.h + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index f3fa0850..e49c3a37 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -63,6 +63,7 @@ libsysprof_public_sources = [ + 'sysprof-time-span.c', + 'sysprof-tracefd-consumer.c', + 'sysprof-tracer.c', ++ 'sysprof-user-sampler.c', + ] + + libsysprof_public_headers = [ +@@ -130,6 +131,7 @@ libsysprof_public_headers = [ + 'sysprof-time-span.h', + 'sysprof-tracefd-consumer.h', + 'sysprof-tracer.h', ++ 'sysprof-user-sampler.h', + ] + + libsysprof_private_sources = [ +@@ -154,6 +156,11 @@ libsysprof_private_sources = [ + 'sysprof-strings.c', + 'sysprof-symbol-cache.c', + 'timsort/gtktimsort.c', ++ ++ gnome.gdbus_codegen('ipc-unwinder', ++ sources: '../sysprofd/org.gnome.Sysprof3.Unwinder.xml', ++ interface_prefix: 'org.gnome.Sysprof3.', ++ namespace: 'Ipc'), + ] + + if debuginfod_dep.found() +diff --git a/src/libsysprof/sysprof-user-sampler.c b/src/libsysprof/sysprof-user-sampler.c +new file mode 100644 +index 00000000..0e3afeae +--- /dev/null ++++ b/src/libsysprof/sysprof-user-sampler.c +@@ -0,0 +1,570 @@ ++/* sysprof-user-sampler.c ++ * ++ * Copyright 2023 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-instrument-private.h" ++#include "sysprof-perf-event-stream-private.h" ++#include "sysprof-recording-private.h" ++#include "sysprof-user-sampler.h" ++#include "sysprof-muxer-source.h" ++ ++#include "ipc-unwinder.h" ++ ++/* The following was provided to Sysprof by Serhei Makarov as part ++ * of the eu-stacktrace prototype work. ++ */ ++#ifdef _ASM_X86_PERF_REGS_H ++/* #define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK -- error on x86_64 due to including segment regs*/ ++#define REG(R) (1ULL << PERF_REG_X86_ ## R) ++#define DWARF_NEEDED_REGS (/* no FLAGS */ REG(IP) | REG(SP) | REG(AX) | REG(CX) | REG(DX) | REG(BX) | REG(SI) | REG(DI) | REG(SP) | REG(BP) | /* no segment regs */ REG(R8) | REG(R9) | REG(R10) | REG(R11) | REG(R12) | REG(R13) | REG(R14) | REG(R15)) ++/* XXX register ordering is defined in linux arch/x86/include/uapi/asm/perf_regs.h; ++ see code in tools/perf/util/intel-pt.c intel_pt_add_gp_regs() ++ and note how registers are added in the same order as the perf_regs.h enum */ ++#define SYSPROF_ARCH_PREFERRED_REGS DWARF_NEEDED_REGS ++/* TODO: add other architectures, imitating the linux tools/perf tree */ ++#else ++# define SYSPROF_ARCH_PREFERRED_REGS PERF_REG_EXTENDED_MASK ++#endif /* _ASM_{arch}_PERF_REGS_H */ ++ ++#define N_WAKEUP_EVENTS 149 ++ ++struct _SysprofUserSampler ++{ ++ SysprofInstrument parent_instance; ++ GArray *perf_fds; ++ int capture_fd; ++ int event_fd; ++ guint stack_size; ++}; ++ ++struct _SysprofUserSamplerClass ++{ ++ SysprofInstrumentClass parent_class; ++}; ++ ++G_DEFINE_FINAL_TYPE (SysprofUserSampler, sysprof_user_sampler, SYSPROF_TYPE_INSTRUMENT) ++ ++static void ++close_fd (gpointer data) ++{ ++ int *fdp = data; ++ ++ if (*fdp != -1) ++ { ++ close (*fdp); ++ *fdp = -1; ++ } ++} ++ ++static void ++sysprof_user_sampler_ioctl (SysprofUserSampler *self, ++ gboolean enable) ++{ ++ for (guint i = 0; i < self->perf_fds->len; i++) ++ { ++ int perf_fd = g_array_index (self->perf_fds, int, i); ++ ++ if (0 != ioctl (perf_fd, enable ? PERF_EVENT_IOC_ENABLE : PERF_EVENT_IOC_DISABLE)) ++ { ++ int errsv = errno; ++ g_warning ("Failed to toggle perf_fd: %s", g_strerror (errsv)); ++ } ++ } ++} ++ ++static char ** ++sysprof_user_sampler_list_required_policy (SysprofInstrument *instrument) ++{ ++ static const char *policy[] = {"org.gnome.sysprof3.profile", NULL}; ++ ++ return g_strdupv ((char **)policy); ++} ++ ++typedef struct _Prepare ++{ ++ SysprofRecording *recording; ++ SysprofUserSampler *sampler; ++ guint stack_size; ++} Prepare; ++ ++static void ++prepare_free (Prepare *prepare) ++{ ++ g_clear_object (&prepare->recording); ++ g_clear_object (&prepare->sampler); ++ g_free (prepare); ++} ++ ++static void ++_perf_event_open_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ GDBusConnection *connection = (GDBusConnection *)object; ++ g_autoptr(DexPromise) promise = user_data; ++ g_autoptr(GUnixFDList) fd_list = NULL; ++ g_autoptr(GVariant) ret = NULL; ++ g_autoptr(GError) error = NULL; ++ ++ g_assert (G_IS_DBUS_CONNECTION (connection)); ++ g_assert (G_IS_ASYNC_RESULT (result)); ++ g_assert (DEX_IS_PROMISE (promise)); ++ ++ if ((ret = g_dbus_connection_call_with_unix_fd_list_finish (connection, &fd_list, result, &error))) ++ { ++ g_autofd int fd = -1; ++ int handle; ++ ++ g_variant_get (ret, "(h)", &handle); ++ ++ if (-1 == (fd = g_unix_fd_list_get (fd_list, handle, &error))) ++ goto failure; ++ ++ dex_promise_resolve_fd (promise, g_steal_fd (&fd)); ++ return; ++ } ++ ++failure: ++ dex_promise_reject (promise, g_steal_pointer (&error)); ++} ++ ++static int ++_perf_event_open (GDBusConnection *connection, ++ int cpu, ++ guint stack_size, ++ GError **error) ++{ ++ g_autoptr(DexPromise) promise = NULL; ++ g_autoptr(GVariant) options = NULL; ++ g_autofd int perf_fd = -1; ++ struct perf_event_attr attr = {0}; ++ gboolean with_mmap2 = TRUE; ++ gboolean use_software = FALSE; ++ ++ g_assert (G_IS_DBUS_CONNECTION (connection)); ++ ++try_again: ++ attr.sample_type = PERF_SAMPLE_IP ++ | PERF_SAMPLE_TID ++ | PERF_SAMPLE_IDENTIFIER ++ | PERF_SAMPLE_CALLCHAIN ++ | PERF_SAMPLE_STACK_USER ++ | PERF_SAMPLE_REGS_USER ++ | PERF_SAMPLE_TIME; ++ attr.wakeup_events = N_WAKEUP_EVENTS; ++ attr.disabled = TRUE; ++ attr.mmap = TRUE; ++ attr.mmap2 = with_mmap2; ++ attr.comm = 1; ++ attr.task = 1; ++ attr.exclude_idle = 1; ++ attr.sample_id_all = 1; ++ ++#ifdef HAVE_PERF_CLOCKID ++ attr.clockid = sysprof_clock; ++ attr.use_clockid = 1; ++#endif ++ ++ attr.sample_stack_user = stack_size; ++ attr.sample_regs_user = SYSPROF_ARCH_PREFERRED_REGS; ++ ++ attr.size = sizeof attr; ++ ++ if (use_software) ++ { ++ attr.type = PERF_TYPE_SOFTWARE; ++ attr.config = PERF_COUNT_SW_CPU_CLOCK; ++ attr.sample_period = 1000000; ++ } ++ else ++ { ++ attr.type = PERF_TYPE_HARDWARE; ++ attr.config = PERF_COUNT_HW_CPU_CYCLES; ++ attr.sample_period = 1200000; ++ } ++ ++ options = _sysprof_perf_event_attr_to_variant (&attr); ++ promise = dex_promise_new (); ++ ++ g_dbus_connection_call_with_unix_fd_list (connection, ++ "org.gnome.Sysprof3", ++ "/org/gnome/Sysprof3", ++ "org.gnome.Sysprof3.Service", ++ "PerfEventOpen", ++ g_variant_new ("(@a{sv}iiht)", ++ options, ++ -1, ++ cpu, ++ -1, ++ 0), ++ G_VARIANT_TYPE ("(h)"), ++ G_DBUS_CALL_FLAGS_NONE, ++ G_MAXUINT, ++ NULL, ++ NULL, ++ _perf_event_open_cb, ++ dex_ref (promise)); ++ ++ if (-1 == (perf_fd = dex_await_fd (dex_ref (promise), error))) ++ { ++ g_clear_pointer (&options, g_variant_unref); ++ ++ if (with_mmap2) ++ { ++ with_mmap2 = FALSE; ++ goto try_again; ++ } ++ ++ if (use_software == FALSE) ++ { ++ with_mmap2 = TRUE; ++ use_software = TRUE; ++ goto try_again; ++ } ++ ++ return -1; ++ } ++ ++ return g_steal_fd (&perf_fd); ++} ++ ++static void ++call_unwind_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr(DexPromise) promise = user_data; ++ g_autoptr(GUnixFDList) out_fd_list = NULL; ++ g_autoptr(GVariant) out_capture_fd = NULL; ++ g_autofd int capture_fd = -1; ++ GError *error = NULL; ++ ++ g_assert (IPC_IS_UNWINDER (object)); ++ g_assert (G_IS_ASYNC_RESULT (result)); ++ g_assert (DEX_IS_PROMISE (promise)); ++ ++ if (ipc_unwinder_call_unwind_finish (IPC_UNWINDER (object), &out_capture_fd, &out_fd_list, result, &error) && ++ -1 != (capture_fd = g_unix_fd_list_get (out_fd_list, g_variant_get_handle (out_capture_fd), &error))) ++ dex_promise_resolve_fd (promise, g_steal_fd (&capture_fd)); ++ else ++ dex_promise_reject (promise, error); ++} ++ ++static void ++create_unwinder_cb (GObject *object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ g_autoptr(DexPromise) promise = user_data; ++ IpcUnwinder *unwinder; ++ GError *error = NULL; ++ ++ if ((unwinder = ipc_unwinder_proxy_new_finish (result, &error))) ++ dex_promise_resolve_object (promise, unwinder); ++ else ++ dex_promise_reject (promise, error); ++} ++ ++static IpcUnwinder * ++create_unwinder (GDBusConnection *connection, ++ GError **error) ++{ ++ g_autoptr(DexPromise) promise = dex_promise_new (); ++ ipc_unwinder_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, ++ "org.gnome.Sysprof3", ++ "/org/gnome/Sysprof3/Unwinder", ++ NULL, ++ create_unwinder_cb, ++ dex_ref (promise)); ++ return dex_await_object (dex_ref (promise), error); ++} ++ ++static DexFuture * ++sysprof_user_sampler_prepare_fiber (gpointer user_data) ++{ ++ Prepare *prepare = user_data; ++ g_autoptr(GDBusConnection) connection = NULL; ++ g_autoptr(GUnixFDList) fd_list = NULL; ++ g_autoptr(GError) error = NULL; ++ GVariantBuilder builder; ++ gboolean all_failed = TRUE; ++ guint n_cpu; ++ ++ g_assert (prepare != NULL); ++ g_assert (SYSPROF_IS_RECORDING (prepare->recording)); ++ g_assert (SYSPROF_IS_USER_SAMPLER (prepare->sampler)); ++ ++ if (!(connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) ++ return dex_future_new_for_error (g_steal_pointer (&error)); ++ ++ if (!dex_await (_sysprof_recording_add_file (prepare->recording, ++ "/proc/kallsyms", ++ TRUE), ++ &error)) ++ { ++ _sysprof_recording_diagnostic (prepare->recording, ++ "Sampler", ++ "Failed to record copy of “kallsyms” to capture: %s", ++ error->message); ++ g_clear_error (&error); ++ } ++ ++ n_cpu = g_get_num_processors (); ++ fd_list = g_unix_fd_list_new (); ++ ++ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(hi)")); ++ ++ for (guint i = 0; i < n_cpu; i++) ++ { ++ g_autofd int fd = _perf_event_open (connection, i, prepare->stack_size, &error); ++ ++ if (fd == -1) ++ { ++ _sysprof_recording_diagnostic (prepare->recording, ++ "Sampler", ++ "Failed to load Perf event stream for CPU %d with stack size %u: %s", ++ i, prepare->stack_size, error->message); ++ g_clear_error (&error); ++ } ++ else ++ { ++ int handle = g_unix_fd_list_append (fd_list, fd, &error); ++ ++ if (handle == -1) ++ { ++ _sysprof_recording_diagnostic (prepare->recording, ++ "Sampler", ++ "Out of FDs to add to FDList: %s", ++ error->message); ++ g_clear_error (&error); ++ } ++ else ++ { ++ g_array_append_val (prepare->sampler->perf_fds, fd); ++ fd = -1; ++ ++ g_variant_builder_add (&builder, "(hi)", handle, i); ++ ++ all_failed = FALSE; ++ } ++ } ++ } ++ ++ if (!all_failed) ++ { ++ g_autoptr(IpcUnwinder) unwinder = create_unwinder (connection, &error); ++ ++ if (unwinder == NULL) ++ { ++ _sysprof_recording_diagnostic (prepare->recording, ++ "Sampler", ++ "Failed to locate unwinder service: %s", ++ error->message); ++ g_clear_error (&error); ++ } ++ else ++ { ++ g_autoptr(DexPromise) promise = dex_promise_new (); ++ int event_fd_handle = g_unix_fd_list_append (fd_list, prepare->sampler->event_fd, NULL); ++ g_autofd int fd = -1; ++ ++ ipc_unwinder_call_unwind (unwinder, ++ prepare->stack_size, ++ g_variant_builder_end (&builder), ++ g_variant_new_handle (event_fd_handle), ++ fd_list, ++ NULL, ++ call_unwind_cb, ++ dex_ref (promise)); ++ ++ fd = dex_await_fd (dex_ref (promise), &error); ++ ++ if (fd == -1) ++ { ++ _sysprof_recording_diagnostic (prepare->recording, ++ "Sampler", ++ "Failed to setup user-space unwinder: %s", ++ error->message); ++ g_clear_error (&error); ++ } ++ else ++ { ++ prepare->sampler->capture_fd = g_steal_fd (&fd); ++ } ++ } ++ } ++ ++ g_variant_builder_clear (&builder); ++ ++ return dex_future_new_for_boolean (TRUE); ++} ++ ++static DexFuture * ++sysprof_user_sampler_prepare (SysprofInstrument *instrument, ++ SysprofRecording *recording) ++{ ++ SysprofUserSampler *self = (SysprofUserSampler *)instrument; ++ Prepare *prepare; ++ ++ g_assert (SYSPROF_IS_INSTRUMENT (instrument)); ++ g_assert (SYSPROF_IS_RECORDING (recording)); ++ ++ prepare = g_new0 (Prepare, 1); ++ prepare->recording = g_object_ref (recording); ++ prepare->sampler = g_object_ref (self); ++ prepare->stack_size = self->stack_size; ++ ++ return dex_scheduler_spawn (NULL, 0, ++ sysprof_user_sampler_prepare_fiber, ++ prepare, ++ (GDestroyNotify)prepare_free); ++} ++ ++typedef struct _Record ++{ ++ SysprofRecording *recording; ++ SysprofUserSampler *sampler; ++ DexFuture *cancellable; ++} Record; ++ ++static void ++record_free (Record *record) ++{ ++ g_clear_object (&record->recording); ++ g_clear_object (&record->sampler); ++ dex_clear (&record->cancellable); ++ g_free (record); ++} ++ ++static DexFuture * ++sysprof_user_sampler_record_fiber (gpointer user_data) ++{ ++ SysprofCaptureWriter *writer; ++ Record *record = user_data; ++ g_autoptr(GError) error = NULL; ++ g_autoptr(GSource) muxer_source = NULL; ++ guint64 exiting = 1234; ++ ++ g_assert (record != NULL); ++ g_assert (SYSPROF_IS_USER_SAMPLER (record->sampler)); ++ g_assert (SYSPROF_IS_RECORDING (record->recording)); ++ g_assert (DEX_IS_FUTURE (record->cancellable)); ++ ++ writer = _sysprof_recording_writer (record->recording); ++ ++ sysprof_user_sampler_ioctl (record->sampler, TRUE); ++ ++ g_debug ("Staring muxer for capture_fd"); ++ muxer_source = sysprof_muxer_source_new (g_steal_fd (&record->sampler->capture_fd), writer); ++ g_source_set_static_name (muxer_source, "[stack-muxer]"); ++ g_source_attach (muxer_source, NULL); ++ ++ if (!dex_await (dex_ref (record->cancellable), &error)) ++ g_debug ("UserSampler shutting down for reason: %s", error->message); ++ ++ write (record->sampler->event_fd, &exiting, sizeof exiting); ++ ++ g_source_destroy (muxer_source); ++ ++ sysprof_user_sampler_ioctl (record->sampler, FALSE); ++ ++ return dex_future_new_for_boolean (TRUE); ++} ++ ++static DexFuture * ++sysprof_user_sampler_record (SysprofInstrument *instrument, ++ SysprofRecording *recording, ++ GCancellable *cancellable) ++{ ++ SysprofUserSampler *self = (SysprofUserSampler *)instrument; ++ Record *record; ++ ++ g_assert (SYSPROF_IS_INSTRUMENT (instrument)); ++ g_assert (SYSPROF_IS_RECORDING (recording)); ++ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); ++ ++ record = g_new0 (Record, 1); ++ record->recording = g_object_ref (recording); ++ record->sampler = g_object_ref (self); ++ record->cancellable = dex_cancellable_new_from_cancellable (cancellable); ++ ++ return dex_scheduler_spawn (NULL, 0, ++ sysprof_user_sampler_record_fiber, ++ record, ++ (GDestroyNotify)record_free); ++} ++ ++static void ++sysprof_user_sampler_finalize (GObject *object) ++{ ++ SysprofUserSampler *self = (SysprofUserSampler *)object; ++ ++ g_clear_pointer (&self->perf_fds, g_array_unref); ++ ++ g_clear_fd (&self->capture_fd, NULL); ++ g_clear_fd (&self->event_fd, NULL); ++ ++ G_OBJECT_CLASS (sysprof_user_sampler_parent_class)->finalize (object); ++} ++ ++static void ++sysprof_user_sampler_class_init (SysprofUserSamplerClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); ++ ++ object_class->finalize = sysprof_user_sampler_finalize; ++ ++ instrument_class->list_required_policy = sysprof_user_sampler_list_required_policy; ++ instrument_class->prepare = sysprof_user_sampler_prepare; ++ instrument_class->record = sysprof_user_sampler_record; ++} ++ ++static void ++sysprof_user_sampler_init (SysprofUserSampler *self) ++{ ++ self->capture_fd = -1; ++ self->event_fd = eventfd (0, EFD_CLOEXEC); ++ ++ self->perf_fds = g_array_new (FALSE, FALSE, sizeof (int)); ++ g_array_set_clear_func (self->perf_fds, close_fd); ++} ++ ++SysprofInstrument * ++sysprof_user_sampler_new (guint stack_size) ++{ ++ SysprofUserSampler *self; ++ ++ g_return_val_if_fail (stack_size > 0, NULL); ++ g_return_val_if_fail (stack_size % sysprof_getpagesize () == 0, NULL); ++ ++ self = g_object_new (SYSPROF_TYPE_USER_SAMPLER, NULL); ++ self->stack_size = stack_size; ++ ++ return SYSPROF_INSTRUMENT (self); ++} +diff --git a/src/libsysprof/sysprof-user-sampler.h b/src/libsysprof/sysprof-user-sampler.h +new file mode 100644 +index 00000000..2d9e56b5 +--- /dev/null ++++ b/src/libsysprof/sysprof-user-sampler.h +@@ -0,0 +1,43 @@ ++/* ++ * sysprof-user-sampler.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-instrument.h" ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_USER_SAMPLER (sysprof_user_sampler_get_type()) ++#define SYSPROF_IS_USER_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_USER_SAMPLER) ++#define SYSPROF_USER_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_USER_SAMPLER, SysprofUserSampler) ++#define SYSPROF_USER_SAMPLER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_USER_SAMPLER, SysprofUserSamplerClass) ++ ++typedef struct _SysprofUserSampler SysprofUserSampler; ++typedef struct _SysprofUserSamplerClass SysprofUserSamplerClass; ++ ++SYSPROF_AVAILABLE_IN_ALL ++GType sysprof_user_sampler_get_type (void) G_GNUC_CONST; ++SYSPROF_AVAILABLE_IN_ALL ++SysprofInstrument *sysprof_user_sampler_new (guint stack_size); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofUserSampler, g_object_unref) ++ ++G_END_DECLS +diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h +index d30f9fd4..514e332b 100644 +--- a/src/libsysprof/sysprof.h ++++ b/src/libsysprof/sysprof.h +@@ -89,6 +89,7 @@ G_BEGIN_DECLS + # include "sysprof-time-span.h" + # include "sysprof-tracefd-consumer.h" + # include "sysprof-tracer.h" ++# include "sysprof-user-sampler.h" + #undef SYSPROF_INSIDE + + G_END_DECLS +-- +2.45.2 + diff --git a/0017-sysprof-cli-add-support-for-live-unwinding.patch b/0017-sysprof-cli-add-support-for-live-unwinding.patch new file mode 100644 index 0000000..f731c11 --- /dev/null +++ b/0017-sysprof-cli-add-support-for-live-unwinding.patch @@ -0,0 +1,60 @@ +From da9f2db255c1f0c63c10993eb62db567265ced6e Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Sun, 3 Nov 2024 10:54:57 -0800 +Subject: [PATCH 17/18] sysprof-cli: add support for live unwinding + +This allows you to specify --stack-size=(multiple_of_page_size) to unwind +from captured stack contents. It will use the new SysprofUserSampler to +unwind stack traces via sysprof-live-unwinder. +--- + src/sysprof-cli/sysprof-cli.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/src/sysprof-cli/sysprof-cli.c b/src/sysprof-cli/sysprof-cli.c +index e00bd6b1..1c9ce928 100644 +--- a/src/sysprof-cli/sysprof-cli.c ++++ b/src/sysprof-cli/sysprof-cli.c +@@ -303,6 +303,7 @@ main (int argc, + gboolean scheduler_details = FALSE; + gboolean system_bus = FALSE; + gboolean session_bus = FALSE; ++ int stack_size = 0; + int pid = -1; + int fd; + int flags; +@@ -335,6 +336,7 @@ main (int argc, + { "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") }, + { "buffer-size", 0, 0, G_OPTION_ARG_INT, &n_buffer_pages, N_("The size of the buffer in pages (1 = 1 page)") }, + { "monitor-bus", 0, 0, G_OPTION_ARG_STRING_ARRAY, &monitor_bus, N_("Additional D-Bus address to monitor") }, ++ { "stack-size", 0, 0, G_OPTION_ARG_INT, &stack_size, N_("Stack size to copy for unwinding in user-space") }, + { NULL } + }; + +@@ -379,6 +381,10 @@ Examples:\n\ + \n\ + # Merge multiple syscap files into one\n\ + sysprof-cli --merge a.syscap b.syscap > c.syscap\n\ ++\n\ ++ # Unwind by capturing stack/register contents instead of frame-pointers\n\ ++ # where the stack-size is a multiple of page-size\n\ ++ sysprof-cli --stack-size=8192\n\ + ")); + + if (!g_option_context_parse (context, &argc, &argv, &error)) +@@ -533,7 +539,12 @@ Examples:\n\ + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); + + if (!no_perf) +- sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); ++ { ++ if (stack_size == 0) ++ sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); ++ else ++ sysprof_profiler_add_instrument (profiler, sysprof_user_sampler_new (stack_size)); ++ } + + if (!no_disk) + sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); +-- +2.45.2 + diff --git a/0018-sysprof-add-UI-for-live-unwinding.patch b/0018-sysprof-add-UI-for-live-unwinding.patch new file mode 100644 index 0000000..8447a4f --- /dev/null +++ b/0018-sysprof-add-UI-for-live-unwinding.patch @@ -0,0 +1,506 @@ +From 927b13bb407105517783ab1d03c025fecf1d59d9 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Sun, 3 Nov 2024 10:57:58 -0800 +Subject: [PATCH 18/18] sysprof: add UI for live unwinding + +This adds UI to specify the amount of stack contents to copy along with +the CPU registers so that you may unwind in user-space. +--- + src/sysprof/meson.build | 1 + + src/sysprof/sysprof-greeter.c | 32 +++++ + src/sysprof/sysprof-greeter.ui | 67 +++++++++++ + src/sysprof/sysprof-recording-template.c | 41 ++++++- + src/sysprof/sysprof-stack-size.c | 141 +++++++++++++++++++++++ + src/sysprof/sysprof-stack-size.h | 35 ++++++ + 6 files changed, 316 insertions(+), 1 deletion(-) + create mode 100644 src/sysprof/sysprof-stack-size.c + create mode 100644 src/sysprof/sysprof-stack-size.h + +diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build +index ca42ead3..ab6a701a 100644 +--- a/src/sysprof/meson.build ++++ b/src/sysprof/meson.build +@@ -57,6 +57,7 @@ sysprof_sources = [ + 'sysprof-sidebar.c', + 'sysprof-single-model.c', + 'sysprof-split-layer.c', ++ 'sysprof-stack-size.c', + 'sysprof-storage-section.c', + 'sysprof-symbol-label.c', + 'sysprof-task-row.c', +diff --git a/src/sysprof/sysprof-greeter.c b/src/sysprof/sysprof-greeter.c +index 72f4dd5d..52eff370 100644 +--- a/src/sysprof/sysprof-greeter.c ++++ b/src/sysprof/sysprof-greeter.c +@@ -31,6 +31,7 @@ + #include "sysprof-power-profiles.h" + #include "sysprof-recording-pad.h" + #include "sysprof-recording-template.h" ++#include "sysprof-stack-size.h" + #include "sysprof-window.h" + + G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) +@@ -56,6 +57,7 @@ struct _SysprofGreeter + GtkSwitch *bundle_symbols; + GtkButton *record_to_memory; + AdwComboRow *power_combo; ++ AdwComboRow *sample_user_stack_size; + SysprofRecordingTemplate *recording_template; + }; + +@@ -455,6 +457,26 @@ translate_power_profile (GtkStringObject *strobj) + return g_strdup (str); + } + ++static void ++on_stack_size_changed_cb (SysprofGreeter *self, ++ GParamSpec *pspec, ++ AdwComboRow *row) ++{ ++ GObject *item; ++ ++ g_assert (SYSPROF_IS_GREETER (self)); ++ g_assert (ADW_IS_COMBO_ROW (row)); ++ ++ if ((item = adw_combo_row_get_selected_item (row))) ++ { ++ guint stack_size = sysprof_stack_size_get_size (SYSPROF_STACK_SIZE (item)); ++ ++ g_object_set (self->recording_template, ++ "stack-size", stack_size, ++ NULL); ++ } ++} ++ + static void + sysprof_greeter_dispose (GObject *object) + { +@@ -492,6 +514,7 @@ sysprof_greeter_class_init (SysprofGreeterClass *klass) + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, recording_template); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_javascript_stacks); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_native_stacks); ++ gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_user_stack_size); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sidebar_list_box); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, view_stack); + +@@ -507,6 +530,7 @@ sysprof_greeter_class_init (SysprofGreeterClass *klass) + + g_type_ensure (SYSPROF_TYPE_ENTRY_POPOVER); + g_type_ensure (SYSPROF_TYPE_RECORDING_TEMPLATE); ++ g_type_ensure (SYSPROF_TYPE_STACK_SIZE); + } + + static void +@@ -540,6 +564,14 @@ sysprof_greeter_init (SysprofGreeter *self) + gtk_list_box_select_row (self->sidebar_list_box, row); + sidebar_row_activated_cb (self, row, self->sidebar_list_box); + ++ g_signal_connect_object (self->sample_user_stack_size, ++ "notify::selected-item", ++ G_CALLBACK (on_stack_size_changed_cb), ++ self, ++ G_CONNECT_SWAPPED); ++ /* Set to 16KB */ ++ adw_combo_row_set_selected (self->sample_user_stack_size, 1); ++ + gtk_widget_grab_focus (GTK_WIDGET (self->record_to_memory)); + } + +diff --git a/src/sysprof/sysprof-greeter.ui b/src/sysprof/sysprof-greeter.ui +index 35e790f5..f7ebbc29 100644 +--- a/src/sysprof/sysprof-greeter.ui ++++ b/src/sysprof/sysprof-greeter.ui +@@ -106,6 +106,41 @@ + + + ++ ++ ++ Unwind Stacks in User Space ++ Copy stack contents and registers for unwinding in user-space ++ ++ ++ ++ end ++ center ++ ++ ++ ++ ++ ++ Stack Size ++ The number of bytes to copy from the stack ++ stack_sizes ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Unwinding in user-space has considerable overhead but may help in situations where frame-pointers are unavailable. ++ 0 ++ 8 ++ ++ ++ + + + +@@ -664,4 +699,36 @@ + + + ++ ++ ++ ++ 8 KB ++ 8192 ++ ++ ++ ++ ++ 16 KB ++ 16384 ++ ++ ++ ++ ++ 24 KB ++ 24576 ++ ++ ++ ++ ++ 32 KB ++ 32768 ++ ++ ++ ++ ++ 64 KB ++ 65536 ++ ++ ++ + +diff --git a/src/sysprof/sysprof-recording-template.c b/src/sysprof/sysprof-recording-template.c +index 50f6c957..ee4d4f01 100644 +--- a/src/sysprof/sysprof-recording-template.c ++++ b/src/sysprof/sysprof-recording-template.c +@@ -22,6 +22,8 @@ + + #include "sysprof-recording-template.h" + ++#define DEFAULT_STACK_SIZE (4096*4) ++ + struct _SysprofRecordingTemplate + { + GObject parent_instance; +@@ -31,6 +33,8 @@ struct _SysprofRecordingTemplate + char *power_profile; + char **environ; + ++ guint stack_size; ++ + guint battery_charge : 1; + guint bundle_symbols : 1; + guint clear_environ : 1; +@@ -49,6 +53,7 @@ struct _SysprofRecordingTemplate + guint session_bus : 1; + guint system_bus : 1; + guint system_log : 1; ++ guint user_stacks : 1; + }; + + enum { +@@ -73,8 +78,10 @@ enum { + PROP_POWER_PROFILE, + PROP_SCHEDULER_DETAILS, + PROP_SESSION_BUS, ++ PROP_STACK_SIZE, + PROP_SYSTEM_BUS, + PROP_SYSTEM_LOG, ++ PROP_USER_STACKS, + N_PROPS + }; + +@@ -185,6 +192,10 @@ sysprof_recording_template_get_property (GObject *object, + g_value_set_boolean (value, self->session_bus); + break; + ++ case PROP_STACK_SIZE: ++ g_value_set_uint (value, self->stack_size); ++ break; ++ + case PROP_SYSTEM_BUS: + g_value_set_boolean (value, self->system_bus); + break; +@@ -193,6 +204,10 @@ sysprof_recording_template_get_property (GObject *object, + g_value_set_boolean (value, self->system_log); + break; + ++ case PROP_USER_STACKS: ++ g_value_set_boolean (value, self->user_stacks); ++ break; ++ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +@@ -289,6 +304,10 @@ sysprof_recording_template_set_property (GObject *object, + self->session_bus = g_value_get_boolean (value); + break; + ++ case PROP_STACK_SIZE: ++ self->stack_size = g_value_get_uint (value); ++ break; ++ + case PROP_SYSTEM_BUS: + self->system_bus = g_value_get_boolean (value); + break; +@@ -297,6 +316,10 @@ sysprof_recording_template_set_property (GObject *object, + self->system_log = g_value_get_boolean (value); + break; + ++ case PROP_USER_STACKS: ++ self->user_stacks = g_value_get_boolean (value); ++ break; ++ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +@@ -421,6 +444,16 @@ sysprof_recording_template_class_init (SysprofRecordingTemplateClass *klass) + TRUE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + ++ properties[PROP_USER_STACKS] = ++ g_param_spec_boolean ("user-stacks", NULL, NULL, ++ FALSE, ++ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ properties[PROP_STACK_SIZE] = ++ g_param_spec_uint ("stack-size", NULL, NULL, ++ 0, G_MAXUINT, DEFAULT_STACK_SIZE, ++ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ + g_object_class_install_properties (object_class, N_PROPS, properties); + } + +@@ -439,6 +472,7 @@ sysprof_recording_template_init (SysprofRecordingTemplate *self) + self->system_log = TRUE; + self->command_line = g_strdup (""); + self->cwd = g_strdup(""); ++ self->stack_size = DEFAULT_STACK_SIZE; + } + + SysprofRecordingTemplate * +@@ -619,7 +653,12 @@ sysprof_recording_template_apply (SysprofRecordingTemplate *self, + sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); + + if (self->native_stacks) +- sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); ++ { ++ if (self->user_stacks) ++ sysprof_profiler_add_instrument (profiler, sysprof_user_sampler_new (self->stack_size)); ++ else ++ sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); ++ } + + if (self->network_usage) + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); +diff --git a/src/sysprof/sysprof-stack-size.c b/src/sysprof/sysprof-stack-size.c +new file mode 100644 +index 00000000..d6652766 +--- /dev/null ++++ b/src/sysprof/sysprof-stack-size.c +@@ -0,0 +1,141 @@ ++/* ++ * sysprof-stack-size.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 "sysprof-stack-size.h" ++ ++struct _SysprofStackSize ++{ ++ GObject parent_instance; ++ char *label; ++ guint size; ++}; ++ ++enum { ++ PROP_0, ++ PROP_SIZE, ++ PROP_LABEL, ++ N_PROPS ++}; ++ ++G_DEFINE_FINAL_TYPE (SysprofStackSize, sysprof_stack_size, G_TYPE_OBJECT) ++ ++static GParamSpec *properties[N_PROPS]; ++ ++static void ++sysprof_stack_size_finalize (GObject *object) ++{ ++ SysprofStackSize *self = (SysprofStackSize *)object; ++ ++ g_clear_pointer (&self->label, g_free); ++ ++ G_OBJECT_CLASS (sysprof_stack_size_parent_class)->finalize (object); ++} ++ ++static void ++sysprof_stack_size_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ SysprofStackSize *self = SYSPROF_STACK_SIZE (object); ++ ++ switch (prop_id) ++ { ++ case PROP_SIZE: ++ g_value_set_uint (value, self->size); ++ break; ++ ++ case PROP_LABEL: ++ g_value_set_string (value, self->label); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++sysprof_stack_size_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ SysprofStackSize *self = SYSPROF_STACK_SIZE (object); ++ ++ switch (prop_id) ++ { ++ case PROP_SIZE: ++ self->size = g_value_get_uint (value); ++ break; ++ ++ case PROP_LABEL: ++ g_set_str (&self->label, g_value_get_string (value)); ++ break; ++ ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++sysprof_stack_size_class_init (SysprofStackSizeClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = sysprof_stack_size_finalize; ++ object_class->get_property = sysprof_stack_size_get_property; ++ object_class->set_property = sysprof_stack_size_set_property; ++ ++ properties[PROP_SIZE] = ++ g_param_spec_uint ("size", NULL, NULL, ++ 0, (4096*32), 0, ++ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ properties[PROP_LABEL] = ++ g_param_spec_string ("label", NULL, NULL, ++ NULL, ++ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); ++ ++ g_object_class_install_properties (object_class, N_PROPS, properties); ++} ++ ++static void ++sysprof_stack_size_init (SysprofStackSize *self) ++{ ++} ++ ++guint ++sysprof_stack_size_get_size (SysprofStackSize *self) ++{ ++ g_return_val_if_fail (SYSPROF_IS_STACK_SIZE (self), 0); ++ ++ return self->size; ++} ++ ++const char * ++sysprof_stack_size_get_label (SysprofStackSize *self) ++{ ++ g_return_val_if_fail (SYSPROF_IS_STACK_SIZE (self), NULL); ++ ++ return self->label; ++} +diff --git a/src/sysprof/sysprof-stack-size.h b/src/sysprof/sysprof-stack-size.h +new file mode 100644 +index 00000000..5dd7f9c2 +--- /dev/null ++++ b/src/sysprof/sysprof-stack-size.h +@@ -0,0 +1,35 @@ ++/* ++ * sysprof-stack-size.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 ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_STACK_SIZE (sysprof_stack_size_get_type()) ++ ++G_DECLARE_FINAL_TYPE (SysprofStackSize, sysprof_stack_size, SYSPROF, STACK_SIZE, GObject) ++ ++guint sysprof_stack_size_get_size (SysprofStackSize *self); ++const char *sysprof_stack_size_get_label (SysprofStackSize *self); ++ ++G_END_DECLS +-- +2.45.2 + diff --git a/0019-sysprof-default-stack-capturing-as-enabled.patch b/0019-sysprof-default-stack-capturing-as-enabled.patch new file mode 100644 index 0000000..a6934f0 --- /dev/null +++ b/0019-sysprof-default-stack-capturing-as-enabled.patch @@ -0,0 +1,37 @@ +From 99f807240e4be653a122b9e443cd38782962bc59 Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 6 Nov 2024 13:09:31 -0800 +Subject: [PATCH] sysprof: default stack-capturing as enabled + +This is for CentOS/RHEL only as other distributions have frame-pointers. +--- + src/sysprof/sysprof-recording-template.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/sysprof/sysprof-recording-template.c b/src/sysprof/sysprof-recording-template.c +index ee4d4f01..b3018763 100644 +--- a/src/sysprof/sysprof-recording-template.c ++++ b/src/sysprof/sysprof-recording-template.c +@@ -446,7 +446,8 @@ sysprof_recording_template_class_init (SysprofRecordingTemplateClass *klass) + + properties[PROP_USER_STACKS] = + g_param_spec_boolean ("user-stacks", NULL, NULL, +- FALSE, ++ /* Default to stack capturing in CentOS/RHEL */ ++ TRUE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_STACK_SIZE] = +@@ -473,6 +474,9 @@ sysprof_recording_template_init (SysprofRecordingTemplate *self) + self->command_line = g_strdup (""); + self->cwd = g_strdup(""); + self->stack_size = DEFAULT_STACK_SIZE; ++ ++ /* Default to stack capturing in CentOS/RHEL */ ++ self->user_stacks = TRUE; + } + + SysprofRecordingTemplate * +-- +2.45.2 + diff --git a/0020-live-unwinder-disable-test-in-build.patch b/0020-live-unwinder-disable-test-in-build.patch new file mode 100644 index 0000000..05da7f9 --- /dev/null +++ b/0020-live-unwinder-disable-test-in-build.patch @@ -0,0 +1,38 @@ +From 44c780aed1d688695c3f15c757e4e9f14cb4214a Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 6 Nov 2024 13:33:34 -0800 +Subject: [PATCH] live-unwinder: disable test in build + +It requires newer libdex features we don't need so best to avoid back +porting those too. +--- + meson.build | 2 +- + src/sysprof-live-unwinder/meson.build | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/meson.build b/meson.build +index 9c37ebc4..462782d9 100644 +--- a/meson.build ++++ b/meson.build +@@ -45,7 +45,7 @@ need_libsysprof = (need_gtk or + get_option('tools') or + get_option('tests')) + +-dex_req = '0.9' ++dex_req = '0.8' + glib_req = '2.76.0' + gtk_req = '4.15' + polkit_req = '0.105' +diff --git a/src/sysprof-live-unwinder/meson.build b/src/sysprof-live-unwinder/meson.build +index 8cef7106..af797490 100644 +--- a/src/sysprof-live-unwinder/meson.build ++++ b/src/sysprof-live-unwinder/meson.build +@@ -16,4 +16,4 @@ sysprof_live_unwinder = executable('sysprof-live-unwinder', sysprof_live_unwinde + install_dir: pkglibexecdir, + ) + +-subdir('tests') ++# subdir('tests') +-- +2.45.2 + diff --git a/0021-sysprof-user-sampler-implement-await-for-FDs.patch b/0021-sysprof-user-sampler-implement-await-for-FDs.patch new file mode 100644 index 0000000..effab50 --- /dev/null +++ b/0021-sysprof-user-sampler-implement-await-for-FDs.patch @@ -0,0 +1,227 @@ +From 781d438e5e3d4189d8a1970707983ccc6897175c Mon Sep 17 00:00:00 2001 +From: Christian Hergert +Date: Wed, 6 Nov 2024 15:09:13 -0800 +Subject: [PATCH] sysprof-user-sampler: implement await for FDs + +This allows us to not need libdex 0.9+ for use by the sampler. +--- + src/libsysprof/meson.build | 1 + + src/libsysprof/sysprof-fd-private.h | 39 ++++++++++++++++ + src/libsysprof/sysprof-fd.c | 67 +++++++++++++++++++++++++++ + src/libsysprof/sysprof-user-sampler.c | 33 +++++++++++-- + 4 files changed, 136 insertions(+), 4 deletions(-) + create mode 100644 src/libsysprof/sysprof-fd-private.h + create mode 100644 src/libsysprof/sysprof-fd.c + +diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build +index e49c3a37..2ae977ca 100644 +--- a/src/libsysprof/meson.build ++++ b/src/libsysprof/meson.build +@@ -144,6 +144,7 @@ libsysprof_private_sources = [ + 'sysprof-document-symbols.c', + 'sysprof-elf-loader.c', + 'sysprof-elf.c', ++ 'sysprof-fd.c', + 'sysprof-leak-detector.c', + 'sysprof-maps-parser.c', + 'sysprof-mount-device.c', +diff --git a/src/libsysprof/sysprof-fd-private.h b/src/libsysprof/sysprof-fd-private.h +new file mode 100644 +index 00000000..1d4dfabc +--- /dev/null ++++ b/src/libsysprof/sysprof-fd-private.h +@@ -0,0 +1,39 @@ ++/* sysprof-fd-private.h ++ * ++ * Copyright 2024 Christian Hergert ++ * ++ * This library is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation; either version 2.1 of the ++ * License, or (at your option) any later version. ++ * ++ * This library 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 ++ * Lesser 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: LGPL-2.1-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++G_BEGIN_DECLS ++ ++#define SYSPROF_TYPE_FD (sysprof_fd_get_type()) ++ ++typedef struct _SysprofFD SysprofFD; ++ ++GType sysprof_fd_get_type (void) G_GNUC_CONST; ++int sysprof_fd_peek (const SysprofFD *fd); ++int sysprof_fd_steal (SysprofFD *fd); ++SysprofFD *sysprof_fd_dup (const SysprofFD *fd); ++void sysprof_fd_free (SysprofFD *fd); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofFD, sysprof_fd_free) ++ ++G_END_DECLS +diff --git a/src/libsysprof/sysprof-fd.c b/src/libsysprof/sysprof-fd.c +new file mode 100644 +index 00000000..5e34f8d9 +--- /dev/null ++++ b/src/libsysprof/sysprof-fd.c +@@ -0,0 +1,67 @@ ++/* sysprof-fd.c ++ * ++ * Copyright 2024 Christian Hergert ++ * ++ * This library is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation; either version 2.1 of the ++ * License, or (at your option) any later version. ++ * ++ * This library 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 ++ * Lesser 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: LGPL-2.1-or-later ++ */ ++ ++#include "config.h" ++ ++#include ++ ++#include "sysprof-fd-private.h" ++ ++int ++sysprof_fd_peek (const SysprofFD *fd) ++{ ++ if (fd == NULL) ++ return -1; ++ ++ return *(int *)fd; ++} ++ ++int ++sysprof_fd_steal (SysprofFD *fd) ++{ ++ if (fd == NULL) ++ return -1; ++ ++ return g_steal_fd ((int *)fd); ++} ++ ++void ++sysprof_fd_free (SysprofFD *fd) ++{ ++ int real = sysprof_fd_steal (fd); ++ if (real != -1) ++ close (real); ++ g_free (fd); ++} ++ ++SysprofFD * ++sysprof_fd_dup (const SysprofFD *fd) ++{ ++ int real = sysprof_fd_peek (fd); ++ ++ if (real == -1) ++ return NULL; ++ ++ real = dup (real); ++ ++ return g_memdup2 (&real, sizeof real); ++} ++ ++G_DEFINE_BOXED_TYPE (SysprofFD, sysprof_fd, sysprof_fd_dup, sysprof_fd_free) +diff --git a/src/libsysprof/sysprof-user-sampler.c b/src/libsysprof/sysprof-user-sampler.c +index 0e3afeae..1ae3ea27 100644 +--- a/src/libsysprof/sysprof-user-sampler.c ++++ b/src/libsysprof/sysprof-user-sampler.c +@@ -32,6 +32,7 @@ + #include "sysprof-recording-private.h" + #include "sysprof-user-sampler.h" + #include "sysprof-muxer-source.h" ++#include "sysprof-fd-private.h" + + #include "ipc-unwinder.h" + +@@ -81,6 +82,30 @@ close_fd (gpointer data) + } + } + ++static void ++promise_resolve_fd (DexPromise *promise, ++ int fd) ++{ ++ GValue gvalue = {SYSPROF_TYPE_FD, {{.v_pointer = &fd}, {.v_int = 0}}}; ++ dex_promise_resolve (promise, &gvalue); ++} ++ ++static int ++await_fd (DexFuture *future, ++ GError **error) ++{ ++ SysprofFD *fd = dex_await_boxed (future, error); ++ int ret = -1; ++ ++ if (fd != NULL) ++ { ++ ret = sysprof_fd_steal (fd); ++ sysprof_fd_free (fd); ++ } ++ ++ return ret; ++} ++ + static void + sysprof_user_sampler_ioctl (SysprofUserSampler *self, + gboolean enable) +@@ -145,7 +170,7 @@ _perf_event_open_cb (GObject *object, + if (-1 == (fd = g_unix_fd_list_get (fd_list, handle, &error))) + goto failure; + +- dex_promise_resolve_fd (promise, g_steal_fd (&fd)); ++ promise_resolve_fd (promise, g_steal_fd (&fd)); + return; + } + +@@ -230,7 +255,7 @@ try_again: + _perf_event_open_cb, + dex_ref (promise)); + +- if (-1 == (perf_fd = dex_await_fd (dex_ref (promise), error))) ++ if (-1 == (perf_fd = await_fd (dex_ref (promise), error))) + { + g_clear_pointer (&options, g_variant_unref); + +@@ -270,7 +295,7 @@ call_unwind_cb (GObject *object, + + if (ipc_unwinder_call_unwind_finish (IPC_UNWINDER (object), &out_capture_fd, &out_fd_list, result, &error) && + -1 != (capture_fd = g_unix_fd_list_get (out_fd_list, g_variant_get_handle (out_capture_fd), &error))) +- dex_promise_resolve_fd (promise, g_steal_fd (&capture_fd)); ++ promise_resolve_fd (promise, g_steal_fd (&capture_fd)); + else + dex_promise_reject (promise, error); + } +@@ -402,7 +427,7 @@ sysprof_user_sampler_prepare_fiber (gpointer user_data) + call_unwind_cb, + dex_ref (promise)); + +- fd = dex_await_fd (dex_ref (promise), &error); ++ fd = await_fd (dex_ref (promise), &error); + + if (fd == -1) + { +-- +2.45.2 + diff --git a/sysprof.spec b/sysprof.spec index fe796a9..228f574 100644 --- a/sysprof.spec +++ b/sysprof.spec @@ -14,11 +14,34 @@ Summary: A system-wide Linux profiler License: GPL-2.0-or-later AND GPL-3.0-or-later AND CC-BY-SA-4.0 AND BSD-2-Clause-Patent URL: http://www.sysprof.com -Source0: https://download.gnome.org/sources/sysprof/46/sysprof-%{tarball_version}.tar.xz +Source0: https://download.gnome.org/sources/sysprof/47/sysprof-%{tarball_version}.tar.xz %if 0%{?bundled_libunwind} Source1: https://github.com/libunwind/libunwind/releases/download/v%{libunwind_version}/libunwind-%{libunwind_version}.tar.gz %endif +# Backports of debuginfod and sysprof-live-unwinder from GNOME 48 +Patch: 0001-sysprof-update-to-AdwSpinner.patch +Patch: 0002-libsysprof-elf-do-not-allow-setting-self-as-debug-li.patch +Patch: 0003-libsysprof-elf-do-not-generate-fallback-names.patch +Patch: 0004-sysprof-add-SysprofDocumentTask-abstraction.patch +Patch: 0005-libsysprof-add-setup-hooks-for-symbolizers.patch +Patch: 0006-libsysprof-hoist-fallback-symbol-creation.patch +Patch: 0007-libsysprof-add-debuginfod-symbolizer.patch +Patch: 0008-libsysprof-ensure-access-to-process-info.patch +Patch: 0009-libsysprof-fix-building-with-Ddebuginfod-auto.patch +Patch: 0010-libsysprof-return-NULL-instance-unless-debuginfod-wo.patch +Patch: 0011-build-always-build-debuginfod-symbolizer.patch +Patch: 0012-libsysprof-remove-unnecessary-address-calculation.patch +Patch: 0013-libsysprof-add-muxer-GSource.patch +Patch: 0014-libsysprof-add-support-for-stack-regs-options-in-att.patch +Patch: 0015-sysprofd-add-support-for-unwinding-without-frame-poi.patch +Patch: 0016-libsysprof-add-SysprofUserSampler-for-live-unwinding.patch +Patch: 0017-sysprof-cli-add-support-for-live-unwinding.patch +Patch: 0018-sysprof-add-UI-for-live-unwinding.patch +Patch: 0019-sysprof-default-stack-capturing-as-enabled.patch +Patch: 0020-live-unwinder-disable-test-in-build.patch +Patch: 0021-sysprof-user-sampler-implement-await-for-FDs.patch + BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: gettext @@ -36,6 +59,8 @@ BuildRequires: pkgconfig(libpanel-1) BuildRequires: pkgconfig(libsystemd) BuildRequires: pkgconfig(polkit-gobject-1) BuildRequires: pkgconfig(systemd) +BuildRequires: pkgconfig(libdw) +BuildRequires: pkgconfig(libdebuginfod) BuildRequires: /usr/bin/appstream-util BuildRequires: /usr/bin/desktop-file-validate @@ -110,6 +135,7 @@ developing applications that use %{name}. %setup -b 1 -n libunwind-%{libunwind_version} %endif %setup -n sysprof-%{tarball_version} +%autopatch -p1 %build @@ -171,6 +197,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %license COPYING %{_bindir}/sysprof-cli %{_libexecdir}/sysprofd +%{_libexecdir}/sysprof-live-unwinder %{_datadir}/dbus-1/system.d/org.gnome.Sysprof3.conf %{_datadir}/dbus-1/system-services/org.gnome.Sysprof3.service %{_datadir}/polkit-1/actions/org.gnome.sysprof3.policy