diff options
Diffstat (limited to 'elf')
-rw-r--r-- | elf/Makefile | 3 | ||||
-rw-r--r-- | elf/dl-load.c | 7 | ||||
-rw-r--r-- | elf/dl-profile.c | 267 | ||||
-rw-r--r-- | elf/dl-reloc.c | 3 | ||||
-rw-r--r-- | elf/dl-runtime.c | 71 | ||||
-rw-r--r-- | elf/dl-support.c | 4 | ||||
-rw-r--r-- | elf/do-rel.h | 6 | ||||
-rw-r--r-- | elf/dynamic-link.h | 13 | ||||
-rw-r--r-- | elf/link.h | 13 | ||||
-rw-r--r-- | elf/rtld.c | 32 |
10 files changed, 403 insertions, 16 deletions
diff --git a/elf/Makefile b/elf/Makefile index e44711471b..904099cbeb 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -27,7 +27,8 @@ routines = $(dl-routines) dl-open dl-close dl-symbol dl-support \ # The core dynamic linking functions are in libc for the static and # profiled libraries. dl-routines = $(addprefix dl-,load cache lookup object reloc deps \ - runtime error init fini debug misc) + runtime error init fini debug misc \ + profile) # But they are absent from the shared libc, because that code is in ld.so. elide-routines.so = $(dl-routines) dl-support enbl-secure diff --git a/elf/dl-load.c b/elf/dl-load.c index 1301e73996..f7c2c53f7e 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -193,7 +193,10 @@ fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep, "cannot create cache for search path"); dirp->dirnamelen = len; - dirp->dirstatus = unknown; + /* We have to make sure all the relative directories are never + ignored. The current directory might change and all our + saved information would be void. */ + dirp->dirstatus = cp[0] != '/' ? existing : unknown; /* Add the name of the machine dependent directory if a machine is defined. */ @@ -212,7 +215,7 @@ fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep, tmp[len + _dl_platformlen + 1] = '\0'; dirp->dirname = tmp; - dirp->machdirstatus = unknown; + dirp->machdirstatus = dirp->dirstatus; if (max_dirnamelen < dirp->machdirnamelen) max_dirnamelen = dirp->machdirnamelen; diff --git a/elf/dl-profile.c b/elf/dl-profile.c new file mode 100644 index 0000000000..cc25b6105f --- /dev/null +++ b/elf/dl-profile.c @@ -0,0 +1,267 @@ +/* Profiling of shared libraries. + Copyright (C) 1997 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <link.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/gmon.h> +#include <sys/gmon_out.h> +#include <sys/mman.h> +#include <sys/stat.h> + +/* The LD_PROFILE feature has to be implemented different to the + normal profiling using the gmon/ functions. The problem is that an + arbitrary amount of processes simulataneously can be run using + profiling and all write the results in the same file. To provide + this mechanism one could implement a complicated mechanism to merge + the content of two profiling runs or one could extend the file + format to allow more than one data set. For the second solution we + would have the problem that the file can grow in size beyond any + limit and both solutions have the problem that the concurrency of + writing the results is a big problem. + + Another much simpler method is to use mmap to map the same file in + all using programs and modify the data in the mmap'ed area and so + also automatically on the disk. Using the MAP_SHARED option of + mmap(2) this can be done without big problems in more than one + file. + + This approach is very different from the normal profiling. We have + to use the profiling data in exactly the way they are expected to + be written to disk. */ + +extern char *_strerror_internal __P ((int, char *buf, size_t)); + +extern int __profile_frequency __P ((void)); + + +static struct gmonparam param; + +/* We define a special type to address the elements of the arc table. + This is basically the `gmon_cg_arc_record' format but it includes + the room for the tag and it uses real types. */ +struct here_cg_arc_record + { + char tag; + uintptr_t from_pc __attribute__ ((packed)); + uintptr_t self_pc __attribute__ ((packed)); + uint32_t count __attribute__ ((packed)); + }; + +static struct here_cg_arc_record *data; + + +void +_dl_start_profile (struct link_map *map, const char *output_dir) +{ + char *filename; + int fd; + struct stat st; + const ElfW(Phdr) *ph; + ElfW(Addr) mapstart = ~((ElfW(Addr)) 0); + ElfW(Addr) mapend = 0; + off_t expected_size; + struct gmon_hdr gmon_hdr; + struct gmon_hist_hdr hist_hdr; + struct gmon_hdr *addr; + char *hist; + + /* Compute the size of the sections which contain program code. */ + for (ph = map->l_phdr; ph < &map->l_phdr[map->l_phnum]; ++ph) + if (ph->p_type == PT_LOAD && (ph->p_flags & PF_X)) + { + ElfW(Addr) start = (ph->p_vaddr & ~(_dl_pagesize - 1)); + ElfW(Addr) end = ((ph->p_vaddr + ph->p_memsz + _dl_pagesize - 1) + & ~(_dl_pagesize - 1)); + + if (start < mapstart) + mapstart = start; + if (end > mapend) + mapend = end; + } + + /* Now we can compute the size of the profiling data. This is done + with the same formulars as in `monstartup' (see gmon.c). */ + param.state = GMON_PROF_OFF; + param.lowpc = mapstart + map->l_addr; + param.highpc = mapend + map->l_addr; + param.textsize = mapend - mapstart; + param.kcountsize = param.textsize / HISTFRACTION; + param.hashfraction = HASHFRACTION; + param.log_hashfraction = -1; + if ((HASHFRACTION & (HASHFRACTION - 1)) == 0) + /* If HASHFRACTION is a power of two, mcount can use shifting + instead of integer division. Precompute shift amount. */ + param.log_hashfraction = ffs (param.hashfraction + * sizeof (*param.froms)) - 1; + param.fromssize = param.textsize / HASHFRACTION; + param.tolimit = param.textsize * ARCDENSITY / 100; + if (param.tolimit < MINARCS) + param.tolimit = MINARCS; + if (param.tolimit > MAXARCS) + param.tolimit = MAXARCS; + param.tossize = param.tolimit * sizeof (struct tostruct); + + expected_size = (sizeof (struct gmon_hdr) + + 1 + sizeof (struct gmon_hist_hdr) + + ((1 + sizeof (struct gmon_cg_arc_record)) + * (param.fromssize / sizeof (*param.froms)))); + + /* Create the gmon_hdr we expect or write. */ + memset (&gmon_hdr, '\0', sizeof (struct gmon_hdr)); + memcpy (&gmon_hdr.cookie[0], GMON_MAGIC, sizeof (gmon_hdr.cookie)); + *(int32_t *) gmon_hdr.version = GMON_VERSION; + + /* Create the hist_hdr we expect or write. */ + *(char **) hist_hdr.low_pc = (char *) mapstart; + *(char **) hist_hdr.high_pc = (char *) mapend; + *(int32_t *) hist_hdr.hist_size = param.kcountsize / sizeof (HISTCOUNTER); + *(int32_t *) hist_hdr.prof_rate = __profile_frequency (); + strncpy (hist_hdr.dimen, "seconds", sizeof (hist_hdr.dimen)); + hist_hdr.dimen_abbrev = 's'; + + /* First determine the output name. We write in the directory + OUTPUT_DIR and the name is composed from the shared objects + soname (or the file name) and the ending ".profile". */ + filename = (char *) alloca (strlen (output_dir) + 1 + strlen (_dl_profile) + + sizeof ".profile"); + __stpcpy (__stpcpy (__stpcpy (__stpcpy (filename, output_dir), "/"), + _dl_profile), + ".profile"); + + fd = __open (filename, O_RDWR | O_CREAT, 0666); + if (fd == -1) + /* We cannot write the profiling data so don't do anthing. */ + return; + + if (fstat (fd, &st) < 0 || !S_ISREG (st.st_mode)) + { + /* Not stat'able or not a regular file => don't use it. */ + close (fd); + return; + } + + /* Test the size. If it does not match what we expect from the size + values in the map MAP we don't use it and warn the user. */ + if (st.st_size == 0) + { + /* We have to create the file. */ + char buf[_dl_pagesize]; + + memset (buf, '\0', _dl_pagesize); + + if (__lseek (fd, expected_size & ~(_dl_pagesize - 1), SEEK_SET) == -1) + { + char buf[400]; + int errnum; + cannot_create: + errnum = errno; + __close (fd); + fprintf (stderr, "%s: cannot create file: %s\n", filename, + _strerror_internal (errnum, buf, sizeof buf)); + return; + } + + if (TEMP_FAILURE_RETRY (__write (fd, buf, (expected_size + & (_dl_pagesize - 1)))) < 0) + goto cannot_create; + } + else if (st.st_size != expected_size) + { + __close (fd); + wrong_format: + fprintf (stderr, "%s: file is no correct profile data file for `%s'\n", + filename, _dl_profile); + return; + } + + addr = (void *) __mmap (NULL, expected_size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, fd, 0); + if (addr == (void *) -1) + { + char buf[400]; + int errnum = errno; + __close (fd); + fprintf (stderr, "%s: cannot map file: %s\n", filename, + _strerror_internal (errnum, buf, sizeof buf)); + return; + } + + /* We don't need the file desriptor anymore. */ + __close (fd); + + /* Pointer to data after the header. */ + hist = (char *) (addr + 1); + + /* Compute pointer to array of the arc information. */ + data = (struct here_cg_arc_record *) (hist + 1 + + sizeof (struct gmon_hist_hdr)); + + if (st.st_size == 0) + { + /* Create the signature. */ + size_t cnt; + + memcpy (addr, &gmon_hdr, sizeof (struct gmon_hdr)); + + *hist = GMON_TAG_TIME_HIST; + memcpy (hist + 1, &hist_hdr, sizeof (struct gmon_hist_hdr)); + + for (cnt = 0; cnt < param.fromssize / sizeof (*param.froms); ++cnt) + data[cnt].tag = GMON_TAG_CG_ARC; + } + else + { + /* Test the signature in the file. */ + if (memcmp (addr, &gmon_hdr, sizeof (struct gmon_hdr)) != 0 + || *hist != GMON_TAG_TIME_HIST + || memcmp (hist + 1, &hist_hdr, sizeof (struct gmon_hist_hdr)) != 0) + goto wrong_format; + } + + /* Turn on profiling. */ + param.state = GMON_PROF_ON; +} + + +void +_dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc) +{ + if (param.state != GMON_PROF_ON) + return; + param.state = GMON_PROF_BUSY; + + /* Compute relative addresses. The shared object can be loaded at + any address. The value of frompc could be anything. We cannot + restrict it in any way, just set to a fixed value (0) in case it + is outside the allowed range. These calls show up as calls from + <external> in the gprof output. */ + frompc -= param.lowpc; + if (frompc >= param.textsize) + frompc = 0; + selfpc -= param.lowpc; + + param.state = GMON_PROF_ON; +} diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c index 5b22a506da..4f6eff8835 100644 --- a/elf/dl-reloc.c +++ b/elf/dl-reloc.c @@ -24,6 +24,7 @@ #include <errno.h> #include "dynamic-link.h" + void _dl_relocate_object (struct link_map *l, struct link_map *scope[], int lazy) { @@ -65,7 +66,7 @@ _dl_relocate_object (struct link_map *l, struct link_map *scope[], int lazy) l->l_name, (flags))) #include "dynamic-link.h" - ELF_DYNAMIC_RELOCATE (l, lazy); + ELF_DYNAMIC_RELOCATE (l, lazy, 1); } /* Mark the object so we know this work has been done. */ diff --git a/elf/dl-runtime.c b/elf/dl-runtime.c index 7a44ea4ce3..1b34026d38 100644 --- a/elf/dl-runtime.c +++ b/elf/dl-runtime.c @@ -137,17 +137,18 @@ fixup ( /* Perform the specified relocation. */ if (l->l_info[VERSYMIDX (DT_VERSYM)]) { - const ElfW(Half) * version = + const ElfW(Half) *version = (const ElfW(Half) *) (l->l_addr + l->l_info[VERSYMIDX (DT_VERSYM)]->d_un.d_ptr); ElfW(Half) ndx = version[ELFW(R_SYM) (reloc->r_info)]; elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)], - &l->l_versions[ndx]); + &l->l_versions[ndx], + (void *) (l->l_addr + reloc->r_offset)); } else elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)], - NULL); + NULL, (void *) (l->l_addr + reloc->r_offset)); } *_dl_global_scope_end = NULL; @@ -161,6 +162,70 @@ fixup ( } +#ifndef PROF +static ElfW(Addr) +profile_fixup ( +#ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS + ELF_MACHINE_RUNTIME_FIXUP_ARGS, +#endif + struct link_map *l, ElfW(Word) reloc_offset, ElfW(Addr) retaddr) +{ + void (*mcount_fct) (ElfW(Addr), ElfW(Addr)) = _dl_mcount; + const ElfW(Sym) *const symtab + = (const ElfW(Sym) *) (l->l_addr + l->l_info[DT_SYMTAB]->d_un.d_ptr); + const char *strtab = + (const char *) (l->l_addr + l->l_info[DT_STRTAB]->d_un.d_ptr); + + const PLTREL *const reloc + = (const void *) (l->l_addr + l->l_info[DT_JMPREL]->d_un.d_ptr + + reloc_offset); + ElfW(Addr) result; + + /* Set up the scope to find symbols referenced by this object. */ + struct link_map **scope = _dl_object_relocation_scope (l); + + { + /* This macro is used as a callback from the elf_machine_relplt code. */ +#define RESOLVE(ref, version, flags) \ + ((version) != NULL && (version)->hash != 0 \ + ? _dl_lookup_versioned_symbol (strtab + (*ref)->st_name, (ref), scope, \ + l->l_name, (version), (flags)) \ + : _dl_lookup_symbol (strtab + (*ref)->st_name, (ref), scope, \ + l->l_name, (flags))) +#include "dynamic-link.h" + + /* Perform the specified relocation. */ + if (l->l_info[VERSYMIDX (DT_VERSYM)]) + { + const ElfW(Half) *version = + (const ElfW(Half) *) (l->l_addr + + l->l_info[VERSYMIDX (DT_VERSYM)]->d_un.d_ptr); + ElfW(Half) ndx = version[ELFW(R_SYM) (reloc->r_info)]; + + elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)], + &l->l_versions[ndx], (void *) &result); + } + else + elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)], + NULL, (void *) &result); + } + + *_dl_global_scope_end = NULL; + + /* Return the address that was written by the relocation. */ +#ifdef ELF_FIXUP_RETURNS_ADDRESS + (*mcount_fct) (retaddr, result); + + return &result; /* XXX This cannot work. What to do??? --drepper */ +#else + (*mcount_fct) (retaddr, result); + + return result; +#endif +} +#endif + + /* This macro is defined in dl-machine.h to define the entry point called by the PLT. The `fixup' function above does the real work, but a little more twiddling is needed to get the stack right and jump to the address diff --git a/elf/dl-support.c b/elf/dl-support.c index 3333bf10fb..41997ccafb 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -42,6 +42,10 @@ int _dl_verbose; /* Structure to store information about search paths. */ struct r_search_path *_dl_search_paths; +/* We never do profiling. */ +const char *_dl_profile; +struct link_map *_dl_profile_map; + static void non_dynamic_init (void) __attribute__ ((unused)); diff --git a/elf/do-rel.h b/elf/do-rel.h index 766e62c219..7f4b1259a2 100644 --- a/elf/do-rel.h +++ b/elf/do-rel.h @@ -63,12 +63,14 @@ elf_dynamic_do_rel (struct link_map *map, { ElfW(Half) ndx = version[ELFW(R_SYM) (r->r_info)]; elf_machine_rel (map, r, &symtab[ELFW(R_SYM) (r->r_info)], - &map->l_versions[ndx]); + &map->l_versions[ndx], + (void *) (map->l_addr + r->r_offset)); } } else for (; r < end; ++r) - elf_machine_rel (map, r, &symtab[ELFW(R_SYM) (r->r_info)], NULL); + elf_machine_rel (map, r, &symtab[ELFW(R_SYM) (r->r_info)], NULL, + (void *) (map->l_addr + r->r_offset)); } } diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h index 3377ee7ff6..4a946f8ad8 100644 --- a/elf/dynamic-link.h +++ b/elf/dynamic-link.h @@ -102,11 +102,14 @@ elf_get_dynamic_info (ElfW(Dyn) *dyn, /* This can't just be an inline function because GCC is too dumb to inline functions containing inlines themselves. */ -#define ELF_DYNAMIC_RELOCATE(map, lazy) \ - do { \ - int edr_lazy = elf_machine_runtime_setup((map), (lazy)); \ - ELF_DYNAMIC_DO_REL ((map), edr_lazy); \ - ELF_DYNAMIC_DO_RELA ((map), edr_lazy); \ +#define ELF_DYNAMIC_RELOCATE(map, lazy, consider_profile) \ + do { \ + int profile = (consider_profile && _dl_profile != NULL \ + && _dl_name_match_p (_dl_profile, (map))); \ + int edr_lazy = elf_machine_runtime_setup ((map), (lazy) || profile, \ + profile); \ + ELF_DYNAMIC_DO_REL ((map), edr_lazy); \ + ELF_DYNAMIC_DO_RELA ((map), edr_lazy); \ } while (0) #endif diff --git a/elf/link.h b/elf/link.h index e0fccd0fca..38a7c88d9a 100644 --- a/elf/link.h +++ b/elf/link.h @@ -225,6 +225,11 @@ extern size_t _dl_pagesize; /* File descriptor referring to the zero-fill device. */ extern int _dl_zerofd; +/* Name of the shared object to be profiled (if any). */ +extern const char *_dl_profile; +/* Map of shared object to be profiled. */ +extern struct link_map *_dl_profile_map; + /* OS-dependent function to open the zero-fill device. */ extern int _dl_sysdep_open_zero_fill (void); /* dl-sysdep.c */ @@ -430,8 +435,14 @@ extern void _dl_debug_state (void); extern struct r_debug *_dl_debug_initialize (ElfW(Addr) ldbase); /* Initialize the basic data structure for the search paths. */ -void _dl_init_paths (void); +extern void _dl_init_paths (void); + +/* Gather the information needed to install the profiling tables and start + the timers. */ +extern void _dl_start_profile (struct link_map *map, const char *output_dir); +/* The actual functions used to keep book on the calls. */ +extern void _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc); __END_DECLS diff --git a/elf/rtld.c b/elf/rtld.c index 7189ca6b2b..726463c882 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -61,6 +61,9 @@ int _dl_verbose; const char *_dl_platform; size_t _dl_platformlen; struct r_search_path *_dl_search_paths; +const char *_dl_profile; +const char *_dl_profile_output; +struct link_map *_dl_profile_map; /* Set nonzero during loading and initialization of executable and libraries, cleared before the executable's entry point runs. This @@ -109,7 +112,7 @@ _dl_start (void *arg) /* Relocate ourselves so we can do normal function calls and data access using the global offset table. */ - ELF_DYNAMIC_RELOCATE (&bootstrap_map, 0); + ELF_DYNAMIC_RELOCATE (&bootstrap_map, 0, 0); /* Now life is sane; we can call functions and access global data. Set up to use the operating system facilities, and find out from @@ -269,6 +272,26 @@ dl_main (const ElfW(Phdr) *phdr, else lazy = !__libc_enable_secure && *(getenv ("LD_BIND_NOW") ?: "") == '\0'; + /* See whether we want to use profiling. */ + _dl_profile = getenv ("LD_PROFILE"); + if (_dl_profile != NULL) + if (_dl_profile[0] == '\0') + /* An empty string is of not much help. Disable profiling. */ + _dl_profile = NULL; + else + { + /* OK, we have the name of a shared object we want to + profile. It's up to the user to provide a good name, it + must match the file name or soname of one of the loaded + objects. Now let's see where we are supposed to place the + result. */ + _dl_profile_output = getenv ("LD_PROFILE_OUTPUT"); + + if (_dl_profile_output == NULL || _dl_profile_output[0] == '\0') + /* This is the default place. */ + _dl_profile_output = "/var/tmp"; + } + /* Set up a flag which tells we are just starting. */ _dl_starting_up = 1; @@ -814,6 +837,11 @@ of this helper program; chances are you did not intend to run this program.\n", _dl_debug_state (); } + /* Now enable profiling if needed. */ + if (_dl_profile_map != NULL) + /* We must prepare the profiling. */ + _dl_start_profile (_dl_profile_map, _dl_profile_output); + /* Once we return, _dl_sysdep_start will invoke the DT_INIT functions and then *USER_ENTRY. */ } @@ -824,6 +852,8 @@ static void print_unresolved (int errcode __attribute__ ((unused)), const char *objname, const char *errstring) { + if (objname[0] == '\0') + objname = _dl_argv[0] ?: "<main program>"; _dl_sysdep_error (errstring, " (", objname, ")\n", NULL); } |