diff --git a/src/libsysprof/sysprof-symbol.c b/src/libsysprof/sysprof-symbol.c index e4ee4649f117ac4c3fba839bb379bfd7376f864a..5659c846ecedba8bed3b0bf68d0a34cf5344875e 100644 --- a/src/libsysprof/sysprof-symbol.c +++ b/src/libsysprof/sysprof-symbol.c @@ -30,6 +30,7 @@ enum { PROP_BINARY_NICK, PROP_BINARY_PATH, PROP_KIND, + PROP_ADDRESS, PROP_NAME, PROP_TOOLTIP_TEXT, N_PROPS @@ -74,6 +75,10 @@ sysprof_symbol_get_property (GObject *object, case PROP_KIND: g_value_set_enum (value, sysprof_symbol_get_kind (self)); break; + + case PROP_ADDRESS: + g_value_set_uint64 (value, sysprof_symbol_get_address (self)); + break; case PROP_TOOLTIP_TEXT: g_value_take_string (value, sysprof_symbol_dup_tooltip_text (self)); @@ -107,6 +112,11 @@ sysprof_symbol_class_init (SysprofSymbolClass *klass) NULL, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + properties [PROP_ADDRESS] = + g_param_spec_uint64 ("address", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + properties [PROP_TOOLTIP_TEXT] = g_param_spec_string ("tooltip-text", NULL, NULL, NULL, @@ -151,6 +161,15 @@ sysprof_symbol_get_binary_path (SysprofSymbol *self) return self->binary_path; } +SysprofAddress +sysprof_symbol_get_address (SysprofSymbol *self) +{ + + g_return_val_if_fail (SYSPROF_IS_SYMBOL (self), 0); + + return self->begin_address; +} + static gboolean sniff_maybe_kernel_process (const char *str) { diff --git a/src/libsysprof/sysprof-symbol.h b/src/libsysprof/sysprof-symbol.h index 7d70cc88029906392b3b684456619d2794839151..de673bb4fd39069c25dd05a078c06e62866b3c85 100644 --- a/src/libsysprof/sysprof-symbol.h +++ b/src/libsysprof/sysprof-symbol.h @@ -55,6 +55,8 @@ const char *sysprof_symbol_get_binary_path (SysprofSymbol *self); SYSPROF_AVAILABLE_IN_ALL SysprofSymbolKind sysprof_symbol_get_kind (SysprofSymbol *self); SYSPROF_AVAILABLE_IN_ALL +SysprofAddress sysprof_symbol_get_address (SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL guint sysprof_symbol_hash (const SysprofSymbol *self); SYSPROF_AVAILABLE_IN_ALL gboolean sysprof_symbol_equal (const SysprofSymbol *a, diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build index 038f58f93aa949398b7459ca2f0f6bf53aa4be4d..8121b8686c0406b11acc98ac6e8aaea4b8ebd057 100644 --- a/src/sysprof/meson.build +++ b/src/sysprof/meson.build @@ -79,6 +79,7 @@ sysprof_sources = [ 'sysprof-xy-layer.c', 'sysprof-xy-series-item.c', 'sysprof-xy-series.c', + 'sysprof-source-view.c', ] sysprof_resources = gnome.compile_resources('sysprof-resources', 'sysprof.gresource.xml', @@ -91,6 +92,8 @@ sysprof_deps = [ dependency('gtk4', version: gtk_req_version), dependency('libadwaita-1', version: '>= 1.6.0'), dependency('libpanel-1', version: '>= 1.4'), + dependency('libdw'), + dependency('gtksourceview-5'), libtree_static_dep, libsysprof_static_dep, diff --git a/src/sysprof/sysprof-callgraph-view-private.h b/src/sysprof/sysprof-callgraph-view-private.h index 8cc040548c30e7e3ecbc06de462deb4e59a0faaf..31474ef921c8e06faf81f957446967fec0dc08c8 100644 --- a/src/sysprof/sysprof-callgraph-view-private.h +++ b/src/sysprof/sysprof-callgraph-view-private.h @@ -23,11 +23,11 @@ #include #include "sysprof-callgraph-view.h" +#include "sysprof-source-view.h" G_BEGIN_DECLS #define SYSPROF_CALLGRAPH_VIEW_GET_CLASS(instance) G_TYPE_INSTANCE_GET_CLASS(instance, SYSPROF_TYPE_CALLGRAPH_VIEW, SysprofCallgraphViewClass) - struct _SysprofCallgraphView { GtkWidget parent_instance; @@ -47,6 +47,7 @@ struct _SysprofCallgraphView GtkScrolledWindow *scrolled_window; GtkWidget *paned; GtkStringFilter *function_filter; + SysprofSourceView *source_view; GCancellable *cancellable; diff --git a/src/sysprof/sysprof-callgraph-view.c b/src/sysprof/sysprof-callgraph-view.c index f83bf9c5563fd2d6aa80ef6cf864f4148f9900b7..44b0021cf079c401e4fd5cc7c004600174716683 100644 --- a/src/sysprof/sysprof-callgraph-view.c +++ b/src/sysprof/sysprof-callgraph-view.c @@ -21,11 +21,14 @@ #include "config.h" #include +#include +#include #include "sysprof-resources.h" #include "sysprof-callgraph-view-private.h" #include "sysprof-category-icon.h" +#include "sysprof-source-view.h" #include "sysprof-symbol-label-private.h" #include "sysprof-tree-expander.h" @@ -237,6 +240,57 @@ descendants_selection_changed_cb (SysprofCallgraphView *self, } } +static gboolean +show_source_location_libdwfl (SysprofCallgraphView *self, + const char *elf_path, + Dwarf_Addr offset) +{ + static const Dwfl_Callbacks callbacks = { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + + Dwfl *dwfl = dwfl_begin (&callbacks); + Dwfl_Module *module; + Dwfl_Line *line; + gboolean success = FALSE; + + if (!dwfl) return FALSE; + + if (!dwfl_report_offline (dwfl, "", elf_path, -1)) { + dwfl_end (dwfl); + return FALSE; + } + + dwfl_report_end (dwfl, NULL, NULL); + module = dwfl_addrmodule (dwfl, offset); + + if (!module) { + dwfl_end (dwfl); + return FALSE; + } + + line = dwfl_getsrc (dwfl, offset); + if (line) { + const char *filename = dwfl_lineinfo (line, NULL, NULL, NULL, NULL, NULL); + int lineno, colno; + + if (dwfl_lineinfo (line, NULL, &lineno, &colno, NULL, NULL)) { + g_debug ("Source location: File: %s, Line: %d, Column: %d", + filename ? filename : "", lineno, colno); + + if (filename && sysprof_source_view_load_file (self->source_view, filename)) { + sysprof_source_view_highlight_line (self->source_view, lineno); + success = TRUE; + } + } + } + + dwfl_end (dwfl); + return success; +} + + static void functions_selection_changed_cb (SysprofCallgraphView *self, guint position, @@ -252,11 +306,22 @@ functions_selection_changed_cb (SysprofCallgraphView *self, { SysprofCallgraphSymbol *sym = SYSPROF_CALLGRAPH_SYMBOL (object); SysprofSymbol *symbol = sysprof_callgraph_symbol_get_symbol (sym); + SysprofAddress symbol_address = sysprof_symbol_get_address (symbol); + const char *symbol_binary_path = sysprof_symbol_get_binary_path (symbol); g_autoptr(GtkSortListModel) callers_sort_model = NULL; g_autoptr(GtkSingleSelection) callers_selection = NULL; g_autoptr(GListModel) callers = sysprof_callgraph_list_callers (self->callgraph, symbol); GtkSorter *column_sorter; + /* + * If we have a symbol address and binary path, we can try to + * show the source location for this symbol. If we fail to, + * then we just hide the source view. + */ + if (!show_source_location_libdwfl (self, symbol_binary_path, (Dwarf_Addr)symbol_address)) + gtk_widget_set_visible (GTK_WIDGET (self->source_view), FALSE); + else + gtk_widget_set_visible (GTK_WIDGET (self->source_view), TRUE); column_sorter = gtk_column_view_get_sorter (self->callers_column_view); callers_sort_model = gtk_sort_list_model_new (g_object_ref (callers), g_object_ref (column_sorter)); @@ -294,6 +359,7 @@ functions_selection_changed_cb (SysprofCallgraphView *self, } else { + gtk_widget_set_visible (GTK_WIDGET (self->source_view), FALSE); gtk_column_view_set_model (self->callers_column_view, NULL); } } @@ -376,6 +442,8 @@ sysprof_callgraph_view_dispose (GObject *object) g_clear_object (&self->utility_summary); g_clear_object (&self->utility_traceables); + g_clear_pointer (&self->source_view, g_free); + G_OBJECT_CLASS (sysprof_callgraph_view_parent_class)->dispose (object); } @@ -600,6 +668,8 @@ sysprof_callgraph_view_init (SysprofCallgraphView *self) { self->categorize_frames = TRUE; + self->source_view = SYSPROF_SOURCE_VIEW (sysprof_source_view_new ()); + self->traceables_signals = g_signal_group_new (G_TYPE_LIST_MODEL); g_signal_connect_object (self->traceables_signals, "bind", @@ -613,6 +683,10 @@ sysprof_callgraph_view_init (SysprofCallgraphView *self) G_CONNECT_SWAPPED); gtk_widget_init_template (GTK_WIDGET (self)); + + if (self->paned) + panel_paned_append (PANEL_PANED (self->paned), GTK_WIDGET (self->source_view)); + } static int diff --git a/src/sysprof/sysprof-source-view-private.h b/src/sysprof/sysprof-source-view-private.h new file mode 100644 index 0000000000000000000000000000000000000000..1f1d46b3298120f6a9208bee69b0cb19534cc2af --- /dev/null +++ b/src/sysprof/sysprof-source-view-private.h @@ -0,0 +1,42 @@ +/* sysprof-source-view-private.h + * + * Copyright 2025 Varun R Mallya + * + * 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 + +struct _SysprofSourceView +{ + GtkWidget parent_instance; + GtkScrolledWindow *scrolled_window; + GtkSourceView *source_view; + GtkSourceBuffer *source_buffer; + GtkHeaderBar *header_bar; + GtkButton *close_button; + gchar *current_file; + gint current_line; + GtkSourceLanguageManager *lang_manager; + GtkSourceStyleSchemeManager *style_manager; +}; + +G_END_DECLS diff --git a/src/sysprof/sysprof-source-view.c b/src/sysprof/sysprof-source-view.c new file mode 100644 index 0000000000000000000000000000000000000000..9da05b922166f2a8d45ed05cc6edf83f5a122a29 --- /dev/null +++ b/src/sysprof/sysprof-source-view.c @@ -0,0 +1,220 @@ +/* sysprof-source-view.c + * + * Copyright 2025 Varun R Mallya + * + * 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-source-view.h" +#include "glib.h" +#include "sysprof-source-view-private.h" + +G_DEFINE_FINAL_TYPE (SysprofSourceView, sysprof_source_view, GTK_TYPE_WIDGET) + +static void +close_button_clicked_cb (SysprofSourceView *self, + GtkButton *button) +{ + g_assert (SYSPROF_IS_SOURCE_VIEW (self)); + + gtk_widget_set_visible (GTK_WIDGET (self->source_view), FALSE); +} + +static void +sysprof_source_view_dispose (GObject *object) +{ + SysprofSourceView *self = (SysprofSourceView *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_SOURCE_VIEW); + + g_clear_pointer (&self->current_file, g_free); + + G_OBJECT_CLASS (sysprof_source_view_parent_class)->dispose (object); +} + +static void +sysprof_source_view_class_init (SysprofSourceViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_source_view_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-source-view.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_css_name (widget_class, "sourceview"); + + gtk_widget_class_bind_template_child (widget_class, SysprofSourceView, scrolled_window); + gtk_widget_class_bind_template_child (widget_class, SysprofSourceView, source_view); + gtk_widget_class_bind_template_child (widget_class, SysprofSourceView, source_buffer); + gtk_widget_class_bind_template_child (widget_class, SysprofSourceView, header_bar); + gtk_widget_class_bind_template_child (widget_class, SysprofSourceView, close_button); + + gtk_widget_class_bind_template_callback (widget_class, close_button_clicked_cb); +} + +static void +sysprof_source_view_init (SysprofSourceView *self) +{ + GtkSourceStyleScheme *scheme; + self->current_line = -1; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->lang_manager = gtk_source_language_manager_get_default (); + self->style_manager = gtk_source_style_scheme_manager_get_default (); + + scheme = gtk_source_style_scheme_manager_get_scheme (self->style_manager, "Adwaita-dark"); + if (scheme) + gtk_source_buffer_set_style_scheme (self->source_buffer, scheme); + + gtk_source_view_set_show_line_numbers (self->source_view, TRUE); + gtk_source_view_set_highlight_current_line (self->source_view, TRUE); + gtk_source_view_set_show_right_margin (self->source_view, TRUE); + gtk_source_view_set_right_margin_position (self->source_view, 80); + gtk_source_view_set_tab_width (self->source_view, 4); + gtk_source_view_set_auto_indent (self->source_view, TRUE); + gtk_text_view_set_monospace (GTK_TEXT_VIEW (self->source_view), TRUE); + gtk_text_view_set_editable (GTK_TEXT_VIEW (self->source_view), FALSE); + + gtk_widget_set_visible (GTK_WIDGET (self), FALSE); +} + +SysprofSourceView * +sysprof_source_view_new (void) +{ + return g_object_new (SYSPROF_TYPE_SOURCE_VIEW, NULL); +} + +gboolean +sysprof_source_view_load_file (SysprofSourceView *self, + const char *filename) +{ + GError *error = NULL; + gchar *contents = NULL; + gsize length; + GtkSourceLanguage *language; + const gchar *basename; + + g_return_val_if_fail (SYSPROF_IS_SOURCE_VIEW (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + if (self->current_file && g_strcmp0 (self->current_file, filename) == 0) + return TRUE; + + if (!g_file_get_contents (filename, &contents, &length, &error)) + { + g_warning ("Could not read file %s: %s", filename, error->message); + g_error_free (error); + return FALSE; + } + + language = gtk_source_language_manager_guess_language (self->lang_manager, + filename, NULL); + + if (language) + gtk_source_buffer_set_language (self->source_buffer, language); + + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (self->source_buffer), contents, -1); + + g_free (self->current_file); + self->current_file = g_strdup (filename); + self->current_line = -1; + + basename = strrchr (filename, '/'); + gtk_header_bar_set_title_widget (self->header_bar, + gtk_label_new (basename ? basename + 1 : filename)); + + gtk_widget_set_visible (GTK_WIDGET (self), TRUE); + + g_free (contents); + return TRUE; +} + +void +sysprof_source_view_highlight_line (SysprofSourceView *self, + gint line_number) +{ + GtkTextIter start, end; + GtkTextBuffer *buffer; + GtkTextTagTable *tag_table; + gint line_count; + GtkTextTag *highlight_tag; + + g_return_if_fail (SYSPROF_IS_SOURCE_VIEW (self)); + g_return_if_fail (line_number > 0); + + if (self->current_line == line_number) + return; + + buffer = GTK_TEXT_BUFFER (self->source_buffer); + tag_table = gtk_text_buffer_get_tag_table (buffer); + + /* Validate line number */ + line_count = gtk_text_buffer_get_line_count (buffer); + if (line_number > line_count) + { + g_warning ("Line number %d exceeds file line count %d", line_number, line_count); + return; + } + + /* Get iterators for the line (convert to 0-based indexing) */ + gtk_text_buffer_get_iter_at_line (buffer, &start, line_number - 1); + gtk_text_buffer_get_iter_at_line (buffer, &end, line_number - 1); + gtk_text_iter_forward_to_line_end (&end); + + highlight_tag = gtk_text_tag_table_lookup (tag_table, "current-line"); + if (highlight_tag) + { + gtk_text_buffer_apply_tag (buffer, highlight_tag, &start, &end); + } + else + { + highlight_tag = gtk_text_buffer_create_tag (buffer, + "current-line", + "background", "#404040", + "foreground", "#ffffff", + NULL); + gtk_text_buffer_apply_tag (buffer, highlight_tag, &start, &end); + } + + /* Scroll to the highlighted line */ + gtk_text_buffer_place_cursor (buffer, &start); + gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (self->source_view), &start, + 0.0, TRUE, 0.0, 0.5); + + self->current_line = line_number; +} + +void +sysprof_source_view_clear (SysprofSourceView *self) +{ + g_return_if_fail (SYSPROF_IS_SOURCE_VIEW (self)); + + /* Clear the buffer content */ + gtk_text_buffer_set_text (GTK_TEXT_BUFFER (self->source_buffer), "", -1); + + /* Clear state */ + g_clear_pointer (&self->current_file, g_free); + self->current_line = -1; + + /* Hide the widget */ + gtk_widget_set_visible (GTK_WIDGET (self), FALSE); +} diff --git a/src/sysprof/sysprof-source-view.h b/src/sysprof/sysprof-source-view.h new file mode 100644 index 0000000000000000000000000000000000000000..901127b78110cce2abbf264d8501a876c02e04d7 --- /dev/null +++ b/src/sysprof/sysprof-source-view.h @@ -0,0 +1,39 @@ +/* sysprof-source-view.h + * + * Copyright 2025 Varun R Mallya + * + * 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_SOURCE_VIEW (sysprof_source_view_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSourceView, sysprof_source_view, SYSPROF, SOURCE_VIEW, GtkWidget) + +SysprofSourceView *sysprof_source_view_new (void); + +gboolean sysprof_source_view_load_file (SysprofSourceView *self, + const char *filename); +void sysprof_source_view_highlight_line (SysprofSourceView *self, + gint line_number); +void sysprof_source_view_clear (SysprofSourceView *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-source-view.ui b/src/sysprof/sysprof-source-view.ui new file mode 100644 index 0000000000000000000000000000000000000000..ddc0760be02c092fd1eb225251798ac41db02811 --- /dev/null +++ b/src/sysprof/sysprof-source-view.ui @@ -0,0 +1,39 @@ + + + + + + + diff --git a/src/sysprof/sysprof.gresource.xml b/src/sysprof/sysprof.gresource.xml index e2474bbe943ad32400f3a7e7a655cd9b61396daa..4117147523ebf013f5da61bdf3b105aaf3ce75fe 100644 --- a/src/sysprof/sysprof.gresource.xml +++ b/src/sysprof/sysprof.gresource.xml @@ -53,6 +53,7 @@ sysprof-samples-section.ui sysprof-session-filters-widget.ui sysprof-sidebar.ui + sysprof-source-view.ui sysprof-storage-section.ui sysprof-task-row.ui sysprof-time-scrubber.ui