From 92b3b77dd20c26089b0f1b64e7f7d3a4c47a6783 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 25 May 2023 14:54:28 -0700 Subject: [PATCH] contrib: create static library for elfparser I want to move a bunch of this "contrib" style sources into their own area so we can statically link them but keep them separate from main sysprof code. --- contrib/elfparser/demangle.cpp | 40 ++ contrib/elfparser/demangle.h | 28 + contrib/elfparser/elfparser.c | 903 +++++++++++++++++++++++++++++ contrib/elfparser/elfparser.h | 68 +++ contrib/elfparser/meson.build | 19 + contrib/meson.build | 1 + meson.build | 1 + src/libsysprof-analyze/meson.build | 4 +- 8 files changed, 1061 insertions(+), 3 deletions(-) create mode 100644 contrib/elfparser/demangle.cpp create mode 100644 contrib/elfparser/demangle.h create mode 100644 contrib/elfparser/elfparser.c create mode 100644 contrib/elfparser/elfparser.h create mode 100644 contrib/elfparser/meson.build create mode 100644 contrib/meson.build diff --git a/contrib/elfparser/demangle.cpp b/contrib/elfparser/demangle.cpp new file mode 100644 index 00000000..1f941cd2 --- /dev/null +++ b/contrib/elfparser/demangle.cpp @@ -0,0 +1,40 @@ +/* demangle.cpp + * + * Copyright (C) 2016 Christian Hergert + * + * 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 . + */ + +#include +#include + +#include "demangle.h" + +gchar * +sysprof_cplus_demangle (const gchar *name) +{ + char *real_name; + gchar *ret; + int status; + + real_name = abi::__cxa_demangle (name, 0, 0, &status); + + if (real_name == NULL) + return NULL; + + ret = g_strdup (real_name); + free (real_name); + + return ret; +} diff --git a/contrib/elfparser/demangle.h b/contrib/elfparser/demangle.h new file mode 100644 index 00000000..26d28fdc --- /dev/null +++ b/contrib/elfparser/demangle.h @@ -0,0 +1,28 @@ +/* demangle.h + * + * Copyright 2016-2019 Christian Hergert + * + * 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 . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +gchar *sysprof_cplus_demangle (const gchar *name); + +G_END_DECLS diff --git a/contrib/elfparser/elfparser.c b/contrib/elfparser/elfparser.c new file mode 100644 index 00000000..2c6b638b --- /dev/null +++ b/contrib/elfparser/elfparser.c @@ -0,0 +1,903 @@ +/* Sysprof -- Sampling, systemwide CPU profiler + * Copyright 2006, 2007, 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 +#include +#ifdef __APPLE__ +# include +#else +# include +#endif +#include + +#include "demangle.h" +#include "elfparser.h" + +typedef struct Section Section; + +struct ElfSym +{ + gulong table; + gulong offset; + gulong address; +}; + +struct Section +{ + const gchar * name; + gsize offset; + gsize size; + gboolean allocated; + gulong load_address; + guint type; +}; + +struct ElfParser +{ + gboolean is_64; + const guchar * data; + gsize length; + + guint n_sections; + Section ** sections; + + guint n_symbols; + ElfSym * symbols; + gsize sym_strings; + + GMappedFile * file; + + char * filename; + + gboolean checked_build_id; + char * build_id; + + const Section * text_section; +}; + +/* FIXME: All of these should in principle do endian swapping, + * but sysprof never has to deal with binaries of a different + * endianness than sysprof itself + */ +#define GET_FIELD(parser, offset, struct_name, idx, field_name) \ + (((parser))->is_64? \ + ((Elf64_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name : \ + ((Elf32_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name) + +#define GET_UINT32(parser, offset) \ + *((uint32_t *)(gpointer)(parser->data + offset)) \ + +#define GET_SIZE(parser, struct_name) \ + (((parser)->is_64? \ + sizeof (Elf64_ ## struct_name) : \ + sizeof (Elf32_ ## struct_name))) + +#define MAKE_ELF_UINT_ACCESSOR(field_name) \ + static uint64_t field_name (ElfParser *parser) \ + { \ + return GET_FIELD (parser, 0, Ehdr, 0, field_name); \ + } + +MAKE_ELF_UINT_ACCESSOR (e_shoff) +MAKE_ELF_UINT_ACCESSOR (e_shnum) +MAKE_ELF_UINT_ACCESSOR (e_shstrndx) + +#define MAKE_SECTION_HEADER_ACCESSOR(field_name) \ + static uint64_t field_name (ElfParser *parser, int nth_section) \ + { \ + gsize offset = e_shoff (parser); \ + \ + return GET_FIELD (parser, offset, Shdr, nth_section, field_name); \ + } + +MAKE_SECTION_HEADER_ACCESSOR (sh_name); +MAKE_SECTION_HEADER_ACCESSOR (sh_type); +MAKE_SECTION_HEADER_ACCESSOR (sh_flags); +MAKE_SECTION_HEADER_ACCESSOR (sh_addr); +MAKE_SECTION_HEADER_ACCESSOR (sh_offset); +MAKE_SECTION_HEADER_ACCESSOR (sh_size); + +#define MAKE_SYMBOL_ACCESSOR(field_name) \ + static uint64_t field_name (ElfParser *parser, gulong offset, gulong nth) \ + { \ + return GET_FIELD (parser, offset, Sym, nth, field_name); \ + } + +MAKE_SYMBOL_ACCESSOR(st_name); +MAKE_SYMBOL_ACCESSOR(st_info); +MAKE_SYMBOL_ACCESSOR(st_value); +MAKE_SYMBOL_ACCESSOR(st_size); +MAKE_SYMBOL_ACCESSOR(st_shndx); + +static gboolean +in_container (void) +{ + static gboolean _in_container; + static gboolean initialized; + + if (!initialized) + { + /* Flatpak has /.flatpak-info + * Podman has /run/.containerenv + * + * Both have access to host files via /var/run/host. + */ + _in_container = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS) || + g_file_test ("/run/.containerenv", G_FILE_TEST_EXISTS); + + initialized = TRUE; + } + + return _in_container; +} + +static void +section_free (Section *section) +{ + g_free (section); +} + +static const Section * +find_section (ElfParser *parser, + const char *name, + guint type) +{ + guint i; + + for (i = 0; i < parser->n_sections; ++i) + { + Section *section = parser->sections[i]; + + if (strcmp (section->name, name) == 0 && section->type == type) + return section; + } + + return NULL; +} + +static gboolean +parse_elf_signature (const guchar *data, + gsize length, + gboolean *is_64, + gboolean *is_be) +{ + /* FIXME: this function should be able to return an error */ + if (length < EI_NIDENT) + { + /* FIXME set error */ + return FALSE; + } + + if (data[EI_CLASS] != ELFCLASS32 && + data[EI_CLASS] != ELFCLASS64) + { + /* FIXME set error */ + return FALSE; + } + + if (data[EI_DATA] != ELFDATA2LSB && + data[EI_DATA] != ELFDATA2MSB) + { + /* FIXME set error */ + return FALSE; + } + + if (is_64) + *is_64 = (data[EI_CLASS] == ELFCLASS64); + + if (is_be) + *is_be = (data[EI_DATA] == ELFDATA2MSB); + + return TRUE; +} + +ElfParser * +elf_parser_new_from_data (const guchar *data, + gsize length) +{ + ElfParser *parser; + gboolean is_64, is_big_endian; + int section_names_idx; + const guchar *section_names; + G_GNUC_UNUSED gsize section_headers; + guint i; + + if (!parse_elf_signature (data, length, &is_64, &is_big_endian)) + { + /* FIXME: set error */ + return NULL; + } + + parser = g_new0 (ElfParser, 1); + + parser->is_64 = is_64; + parser->data = data; + parser->length = length; + +#if 0 + g_print (" new parser : %p\n", parser); +#endif + + /* Read ELF header */ + + parser->n_sections = e_shnum (parser); + section_names_idx = e_shstrndx (parser); + section_headers = e_shoff (parser); + + /* Read section headers */ + parser->sections = g_new0 (Section *, parser->n_sections); + + section_names = parser->data + sh_offset (parser, section_names_idx); + + for (i = 0; i < parser->n_sections; ++i) + { + Section *section = g_new (Section, 1); + + section->name = (char *)(section_names + sh_name (parser, i)); + section->size = sh_size (parser, i); + section->offset = sh_offset (parser, i); + section->allocated = !!(sh_flags (parser, i) & SHF_ALLOC); + + if (section->allocated) + section->load_address = sh_addr (parser, i); + else + section->load_address = 0; + + section->type = sh_type (parser, i); + + parser->sections[i] = section; + } + + /* Cache the text section */ + parser->text_section = find_section (parser, ".text", SHT_PROGBITS); + if (!parser->text_section) + parser->text_section = find_section (parser, ".text", SHT_NOBITS); + + parser->filename = NULL; + parser->build_id = NULL; + + return parser; +} + +static GMappedFile * +open_mapped_file (const char *filename, + GError **error) +{ + GMappedFile *file = NULL; + char *alternate = NULL; + + if (in_container () && !g_str_has_prefix (filename, g_get_home_dir ())) + { + alternate = g_build_filename ("/var/run/host", filename, NULL); + file = g_mapped_file_new (alternate, FALSE, NULL); + g_free (alternate); + } + + /* Flatpaks with filesystem=host don't have Silverblue's /sysroot in /var/run/host, + * yet they have it in /, which means the original path might work. + */ + if (!file) + file = g_mapped_file_new (filename, FALSE, error); + + return file; +} + +ElfParser * +elf_parser_new_from_mmap (GMappedFile *file, + GError **error) +{ + const guchar *data; + gsize length; + ElfParser *parser; + + if (file == NULL) + return NULL; + + data = (guchar *)g_mapped_file_get_contents (file); + length = g_mapped_file_get_length (file); + parser = elf_parser_new_from_data (data, length); + + if (!parser) + { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + "Failed to load ELF from mmap region"); + g_mapped_file_unref (file); + return NULL; + } + + parser->filename = NULL; + parser->file = file; + + return parser; +} + +ElfParser * +elf_parser_new (const char *filename, + GError **error) +{ + GMappedFile *file; + const guchar *data; + gsize length; + ElfParser *parser; + + if (!(file = open_mapped_file (filename, error))) + return NULL; + +#if 0 + g_print ("elf parser new : %s\n", filename); +#endif + + data = (guchar *)g_mapped_file_get_contents (file); + length = g_mapped_file_get_length (file); + +#if 0 + g_print ("data %p: for %s\n", data, filename); +#endif + + parser = elf_parser_new_from_data (data, length); + +#if 0 + g_print ("Parser for %s: %p\n", filename, parser); +#endif + + if (!parser) + { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + "Failed to load ELF from file %s", + filename); + g_mapped_file_unref (file); + return NULL; + } + + parser->filename = g_strdup (filename); + + parser->file = file; + +#if 0 + g_print ("Elf file: %s (debug: %s)\n", + filename, elf_parser_get_debug_link (parser, NULL)); + + if (!parser->symbols) + g_print ("at this point %s has no symbols\n", filename); +#endif + + return parser; +} + +guint32 +elf_parser_get_crc32 (ElfParser *parser) +{ + static const unsigned long crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + const guchar *data; + gsize length; + gulong crc; + gsize i; + + data = parser->data; + length = parser->length; + + crc = 0xffffffff; + + madvise ((char *)data, length, MADV_SEQUENTIAL); + + for (i = 0; i < length; ++i) + crc = crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8); + + /* We just read the entire file into memory, but we only really + * need the symbol table, so swap the whole thing out. + * + * We could be more exact here, but it's only a few minor + * pagefaults. + */ + if (parser->file) + madvise ((char *)data, length, MADV_DONTNEED); + + return ~crc & 0xffffffff; +} + +void +elf_parser_free (ElfParser *parser) +{ + guint i; + + for (i = 0; i < parser->n_sections; ++i) + section_free (parser->sections[i]); + g_free (parser->sections); + + if (parser->file) + g_mapped_file_unref (parser->file); + + g_free (parser->symbols); + + if (parser->filename) + g_free (parser->filename); + + if (parser->build_id) + g_free (parser->build_id); + + g_free (parser); +} + +gchar * +elf_demangle (const char *name) +{ + gchar *demangled = sysprof_cplus_demangle (name); + + if (demangled) + return demangled; + else + return g_strdup (name); +} + +/* + * Looking up symbols + */ +static int +compare_sym (const void *a, const void *b) +{ + const ElfSym *sym_a = a; + const ElfSym *sym_b = b; + + if (sym_a->address < sym_b->address) + return -1; + else if (sym_a->address == sym_b->address) + return 0; + else + return 1; +} + +#if 0 +static void +dump_symbols (ElfParser *parser, ElfSym *syms, guint n_syms) +{ + int i; + + for (i = 0; i < n_syms; ++i) + { + ElfSym *s = &(syms[i]); + + g_print (" %s: %lx\n", elf_parser_get_sym_name (parser, s), s->address); + } +} +#endif + +static void +read_table (ElfParser *parser, + const Section *sym_table, + const Section *str_table) +{ + int sym_size = GET_SIZE (parser, Sym); + guint i, n_symbols; + +#if 0 + g_print ("elf: Reading table for %s\n", parser->filename? parser->filename : ""); +#endif + + parser->n_symbols = sym_table->size / sym_size; + parser->symbols = g_new (ElfSym, parser->n_symbols); + +#if 0 + g_print ("sym table offset: %d\n", sym_table->offset); +#endif + + n_symbols = 0; +#if 0 + g_print ("n syms: %d\n", parser->n_symbols); +#endif + for (i = 0; i < parser->n_symbols; ++i) + { + guint info; + gulong addr; + gulong shndx; + + info = st_info (parser, sym_table->offset, i); + addr = st_value (parser, sym_table->offset, i); + shndx = st_shndx (parser, sym_table->offset, i); + +#if 0 + g_print ("read symbol: %s (section: %d)\n", get_string_indirct (parser->parser, + parser->sym_format, "st_name", + str_table->offset), + shndx); +#endif + + if (addr != 0 && + shndx < parser->n_sections && + parser->sections[shndx] == parser->text_section && + (info & 0xf) == STT_FUNC && + ((info >> 4) == STB_GLOBAL || + (info >> 4) == STB_LOCAL || + (info >> 4) == STB_WEAK)) + { + parser->symbols[n_symbols].address = addr; + parser->symbols[n_symbols].table = sym_table->offset; + parser->symbols[n_symbols].offset = i; + + n_symbols++; + +#if 0 + g_print (" symbol: %s: %lx\n", + get_string_indirect (parser->parser, + parser->sym_format, "st_name", + str_table->offset), + addr - parser->text_section->load_address); + g_print (" sym %d in %p (info: %d:%d) (func:global %d:%d)\n", + addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL); +#endif + } + else if (addr != 0) + { +#if 0 + g_print (" rejecting %d in %p (info: %d:%d) (func:global %d:%d)\n", + addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL); +#endif + } + } + + parser->sym_strings = str_table->offset; + parser->n_symbols = n_symbols; + + /* Allocate space for at least one symbol, so that parser->symbols will be + * non-NULL. If it ends up being NULL, we will be parsing the file over and + * over. + */ + parser->symbols = g_renew (ElfSym, parser->symbols, parser->n_symbols + 1); + + qsort (parser->symbols, parser->n_symbols, sizeof (ElfSym), compare_sym); +} + +static void +read_symbols (ElfParser *parser) +{ + const Section *symtab = find_section (parser, ".symtab", SHT_SYMTAB); + const Section *strtab = find_section (parser, ".strtab", SHT_STRTAB); + const Section *dynsym = find_section (parser, ".dynsym", SHT_DYNSYM); + const Section *dynstr = find_section (parser, ".dynstr", SHT_STRTAB); + + if (symtab && strtab) + { +#if 0 + g_print ("reading symbol table of %s\n", parser->filename); +#endif + read_table (parser, symtab, strtab); + } + else if (dynsym && dynstr) + { +#if 0 + g_print ("reading dynamic symbol table of %s\n", parser->filename); +#endif + read_table (parser, dynsym, dynstr); + } + else + { + /* To make sure parser->symbols is non-NULL */ + parser->n_symbols = 0; + parser->symbols = g_new (ElfSym, 1); + } +} + +static ElfSym * +do_lookup (ElfSym *symbols, + gulong address, + int first, + int last) +{ + if (address >= symbols[last].address) + { + return &(symbols[last]); + } + else if (last - first < 3) + { + while (last >= first) + { + if (address >= symbols[last].address) + return &(symbols[last]); + + last--; + } + + return NULL; + } + else + { + int mid = (first + last) / 2; + + if (symbols[mid].address > address) + return do_lookup (symbols, address, first, mid); + else + return do_lookup (symbols, address, mid, last); + } +} + +/* Address should be given in 'offset into text segment' */ +const ElfSym * +elf_parser_lookup_symbol (ElfParser *parser, + gulong address) +{ + const ElfSym *result; + + if (!parser->symbols) + { +#if 0 + g_print ("reading symbols at %p\n", parser); +#endif + read_symbols (parser); + } + + if (parser->n_symbols == 0) + return NULL; + + if (!parser->text_section) + return NULL; + + address += parser->text_section->load_address; + +#if 0 + g_print ("elf: the address we are looking up is %p\n", address); +#endif + + result = do_lookup (parser->symbols, address, 0, parser->n_symbols - 1); + +#if 0 + if (result) + { + g_print (" elf: found %s at %lx\n", elf_parser_get_sym_name (parser, result), result->address); + } + else + { + g_print ("elf: not found\n"); + } +#endif + + if (result) + { + gulong size = st_size (parser, result->table, result->offset); + + if (size > 0 && result->address + size <= address) + { +#if 0 + g_print (" elf: ends at %lx, so rejecting\n", + result->address + size); +#endif + result = NULL; + } + } + + if (result) + { + /* Reject the symbols if the address is outside the text section */ + if (address > parser->text_section->load_address + parser->text_section->size) + result = NULL; + } + + return result; +} + +gulong +elf_parser_get_text_offset (ElfParser *parser) +{ + g_return_val_if_fail (parser != NULL, (gulong)-1); + + if (!parser->text_section) + return (gulong)-1; + + return parser->text_section->offset; +} + +static gchar * +make_hex_string (const guchar *data, int n_bytes) +{ + static const char hex_digits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + GString *string = g_string_new (NULL); + int i; + + for (i = 0; i < n_bytes; ++i) + { + char c = data[i]; + + g_string_append_c (string, hex_digits[(c & 0xf0) >> 4]); + g_string_append_c (string, hex_digits[(c & 0x0f)]); + } + + return g_string_free (string, FALSE); +} + +const gchar * +elf_parser_get_build_id (ElfParser *parser) +{ + if (!parser->checked_build_id) + { + const Section *build_id = + find_section (parser, ".note.gnu.build-id", SHT_NOTE); + guint64 name_size; + guint64 desc_size; + guint64 type; + const char *name; + guint64 offset; + + parser->checked_build_id = TRUE; + + if (!build_id) + return NULL; + + offset = build_id->offset; + + name_size = GET_FIELD (parser, offset, Nhdr, 0, n_namesz); + desc_size = GET_FIELD (parser, offset, Nhdr, 0, n_descsz); + type = GET_FIELD (parser, offset, Nhdr, 0, n_type); + + offset += GET_SIZE (parser, Nhdr); + + name = (char *)(parser->data + offset); + + if (strncmp (name, ELF_NOTE_GNU, name_size) != 0 || type != NT_GNU_BUILD_ID) + return NULL; + + offset += strlen (name); + + offset = (offset + 3) & (~0x3); + + parser->build_id = make_hex_string (parser->data + offset, desc_size); + } + + return parser->build_id; +} + +const char * +elf_parser_get_debug_link (ElfParser *parser, + guint32 *crc32) +{ + guint64 offset; + const Section *debug_link = find_section (parser, ".gnu_debuglink", + SHT_PROGBITS); + const gchar *result; + + if (!debug_link) + return NULL; + + offset = debug_link->offset; + + result = (char *)(parser->data + offset); + + if (crc32) + { + int len = strlen (result) + 1; + offset = (offset + len + 3) & ~0x3; + + *crc32 = GET_UINT32 (parser, offset); + } + + return result; +} + +static const guchar * +get_section (ElfParser *parser, + const char *name) +{ + const Section *section = find_section (parser, name, SHT_PROGBITS); + + if (section) + return parser->data + section->offset; + else + return NULL; +} + +const guchar * +elf_parser_get_eh_frame (ElfParser *parser) +{ + return get_section (parser, ".eh_frame"); +} + +const guchar * +elf_parser_get_debug_frame (ElfParser *parser) +{ + return get_section (parser, ".debug_frame"); +} + +const char * +elf_parser_get_sym_name (ElfParser *parser, + const ElfSym *sym) +{ + g_return_val_if_fail (parser != NULL, NULL); + + return (char *)(parser->data + parser->sym_strings + + st_name (parser, sym->table, sym->offset)); +} + +gboolean +elf_parser_owns_symbol (ElfParser *parser, + const ElfSym *sym) +{ + ElfSym *first, *last; + + if (!parser->n_symbols) + return FALSE; + + first = parser->symbols; + last = parser->symbols + parser->n_symbols - 1; + + return first <= sym && sym <= last; +} + +gulong +elf_parser_get_sym_address (ElfParser *parser, + const ElfSym *sym) +{ + return sym->address - parser->text_section->load_address; +} + +void +elf_parser_get_sym_address_range (ElfParser *parser, + const ElfSym *sym, + gulong *begin, + gulong *end) +{ + *begin = sym->address - parser->text_section->load_address; + *end = *begin + st_size (parser, sym->table, sym->offset); +} diff --git a/contrib/elfparser/elfparser.h b/contrib/elfparser/elfparser.h new file mode 100644 index 00000000..fd19e78d --- /dev/null +++ b/contrib/elfparser/elfparser.h @@ -0,0 +1,68 @@ +/* Sysprof -- Sampling, systemwide CPU profiler + * Copyright 2006, 2007, 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 + +#ifndef NT_GNU_BUILD_ID +#define NT_GNU_BUILD_ID 3 +#endif + +typedef struct ElfSym ElfSym; +typedef struct ElfParser ElfParser; + +ElfParser *elf_parser_new_from_data (const guchar *data, + gsize length); +ElfParser *elf_parser_new_from_mmap (GMappedFile *mapped_file, + GError **err); +ElfParser *elf_parser_new (const char *filename, + GError **err); +void elf_parser_free (ElfParser *parser); +const char *elf_parser_get_debug_link (ElfParser *parser, + guint32 *crc32); +const gchar *elf_parser_get_build_id (ElfParser *parser); +const guchar *elf_parser_get_eh_frame (ElfParser *parser); +const guchar *elf_parser_get_debug_frame (ElfParser *parser); +gulong elf_parser_get_text_offset (ElfParser *parser); + + +/* Lookup a symbol in the file. + * + * The symbol returned is const, so don't free it. It is valid until + * elf_parser_free() is called on the parser. + * + * The address should be given in "file coordinates". This means that + * if the file is mapped at address m and offset o, then an address a + * should be looked up as "a - (m - o)". (m - o) is where the start + * of the file would have been mapped, so a - (m - o) is the position + * in the file of a. + */ +const ElfSym *elf_parser_lookup_symbol (ElfParser *parser, + gulong address); +guint32 elf_parser_get_crc32 (ElfParser *parser); +const char *elf_parser_get_sym_name (ElfParser *parser, + const ElfSym *sym); +gulong elf_parser_get_sym_address (ElfParser *parser, + const ElfSym *sym); +gboolean elf_parser_owns_symbol (ElfParser *parser, + const ElfSym *sym); +char *elf_demangle (const char *name); +void elf_parser_get_sym_address_range (ElfParser *parser, + const ElfSym *sym, + gulong *begin, + gulong *end); + diff --git a/contrib/elfparser/meson.build b/contrib/elfparser/meson.build new file mode 100644 index 00000000..b3c75e6b --- /dev/null +++ b/contrib/elfparser/meson.build @@ -0,0 +1,19 @@ +libelfparser_sources = [ + 'demangle.cpp', + 'elfparser.c', +] + +libelfparser_deps = [ + dependency('glib-2.0', version: glib_req_version), +] + +libelfparser_static = static_library('elfparser', libelfparser_sources, + dependencies: libelfparser_deps, + gnu_symbol_visibility: 'hidden', +) + +libelfparser_static_dep = declare_dependency( + include_directories: include_directories('.'), + dependencies: libelfparser_deps, + link_with: libelfparser_static, +) diff --git a/contrib/meson.build b/contrib/meson.build new file mode 100644 index 00000000..691bcd0d --- /dev/null +++ b/contrib/meson.build @@ -0,0 +1 @@ +subdir('elfparser') diff --git a/meson.build b/meson.build index e9e22034..320bbe79 100644 --- a/meson.build +++ b/meson.build @@ -238,6 +238,7 @@ endif needs_service_access = get_option('libsysprof') or get_option('agent') install_service_files = needs_service_access or get_option('sysprofd') == 'bundled' +subdir('contrib') subdir('src') subdir('data') subdir('po') diff --git a/src/libsysprof-analyze/meson.build b/src/libsysprof-analyze/meson.build index d8d25a12..d126823e 100644 --- a/src/libsysprof-analyze/meson.build +++ b/src/libsysprof-analyze/meson.build @@ -33,9 +33,6 @@ libsysprof_analyze_public_sources = [ ] libsysprof_analyze_private_sources = [ - '../libsysprof/binfile.c', - '../libsysprof/demangle.cpp', - '../libsysprof/elfparser.c', 'sysprof-address-layout.c', 'sysprof-document-bitset-index.c', 'sysprof-document-symbols.c', @@ -89,6 +86,7 @@ libsysprof_analyze_deps = [ dependency('gio-2.0', version: glib_req_version), dependency('gtk4', version: gtk_req_version), + libelfparser_static_dep, libsysprof_capture_dep, ]