[sysprof] proc: translate paths found in alternate mount namespaces



commit 5a3ebeaa6f6a1d0ac772a54f746f29f52c07de52
Author: Christian Hergert <chergert redhat com>
Date:   Sun Feb 26 16:59:48 2017 -0800

    proc: translate paths found in alternate mount namespaces
    
    If we come across a map that is in another namespace, we might need to
    translate it to the filesystem/path inside that mount namespace. Otherwise,
    we won't be able to accurately decode ELF symbols due to loading the
    incorrect binary/debug files.

 lib/sp-proc-source.c |  178 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 174 insertions(+), 4 deletions(-)
---
diff --git a/lib/sp-proc-source.c b/lib/sp-proc-source.c
index efe7093..a3e5024 100644
--- a/lib/sp-proc-source.c
+++ b/lib/sp-proc-source.c
@@ -151,11 +151,154 @@ sp_proc_source_populate_process (SpProcSource *self,
     }
 }
 
+static gboolean
+strv_at_least_len (GStrv strv,
+                   guint len)
+{
+  for (guint i = 0; i < len; i++)
+    {
+      if (strv[i] == NULL)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gchar *
+find_mount (GStrv        mounts,
+            const gchar *mount)
+{
+  gsize len = strlen (mount);
+
+  for (guint i = 0; mounts[i]; i++)
+    {
+      const gchar *endptr;
+      const gchar *begin;
+
+      if (!g_str_has_prefix (mounts[i], mount))
+        continue;
+
+      if (mounts[i][len] != ' ')
+        continue;
+
+      begin = &mounts[i][len + 1];
+      endptr = strchr (begin, ' ');
+      if (endptr == NULL)
+        continue;
+
+      return g_strndup (begin, endptr - begin);
+    }
+
+  return NULL;
+}
+
+/**
+ * sp_proc_source_translate_path:
+ * @file: the path to the file inside target mount namespace
+ * @mountinfo: mount info to locate translated path
+ * @out_file: (out): location for the translated path
+ * @out_file_len: length of @out_file
+ *
+ * This function will use @mountinfo to locate the longest common prefix
+ * to determine which mount contains @file. That will be used to translate
+ * the path pointed to by @file into the host namespace.
+ *
+ * The result is stored in @out_file and will always be NULL terminated.
+ */
+static void
+sp_proc_source_translate_path (const gchar *file,
+                               GStrv        mountinfo,
+                               GStrv        mounts,
+                               gchar       *out_file,
+                               gsize        out_file_len)
+{
+  g_autofree gchar *closest_host = NULL;
+  g_autofree gchar *closest_guest = NULL;
+  g_autofree gchar *closest_mount = NULL;
+  gsize closest_len = 0;
+
+  g_assert (file != NULL);
+  g_assert (g_str_has_prefix (file, "/newroot/"));
+  g_assert (mountinfo != NULL);
+  g_assert (out_file != NULL);
+
+  if (!g_str_has_prefix (file, "/newroot/"))
+    goto failure;
+
+  file += strlen ("/newroot");
+
+  for (guint i = 0; mountinfo[i] != NULL; i++)
+    {
+      g_auto(GStrv) parts = g_strsplit (mountinfo[i], " ", 11);
+      const gchar *host;
+      const gchar *guest;
+      const gchar *mount;
+
+      /*
+       * Not ideal to do the string split here, but it is much easier
+       * to just do that until we get this right, and then improve
+       * things later when a strok()/etc parser.
+       */
+
+      if (!strv_at_least_len (parts, 10))
+        continue;
+
+      host = parts[3];
+      guest = parts[4];
+      mount = parts[9];
+
+      if (g_str_has_prefix (file, guest))
+        {
+          gsize len = strlen (guest);
+
+          if (len > closest_len && (file[len] == '\0' || file[len] == '/'))
+            {
+              g_free (closest_host);
+              g_free (closest_guest);
+              g_free (closest_mount);
+
+              closest_guest = g_strdup (guest);
+              closest_host = g_strdup (host);
+              closest_mount = g_strdup (mount);
+
+              closest_len = len;
+            }
+        }
+    }
+
+  if (closest_len > 0)
+    {
+      /*
+       * The translated path is relative to the mount. So we need to add that
+       * prefix to this as well, based on matching it from the closest_mount.
+       */
+      g_autofree gchar *mount = NULL;
+
+      mount = find_mount (mounts, closest_mount);
+
+      if (mount != NULL)
+        {
+          g_autofree gchar *path = NULL;
+
+          path = g_build_filename (mount, closest_host, file + strlen (closest_guest), NULL);
+          g_strlcpy (out_file, path, out_file_len);
+
+          return;
+        }
+    }
+
+failure:
+  /* Fallback to just copying the source */
+  g_strlcpy (out_file, file, out_file_len);
+}
+
 static void
 sp_proc_source_populate_maps (SpProcSource *self,
-                              GPid          pid)
+                              GPid          pid,
+                              GStrv         mounts)
 {
   g_auto(GStrv) lines = NULL;
+  g_auto(GStrv) mountinfo = NULL;
   guint i;
 
   g_assert (SP_IS_PROC_SOURCE (self));
@@ -164,9 +307,14 @@ sp_proc_source_populate_maps (SpProcSource *self,
   if (NULL == (lines = proc_readlines ("/proc/%d/maps", pid)))
     return;
 
+  if (NULL == (mountinfo = proc_readlines ("/proc/%d/mountinfo", pid)))
+    return;
+
   for (i = 0; lines [i] != NULL; i++)
     {
       gchar file[256];
+      gchar translated[256];
+      const gchar *fileptr = file;
       gulong start;
       gulong end;
       gulong offset;
@@ -197,6 +345,24 @@ sp_proc_source_populate_maps (SpProcSource *self,
           inode = 0;
         }
 
+      if (g_str_has_prefix (file, "/newroot/"))
+        {
+          /*
+           * If this file starts with /newroot/, then it is in a different
+           * mount-namespace from our profiler process. This means that we need
+           * to translate the filename to the real path on disk inside our
+           * (hopefully the default) mount-namespace. To do this, we have to
+           * look at /proc/$pid/mountinfo to locate the longest-common-prefix
+           * for the path.
+           */
+          sp_proc_source_translate_path (file,
+                                         mountinfo,
+                                         mounts,
+                                         translated,
+                                         sizeof translated);
+          fileptr = translated;
+        }
+
       sp_capture_writer_add_map (self->writer,
                                  SP_CAPTURE_CURRENT_TIME,
                                  -1,
@@ -205,18 +371,22 @@ sp_proc_source_populate_maps (SpProcSource *self,
                                  end,
                                  offset,
                                  inode,
-                                 file);
+                                 fileptr);
     }
 }
 
 static void
 sp_proc_source_populate (SpProcSource *self)
 {
+  g_auto(GStrv) mounts = NULL;
   const gchar *name;
   GDir *dir;
 
   g_assert (SP_IS_PROC_SOURCE (self));
 
+  if (NULL == (mounts = proc_readlines ("/proc/mounts")))
+    return;
+
   if (self->pids->len > 0)
     {
       guint i;
@@ -226,7 +396,7 @@ sp_proc_source_populate (SpProcSource *self)
           GPid pid = g_array_index (self->pids, GPid, i);
 
           sp_proc_source_populate_process (self, pid);
-          sp_proc_source_populate_maps (self, pid);
+          sp_proc_source_populate_maps (self, pid, mounts);
         }
 
       return;
@@ -245,7 +415,7 @@ sp_proc_source_populate (SpProcSource *self)
         continue;
 
       sp_proc_source_populate_process (self, pid);
-      sp_proc_source_populate_maps (self, pid);
+      sp_proc_source_populate_maps (self, pid, mounts);
     }
 
   g_dir_close (dir);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]