]>
Commit | Line | Data |
---|---|---|
3996f34b UD |
1 | /* Profiling of shared libraries. |
2 | Copyright (C) 1997 Free Software Foundation, Inc. | |
3 | This file is part of the GNU C Library. | |
4 | Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997. | |
5 | ||
6 | The GNU C Library is free software; you can redistribute it and/or | |
7 | modify it under the terms of the GNU Library General Public License as | |
8 | published by the Free Software Foundation; either version 2 of the | |
9 | License, or (at your option) any later version. | |
10 | ||
11 | The GNU C Library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Library General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Library General Public | |
17 | License along with the GNU C Library; see the file COPYING.LIB. If not, | |
18 | write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
19 | Boston, MA 02111-1307, USA. */ | |
20 | ||
21 | #include <errno.h> | |
22 | #include <fcntl.h> | |
23 | #include <inttypes.h> | |
24 | #include <link.h> | |
25 | #include <stdio.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <unistd.h> | |
29 | #include <sys/gmon.h> | |
30 | #include <sys/gmon_out.h> | |
31 | #include <sys/mman.h> | |
32 | #include <sys/stat.h> | |
33 | ||
34 | /* The LD_PROFILE feature has to be implemented different to the | |
35 | normal profiling using the gmon/ functions. The problem is that an | |
36 | arbitrary amount of processes simulataneously can be run using | |
37 | profiling and all write the results in the same file. To provide | |
38 | this mechanism one could implement a complicated mechanism to merge | |
39 | the content of two profiling runs or one could extend the file | |
40 | format to allow more than one data set. For the second solution we | |
41 | would have the problem that the file can grow in size beyond any | |
42 | limit and both solutions have the problem that the concurrency of | |
43 | writing the results is a big problem. | |
44 | ||
45 | Another much simpler method is to use mmap to map the same file in | |
46 | all using programs and modify the data in the mmap'ed area and so | |
47 | also automatically on the disk. Using the MAP_SHARED option of | |
48 | mmap(2) this can be done without big problems in more than one | |
49 | file. | |
50 | ||
51 | This approach is very different from the normal profiling. We have | |
52 | to use the profiling data in exactly the way they are expected to | |
53 | be written to disk. */ | |
54 | ||
55 | extern char *_strerror_internal __P ((int, char *buf, size_t)); | |
56 | ||
57 | extern int __profile_frequency __P ((void)); | |
58 | ||
59 | ||
60 | static struct gmonparam param; | |
61 | ||
62 | /* We define a special type to address the elements of the arc table. | |
63 | This is basically the `gmon_cg_arc_record' format but it includes | |
64 | the room for the tag and it uses real types. */ | |
65 | struct here_cg_arc_record | |
66 | { | |
67 | char tag; | |
68 | uintptr_t from_pc __attribute__ ((packed)); | |
69 | uintptr_t self_pc __attribute__ ((packed)); | |
70 | uint32_t count __attribute__ ((packed)); | |
71 | }; | |
72 | ||
73 | static struct here_cg_arc_record *data; | |
74 | ||
75 | ||
76 | void | |
77 | _dl_start_profile (struct link_map *map, const char *output_dir) | |
78 | { | |
79 | char *filename; | |
80 | int fd; | |
81 | struct stat st; | |
82 | const ElfW(Phdr) *ph; | |
83 | ElfW(Addr) mapstart = ~((ElfW(Addr)) 0); | |
84 | ElfW(Addr) mapend = 0; | |
85 | off_t expected_size; | |
86 | struct gmon_hdr gmon_hdr; | |
87 | struct gmon_hist_hdr hist_hdr; | |
88 | struct gmon_hdr *addr; | |
89 | char *hist; | |
90 | ||
91 | /* Compute the size of the sections which contain program code. */ | |
92 | for (ph = map->l_phdr; ph < &map->l_phdr[map->l_phnum]; ++ph) | |
93 | if (ph->p_type == PT_LOAD && (ph->p_flags & PF_X)) | |
94 | { | |
95 | ElfW(Addr) start = (ph->p_vaddr & ~(_dl_pagesize - 1)); | |
96 | ElfW(Addr) end = ((ph->p_vaddr + ph->p_memsz + _dl_pagesize - 1) | |
97 | & ~(_dl_pagesize - 1)); | |
98 | ||
99 | if (start < mapstart) | |
100 | mapstart = start; | |
101 | if (end > mapend) | |
102 | mapend = end; | |
103 | } | |
104 | ||
105 | /* Now we can compute the size of the profiling data. This is done | |
106 | with the same formulars as in `monstartup' (see gmon.c). */ | |
107 | param.state = GMON_PROF_OFF; | |
108 | param.lowpc = mapstart + map->l_addr; | |
109 | param.highpc = mapend + map->l_addr; | |
110 | param.textsize = mapend - mapstart; | |
111 | param.kcountsize = param.textsize / HISTFRACTION; | |
112 | param.hashfraction = HASHFRACTION; | |
113 | param.log_hashfraction = -1; | |
114 | if ((HASHFRACTION & (HASHFRACTION - 1)) == 0) | |
115 | /* If HASHFRACTION is a power of two, mcount can use shifting | |
116 | instead of integer division. Precompute shift amount. */ | |
117 | param.log_hashfraction = ffs (param.hashfraction | |
118 | * sizeof (*param.froms)) - 1; | |
119 | param.fromssize = param.textsize / HASHFRACTION; | |
120 | param.tolimit = param.textsize * ARCDENSITY / 100; | |
121 | if (param.tolimit < MINARCS) | |
122 | param.tolimit = MINARCS; | |
123 | if (param.tolimit > MAXARCS) | |
124 | param.tolimit = MAXARCS; | |
125 | param.tossize = param.tolimit * sizeof (struct tostruct); | |
126 | ||
127 | expected_size = (sizeof (struct gmon_hdr) | |
128 | + 1 + sizeof (struct gmon_hist_hdr) | |
129 | + ((1 + sizeof (struct gmon_cg_arc_record)) | |
130 | * (param.fromssize / sizeof (*param.froms)))); | |
131 | ||
132 | /* Create the gmon_hdr we expect or write. */ | |
133 | memset (&gmon_hdr, '\0', sizeof (struct gmon_hdr)); | |
134 | memcpy (&gmon_hdr.cookie[0], GMON_MAGIC, sizeof (gmon_hdr.cookie)); | |
135 | *(int32_t *) gmon_hdr.version = GMON_VERSION; | |
136 | ||
137 | /* Create the hist_hdr we expect or write. */ | |
138 | *(char **) hist_hdr.low_pc = (char *) mapstart; | |
139 | *(char **) hist_hdr.high_pc = (char *) mapend; | |
140 | *(int32_t *) hist_hdr.hist_size = param.kcountsize / sizeof (HISTCOUNTER); | |
141 | *(int32_t *) hist_hdr.prof_rate = __profile_frequency (); | |
142 | strncpy (hist_hdr.dimen, "seconds", sizeof (hist_hdr.dimen)); | |
143 | hist_hdr.dimen_abbrev = 's'; | |
144 | ||
145 | /* First determine the output name. We write in the directory | |
146 | OUTPUT_DIR and the name is composed from the shared objects | |
147 | soname (or the file name) and the ending ".profile". */ | |
148 | filename = (char *) alloca (strlen (output_dir) + 1 + strlen (_dl_profile) | |
149 | + sizeof ".profile"); | |
150 | __stpcpy (__stpcpy (__stpcpy (__stpcpy (filename, output_dir), "/"), | |
151 | _dl_profile), | |
152 | ".profile"); | |
153 | ||
154 | fd = __open (filename, O_RDWR | O_CREAT, 0666); | |
155 | if (fd == -1) | |
156 | /* We cannot write the profiling data so don't do anthing. */ | |
157 | return; | |
158 | ||
159 | if (fstat (fd, &st) < 0 || !S_ISREG (st.st_mode)) | |
160 | { | |
161 | /* Not stat'able or not a regular file => don't use it. */ | |
162 | close (fd); | |
163 | return; | |
164 | } | |
165 | ||
166 | /* Test the size. If it does not match what we expect from the size | |
167 | values in the map MAP we don't use it and warn the user. */ | |
168 | if (st.st_size == 0) | |
169 | { | |
170 | /* We have to create the file. */ | |
171 | char buf[_dl_pagesize]; | |
172 | ||
173 | memset (buf, '\0', _dl_pagesize); | |
174 | ||
175 | if (__lseek (fd, expected_size & ~(_dl_pagesize - 1), SEEK_SET) == -1) | |
176 | { | |
177 | char buf[400]; | |
178 | int errnum; | |
179 | cannot_create: | |
180 | errnum = errno; | |
181 | __close (fd); | |
182 | fprintf (stderr, "%s: cannot create file: %s\n", filename, | |
183 | _strerror_internal (errnum, buf, sizeof buf)); | |
184 | return; | |
185 | } | |
186 | ||
187 | if (TEMP_FAILURE_RETRY (__write (fd, buf, (expected_size | |
188 | & (_dl_pagesize - 1)))) < 0) | |
189 | goto cannot_create; | |
190 | } | |
191 | else if (st.st_size != expected_size) | |
192 | { | |
193 | __close (fd); | |
194 | wrong_format: | |
195 | fprintf (stderr, "%s: file is no correct profile data file for `%s'\n", | |
196 | filename, _dl_profile); | |
197 | return; | |
198 | } | |
199 | ||
200 | addr = (void *) __mmap (NULL, expected_size, PROT_READ|PROT_WRITE, | |
201 | MAP_SHARED|MAP_FILE, fd, 0); | |
202 | if (addr == (void *) -1) | |
203 | { | |
204 | char buf[400]; | |
205 | int errnum = errno; | |
206 | __close (fd); | |
207 | fprintf (stderr, "%s: cannot map file: %s\n", filename, | |
208 | _strerror_internal (errnum, buf, sizeof buf)); | |
209 | return; | |
210 | } | |
211 | ||
212 | /* We don't need the file desriptor anymore. */ | |
213 | __close (fd); | |
214 | ||
215 | /* Pointer to data after the header. */ | |
216 | hist = (char *) (addr + 1); | |
217 | ||
218 | /* Compute pointer to array of the arc information. */ | |
219 | data = (struct here_cg_arc_record *) (hist + 1 | |
220 | + sizeof (struct gmon_hist_hdr)); | |
221 | ||
222 | if (st.st_size == 0) | |
223 | { | |
224 | /* Create the signature. */ | |
225 | size_t cnt; | |
226 | ||
227 | memcpy (addr, &gmon_hdr, sizeof (struct gmon_hdr)); | |
228 | ||
229 | *hist = GMON_TAG_TIME_HIST; | |
230 | memcpy (hist + 1, &hist_hdr, sizeof (struct gmon_hist_hdr)); | |
231 | ||
232 | for (cnt = 0; cnt < param.fromssize / sizeof (*param.froms); ++cnt) | |
233 | data[cnt].tag = GMON_TAG_CG_ARC; | |
234 | } | |
235 | else | |
236 | { | |
237 | /* Test the signature in the file. */ | |
238 | if (memcmp (addr, &gmon_hdr, sizeof (struct gmon_hdr)) != 0 | |
239 | || *hist != GMON_TAG_TIME_HIST | |
240 | || memcmp (hist + 1, &hist_hdr, sizeof (struct gmon_hist_hdr)) != 0) | |
241 | goto wrong_format; | |
242 | } | |
243 | ||
244 | /* Turn on profiling. */ | |
245 | param.state = GMON_PROF_ON; | |
246 | } | |
247 | ||
248 | ||
249 | void | |
250 | _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc) | |
251 | { | |
252 | if (param.state != GMON_PROF_ON) | |
253 | return; | |
254 | param.state = GMON_PROF_BUSY; | |
255 | ||
256 | /* Compute relative addresses. The shared object can be loaded at | |
257 | any address. The value of frompc could be anything. We cannot | |
258 | restrict it in any way, just set to a fixed value (0) in case it | |
259 | is outside the allowed range. These calls show up as calls from | |
260 | <external> in the gprof output. */ | |
261 | frompc -= param.lowpc; | |
262 | if (frompc >= param.textsize) | |
263 | frompc = 0; | |
264 | selfpc -= param.lowpc; | |
265 | ||
266 | param.state = GMON_PROF_ON; | |
267 | } |