mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
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.
518 lines
13 KiB
C
518 lines
13 KiB
C
/* sp-proc-source.c
|
|
*
|
|
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Sysprof -- Sampling, systemwide CPU profiler
|
|
* Copyright 2004, Red Hat, Inc.
|
|
* Copyright 2004, 2005, Soeren Sandmann
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "sp-proc-source.h"
|
|
|
|
struct _SpProcSource
|
|
{
|
|
GObject parent_instance;
|
|
SpCaptureWriter *writer;
|
|
GArray *pids;
|
|
};
|
|
|
|
static void source_iface_init (SpSourceInterface *iface);
|
|
static gchar **proc_readlines (const gchar *format,
|
|
...)
|
|
G_GNUC_PRINTF (1, 2);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (SpProcSource, sp_proc_source, G_TYPE_OBJECT, 0,
|
|
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
|
|
|
static gchar **
|
|
proc_readlines (const gchar *format,
|
|
...)
|
|
{
|
|
gchar **ret = NULL;
|
|
gchar *filename = NULL;
|
|
gchar *contents = NULL;
|
|
va_list args;
|
|
gsize len;
|
|
|
|
g_assert (format != NULL);
|
|
|
|
va_start (args, format);
|
|
filename = g_strdup_vprintf (format, args);
|
|
va_end (args);
|
|
|
|
if (g_file_get_contents (filename, &contents, &len, NULL))
|
|
ret = g_strsplit (contents, "\n", 0);
|
|
|
|
g_free (contents);
|
|
g_free (filename);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gchar *
|
|
sp_proc_source_get_command_line (GPid pid,
|
|
gboolean *is_kernel)
|
|
{
|
|
gchar *ret;
|
|
gchar **lines;
|
|
|
|
|
|
if (is_kernel)
|
|
*is_kernel = FALSE;
|
|
|
|
/*
|
|
* Get the full command line from /proc/pid/cmdline.
|
|
*/
|
|
if (NULL != (lines = proc_readlines ("/proc/%d/cmdline", pid)))
|
|
{
|
|
if (lines [0] && lines [0][0])
|
|
{
|
|
ret = g_strdup (lines [0]);
|
|
g_strfreev (lines);
|
|
return ret;
|
|
}
|
|
|
|
g_strfreev (lines);
|
|
}
|
|
|
|
/*
|
|
* We are guessing this is a kernel process based on cmdline being null.
|
|
*/
|
|
if (is_kernel)
|
|
*is_kernel = TRUE;
|
|
|
|
/*
|
|
* Check the first line of /proc/pid/status for Name: foo
|
|
*/
|
|
if (NULL != (lines = proc_readlines ("/proc/%d/status", pid)))
|
|
{
|
|
if (lines [0] && g_str_has_prefix (lines [0], "Name:"))
|
|
{
|
|
ret = g_strstrip (g_strdup (lines [0] + 5));
|
|
g_strfreev (lines);
|
|
return ret;
|
|
}
|
|
|
|
g_strfreev (lines);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_populate_process (SpProcSource *self,
|
|
GPid pid)
|
|
{
|
|
gchar *cmdline;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
g_assert (pid > 0);
|
|
|
|
if (NULL != (cmdline = sp_proc_source_get_command_line (pid, NULL)))
|
|
{
|
|
sp_capture_writer_add_process (self->writer,
|
|
SP_CAPTURE_CURRENT_TIME,
|
|
-1,
|
|
pid,
|
|
cmdline);
|
|
g_free (cmdline);
|
|
}
|
|
}
|
|
|
|
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,
|
|
GStrv mounts)
|
|
{
|
|
g_auto(GStrv) lines = NULL;
|
|
g_auto(GStrv) mountinfo = NULL;
|
|
guint i;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
g_assert (pid > 0);
|
|
|
|
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;
|
|
gulong inode;
|
|
gint r;
|
|
|
|
r = sscanf (lines [i],
|
|
"%lx-%lx %*15s %lx %*x:%*x %lu %255s",
|
|
&start, &end, &offset, &inode, file);
|
|
|
|
file [sizeof file - 1] = '\0';
|
|
|
|
if (r != 5)
|
|
continue;
|
|
|
|
if (strcmp ("[vdso]", file) == 0)
|
|
{
|
|
/*
|
|
* Søren Sandmann Pedersen says:
|
|
*
|
|
* For the vdso, the kernel reports 'offset' as the
|
|
* the same as the mapping addres. This doesn't make
|
|
* any sense to me, so we just zero it here. There
|
|
* is code in binfile.c (read_inode) that returns 0
|
|
* for [vdso].
|
|
*/
|
|
offset = 0;
|
|
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,
|
|
pid,
|
|
start,
|
|
end,
|
|
offset,
|
|
inode,
|
|
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;
|
|
|
|
for (i = 0; i < self->pids->len; i++)
|
|
{
|
|
GPid pid = g_array_index (self->pids, GPid, i);
|
|
|
|
sp_proc_source_populate_process (self, pid);
|
|
sp_proc_source_populate_maps (self, pid, mounts);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (NULL == (dir = g_dir_open ("/proc", 0, NULL)))
|
|
return;
|
|
|
|
while (NULL != (name = g_dir_read_name (dir)))
|
|
{
|
|
GPid pid;
|
|
char *end;
|
|
|
|
pid = strtol (name, &end, 10);
|
|
if (pid <= 0 || *end != '\0')
|
|
continue;
|
|
|
|
sp_proc_source_populate_process (self, pid);
|
|
sp_proc_source_populate_maps (self, pid, mounts);
|
|
}
|
|
|
|
g_dir_close (dir);
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_start (SpSource *source)
|
|
{
|
|
SpProcSource *self = (SpProcSource *)source;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
g_assert (self->writer != NULL);
|
|
|
|
sp_proc_source_populate (self);
|
|
sp_source_emit_finished (source);
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_stop (SpSource *source)
|
|
{
|
|
SpProcSource *self = (SpProcSource *)source;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
|
|
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_set_writer (SpSource *source,
|
|
SpCaptureWriter *writer)
|
|
{
|
|
SpProcSource *self = (SpProcSource *)source;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
g_assert (writer != NULL);
|
|
|
|
self->writer = sp_capture_writer_ref (writer);
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_add_pid (SpSource *source,
|
|
GPid pid)
|
|
{
|
|
SpProcSource *self = (SpProcSource *)source;
|
|
guint i;
|
|
|
|
g_assert (SP_IS_PROC_SOURCE (self));
|
|
g_assert (pid > -1);
|
|
|
|
for (i = 0; i < self->pids->len; i++)
|
|
{
|
|
GPid ele = g_array_index (self->pids, GPid, i);
|
|
|
|
if (ele == pid)
|
|
return;
|
|
}
|
|
|
|
g_array_append_val (self->pids, pid);
|
|
}
|
|
|
|
static void
|
|
source_iface_init (SpSourceInterface *iface)
|
|
{
|
|
iface->set_writer = sp_proc_source_set_writer;
|
|
iface->start = sp_proc_source_start;
|
|
iface->stop = sp_proc_source_stop;
|
|
iface->add_pid = sp_proc_source_add_pid;
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_finalize (GObject *object)
|
|
{
|
|
SpProcSource *self = (SpProcSource *)object;
|
|
|
|
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
|
g_clear_pointer (&self->pids, g_array_unref);
|
|
|
|
G_OBJECT_CLASS (sp_proc_source_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_class_init (SpProcSourceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = sp_proc_source_finalize;
|
|
}
|
|
|
|
static void
|
|
sp_proc_source_init (SpProcSource *self)
|
|
{
|
|
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
|
}
|
|
|
|
SpSource *
|
|
sp_proc_source_new (void)
|
|
{
|
|
return g_object_new (SP_TYPE_PROC_SOURCE, NULL);
|
|
}
|