Blob Blame History Raw
From 574f2cf39e37ac6ecdb6d9b8b38626b4d6502822 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
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 <http://www.gnu.org/licenses/>.
  */
 
 #pragma once
 
 #include <glib/gi18n-lib.h>
 #include <sys/time.h>
 
 #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 <http://www.gnu.org/licenses/>.
  */
 
 
 #define _POSIX_C_SOURCE 200112L /* for fdopen() */
 
 #include "config.h"
 
 #include "core/display-private.h"
 #include "core/util-private.h"
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 #include <string.h>
+#include <sys/wait.h>
 
 #ifdef HAVE_SYS_PRCTL
 #include <sys/prctl.h>
 #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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+<policyconfig>
+  <action id="org.gnome.mutter.backtrace">
+    <message>Authentication is required to run mutter-backtrace</message>
+    <icon_name>dialog-password</icon_name>
+    <defaults>
+      <allow_any>auth_admin</allow_any>
+      <allow_inactive>auth_admin</allow_inactive>
+      <allow_active>auth_admin</allow_active>
+    </defaults>
+    <annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/mutter-backtrace</annotate>
+  </action>
+</policyconfig>
+
-- 
2.41.0