Commit | Line | Data |
---|---|---|
81d3858d JR |
1 | /* |
2 | * recordmcount.c: construct a table of the locations of calls to 'mcount' | |
3 | * so that ftrace can find them quickly. | |
4 | * Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved. | |
5 | * Licensed under the GNU General Public License, version 2 (GPLv2). | |
6 | * | |
7 | * Restructured to fit Linux format, as well as other updates: | |
8 | * Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. | |
9 | */ | |
10 | ||
11 | /* | |
12 | * Strategy: alter the .o file in-place. | |
13 | * | |
14 | * Append a new STRTAB that has the new section names, followed by a new array | |
15 | * ElfXX_Shdr[] that has the new section headers, followed by the section | |
16 | * contents for __mcount_loc and its relocations. The old shstrtab strings, | |
17 | * and the old ElfXX_Shdr[] array, remain as "garbage" (commonly, a couple | |
18 | * kilobytes.) Subsequent processing by /bin/ld (or the kernel module loader) | |
19 | * will ignore the garbage regions, because they are not designated by the | |
20 | * new .e_shoff nor the new ElfXX_Shdr[]. [In order to remove the garbage, | |
21 | * then use "ld -r" to create a new file that omits the garbage.] | |
22 | */ | |
23 | ||
24 | #include <sys/types.h> | |
25 | #include <sys/mman.h> | |
26 | #include <sys/stat.h> | |
27 | #include <elf.h> | |
28 | #include <fcntl.h> | |
29 | #include <setjmp.h> | |
30 | #include <stdio.h> | |
31 | #include <stdlib.h> | |
32 | #include <string.h> | |
33 | #include <unistd.h> | |
34 | ||
35 | static int fd_map; /* File descriptor for file being modified. */ | |
36 | static int mmap_failed; /* Boolean flag. */ | |
37 | static void *ehdr_curr; /* current ElfXX_Ehdr * for resource cleanup */ | |
38 | static char gpfx; /* prefix for global symbol name (sometimes '_') */ | |
39 | static struct stat sb; /* Remember .st_size, etc. */ | |
40 | static jmp_buf jmpenv; /* setjmp/longjmp per-file error escape */ | |
41 | ||
42 | /* setjmp() return values */ | |
43 | enum { | |
44 | SJ_SETJMP = 0, /* hardwired first return */ | |
45 | SJ_FAIL, | |
46 | SJ_SUCCEED | |
47 | }; | |
48 | ||
49 | /* Per-file resource cleanup when multiple files. */ | |
50 | static void | |
51 | cleanup(void) | |
52 | { | |
53 | if (!mmap_failed) | |
54 | munmap(ehdr_curr, sb.st_size); | |
55 | else | |
56 | free(ehdr_curr); | |
57 | close(fd_map); | |
58 | } | |
59 | ||
60 | static void __attribute__((noreturn)) | |
61 | fail_file(void) | |
62 | { | |
63 | cleanup(); | |
64 | longjmp(jmpenv, SJ_FAIL); | |
65 | } | |
66 | ||
67 | static void __attribute__((noreturn)) | |
68 | succeed_file(void) | |
69 | { | |
70 | cleanup(); | |
71 | longjmp(jmpenv, SJ_SUCCEED); | |
72 | } | |
73 | ||
74 | /* ulseek, uread, ...: Check return value for errors. */ | |
75 | ||
76 | static off_t | |
77 | ulseek(int const fd, off_t const offset, int const whence) | |
78 | { | |
79 | off_t const w = lseek(fd, offset, whence); | |
80 | if ((off_t)-1 == w) { | |
81 | perror("lseek"); | |
82 | fail_file(); | |
83 | } | |
84 | return w; | |
85 | } | |
86 | ||
87 | static size_t | |
88 | uread(int const fd, void *const buf, size_t const count) | |
89 | { | |
90 | size_t const n = read(fd, buf, count); | |
91 | if (n != count) { | |
92 | perror("read"); | |
93 | fail_file(); | |
94 | } | |
95 | return n; | |
96 | } | |
97 | ||
98 | static size_t | |
99 | uwrite(int const fd, void const *const buf, size_t const count) | |
100 | { | |
101 | size_t const n = write(fd, buf, count); | |
102 | if (n != count) { | |
103 | perror("write"); | |
104 | fail_file(); | |
105 | } | |
106 | return n; | |
107 | } | |
108 | ||
109 | static void * | |
110 | umalloc(size_t size) | |
111 | { | |
112 | void *const addr = malloc(size); | |
113 | if (0 == addr) { | |
114 | fprintf(stderr, "malloc failed: %zu bytes\n", size); | |
115 | fail_file(); | |
116 | } | |
117 | return addr; | |
118 | } | |
119 | ||
120 | /* | |
121 | * Get the whole file as a programming convenience in order to avoid | |
122 | * malloc+lseek+read+free of many pieces. If successful, then mmap | |
123 | * avoids copying unused pieces; else just read the whole file. | |
124 | * Open for both read and write; new info will be appended to the file. | |
125 | * Use MAP_PRIVATE so that a few changes to the in-memory ElfXX_Ehdr | |
126 | * do not propagate to the file until an explicit overwrite at the last. | |
127 | * This preserves most aspects of consistency (all except .st_size) | |
128 | * for simultaneous readers of the file while we are appending to it. | |
129 | * However, multiple writers still are bad. We choose not to use | |
130 | * locking because it is expensive and the use case of kernel build | |
131 | * makes multiple writers unlikely. | |
132 | */ | |
133 | static void *mmap_file(char const *fname) | |
134 | { | |
135 | void *addr; | |
136 | ||
137 | fd_map = open(fname, O_RDWR); | |
138 | if (0 > fd_map || 0 > fstat(fd_map, &sb)) { | |
139 | perror(fname); | |
140 | fail_file(); | |
141 | } | |
142 | if (!S_ISREG(sb.st_mode)) { | |
143 | fprintf(stderr, "not a regular file: %s\n", fname); | |
144 | fail_file(); | |
145 | } | |
146 | addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, | |
147 | fd_map, 0); | |
148 | mmap_failed = 0; | |
149 | if (MAP_FAILED == addr) { | |
150 | mmap_failed = 1; | |
151 | addr = umalloc(sb.st_size); | |
152 | uread(fd_map, addr, sb.st_size); | |
153 | } | |
154 | return addr; | |
155 | } | |
156 | ||
157 | /* w8rev, w8nat, ...: Handle endianness. */ | |
158 | ||
159 | static uint64_t w8rev(uint64_t const x) | |
160 | { | |
161 | return ((0xff & (x >> (0 * 8))) << (7 * 8)) | |
162 | | ((0xff & (x >> (1 * 8))) << (6 * 8)) | |
163 | | ((0xff & (x >> (2 * 8))) << (5 * 8)) | |
164 | | ((0xff & (x >> (3 * 8))) << (4 * 8)) | |
165 | | ((0xff & (x >> (4 * 8))) << (3 * 8)) | |
166 | | ((0xff & (x >> (5 * 8))) << (2 * 8)) | |
167 | | ((0xff & (x >> (6 * 8))) << (1 * 8)) | |
168 | | ((0xff & (x >> (7 * 8))) << (0 * 8)); | |
169 | } | |
170 | ||
171 | static uint32_t w4rev(uint32_t const x) | |
172 | { | |
173 | return ((0xff & (x >> (0 * 8))) << (3 * 8)) | |
174 | | ((0xff & (x >> (1 * 8))) << (2 * 8)) | |
175 | | ((0xff & (x >> (2 * 8))) << (1 * 8)) | |
176 | | ((0xff & (x >> (3 * 8))) << (0 * 8)); | |
177 | } | |
178 | ||
179 | static uint32_t w2rev(uint16_t const x) | |
180 | { | |
181 | return ((0xff & (x >> (0 * 8))) << (1 * 8)) | |
182 | | ((0xff & (x >> (1 * 8))) << (0 * 8)); | |
183 | } | |
184 | ||
185 | static uint64_t w8nat(uint64_t const x) | |
186 | { | |
187 | return x; | |
188 | } | |
189 | ||
190 | static uint32_t w4nat(uint32_t const x) | |
191 | { | |
192 | return x; | |
193 | } | |
194 | ||
195 | static uint32_t w2nat(uint16_t const x) | |
196 | { | |
197 | return x; | |
198 | } | |
199 | ||
200 | static uint64_t (*w8)(uint64_t); | |
201 | static uint32_t (*w)(uint32_t); | |
202 | static uint32_t (*w2)(uint16_t); | |
203 | ||
204 | /* Names of the sections that could contain calls to mcount. */ | |
205 | static int | |
206 | is_mcounted_section_name(char const *const txtname) | |
207 | { | |
208 | return 0 == strcmp(".text", txtname) || | |
209 | 0 == strcmp(".sched.text", txtname) || | |
210 | 0 == strcmp(".spinlock.text", txtname) || | |
211 | 0 == strcmp(".irqentry.text", txtname) || | |
212 | 0 == strcmp(".text.unlikely", txtname); | |
213 | } | |
214 | ||
215 | /* Append the new shstrtab, Elf32_Shdr[], __mcount_loc and its relocations. */ | |
216 | static void append32(Elf32_Ehdr *const ehdr, | |
217 | Elf32_Shdr *const shstr, | |
218 | uint32_t const *const mloc0, | |
219 | uint32_t const *const mlocp, | |
220 | Elf32_Rel const *const mrel0, | |
221 | Elf32_Rel const *const mrelp, | |
222 | unsigned int const rel_entsize, | |
223 | unsigned int const symsec_sh_link) | |
224 | { | |
225 | /* Begin constructing output file */ | |
226 | Elf32_Shdr mcsec; | |
227 | char const *mc_name = (sizeof(Elf32_Rela) == rel_entsize) | |
228 | ? ".rela__mcount_loc" | |
229 | : ".rel__mcount_loc"; | |
230 | unsigned const old_shnum = w2(ehdr->e_shnum); | |
231 | uint32_t const old_shoff = w(ehdr->e_shoff); | |
232 | uint32_t const old_shstr_sh_size = w(shstr->sh_size); | |
233 | uint32_t const old_shstr_sh_offset = w(shstr->sh_offset); | |
234 | uint32_t t = 1 + strlen(mc_name) + w(shstr->sh_size); | |
235 | uint32_t new_e_shoff; | |
236 | ||
237 | shstr->sh_size = w(t); | |
238 | shstr->sh_offset = w(sb.st_size); | |
239 | t += sb.st_size; | |
240 | t += (3u & -t); /* 4-byte align */ | |
241 | new_e_shoff = t; | |
242 | ||
243 | /* body for new shstrtab */ | |
244 | ulseek(fd_map, sb.st_size, SEEK_SET); | |
245 | uwrite(fd_map, old_shstr_sh_offset + (void *)ehdr, old_shstr_sh_size); | |
246 | uwrite(fd_map, mc_name, 1 + strlen(mc_name)); | |
247 | ||
248 | /* old(modified) Elf32_Shdr table, 4-byte aligned */ | |
249 | ulseek(fd_map, t, SEEK_SET); | |
250 | t += sizeof(Elf32_Shdr) * old_shnum; | |
251 | uwrite(fd_map, old_shoff + (void *)ehdr, | |
252 | sizeof(Elf32_Shdr) * old_shnum); | |
253 | ||
254 | /* new sections __mcount_loc and .rel__mcount_loc */ | |
255 | t += 2*sizeof(mcsec); | |
256 | mcsec.sh_name = w((sizeof(Elf32_Rela) == rel_entsize) + strlen(".rel") | |
257 | + old_shstr_sh_size); | |
258 | mcsec.sh_type = w(SHT_PROGBITS); | |
259 | mcsec.sh_flags = w(SHF_ALLOC); | |
260 | mcsec.sh_addr = 0; | |
261 | mcsec.sh_offset = w(t); | |
262 | mcsec.sh_size = w((void *)mlocp - (void *)mloc0); | |
263 | mcsec.sh_link = 0; | |
264 | mcsec.sh_info = 0; | |
265 | mcsec.sh_addralign = w(4); | |
266 | mcsec.sh_entsize = w(4); | |
267 | uwrite(fd_map, &mcsec, sizeof(mcsec)); | |
268 | ||
269 | mcsec.sh_name = w(old_shstr_sh_size); | |
270 | mcsec.sh_type = (sizeof(Elf32_Rela) == rel_entsize) | |
271 | ? w(SHT_RELA) | |
272 | : w(SHT_REL); | |
273 | mcsec.sh_flags = 0; | |
274 | mcsec.sh_addr = 0; | |
275 | mcsec.sh_offset = w((void *)mlocp - (void *)mloc0 + t); | |
276 | mcsec.sh_size = w((void *)mrelp - (void *)mrel0); | |
277 | mcsec.sh_link = w(symsec_sh_link); | |
278 | mcsec.sh_info = w(old_shnum); | |
279 | mcsec.sh_addralign = w(4); | |
280 | mcsec.sh_entsize = w(rel_entsize); | |
281 | uwrite(fd_map, &mcsec, sizeof(mcsec)); | |
282 | ||
283 | uwrite(fd_map, mloc0, (void *)mlocp - (void *)mloc0); | |
284 | uwrite(fd_map, mrel0, (void *)mrelp - (void *)mrel0); | |
285 | ||
286 | ehdr->e_shoff = w(new_e_shoff); | |
287 | ehdr->e_shnum = w2(2 + w2(ehdr->e_shnum)); /* {.rel,}__mcount_loc */ | |
288 | ulseek(fd_map, 0, SEEK_SET); | |
289 | uwrite(fd_map, ehdr, sizeof(*ehdr)); | |
290 | } | |
291 | ||
292 | /* | |
293 | * append64 and append32 (and other analogous pairs) could be templated | |
294 | * using C++, but the complexity is high. (For an example, look at p_elf.h | |
295 | * in the source for UPX, http://upx.sourceforge.net) So: remember to make | |
296 | * the corresponding change in the routine for the other size. | |
297 | */ | |
298 | static void append64(Elf64_Ehdr *const ehdr, | |
299 | Elf64_Shdr *const shstr, | |
300 | uint64_t const *const mloc0, | |
301 | uint64_t const *const mlocp, | |
302 | Elf64_Rel const *const mrel0, | |
303 | Elf64_Rel const *const mrelp, | |
304 | unsigned int const rel_entsize, | |
305 | unsigned int const symsec_sh_link) | |
306 | { | |
307 | /* Begin constructing output file */ | |
308 | Elf64_Shdr mcsec; | |
309 | char const *mc_name = (sizeof(Elf64_Rela) == rel_entsize) | |
310 | ? ".rela__mcount_loc" | |
311 | : ".rel__mcount_loc"; | |
312 | unsigned const old_shnum = w2(ehdr->e_shnum); | |
313 | uint64_t const old_shoff = w8(ehdr->e_shoff); | |
314 | uint64_t const old_shstr_sh_size = w8(shstr->sh_size); | |
315 | uint64_t const old_shstr_sh_offset = w8(shstr->sh_offset); | |
316 | uint64_t t = 1 + strlen(mc_name) + w8(shstr->sh_size); | |
317 | uint64_t new_e_shoff; | |
318 | ||
319 | shstr->sh_size = w8(t); | |
320 | shstr->sh_offset = w8(sb.st_size); | |
321 | t += sb.st_size; | |
322 | t += (7u & -t); /* 8-byte align */ | |
323 | new_e_shoff = t; | |
324 | ||
325 | /* body for new shstrtab */ | |
326 | ulseek(fd_map, sb.st_size, SEEK_SET); | |
327 | uwrite(fd_map, old_shstr_sh_offset + (void *)ehdr, old_shstr_sh_size); | |
328 | uwrite(fd_map, mc_name, 1 + strlen(mc_name)); | |
329 | ||
330 | /* old(modified) Elf64_Shdr table, 8-byte aligned */ | |
331 | ulseek(fd_map, t, SEEK_SET); | |
332 | t += sizeof(Elf64_Shdr) * old_shnum; | |
333 | uwrite(fd_map, old_shoff + (void *)ehdr, | |
334 | sizeof(Elf64_Shdr) * old_shnum); | |
335 | ||
336 | /* new sections __mcount_loc and .rel__mcount_loc */ | |
337 | t += 2*sizeof(mcsec); | |
338 | mcsec.sh_name = w((sizeof(Elf64_Rela) == rel_entsize) + strlen(".rel") | |
339 | + old_shstr_sh_size); | |
340 | mcsec.sh_type = w(SHT_PROGBITS); | |
341 | mcsec.sh_flags = w8(SHF_ALLOC); | |
342 | mcsec.sh_addr = 0; | |
343 | mcsec.sh_offset = w8(t); | |
344 | mcsec.sh_size = w8((void *)mlocp - (void *)mloc0); | |
345 | mcsec.sh_link = 0; | |
346 | mcsec.sh_info = 0; | |
347 | mcsec.sh_addralign = w8(8); | |
348 | mcsec.sh_entsize = w8(8); | |
349 | uwrite(fd_map, &mcsec, sizeof(mcsec)); | |
350 | ||
351 | mcsec.sh_name = w(old_shstr_sh_size); | |
352 | mcsec.sh_type = (sizeof(Elf64_Rela) == rel_entsize) | |
353 | ? w(SHT_RELA) | |
354 | : w(SHT_REL); | |
355 | mcsec.sh_flags = 0; | |
356 | mcsec.sh_addr = 0; | |
357 | mcsec.sh_offset = w8((void *)mlocp - (void *)mloc0 + t); | |
358 | mcsec.sh_size = w8((void *)mrelp - (void *)mrel0); | |
359 | mcsec.sh_link = w(symsec_sh_link); | |
360 | mcsec.sh_info = w(old_shnum); | |
361 | mcsec.sh_addralign = w8(8); | |
362 | mcsec.sh_entsize = w8(rel_entsize); | |
363 | uwrite(fd_map, &mcsec, sizeof(mcsec)); | |
364 | ||
365 | uwrite(fd_map, mloc0, (void *)mlocp - (void *)mloc0); | |
366 | uwrite(fd_map, mrel0, (void *)mrelp - (void *)mrel0); | |
367 | ||
368 | ehdr->e_shoff = w8(new_e_shoff); | |
369 | ehdr->e_shnum = w2(2 + w2(ehdr->e_shnum)); /* {.rel,}__mcount_loc */ | |
370 | ulseek(fd_map, 0, SEEK_SET); | |
371 | uwrite(fd_map, ehdr, sizeof(*ehdr)); | |
372 | } | |
373 | ||
374 | /* | |
375 | * Look at the relocations in order to find the calls to mcount. | |
376 | * Accumulate the section offsets that are found, and their relocation info, | |
377 | * onto the end of the existing arrays. | |
378 | */ | |
379 | static uint32_t *sift32_rel_mcount(uint32_t *mlocp, | |
380 | unsigned const offbase, | |
381 | Elf32_Rel **const mrelpp, | |
382 | Elf32_Shdr const *const relhdr, | |
383 | Elf32_Ehdr const *const ehdr, | |
384 | unsigned const recsym, | |
385 | uint32_t const recval, | |
386 | unsigned const reltype) | |
387 | { | |
388 | uint32_t *const mloc0 = mlocp; | |
389 | Elf32_Rel *mrelp = *mrelpp; | |
390 | Elf32_Shdr *const shdr0 = (Elf32_Shdr *)(w(ehdr->e_shoff) | |
391 | + (void *)ehdr); | |
392 | unsigned const symsec_sh_link = w(relhdr->sh_link); | |
393 | Elf32_Shdr const *const symsec = &shdr0[symsec_sh_link]; | |
394 | Elf32_Sym const *const sym0 = (Elf32_Sym const *)(w(symsec->sh_offset) | |
395 | + (void *)ehdr); | |
396 | ||
397 | Elf32_Shdr const *const strsec = &shdr0[w(symsec->sh_link)]; | |
398 | char const *const str0 = (char const *)(w(strsec->sh_offset) | |
399 | + (void *)ehdr); | |
400 | ||
401 | Elf32_Rel const *const rel0 = (Elf32_Rel const *)(w(relhdr->sh_offset) | |
402 | + (void *)ehdr); | |
403 | unsigned rel_entsize = w(relhdr->sh_entsize); | |
404 | unsigned const nrel = w(relhdr->sh_size) / rel_entsize; | |
405 | Elf32_Rel const *relp = rel0; | |
406 | ||
407 | unsigned mcountsym = 0; | |
408 | unsigned t; | |
409 | ||
410 | for (t = nrel; t; --t) { | |
411 | if (!mcountsym) { | |
412 | Elf32_Sym const *const symp = | |
413 | &sym0[ELF32_R_SYM(w(relp->r_info))]; | |
414 | ||
415 | if (0 == strcmp((('_' == gpfx) ? "_mcount" : "mcount"), | |
416 | &str0[w(symp->st_name)])) | |
417 | mcountsym = ELF32_R_SYM(w(relp->r_info)); | |
418 | } | |
419 | if (mcountsym == ELF32_R_SYM(w(relp->r_info))) { | |
420 | uint32_t const addend = w(w(relp->r_offset) - recval); | |
421 | mrelp->r_offset = w(offbase | |
422 | + ((void *)mlocp - (void *)mloc0)); | |
423 | mrelp->r_info = w(ELF32_R_INFO(recsym, reltype)); | |
424 | if (sizeof(Elf32_Rela) == rel_entsize) { | |
425 | ((Elf32_Rela *)mrelp)->r_addend = addend; | |
426 | *mlocp++ = 0; | |
427 | } else | |
428 | *mlocp++ = addend; | |
429 | ||
430 | mrelp = (Elf32_Rel *)(rel_entsize + (void *)mrelp); | |
431 | } | |
432 | relp = (Elf32_Rel const *)(rel_entsize + (void *)relp); | |
433 | } | |
434 | *mrelpp = mrelp; | |
435 | return mlocp; | |
436 | } | |
437 | ||
438 | static uint64_t *sift64_rel_mcount(uint64_t *mlocp, | |
439 | unsigned const offbase, | |
440 | Elf64_Rel **const mrelpp, | |
441 | Elf64_Shdr const *const relhdr, | |
442 | Elf64_Ehdr const *const ehdr, | |
443 | unsigned const recsym, | |
444 | uint64_t const recval, | |
445 | unsigned const reltype) | |
446 | { | |
447 | uint64_t *const mloc0 = mlocp; | |
448 | Elf64_Rel *mrelp = *mrelpp; | |
449 | Elf64_Shdr *const shdr0 = (Elf64_Shdr *)(w8(ehdr->e_shoff) | |
450 | + (void *)ehdr); | |
451 | unsigned const symsec_sh_link = w(relhdr->sh_link); | |
452 | Elf64_Shdr const *const symsec = &shdr0[symsec_sh_link]; | |
453 | Elf64_Sym const *const sym0 = (Elf64_Sym const *)(w8(symsec->sh_offset) | |
454 | + (void *)ehdr); | |
455 | ||
456 | Elf64_Shdr const *const strsec = &shdr0[w(symsec->sh_link)]; | |
457 | char const *const str0 = (char const *)(w8(strsec->sh_offset) | |
458 | + (void *)ehdr); | |
459 | ||
460 | Elf64_Rel const *const rel0 = (Elf64_Rel const *)(w8(relhdr->sh_offset) | |
461 | + (void *)ehdr); | |
462 | unsigned rel_entsize = w8(relhdr->sh_entsize); | |
463 | unsigned const nrel = w8(relhdr->sh_size) / rel_entsize; | |
464 | Elf64_Rel const *relp = rel0; | |
465 | ||
466 | unsigned mcountsym = 0; | |
467 | unsigned t; | |
468 | ||
469 | for (t = nrel; 0 != t; --t) { | |
470 | if (!mcountsym) { | |
471 | Elf64_Sym const *const symp = | |
472 | &sym0[ELF64_R_SYM(w8(relp->r_info))]; | |
473 | char const *symname = &str0[w(symp->st_name)]; | |
474 | ||
475 | if ('.' == symname[0]) | |
476 | ++symname; /* ppc64 hack */ | |
477 | if (0 == strcmp((('_' == gpfx) ? "_mcount" : "mcount"), | |
478 | symname)) | |
479 | mcountsym = ELF64_R_SYM(w8(relp->r_info)); | |
480 | } | |
481 | ||
482 | if (mcountsym == ELF64_R_SYM(w8(relp->r_info))) { | |
483 | uint64_t const addend = w8(w8(relp->r_offset) - recval); | |
484 | ||
485 | mrelp->r_offset = w8(offbase | |
486 | + ((void *)mlocp - (void *)mloc0)); | |
487 | mrelp->r_info = w8(ELF64_R_INFO(recsym, reltype)); | |
488 | if (sizeof(Elf64_Rela) == rel_entsize) { | |
489 | ((Elf64_Rela *)mrelp)->r_addend = addend; | |
490 | *mlocp++ = 0; | |
491 | } else | |
492 | *mlocp++ = addend; | |
493 | ||
494 | mrelp = (Elf64_Rel *)(rel_entsize + (void *)mrelp); | |
495 | } | |
496 | relp = (Elf64_Rel const *)(rel_entsize + (void *)relp); | |
497 | } | |
498 | *mrelpp = mrelp; | |
499 | ||
500 | return mlocp; | |
501 | } | |
502 | ||
503 | /* | |
504 | * Find a symbol in the given section, to be used as the base for relocating | |
505 | * the table of offsets of calls to mcount. A local or global symbol suffices, | |
506 | * but avoid a Weak symbol because it may be overridden; the change in value | |
507 | * would invalidate the relocations of the offsets of the calls to mcount. | |
508 | * Often the found symbol will be the unnamed local symbol generated by | |
509 | * GNU 'as' for the start of each section. For example: | |
510 | * Num: Value Size Type Bind Vis Ndx Name | |
511 | * 2: 00000000 0 SECTION LOCAL DEFAULT 1 | |
512 | */ | |
513 | static unsigned find32_secsym_ndx(unsigned const txtndx, | |
514 | char const *const txtname, | |
515 | uint32_t *const recvalp, | |
516 | Elf32_Shdr const *const symhdr, | |
517 | Elf32_Ehdr const *const ehdr) | |
518 | { | |
519 | Elf32_Sym const *const sym0 = (Elf32_Sym const *)(w(symhdr->sh_offset) | |
520 | + (void *)ehdr); | |
521 | unsigned const nsym = w(symhdr->sh_size) / w(symhdr->sh_entsize); | |
522 | Elf32_Sym const *symp; | |
523 | unsigned t; | |
524 | ||
525 | for (symp = sym0, t = nsym; t; --t, ++symp) { | |
526 | unsigned int const st_bind = ELF32_ST_BIND(symp->st_info); | |
527 | ||
528 | if (txtndx == w2(symp->st_shndx) | |
529 | /* avoid STB_WEAK */ | |
530 | && (STB_LOCAL == st_bind || STB_GLOBAL == st_bind)) { | |
531 | *recvalp = w(symp->st_value); | |
532 | return symp - sym0; | |
533 | } | |
534 | } | |
535 | fprintf(stderr, "Cannot find symbol for section %d: %s.\n", | |
536 | txtndx, txtname); | |
537 | fail_file(); | |
538 | } | |
539 | ||
540 | static unsigned find64_secsym_ndx(unsigned const txtndx, | |
541 | char const *const txtname, | |
542 | uint64_t *const recvalp, | |
543 | Elf64_Shdr const *const symhdr, | |
544 | Elf64_Ehdr const *const ehdr) | |
545 | { | |
546 | Elf64_Sym const *const sym0 = (Elf64_Sym const *)(w8(symhdr->sh_offset) | |
547 | + (void *)ehdr); | |
548 | unsigned const nsym = w8(symhdr->sh_size) / w8(symhdr->sh_entsize); | |
549 | Elf64_Sym const *symp; | |
550 | unsigned t; | |
551 | ||
552 | for (symp = sym0, t = nsym; t; --t, ++symp) { | |
553 | unsigned int const st_bind = ELF64_ST_BIND(symp->st_info); | |
554 | ||
555 | if (txtndx == w2(symp->st_shndx) | |
556 | /* avoid STB_WEAK */ | |
557 | && (STB_LOCAL == st_bind || STB_GLOBAL == st_bind)) { | |
558 | *recvalp = w8(symp->st_value); | |
559 | return symp - sym0; | |
560 | } | |
561 | } | |
562 | fprintf(stderr, "Cannot find symbol for section %d: %s.\n", | |
563 | txtndx, txtname); | |
564 | fail_file(); | |
565 | } | |
566 | ||
567 | /* | |
568 | * Evade ISO C restriction: no declaration after statement in | |
569 | * has32_rel_mcount. | |
570 | */ | |
571 | static char const * | |
572 | __has32_rel_mcount(Elf32_Shdr const *const relhdr, /* is SHT_REL or SHT_RELA */ | |
573 | Elf32_Shdr const *const shdr0, | |
574 | char const *const shstrtab, | |
575 | char const *const fname) | |
576 | { | |
577 | /* .sh_info depends on .sh_type == SHT_REL[,A] */ | |
578 | Elf32_Shdr const *const txthdr = &shdr0[w(relhdr->sh_info)]; | |
579 | char const *const txtname = &shstrtab[w(txthdr->sh_name)]; | |
580 | ||
581 | if (0 == strcmp("__mcount_loc", txtname)) { | |
582 | fprintf(stderr, "warning: __mcount_loc already exists: %s\n", | |
583 | fname); | |
584 | succeed_file(); | |
585 | } | |
586 | if (SHT_PROGBITS != w(txthdr->sh_type) || | |
587 | !is_mcounted_section_name(txtname)) | |
588 | return NULL; | |
589 | return txtname; | |
590 | } | |
591 | ||
592 | static char const *has32_rel_mcount(Elf32_Shdr const *const relhdr, | |
593 | Elf32_Shdr const *const shdr0, | |
594 | char const *const shstrtab, | |
595 | char const *const fname) | |
596 | { | |
597 | if (SHT_REL != w(relhdr->sh_type) && SHT_RELA != w(relhdr->sh_type)) | |
598 | return NULL; | |
599 | return __has32_rel_mcount(relhdr, shdr0, shstrtab, fname); | |
600 | } | |
601 | ||
602 | static char const *__has64_rel_mcount(Elf64_Shdr const *const relhdr, | |
603 | Elf64_Shdr const *const shdr0, | |
604 | char const *const shstrtab, | |
605 | char const *const fname) | |
606 | { | |
607 | /* .sh_info depends on .sh_type == SHT_REL[,A] */ | |
608 | Elf64_Shdr const *const txthdr = &shdr0[w(relhdr->sh_info)]; | |
609 | char const *const txtname = &shstrtab[w(txthdr->sh_name)]; | |
610 | ||
611 | if (0 == strcmp("__mcount_loc", txtname)) { | |
612 | fprintf(stderr, "warning: __mcount_loc already exists: %s\n", | |
613 | fname); | |
614 | succeed_file(); | |
615 | } | |
616 | if (SHT_PROGBITS != w(txthdr->sh_type) || | |
617 | !is_mcounted_section_name(txtname)) | |
618 | return NULL; | |
619 | return txtname; | |
620 | } | |
621 | ||
622 | static char const *has64_rel_mcount(Elf64_Shdr const *const relhdr, | |
623 | Elf64_Shdr const *const shdr0, | |
624 | char const *const shstrtab, | |
625 | char const *const fname) | |
626 | { | |
627 | if (SHT_REL != w(relhdr->sh_type) && SHT_RELA != w(relhdr->sh_type)) | |
628 | return NULL; | |
629 | return __has64_rel_mcount(relhdr, shdr0, shstrtab, fname); | |
630 | } | |
631 | ||
632 | static unsigned tot32_relsize(Elf32_Shdr const *const shdr0, | |
633 | unsigned nhdr, | |
634 | const char *const shstrtab, | |
635 | const char *const fname) | |
636 | { | |
637 | unsigned totrelsz = 0; | |
638 | Elf32_Shdr const *shdrp = shdr0; | |
639 | for (; 0 != nhdr; --nhdr, ++shdrp) { | |
640 | if (has32_rel_mcount(shdrp, shdr0, shstrtab, fname)) | |
641 | totrelsz += w(shdrp->sh_size); | |
642 | } | |
643 | return totrelsz; | |
644 | } | |
645 | ||
646 | static unsigned tot64_relsize(Elf64_Shdr const *const shdr0, | |
647 | unsigned nhdr, | |
648 | const char *const shstrtab, | |
649 | const char *const fname) | |
650 | { | |
651 | unsigned totrelsz = 0; | |
652 | Elf64_Shdr const *shdrp = shdr0; | |
653 | ||
654 | for (; nhdr; --nhdr, ++shdrp) { | |
655 | if (has64_rel_mcount(shdrp, shdr0, shstrtab, fname)) | |
656 | totrelsz += w8(shdrp->sh_size); | |
657 | } | |
658 | return totrelsz; | |
659 | } | |
660 | ||
661 | /* Overall supervision for Elf32 ET_REL file. */ | |
662 | static void | |
663 | do32(Elf32_Ehdr *const ehdr, char const *const fname, unsigned const reltype) | |
664 | { | |
665 | Elf32_Shdr *const shdr0 = (Elf32_Shdr *)(w(ehdr->e_shoff) | |
666 | + (void *)ehdr); | |
667 | unsigned const nhdr = w2(ehdr->e_shnum); | |
668 | Elf32_Shdr *const shstr = &shdr0[w2(ehdr->e_shstrndx)]; | |
669 | char const *const shstrtab = (char const *)(w(shstr->sh_offset) | |
670 | + (void *)ehdr); | |
671 | ||
672 | Elf32_Shdr const *relhdr; | |
673 | unsigned k; | |
674 | ||
675 | /* Upper bound on space: assume all relevant relocs are for mcount. */ | |
676 | unsigned const totrelsz = tot32_relsize(shdr0, nhdr, shstrtab, fname); | |
677 | Elf32_Rel *const mrel0 = umalloc(totrelsz); | |
678 | Elf32_Rel * mrelp = mrel0; | |
679 | ||
680 | /* 2*sizeof(address) <= sizeof(Elf32_Rel) */ | |
681 | uint32_t *const mloc0 = umalloc(totrelsz>>1); | |
682 | uint32_t * mlocp = mloc0; | |
683 | ||
684 | unsigned rel_entsize = 0; | |
685 | unsigned symsec_sh_link = 0; | |
686 | ||
687 | for (relhdr = shdr0, k = nhdr; k; --k, ++relhdr) { | |
688 | char const *const txtname = has32_rel_mcount(relhdr, shdr0, | |
689 | shstrtab, fname); | |
690 | if (txtname) { | |
691 | uint32_t recval = 0; | |
692 | unsigned const recsym = find32_secsym_ndx( | |
693 | w(relhdr->sh_info), txtname, &recval, | |
694 | &shdr0[symsec_sh_link = w(relhdr->sh_link)], | |
695 | ehdr); | |
696 | ||
697 | rel_entsize = w(relhdr->sh_entsize); | |
698 | mlocp = sift32_rel_mcount(mlocp, | |
699 | (void *)mlocp - (void *)mloc0, &mrelp, | |
700 | relhdr, ehdr, recsym, recval, reltype); | |
701 | } | |
702 | } | |
703 | if (mloc0 != mlocp) { | |
704 | append32(ehdr, shstr, mloc0, mlocp, mrel0, mrelp, | |
705 | rel_entsize, symsec_sh_link); | |
706 | } | |
707 | free(mrel0); | |
708 | free(mloc0); | |
709 | } | |
710 | ||
711 | static void | |
712 | do64(Elf64_Ehdr *const ehdr, char const *const fname, unsigned const reltype) | |
713 | { | |
714 | Elf64_Shdr *const shdr0 = (Elf64_Shdr *)(w8(ehdr->e_shoff) | |
715 | + (void *)ehdr); | |
716 | unsigned const nhdr = w2(ehdr->e_shnum); | |
717 | Elf64_Shdr *const shstr = &shdr0[w2(ehdr->e_shstrndx)]; | |
718 | char const *const shstrtab = (char const *)(w8(shstr->sh_offset) | |
719 | + (void *)ehdr); | |
720 | ||
721 | Elf64_Shdr const *relhdr; | |
722 | unsigned k; | |
723 | ||
724 | /* Upper bound on space: assume all relevant relocs are for mcount. */ | |
725 | unsigned const totrelsz = tot64_relsize(shdr0, nhdr, shstrtab, fname); | |
726 | Elf64_Rel *const mrel0 = umalloc(totrelsz); | |
727 | Elf64_Rel * mrelp = mrel0; | |
728 | ||
729 | /* 2*sizeof(address) <= sizeof(Elf64_Rel) */ | |
730 | uint64_t *const mloc0 = umalloc(totrelsz>>1); | |
731 | uint64_t * mlocp = mloc0; | |
732 | ||
733 | unsigned rel_entsize = 0; | |
734 | unsigned symsec_sh_link = 0; | |
735 | ||
736 | for ((relhdr = shdr0), k = nhdr; k; --k, ++relhdr) { | |
737 | char const *const txtname = has64_rel_mcount(relhdr, shdr0, | |
738 | shstrtab, fname); | |
739 | if (txtname) { | |
740 | uint64_t recval = 0; | |
741 | unsigned const recsym = find64_secsym_ndx( | |
742 | w(relhdr->sh_info), txtname, &recval, | |
743 | &shdr0[symsec_sh_link = w(relhdr->sh_link)], | |
744 | ehdr); | |
745 | ||
746 | rel_entsize = w8(relhdr->sh_entsize); | |
747 | mlocp = sift64_rel_mcount(mlocp, | |
748 | (void *)mlocp - (void *)mloc0, &mrelp, | |
749 | relhdr, ehdr, recsym, recval, reltype); | |
750 | } | |
751 | } | |
752 | if (mloc0 != mlocp) { | |
753 | append64(ehdr, shstr, mloc0, mlocp, mrel0, mrelp, | |
754 | rel_entsize, symsec_sh_link); | |
755 | } | |
756 | free(mrel0); | |
757 | free(mloc0); | |
758 | } | |
759 | ||
760 | static void | |
761 | do_file(char const *const fname) | |
762 | { | |
763 | Elf32_Ehdr *const ehdr = mmap_file(fname); | |
764 | unsigned int reltype = 0; | |
765 | ||
766 | ehdr_curr = ehdr; | |
767 | w = w4nat; | |
768 | w2 = w2nat; | |
769 | w8 = w8nat; | |
770 | switch (ehdr->e_ident[EI_DATA]) { | |
771 | static unsigned int const endian = 1; | |
772 | default: { | |
773 | fprintf(stderr, "unrecognized ELF data encoding %d: %s\n", | |
774 | ehdr->e_ident[EI_DATA], fname); | |
775 | fail_file(); | |
776 | } break; | |
777 | case ELFDATA2LSB: { | |
778 | if (1 != *(unsigned char const *)&endian) { | |
779 | /* main() is big endian, file.o is little endian. */ | |
780 | w = w4rev; | |
781 | w2 = w2rev; | |
782 | w8 = w8rev; | |
783 | } | |
784 | } break; | |
785 | case ELFDATA2MSB: { | |
786 | if (0 != *(unsigned char const *)&endian) { | |
787 | /* main() is little endian, file.o is big endian. */ | |
788 | w = w4rev; | |
789 | w2 = w2rev; | |
790 | w8 = w8rev; | |
791 | } | |
792 | } break; | |
793 | } /* end switch */ | |
794 | if (0 != memcmp(ELFMAG, ehdr->e_ident, SELFMAG) | |
795 | || ET_REL != w2(ehdr->e_type) | |
796 | || EV_CURRENT != ehdr->e_ident[EI_VERSION]) { | |
797 | fprintf(stderr, "unrecognized ET_REL file %s\n", fname); | |
798 | fail_file(); | |
799 | } | |
800 | ||
801 | gpfx = 0; | |
802 | switch (w2(ehdr->e_machine)) { | |
803 | default: { | |
804 | fprintf(stderr, "unrecognized e_machine %d %s\n", | |
805 | w2(ehdr->e_machine), fname); | |
806 | fail_file(); | |
807 | } break; | |
808 | case EM_386: reltype = R_386_32; break; | |
809 | case EM_ARM: reltype = R_ARM_ABS32; break; | |
810 | case EM_IA_64: reltype = R_IA64_IMM64; gpfx = '_'; break; | |
811 | case EM_PPC: reltype = R_PPC_ADDR32; gpfx = '_'; break; | |
812 | case EM_PPC64: reltype = R_PPC64_ADDR64; gpfx = '_'; break; | |
813 | case EM_S390: /* reltype: e_class */ gpfx = '_'; break; | |
814 | case EM_SH: reltype = R_SH_DIR32; break; | |
815 | case EM_SPARCV9: reltype = R_SPARC_64; gpfx = '_'; break; | |
816 | case EM_X86_64: reltype = R_X86_64_64; break; | |
817 | } /* end switch */ | |
818 | ||
819 | switch (ehdr->e_ident[EI_CLASS]) { | |
820 | default: { | |
821 | fprintf(stderr, "unrecognized ELF class %d %s\n", | |
822 | ehdr->e_ident[EI_CLASS], fname); | |
823 | fail_file(); | |
824 | } break; | |
825 | case ELFCLASS32: { | |
826 | if (sizeof(Elf32_Ehdr) != w2(ehdr->e_ehsize) | |
827 | || sizeof(Elf32_Shdr) != w2(ehdr->e_shentsize)) { | |
828 | fprintf(stderr, | |
829 | "unrecognized ET_REL file: %s\n", fname); | |
830 | fail_file(); | |
831 | } | |
832 | if (EM_S390 == w2(ehdr->e_machine)) | |
833 | reltype = R_390_32; | |
834 | do32(ehdr, fname, reltype); | |
835 | } break; | |
836 | case ELFCLASS64: { | |
837 | Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr; | |
838 | if (sizeof(Elf64_Ehdr) != w2(ghdr->e_ehsize) | |
839 | || sizeof(Elf64_Shdr) != w2(ghdr->e_shentsize)) { | |
840 | fprintf(stderr, | |
841 | "unrecognized ET_REL file: %s\n", fname); | |
842 | fail_file(); | |
843 | } | |
844 | if (EM_S390 == w2(ghdr->e_machine)) | |
845 | reltype = R_390_64; | |
846 | do64(ghdr, fname, reltype); | |
847 | } break; | |
848 | } /* end switch */ | |
849 | ||
850 | cleanup(); | |
851 | } | |
852 | ||
853 | int | |
854 | main(int argc, char const *argv[]) | |
855 | { | |
856 | int n_error = 0; /* gcc-4.3.0 false positive complaint */ | |
857 | if (argc <= 1) | |
858 | fprintf(stderr, "usage: recordmcount file.o...\n"); | |
859 | else /* Process each file in turn, allowing deep failure. */ | |
860 | for (--argc, ++argv; 0 < argc; --argc, ++argv) { | |
861 | int const sjval = setjmp(jmpenv); | |
862 | switch (sjval) { | |
863 | default: { | |
864 | fprintf(stderr, "internal error: %s\n", argv[0]); | |
865 | exit(1); | |
866 | } break; | |
867 | case SJ_SETJMP: { /* normal sequence */ | |
868 | /* Avoid problems if early cleanup() */ | |
869 | fd_map = -1; | |
870 | ehdr_curr = NULL; | |
871 | mmap_failed = 1; | |
872 | do_file(argv[0]); | |
873 | } break; | |
874 | case SJ_FAIL: { /* error in do_file or below */ | |
875 | ++n_error; | |
876 | } break; | |
877 | case SJ_SUCCEED: { /* premature success */ | |
878 | /* do nothing */ | |
879 | } break; | |
880 | } /* end switch */ | |
881 | } | |
882 | return !!n_error; | |
883 | } | |
884 | ||
885 |