From 5a3ebeaa6f6a1d0ac772a54f746f29f52c07de52 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 26 Feb 2017 16:59:48 -0800 Subject: [PATCH] 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 file changed, 174 insertions(+), 4 deletions(-) diff --git a/lib/sp-proc-source.c b/lib/sp-proc-source.c index efe70932..a3e50247 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);