diff options
Diffstat (limited to 'elf')
-rw-r--r-- | elf/dl-load.c | 97 | ||||
-rw-r--r-- | elf/dl-minimal.c | 2 | ||||
-rw-r--r-- | elf/dl-misc.c | 2 | ||||
-rw-r--r-- | elf/dl-profile.c | 251 |
4 files changed, 254 insertions, 98 deletions
diff --git a/elf/dl-load.c b/elf/dl-load.c index f7c2c53f7e..f0af29292a 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -107,27 +107,51 @@ local_strdup (const char *s) return (char *) memcpy (new, s, len); } - -/* Implement cache for search path lookup. */ -#if 0 -/* This is how generated should look like. I'll remove this once I'm - sure everything works correctly. */ -static struct r_search_path_elem rtld_search_dir1 = - { "/lib/", 5, unknown, 0, unknown, NULL }; -static struct r_search_path_elem rtld_search_dir2 = - { "/usr/lib/", 9, unknown, 0, unknown, &r ld_search_dir1 }; - -static struct r_search_path_elem *rtld_search_dirs[] = +/* Add `name' to the list of names for a particular shared object. + `name' is expected to have been allocated with malloc and will + be freed if the shared object already has this name. + Returns false if the object already had this name. */ +static int +add_name_to_object (struct link_map *l, char *name) { - &rtld_search_dir1, - &rtld_search_dir2, - NULL -}; + struct libname_list *lnp, *lastp; + struct libname_list *newname; -static struct r_search_path_elem *all_dirs = &rtld_search_dir2; -#else -# include "rtldtbl.h" -#endif + if (name == NULL) + { + /* No more memory. */ + _dl_signal_error (ENOMEM, NULL, _("could not allocate name string")); + return 0; + } + + lastp = NULL; + for (lnp = l->l_libname; lnp != NULL; lastp = lnp, lnp = lnp->next) + if (strcmp (name, lnp->name) == 0) + { + free (name); + return 0; + } + + newname = malloc (sizeof *newname); + if (newname == NULL) + { + /* No more memory. */ + _dl_signal_error (ENOMEM, name, _("cannot allocate name record")); + free(name); + return 0; + } + /* The object should have a libname set from _dl_new_object. */ + assert (lastp != NULL); + + newname->name = name; + newname->next = NULL; + lastp->next = newname; + return 1; +} + + +/* Implement cache for search path lookup. */ +#include "rtldtbl.h" static size_t max_dirnamelen; @@ -423,9 +447,10 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, l->l_next->l_prev = l->l_prev; free (l); } - free (name); /* XXX Can this be correct? --drepper */ free (realname); _dl_signal_error (code, name, msg); + free (name); /* Hmmm. Can this leak memory? Better + than a segfault, anyway. */ } inline caddr_t map_segment (ElfW(Addr) mapstart, size_t len, @@ -434,7 +459,7 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, caddr_t mapat = __mmap ((caddr_t) mapstart, len, prot, fixed|MAP_COPY|MAP_FILE, fd, offset); - if (mapat == (caddr_t) -1) + if (mapat == MAP_FAILED) lose (errno, "failed to map segment from shared object"); return mapat; } @@ -451,7 +476,7 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, mapping_size &= ~(_dl_pagesize - 1); result = __mmap (file_mapping, mapping_size, PROT_READ, MAP_COPY|MAP_FILE, fd, 0); - if (result == (void *) -1) + if (result == MAP_FAILED) lose (errno, "cannot map file data"); file_mapping = result; } @@ -467,7 +492,6 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, for (l = _dl_loaded; l; l = l->l_next) if (! strcmp (realname, l->l_name)) { - struct libname_list *lnp, *lastp; /* The object is already loaded. Just bump its reference count and return it. */ __close (fd); @@ -475,26 +499,7 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, /* If the name is not in the list of names for this object add it. */ free (realname); - lastp = NULL; - for (lnp = l->l_libname; lnp != NULL; lastp = lnp, lnp = lnp->next) - if (strcmp (name, lnp->name) == 0) - { - free (name); - break; - } - if (lnp == NULL) - { - struct libname_list *newname = malloc (sizeof *newname); - if (newname == NULL) - /* No more memory. */ - lose (ENOMEM, "cannot allocate name record"); - /* The object should have a libname set. */ - assert (lastp != NULL); - - newname->name = name; - newname->next = NULL; - lastp->next = newname; - } + add_name_to_object (l, name); ++l->l_opencount; return l; } @@ -701,7 +706,7 @@ _dl_map_object_from_fd (char *name, int fd, char *realname, mapat = __mmap ((caddr_t) zeropage, zeroend - zeropage, c->prot, MAP_ANON|MAP_PRIVATE|MAP_FIXED, ANONFD, 0); - if (mapat == (caddr_t) -1) + if (mapat == MAP_FAILED) lose (errno, "cannot map zero-fill pages"); } } @@ -876,6 +881,10 @@ _dl_map_object (struct link_map *loader, const char *name, int type, { /* The object is already loaded. Just bump its reference count and return it. */ + const char *soname = (const char *) (l->l_addr + + l->l_info[DT_STRTAB]->d_un.d_ptr + + l->l_info[DT_SONAME]->d_un.d_val); + add_name_to_object (l, local_strdup (soname)); ++l->l_opencount; return l; } diff --git a/elf/dl-minimal.c b/elf/dl-minimal.c index f5b256808a..486e5bc608 100644 --- a/elf/dl-minimal.c +++ b/elf/dl-minimal.c @@ -66,7 +66,7 @@ malloc (size_t n) assert (n <= _dl_pagesize); page = __mmap (0, _dl_pagesize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, _dl_zerofd, 0); - assert (page != (caddr_t) -1); + assert (page != MAP_FAILED); if (page != alloc_end) alloc_ptr = page; alloc_end = page + _dl_pagesize; diff --git a/elf/dl-misc.c b/elf/dl-misc.c index d5b1464a6b..0894a7216b 100644 --- a/elf/dl-misc.c +++ b/elf/dl-misc.c @@ -62,7 +62,7 @@ _dl_sysdep_read_whole_file (const char *file, size_t *sizep, int prot) | MAP_FILE #endif , fd, 0); - if (result == (void *) -1) + if (result == MAP_FAILED) result = NULL; else *sizep = st.st_size; diff --git a/elf/dl-profile.c b/elf/dl-profile.c index 805624909b..c8028a7c5a 100644 --- a/elf/dl-profile.c +++ b/elf/dl-profile.c @@ -50,29 +50,129 @@ 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. */ + be written to disk. But the normal format used by gprof is not usable + to do this. It is optimized for size. It writes the tags as single + bytes but this means that the following 32/64 bit values are + unaligned. + + Therefore we use a new format. This will look like this + + 0 1 2 3 <- byte is 32 bit word + 0000 g m o n + 0004 *version* <- GMON_SHOBJ_VERSION + 0008 00 00 00 00 + 000c 00 00 00 00 + 0010 00 00 00 00 + + 0014 *tag* <- GMON_TAG_TIME_HIST + 0018 ?? ?? ?? ?? + ?? ?? ?? ?? <- 32/64 bit LowPC + 0018+A ?? ?? ?? ?? + ?? ?? ?? ?? <- 32/64 bit HighPC + 0018+2*A *histsize* + 001c+2*A *profrate* + 0020+2*A s e c o + 0024+2*A n d s \0 + 0028+2*A \0 \0 \0 \0 + 002c+2*A \0 \0 \0 + 002f+2*A s + + 0030+2*A ?? ?? ?? ?? <- Count data + ... ... + 0030+2*A+K ?? ?? ?? ?? + + 0030+2*A+K *tag* <- GMON_TAG_CG_ARC + 0034+2*A+K *lastused* + 0038+2*A+K ?? ?? ?? ?? + ?? ?? ?? ?? <- FromPC#1 + 0038+3*A+K ?? ?? ?? ?? + ?? ?? ?? ?? <- ToPC#1 + 0038+4*A+K ?? ?? ?? ?? <- Count#1 + ... ... ... + 0038+(2*(CN-1)+2)*A+(CN-1)*4+K ?? ?? ?? ?? + ?? ?? ?? ?? <- FromPC#CGN + 0038+(2*(CN-1)+3)*A+(CN-1)*4+K ?? ?? ?? ?? + ?? ?? ?? ?? <- ToPC#CGN + 0038+(2*CN+2)*A+(CN-1)*4+K ?? ?? ?? ?? <- Count#CGN + + We put (for now? no basic block information in the file since this would + introduce rase conditions among all the processes who want to write them. + + `K' is the number of count entries which is computed as + + textsize / HISTFRACTION + + `CG' in the above table is the number of call graph arcs. Normally, + the table is sparse and the profiling code writes out only the those + entries which are really used in the program run. But since we must + not extend this table (the profiling file) we'll keep them all here. + So CN can be executed in advance as + + MINARCS <= textsize*(ARCDENSITY/100) <= MAXARCS + + Now the remaining question is: how to build the data structures we can + work with from this data. We need the from set and must associate the + froms with all the associated tos. We will do this by constructing this + data structures at the program start. To do this we'll simply visit all + entries in the call graph table and add it to the appropriate list. */ 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)); - }; + uintptr_t from_pc; + uintptr_t self_pc; + uint32_t count; + } __attribute__ ((packed)); static struct here_cg_arc_record *data; +/* This is the number of entry which have been incorporated in the toset. */ +static uint32_t narcs; +/* This is a pointer to the object representing the number of entries + currently in the mmaped file. At no point of time this has to be the + same as NARCS. If it is equal all entries from the file are in our + lists. */ +static uint32_t *narcsp; + +/* Description of the currently profiled object. */ +static long int state; +static volatile uint16_t *kcount; +static size_t kcountsize; + +struct here_tostruct + { + struct here_cg_arc_record volatile *here; + uint16_t link; + }; + +static uint16_t *froms; +static size_t fromssize; + +static struct here_tostruct *tos; +static size_t tossize; +static size_t tolimit; +static size_t toidx; + +static uintptr_t lowpc; +static uintptr_t highpc; +static size_t textsize; +static unsigned int hashfraction; +static unsigned int log_hashfraction; + +/* This is the information about the mmaped memory. */ +static struct gmon_hdr *addr; +static off_t expected_size; + + +/* Set up profiling data to profile object desribed by MAP. The output + file is found (or created) in OUTPUT_DIR. */ void _dl_start_profile (struct link_map *map, const char *output_dir) { @@ -82,11 +182,10 @@ _dl_start_profile (struct link_map *map, const char *output_dir) 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; + size_t idx; /* Compute the size of the sections which contain program code. */ for (ph = map->l_phdr; ph < &map->l_phdr[map->l_phnum]; ++ph) @@ -104,40 +203,41 @@ _dl_start_profile (struct link_map *map, const char *output_dir) /* 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; + state = GMON_PROF_OFF; + lowpc = ROUNDDOWN (mapstart + map->l_addr, + HISTFRACTION * sizeof(HISTCOUNTER)); + highpc = ROUNDUP (mapend + map->l_addr, + HISTFRACTION * sizeof(HISTCOUNTER)); + textsize = highpc - lowpc; + kcountsize = textsize / HISTFRACTION; + hashfraction = HASHFRACTION; 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); + log_hashfraction = __builtin_ffs (hashfraction * sizeof (*froms)) - 1; + else + log_hashfraction = -1; + fromssize = textsize / HASHFRACTION; + tolimit = textsize * ARCDENSITY / 100; + if (tolimit < MINARCS) + tolimit = MINARCS; + if (tolimit > MAXARCS) + tolimit = MAXARCS; + tossize = tolimit * sizeof (struct here_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)))); + + 4 + sizeof (struct gmon_hist_hdr) + kcountsize + + 4 + 4 + tossize * sizeof (struct here_cg_arc_record)); /* 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; + *(int32_t *) gmon_hdr.version = GMON_SHOBJ_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.hist_size = 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'; @@ -193,15 +293,19 @@ _dl_start_profile (struct link_map *map, const char *output_dir) { __close (fd); wrong_format: + + if (addr != NULL) + __munmap ((void *) addr, expected_size); + _dl_sysdep_error (filename, ": file is no correct profile data file for `", _dl_profile, "'\n", NULL); return; } - addr = (void *) __mmap (NULL, expected_size, PROT_READ|PROT_WRITE, - MAP_SHARED|MAP_FILE, fd, 0); - if (addr == (void *) -1) + addr = (struct gmon_hdr *) __mmap (NULL, expected_size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, fd, 0); + if (addr == (struct gmon_hdr *) MAP_FAILED) { char buf[400]; int errnum = errno; @@ -217,54 +321,97 @@ _dl_start_profile (struct link_map *map, const char *output_dir) /* Pointer to data after the header. */ hist = (char *) (addr + 1); + kcount = (uint16_t *) ((char *) hist + sizeof (uint32_t) + + sizeof (struct gmon_hist_hdr)); /* Compute pointer to array of the arc information. */ - data = (struct here_cg_arc_record *) (hist + 1 - + sizeof (struct gmon_hist_hdr)); + data = (struct here_cg_arc_record *) ((char *) kcount + kcountsize + + 2 * sizeof (uint32_t)); + narcsp = (uint32_t *) (hist + sizeof (uint32_t) + + sizeof (struct gmon_hist_hdr) + sizeof (uint32_t)); 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)); + *(uint32_t *) hist = GMON_TAG_TIME_HIST; + memcpy (hist + sizeof (uint32_t), &hist_hdr, + sizeof (struct gmon_hist_hdr)); - for (cnt = 0; cnt < param.fromssize / sizeof (*param.froms); ++cnt) - data[cnt].tag = GMON_TAG_CG_ARC; + *(uint32_t *) (hist + sizeof (uint32_t) + sizeof (struct gmon_hist_hdr) + + kcountsize) = 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) + || *(uint32_t *) hist != GMON_TAG_TIME_HIST + || memcmp (hist + sizeof (uint32_t), &hist_hdr, + sizeof (struct gmon_hist_hdr)) != 0 + || (*(uint32_t *) (hist + sizeof (uint32_t) + + sizeof (struct gmon_hist_hdr) + kcountsize) + != GMON_TAG_CG_ARC)) goto wrong_format; } + /* Allocate memory for the froms data and the pointer to the tos records. */ + froms = (uint16_t *) calloc (fromssize + tossize, 1); + if (froms == NULL) + { + __munmap ((void *) addr, expected_size); + _dl_sysdep_fatal ("Out of memory while initializing profiler", NULL); + /* NOTREACHED */ + } + + tos = (struct here_tostruct *) ((char *) froms + fromssize); + toidx = 0; + + /* Now we have to process all the arc count entries. BTW: it is + not critical whether the *NARCSP value changes meanwhile. Before + we enter a new entry in to toset we will check that everything is + available in TOS. This happens in _dl_mcount. + + Loading the entries in reverse order should help to get the most + frequently used entries at the front of the list. */ + for (idx = narcs = *narcsp; idx > 0; ) + { + size_t from_index; + size_t newtoidx; + --idx; + from_index = ((data[idx].from_pc - lowpc) + / (hashfraction * sizeof (*froms))); + newtoidx = toidx++; + tos[newtoidx].here = &data[idx]; + tos[newtoidx].link = froms[from_index]; + froms[from_index] = newtoidx; + } + /* Turn on profiling. */ - param.state = GMON_PROF_ON; + state = GMON_PROF_ON; } void _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc) { - if (param.state != GMON_PROF_ON) + if (state != GMON_PROF_ON) return; - param.state = GMON_PROF_BUSY; + 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 -= lowpc; + if (frompc >= textsize) frompc = 0; - selfpc -= param.lowpc; + selfpc -= lowpc; + if (selfpc >= textsize) + goto done; + - param.state = GMON_PROF_ON; + done: + state = GMON_PROF_ON; } |