/* This file is part of odin, a memory profiler with fragmentation analysis. Copyright (C) 2007 Chris Wilson Based on: MemProf -- memory profiler and leak detector Copyright 1999, 2000, 2001, Red Hat, Inc. Copyright 2002, Kristian Rietveld Sysprof -- Sampling, systemwide CPU profiler Copyright 2004, 2005, 2006, 2007, Soeren Sandmann odin 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. odin 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 odin. If not, see / The GNU General Public License is contained in the file COPYING. */ /* Most interesting code in this file is lifted from bfdutils.c * and process.c from Memprof, */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include "binfile.h" #include "elfparser.h" #define DEBUGDIR "/usr/lib/debug" static const char * const vdso = "[vdso]"; static ino_t read_inode (const char *filename) { struct stat statbuf; if (strcmp (filename, vdso) == 0) return (ino_t)0; if (stat (filename, &statbuf) < 0) return (ino_t)-1; return statbuf.st_ino; } static ElfParser * separate_debug_file_exists (const char *name, guint32 crc, gboolean force) { ElfParser *parser; parser = elf_parser_new (name, NULL); if (parser == NULL) return NULL; if (elf_parser_get_crc32 (parser) != crc) { if (force) { g_warning ("debug crc mismatch for '%s' (forced load)", name); } else { elf_parser_free (parser); return NULL; } } return parser; } static ElfParser * get_debug_file (ElfParser *elf, const char *filename, char **new_name) { #define N_TRIES 4 const char *basename; char *dir; guint32 crc32; char *tries[N_TRIES]; int i; ElfParser *result; if (!elf) return NULL; basename = elf_parser_get_debug_link (elf, &crc32); if (basename == NULL) return NULL; dir = g_path_get_dirname (filename); tries[0] = g_build_filename (dir, basename, NULL); tries[1] = g_build_filename (dir, ".debug", basename, NULL); tries[2] = g_build_filename ("/usr" G_DIR_SEPARATOR_S "lib" G_DIR_SEPARATOR_S "debug", dir, basename, NULL); tries[3] = g_build_filename (DEBUGDIR, dir, basename, NULL); for (i = 0; i < N_TRIES; ++i) { result = separate_debug_file_exists (tries[i], crc32, FALSE); if (result) { if (new_name) *new_name = g_strdup (tries[i]); break; } } for (i = 1; i < N_TRIES; ++i) { result = separate_debug_file_exists (tries[i], crc32, TRUE); if (result) { if (new_name) *new_name = g_strdup (tries[i]); break; } } g_free (dir); for (i = 0; i < N_TRIES; ++i) g_free (tries[i]); return result; } static ElfParser * find_separate_debug_file (ElfParser *elf, const char *filename, char **real_filename) { ElfParser *debug, *ret = NULL; char *debug_name = NULL; char *fname; GHashTable *seen_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); fname = g_strdup (filename); do { if (g_hash_table_lookup (seen_names, fname)) { /* cycle detected, just return the original elf file itself */ break; } debug = get_debug_file (elf, fname, &debug_name); if (debug != NULL) { elf_parser_free (elf); elf = ret = debug; g_hash_table_insert (seen_names, fname, fname); fname = debug_name; } } while (debug); *real_filename = fname; g_hash_table_destroy (seen_names); return ret; } void bin_file_init (BinFile *bf, const char *filename) { bf->elf = NULL; bf->real_filename = NULL; bf->inode_check = FALSE; if (strcmp (filename, vdso) == 0) { bf->filename = (char *) vdso; #if 0 gsize length; const guint8 *vdso_bytes = NULL; vdso_bytes = get_vdso_bytes (&length); if (vdso_bytes != NULL) bf->elf = elf_parser_new_from_data (vdso_bytes, length); else bf->elf = NULL; #endif } else { bf->filename = g_strdup (filename); bf->elf = elf_parser_new (filename, NULL); } /* We need the text offset of the actual binary, not the * (potential) debug binary */ if (bf->elf != NULL) { bf->text_offset = elf_parser_get_text_offset (bf->elf); bf->elf = find_separate_debug_file (bf->elf, filename, &bf->real_filename); bf->inode = read_inode (filename); } } void bin_file_open (BinFile *bf) { if (bf->elf != NULL) return; if (bf->filename == vdso) { /* permanent */ } else if (bf->real_filename != NULL) { bf->elf = elf_parser_new (bf->real_filename, NULL); bf->inode = read_inode (bf->filename); } } void bin_file_close (BinFile *bf) { if (bf->elf == NULL) return; if (bf->filename == vdso) { elf_parser_free (bf->elf); bf->elf = NULL; } } void bin_file_fini (BinFile *bf) { if (bf->elf != NULL) elf_parser_free (bf->elf); g_free (bf->real_filename); if (bf->filename != vdso) g_free (bf->filename); } gboolean bin_file_lookup_symbol (BinFile *bf, gulong address, const char **function, const char **filename, const char **path, guint *line) { g_assert (bf->elf != NULL); return elf_parser_lookup_symbol (bf->elf, address - bf->text_offset, function, filename, path, line); } gboolean bin_file_check_inode (BinFile *bf, ino_t inode) { if (bf->inode == inode) return TRUE; if (bf->elf == NULL) return FALSE; if (! bf->inode_check) { g_print ("warning: Inode mismatch for %s (disk: %" G_GUINT64_FORMAT ", memory: %" G_GUINT64_FORMAT ")\n", bf->filename, (guint64) bf->inode, (guint64) inode); bf->inode_check = TRUE; } return FALSE; }