From 574f2cf39e37ac6ecdb6d9b8b38626b4d6502822 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 25 Sep 2023 22:22:08 +0200 Subject: [PATCH 1/3] util: Add way to print backtraces Now that mutter can use a realtime thread its very important that it doesn't stall for too long, since that can result in the kernel killing it. Ironically, a main reason mutter could stall is kernel bugs. When a stall happens, we need a way to see why. This commit adds a new function, "meta_print_backtrace" to print backtrace of the current process and the kernel (if possible). A future commit will use this new function. --- meson.build | 8 ++++ meson_options.txt | 6 +++ src/50-mutter.rules | 9 ++++ src/core/util-private.h | 2 + src/core/util.c | 57 ++++++++++++++++++++++++ src/meson.build | 27 +++++++++++ src/mutter-backtrace | 17 +++++++ src/org.gnome.mutter.backtrace.policy.in | 17 +++++++ 8 files changed, 143 insertions(+) create mode 100644 src/50-mutter.rules create mode 100755 src/mutter-backtrace create mode 100644 src/org.gnome.mutter.backtrace.policy.in diff --git a/meson.build b/meson.build index d08401b3c..5482dfb9e 100644 --- a/meson.build +++ b/meson.build @@ -270,60 +270,67 @@ if have_wayland or have_native_backend libdrm_dep = dependency('libdrm') endif have_egl_device = get_option('egl_device') have_wayland_eglstream = get_option('wayland_eglstream') if have_wayland_eglstream wayland_eglstream_protocols_dep = dependency('wayland-eglstream-protocols') dl_dep = cc.find_library('dl', required: true) if not have_wayland error('Wayland EGLStream support requires Wayland to be enabled') endif endif have_sm = get_option('sm') if have_sm sm_dep = dependency('sm') endif have_libwacom = get_option('libwacom') if have_libwacom libwacom_dep = dependency('libwacom', version: libwacom_req) endif have_pango_ft2 = get_option('pango_ft2') if have_pango_ft2 pangoft2_dep = dependency('pangoft2') endif +have_polkit = get_option('polkit') +if have_polkit + polkit_dep = dependency('polkit-gobject-1') + polkit_policy_dir = polkit_dep.get_variable('policydir') + polkit_action_dir = polkit_dep.get_variable('actiondir') +endif + have_startup_notification = get_option('startup_notification') if have_startup_notification if have_x11_client libstartup_notification_dep = dependency('libstartup-notification-1.0', version: libstartup_notification_req) else error('startup_notification requires X11 or Xwayland to be enabled') endif endif have_remote_desktop = get_option('remote_desktop') if have_remote_desktop libpipewire_dep = dependency('libpipewire-0.3', version: libpipewire_req) endif have_introspection = get_option('introspection') if have_introspection gobject_introspection_dep = dependency('gobject-introspection-1.0') introspection_args = [ '--quiet', '-U_GNU_SOURCE', ] endif have_documentation = get_option('docs') if have_documentation gidocgen_dep = dependency('gi-docgen', version: '>= 2021.1', fallback: ['gi-docgen', 'dummy_dep']) endif @@ -671,50 +678,51 @@ if have_documentation subdir('doc/reference') endif gnome.post_install( glib_compile_schemas: true, ) meson.add_dist_script('meson/check-version.py', meson.project_version(), 'NEWS') summary('prefix', prefix, section: 'Directories') summary('libexecdir', libexecdir, section: 'Directories') summary('pkgdatadir', pkgdatadir, section: 'Directories') summary('buildtype', get_option('buildtype'), section: 'Build Configuration') summary('debug', get_option('debug'), section: 'Build Configuration') summary('OpenGL', have_gl, section: 'Rendering APIs') summary('GLES2', have_gles2, section: 'Rendering APIs') summary('EGL', have_egl, section: 'Rendering APIs') summary('GLX', have_glx, section: 'Rendering APIs') summary('Wayland', have_wayland, section: 'Options') summary('Wayland EGLStream', have_wayland_eglstream, section: 'Options') summary('X11', have_x11, section: 'Options') summary('XWayland', have_xwayland, section: 'Options') summary('Native Backend', have_native_backend, section: 'Options') summary('EGL Device', have_egl_device, section: 'Options') summary('Remote desktop', have_remote_desktop, section: 'Options') summary('libgnome-desktop', have_gnome_desktop, section: 'Options') summary('libdisplay-info', have_libdisplay_info, section: 'Options') +summary('Polkit enhanced backtrace support', have_polkit, section: 'Options') summary('Sound player', have_sound_player, section: 'Options') summary('gudev', have_libgudev, section: 'Options') summary('Wacom', have_libwacom, section: 'Options') summary('SM', have_sm, section: 'Options') summary('Startup notification', have_startup_notification, section: 'Options') summary('Introspection', have_introspection, section: 'Options') summary('Documentation', have_documentation, section: 'Options') summary('Profiler', have_profiler, section: 'Options') summary('Xwayland initfd', have_xwayland_initfd, section: 'Options') summary('Xwayland listenfd', have_xwayland_listenfd, section: 'Options') summary('Xwayland terminate delay', have_xwayland_terminate_delay, section: 'Options') summary('Xwayland byte-swapped clients', have_xwayland_byte_swapped_clients, section: 'Options') summary('Enabled', have_tests, section: 'Tests') summary('Core tests', have_core_tests, section: 'Tests') summary('Cogl tests', have_cogl_tests, section: 'Tests') summary('Clutter tests', have_clutter_tests, section: 'Tests') summary('KVM tests', get_option('kvm_tests'), section: 'Tests') summary('Installed tests', have_installed_tests, section: 'Tests') summary('Coverage', get_option('b_coverage'), section: 'Tests') diff --git a/meson_options.txt b/meson_options.txt index b5d215b24..b6b18ebdf 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -84,60 +84,66 @@ option('wayland_eglstream', option('udev', type: 'boolean', value: true, description: 'Enable udev support when using the X11 backend' ) option('udev_dir', type: 'string', value: '', description: 'Absolute path of the udev base directory' ) option('libwacom', type: 'boolean', value: true, description: 'Enable libwacom support' ) option('sound_player', type: 'boolean', value: true, description: 'Enable sound player support using libcanberra', ) option('pango_ft2', type: 'boolean', value: true, description: 'Enable PangoFt2 support' ) +option('polkit', + type: 'boolean', + value: true, + description: 'Enable Polkit enhanced backtrace support' +) + option('startup_notification', type: 'boolean', value: true, description: 'Enable startup notification support' ) option('sm', type: 'boolean', value: true, description: 'Enable X11 session management support' ) option('introspection', type: 'boolean', value: true, description: 'Enable GObject introspection' ) option('docs', type: 'boolean', value: false, description: 'Enable gi-docgen documentation' ) option('cogl_tests', type: 'boolean', value: true, description: 'Enable cogl tests' ) diff --git a/src/50-mutter.rules b/src/50-mutter.rules new file mode 100644 index 000000000..d14735fe6 --- /dev/null +++ b/src/50-mutter.rules @@ -0,0 +1,9 @@ +polkit.addRule(function(action, subject) { + if (subject.isInGroup("wheel") && + subject.active && + subject.local && + action.id == "org.gnome.mutter.backtrace") { + return polkit.Result.YES; + } +}); + diff --git a/src/core/util-private.h b/src/core/util-private.h index 18ae68de7..32798ca4e 100644 --- a/src/core/util-private.h +++ b/src/core/util-private.h @@ -13,46 +13,48 @@ * * 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 . */ #pragma once #include #include #include "meta/util.h" #include "meta/common.h" /* META_EXPORT_TEST should be used to export symbols that are exported only * for testability purposes */ #define META_EXPORT_TEST META_EXPORT void meta_set_verbose (gboolean setting); void meta_set_debugging (gboolean setting); void meta_set_is_wayland_compositor (gboolean setting); char * meta_generate_random_id (GRand *rand, int length); +void meta_print_backtrace (void); + void meta_init_debug_utils (void); static inline int64_t meta_timeval_to_microseconds (const struct timeval *tv) { return ((int64_t) tv->tv_sec) * G_USEC_PER_SEC + tv->tv_usec; } #define META_POINT_IN_RECT(xcoord, ycoord, rect) \ ((xcoord) >= (rect).x && \ (xcoord) < ((rect).x + (rect).width) && \ (ycoord) >= (rect).y && \ (ycoord) < ((rect).y + (rect).height)) #define META_CONTAINER_OF(ptr, type, member) \ (type *) ((uint8_t *) (ptr) - G_STRUCT_OFFSET (type, member)) diff --git a/src/core/util.c b/src/core/util.c index 05a0dea39..dc547713c 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -1,107 +1,110 @@ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2005 Elijah Newren * * 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 2 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 . */ #define _POSIX_C_SOURCE 200112L /* for fdopen() */ #include "config.h" #include "core/display-private.h" #include "core/util-private.h" +#include #include #include #include #include #include +#include #ifdef HAVE_SYS_PRCTL #include #endif #include "clutter/clutter-mutter.h" #include "cogl/cogl.h" #include "meta/common.h" #include "meta/main.h" static const GDebugKey meta_debug_keys[] = { { "focus", META_DEBUG_FOCUS }, { "workarea", META_DEBUG_WORKAREA }, { "stack", META_DEBUG_STACK }, { "sm", META_DEBUG_SM }, { "events", META_DEBUG_EVENTS }, { "window-state", META_DEBUG_WINDOW_STATE }, { "window-ops", META_DEBUG_WINDOW_OPS }, { "geometry", META_DEBUG_GEOMETRY }, { "placement", META_DEBUG_PLACEMENT }, { "ping", META_DEBUG_PING }, { "keybindings", META_DEBUG_KEYBINDINGS }, { "sync", META_DEBUG_SYNC }, { "startup", META_DEBUG_STARTUP }, { "prefs", META_DEBUG_PREFS }, { "groups", META_DEBUG_GROUPS }, { "resizing", META_DEBUG_RESIZING }, { "shapes", META_DEBUG_SHAPES }, { "edge-resistance", META_DEBUG_EDGE_RESISTANCE }, { "dbus", META_DEBUG_DBUS }, { "input", META_DEBUG_INPUT }, { "wayland", META_DEBUG_WAYLAND }, { "kms", META_DEBUG_KMS }, { "screen-cast", META_DEBUG_SCREEN_CAST }, { "remote-desktop", META_DEBUG_REMOTE_DESKTOP }, { "backend", META_DEBUG_BACKEND }, { "render", META_DEBUG_RENDER }, { "color", META_DEBUG_COLOR }, { "input-events", META_DEBUG_INPUT_EVENTS }, { "eis", META_DEBUG_EIS }, }; static gint verbose_topics = 0; static gboolean is_wayland_compositor = FALSE; static int debug_paint_flags = 0; static GLogLevelFlags mutter_log_level = G_LOG_LEVEL_MESSAGE; +static char *backtrace_command = NULL; #ifdef WITH_VERBOSE_MODE static FILE* logfile = NULL; static void ensure_logfile (void) { if (logfile == NULL && g_getenv ("MUTTER_USE_LOGFILE")) { char *filename = NULL; char *tmpl; int fd; GError *err; tmpl = g_strdup_printf ("mutter-%d-debug-log-XXXXXX", (int) getpid ()); err = NULL; fd = g_file_open_tmp (tmpl, &filename, &err); g_free (tmpl); if (err != NULL) { meta_warning ("Failed to open debug log: %s", err->message); g_error_free (err); return; @@ -186,60 +189,70 @@ meta_remove_verbose_topic (MetaDebugTopic topic) verbose_topics = 0; else verbose_topics &= ~topic; } void meta_init_debug_utils (void) { const char *debug_env; #ifdef HAVE_SYS_PRCTL prctl (PR_SET_DUMPABLE, 1); #endif if (g_getenv ("MUTTER_VERBOSE")) meta_set_verbose (TRUE); debug_env = g_getenv ("MUTTER_DEBUG"); if (debug_env) { MetaDebugTopic topics; topics = g_parse_debug_string (debug_env, meta_debug_keys, G_N_ELEMENTS (meta_debug_keys)); meta_add_verbose_topic (topics); } if (g_test_initialized ()) mutter_log_level = G_LOG_LEVEL_DEBUG; + + /* If pkexec works we'll get a kernel backtrace too, + * but it may not work if the user isn't in the wheel group. + * If it fails, we fall back to running unprivileged to at + * least get a backtrace of the process. + */ + if (backtrace_command == NULL) + backtrace_command = g_strdup_printf ("pkexec %1$s %2$d || %1$s %2$d", + MUTTER_LIBEXECDIR "/mutter-backtrace", + (int) getpid ()); } gboolean meta_is_wayland_compositor (void) { return is_wayland_compositor; } void meta_set_is_wayland_compositor (gboolean value) { is_wayland_compositor = value; } char * meta_g_utf8_strndup (const gchar *src, gsize n) { const gchar *s = src; while (n && *s) { s = g_utf8_next_char (s); n--; } return g_strndup (src, s - src); } static int utf8_fputs (const char *str, @@ -513,60 +526,104 @@ MetaLocaleDirection meta_get_locale_direction (void) { switch (clutter_get_text_direction ()) { case CLUTTER_TEXT_DIRECTION_LTR: return META_LOCALE_DIRECTION_LTR; case CLUTTER_TEXT_DIRECTION_RTL: return META_LOCALE_DIRECTION_RTL; default: g_assert_not_reached (); return 0; } } char * meta_generate_random_id (GRand *rand, int length) { char *id; int i; /* Generate a random string of printable ASCII characters. */ id = g_new0 (char, length + 1); for (i = 0; i < length; i++) id[i] = (char) g_rand_int_range (rand, 32, 127); return id; } +static gboolean +unix_signal_safe_run_command (const char *command) +{ + pid_t pid; + int status; + int ret; + + pid = fork (); + if (pid == -1) + return FALSE; + + if (pid == 0) + { + int stdin_fd; + + const char *argv[] = { + "/bin/sh", + "-c", + command, + NULL + }; + + stdin_fd = open ("/dev/null", O_RDWR); + dup2 (stdin_fd, STDIN_FILENO); + + execve (argv[0], (char **) argv, environ); + + _exit (1); + } + + do + { + ret = waitpid (pid, &status, 0); + } + while (ret != 0 && errno == EINTR); + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; +} + +void +meta_print_backtrace (void) +{ + unix_signal_safe_run_command (backtrace_command); +} void meta_add_clutter_debug_flags (ClutterDebugFlag debug_flags, ClutterDrawDebugFlag draw_flags, ClutterPickDebugFlag pick_flags) { clutter_add_debug_flags (debug_flags, draw_flags, pick_flags); } void meta_remove_clutter_debug_flags (ClutterDebugFlag debug_flags, ClutterDrawDebugFlag draw_flags, ClutterPickDebugFlag pick_flags) { clutter_remove_debug_flags (debug_flags, draw_flags, pick_flags); } /** * meta_get_clutter_debug_flags: * @debug_flags: (out) (optional): return location for debug flags * @draw_flags: (out) (optional): return location for draw debug flags * @pick_flags: (out) (optional): return location for pick debug flags */ void meta_get_clutter_debug_flags (ClutterDebugFlag *debug_flags, ClutterDrawDebugFlag *draw_flags, ClutterPickDebugFlag *pick_flags) { clutter_get_debug_flags (debug_flags, draw_flags, pick_flags); } diff --git a/src/meson.build b/src/meson.build index ca2ef166c..4aabb6a14 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1197,60 +1197,87 @@ libmutter = shared_library(libmutter_name, ], install_rpath: pkglibdir, install_dir: libdir, install: true, ) libmutter_dep = declare_dependency( link_with: libmutter, include_directories: mutter_includes, sources: mutter_built_headers, dependencies: [ libmutter_cogl_dep, libmutter_clutter_dep, mutter_deps, ], ) mutter = executable('mutter', sources: [ files('core/mutter.c'), ], include_directories: mutter_includes, c_args: [ mutter_c_args, '-DG_LOG_DOMAIN="mutter"', ], dependencies: [libmutter_dep], install_dir: bindir, install: true, ) + +install_data( + 'mutter-backtrace', + install_dir : libexecdir, + install_mode : 'rwxr-xr-x' +) + +if have_polkit + backtrace_polkit_policy = configure_file( + input : 'org.gnome.mutter.backtrace.policy.in', + output : 'org.gnome.mutter.backtrace.policy', + configuration : {'LIBEXECDIR': libexecdir} + ) + + install_data( + backtrace_polkit_policy, + install_dir : polkit_policy_dir, + install_mode : 'rw-r--r--' + ) + + install_data( + '50-mutter.rules', + install_dir : polkit_action_dir, + install_mode : 'rw-r--r--' + ) +endif + if have_x11 executable('mutter-restart-helper', sources: [ files('core/restart-helper.c'), ], include_directories: [ top_includepath, ], c_args: [ mutter_c_args, '-DG_LOG_DOMAIN="mutter-restart-helper"', ], dependencies: [ x11_dep, xcomposite_dep, ], install_dir: libexecdir, install: true, ) endif if have_introspection mutter_introspected_sources = [] foreach source : mutter_sources if source.endswith('.c') mutter_introspected_sources += source endif endforeach libmutter_gir = gnome.generate_gir(libmutter, diff --git a/src/mutter-backtrace b/src/mutter-backtrace new file mode 100755 index 000000000..20c9d4e75 --- /dev/null +++ b/src/mutter-backtrace @@ -0,0 +1,17 @@ +#!/bin/bash + +[ $# -ne 1 ] && exit 1 + +gdb_command=(gdb -p "$1" -ex 'thread apply all bt full' -batch) + +if [ -n $PKEXEC_UID -a $(id -u) -eq 0 ]; then + cd /proc/$1/task + for thread in *; do + echo "Thread ${thread}:" + cat $thread/stack + done + pkexec --user $(id -u -n $PKEXEC_UID) "${gdb_command[@]}" + exit 0 +fi + +${gdb_command[@]} diff --git a/src/org.gnome.mutter.backtrace.policy.in b/src/org.gnome.mutter.backtrace.policy.in new file mode 100644 index 000000000..39985acb9 --- /dev/null +++ b/src/org.gnome.mutter.backtrace.policy.in @@ -0,0 +1,17 @@ + + + + + Authentication is required to run mutter-backtrace + dialog-password + + auth_admin + auth_admin + auth_admin + + @LIBEXECDIR@/mutter-backtrace + + + -- 2.41.0