blob: 6bf7e749225e42c1cf43270b1240a687c6db9ccc [file] [log] [blame]
Ulrich Drepperb08d5a82005-07-26 05:00:05 +00001/* Compare relevant content of two ELF files.
2 Copyright (C) 2005 Red Hat, Inc.
3 Written by Ulrich Drepper <drepper@redhat.com>, 2005.
4
5 This program is Open Source software; you can redistribute it and/or
6 modify it under the terms of the Open Software License version 1.0 as
7 published by the Open Source Initiative.
8
9 You should have received a copy of the Open Software License along
10 with this program; if not, you may obtain a copy of the Open Software
11 License version 1.0 from http://www.opensource.org/licenses/osl.php or
12 by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
13 3001 King Ranch Road, Ukiah, CA 95482. */
14
15#ifdef HAVE_CONFIG_H
16# include <config.h>
17#endif
18
19#include <argp.h>
20#include <assert.h>
21#include <errno.h>
22#include <error.h>
23#include <fcntl.h>
24#include <libintl.h>
25#include <stdbool.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <unistd.h>
30
31#include "../libelf/elf-knowledge.h"
32#include "../libebl/libeblP.h"
33
34
35/* Prototypes of local functions. */
36static Elf *open_file (const char *fname, int *fdp, Ebl **eblp);
37static bool search_for_copy_reloc (Ebl *ebl, size_t scnndx, int symndx);
38static int regioncompare (const void *p1, const void *p2);
39
40
41/* Name and version of program. */
42static void print_version (FILE *stream, struct argp_state *state);
43void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
44
45/* Bug report address. */
46const char *argp_program_bug_address = PACKAGE_BUGREPORT;
47
48/* Values for the parameters which have no short form. */
49#define OPT_GAPS 0x100
50
51/* Definitions of arguments for argp functions. */
52static const struct argp_option options[] =
53{
54 { NULL, 0, NULL, 0, N_("Control options:"), 0 },
55 { "gaps", OPT_GAPS, "ACTION", 0, N_("Control treatment of gaps in loadable segments [ignore|match] (default: ignore)"), 0 },
56 { "quiet", 'q', NULL, 0, N_("Output nothing; yield exit status only"), 0 },
57
58 { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 },
59 { NULL, 0, NULL, 0, NULL, 0 }
60};
61
62/* Short description of program. */
63static const char doc[] = N_("\
64Compare relevant parts of two ELF files for equality.");
65
66/* Strings for arguments in help texts. */
67static const char args_doc[] = N_("FILE1 FILE2");
68
69/* Prototype for option handler. */
70static error_t parse_opt (int key, char *arg, struct argp_state *state);
71
72/* Data structure to communicate with argp functions. */
73static struct argp argp =
74{
75 options, parse_opt, args_doc, doc, NULL, NULL, NULL
76};
77
78
79/* How to treat gaps in loadable segments. */
80static enum
81 {
82 gaps_ignore = 0,
83 gaps_match
84 }
85 gaps;
86
87/* Structure to hold information about used regions. */
88struct region
89{
90 GElf_Addr from;
91 GElf_Addr to;
92 struct region *next;
93};
94
95/* Nonzero if only exit status is wanted. */
96static bool quiet;
97
98
99int
100main (int argc, char *argv[])
101{
102 /* Set locale. */
103 (void) setlocale (LC_ALL, "");
104
105 /* Make sure the message catalog can be found. */
106 (void) bindtextdomain (PACKAGE, LOCALEDIR);
107
108 /* Initialize the message catalog. */
109 (void) textdomain (PACKAGE);
110
111 /* Parse and process arguments. */
112 int remaining;
113 (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
114
115 /* We expect exactly two non-option parameters. */
116 if (remaining + 2 != argc)
117 {
118 fputs (gettext ("Invalid number of parameters.\n"), stderr);
119 argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name);
120 exit (1);
121 }
122
123 /* Comparing the files is done in two phases:
124 1. compare all sections. Sections which are irrelevant (i.e., if
125 strip would remove them) are ignored. Some section types are
126 handled special.
127 2. all parts of the loadable segments which are not parts of any
128 section is compared according to the rules of the --gaps option.
129 */
130 int result = 0;
131 elf_version (EV_CURRENT);
132
133 const char *const fname1 = argv[remaining];
134 int fd1;
135 Ebl *ebl1;
136 Elf *elf1 = open_file (fname1, &fd1, &ebl1);
137
138 const char *const fname2 = argv[remaining + 1];
139 int fd2;
140 Ebl *ebl2;
141 Elf *elf2 = open_file (fname2, &fd2, &ebl2);
142
143 GElf_Ehdr ehdr1_mem;
144 GElf_Ehdr *ehdr1 = gelf_getehdr (elf1, &ehdr1_mem);
145 if (ehdr1 == NULL)
146 error (EXIT_FAILURE, 0, gettext ("cannot get ELF header of \"%s\": %s"),
147 fname1, elf_errmsg (-1));
148 GElf_Ehdr ehdr2_mem;
149 GElf_Ehdr *ehdr2 = gelf_getehdr (elf2, &ehdr2_mem);
150 if (ehdr2 == NULL)
151 error (EXIT_FAILURE, 0, gettext ("cannot get ELF header of \"%s\": %s"),
152 fname2, elf_errmsg (-1));
153
154 /* Compare the ELF headers. */
155 if (memcmp (ehdr1->e_ident, ehdr2->e_ident, EI_NIDENT) != 0
156 || ehdr1->e_type != ehdr2->e_type
157 || ehdr1->e_machine != ehdr2->e_machine
158 || ehdr1->e_version != ehdr2->e_version
159 || ehdr1->e_entry != ehdr2->e_entry
160 || ehdr1->e_phoff != ehdr2->e_phoff
161 || ehdr1->e_flags != ehdr2->e_flags
162 || ehdr1->e_ehsize != ehdr2->e_ehsize
163 || ehdr1->e_phentsize != ehdr2->e_phentsize
164 || ehdr1->e_phnum != ehdr2->e_phnum
165 || ehdr1->e_shentsize != ehdr2->e_shentsize
166 || ehdr1->e_shnum != ehdr2->e_shnum
167 || ehdr1->e_shstrndx != ehdr2->e_shstrndx)
168 {
169 if (! quiet)
170 error (0, 0, gettext ("%s %s diff: ELF header"), fname1, fname2);
171 result = 1;
172 goto out;
173 }
174
175 /* Iterate over all sections. We expect the sections in the two
176 files to match exactly. */
177 Elf_Scn *scn1 = NULL;
178 Elf_Scn *scn2 = NULL;
179 struct region *regions = NULL;
180 size_t nregions = 0;
181 while (1)
182 {
183 GElf_Shdr shdr1_mem;
184 GElf_Shdr *shdr1;
185 const char *sname1 = NULL;
186 do
187 {
188 scn1 = elf_nextscn (elf1, scn1);
189 shdr1 = gelf_getshdr (scn1, &shdr1_mem);
190 if (shdr1 != NULL)
191 sname1 = elf_strptr (elf1, ehdr1->e_shstrndx, shdr1->sh_name);
192 }
193 while (scn1 != NULL
194 && ebl_section_strip_p (ebl1, ehdr1, shdr1, sname1, true, false));
195
196 GElf_Shdr shdr2_mem;
197 GElf_Shdr *shdr2;
198 const char *sname2 = NULL;
199 do
200 {
201 scn2 = elf_nextscn (elf2, scn2);
202 shdr2 = gelf_getshdr (scn2, &shdr2_mem);
203 if (shdr2 != NULL)
204 sname2 = elf_strptr (elf2, ehdr2->e_shstrndx, shdr2->sh_name);
205 }
206 while (scn2 != NULL
207 && ebl_section_strip_p (ebl2, ehdr2, shdr2, sname2, true, false));
208
209 if (scn1 == NULL || scn2 == NULL)
210 break;
211
212 if (gaps != gaps_ignore && (shdr1->sh_flags & SHF_ALLOC) != 0)
213 {
214 struct region *newp = (struct region *) alloca (sizeof (*newp));
215 newp->from = shdr1->sh_offset;
216 newp->to = shdr1->sh_offset + shdr1->sh_size;
217 newp->next = regions;
218 regions = newp;
219
220 ++nregions;
221 }
222
223 /* Compare the headers. We allow the name to be at a different
224 location. */
225 if (strcmp (sname1, sname2) != 0)
226 {
227 header_mismatch:
228 error (0, 0, gettext ("%s %s differ: section header"),
229 fname1, fname2);
230 result = 1;
231 goto out;
232 }
233
234 /* We ignore certain sections. */
235 if (strcmp (sname1, ".gnu_debuglink") == 0
236 || strcmp (sname1, ".gnu.prelink_undo") == 0)
237 continue;
238
239 if (shdr1->sh_type != shdr2->sh_type
240 // XXX Any flags which should be ignored?
241 || shdr1->sh_flags != shdr2->sh_flags
242 || shdr1->sh_addr != shdr2->sh_addr
243 || shdr1->sh_offset != shdr2->sh_offset
244 || shdr1->sh_size != shdr2->sh_size
245 || shdr1->sh_link != shdr2->sh_link
246 || shdr1->sh_info != shdr2->sh_info
247 || shdr1->sh_addralign != shdr2->sh_addralign
248 || shdr1->sh_entsize != shdr2->sh_entsize)
249 goto header_mismatch;
250
251 Elf_Data *data1 = elf_getdata (scn1, NULL);
252 if (data1 == NULL)
253 error (EXIT_FAILURE, 0,
254 gettext ("cannot get content of section %zu in \"%s\": %s"),
255 elf_ndxscn (scn1), fname1, elf_errmsg (-1));
256
257 Elf_Data *data2 = elf_getdata (scn2, NULL);
258 if (data2 == NULL)
259 error (EXIT_FAILURE, 0,
260 gettext ("cannot get content of section %zu in \"%s\": %s"),
261 elf_ndxscn (scn2), fname2, elf_errmsg (-1));
262
263 switch (shdr1->sh_type)
264 {
265 case SHT_DYNSYM:
266 case SHT_SYMTAB:
267 /* Iterate over the symbol table. We ignore the st_size
268 value of undefined symbols. */
269 for (int ndx = 0; ndx < (int) (shdr1->sh_size / shdr1->sh_entsize);
270 ++ndx)
271 {
272 GElf_Sym sym1_mem;
273 GElf_Sym *sym1 = gelf_getsym (data1, ndx, &sym1_mem);
274 if (sym1 == NULL)
275 error (EXIT_FAILURE, 0,
276 gettext ("cannot get symbol in \"%s\": %s"),
277 fname1, elf_errmsg (-1));
278 GElf_Sym sym2_mem;
279 GElf_Sym *sym2 = gelf_getsym (data2, ndx, &sym2_mem);
280 if (sym2 == NULL)
281 error (EXIT_FAILURE, 0,
282 gettext ("cannot get symbol in \"%s\": %s"),
283 fname2, elf_errmsg (-1));
284
285 const char *name1 = elf_strptr (elf1, shdr1->sh_link,
286 sym1->st_name);
287 const char *name2 = elf_strptr (elf2, shdr2->sh_link,
288 sym2->st_name);
289 if (strcmp (name1, name2) != 0
290 || sym1->st_value != sym2->st_value
291 || (sym1->st_size != sym2->st_size
292 && sym1->st_shndx != SHN_UNDEF)
293 || sym1->st_info != sym2->st_info
294 || sym1->st_other != sym2->st_other
295 || sym1->st_shndx != sym1->st_shndx)
296 {
297 // XXX Do we want to allow reordered symbol tables?
298 if (! quiet)
299 error (0, 0, gettext ("%s %s differ: symbol table"),
300 fname1, fname2);
301 result = 1;
302 goto out;
303 }
304
305 if (sym1->st_shndx == SHN_UNDEF
306 && sym1->st_size != sym2->st_size)
307 {
308 /* The size of the symbol in the object defining it
309 might have changed. That is OK unless the symbol
310 is used in a copy relocation. Look over the
311 sections in both files and determine which
312 relocation section uses this symbol table
313 section. Then look through the relocations to
314 see whether any copy relocation references this
315 symbol. */
316 if (search_for_copy_reloc (ebl1, elf_ndxscn (scn1), ndx)
317 || search_for_copy_reloc (ebl2, elf_ndxscn (scn2), ndx))
318 {
319 if (! quiet)
320 error (0, 0, gettext ("%s %s differ: symbol table"),
321 fname1, fname2);
322 result = 1;
323 goto out;
324 }
325 }
326 }
327 break;
328
329 default:
330 /* Compare the section content byte for byte. */
331 assert (shdr1->sh_type == SHT_NOBITS
332 || (data1->d_buf != NULL || data1->d_size == 0));
333 assert (shdr2->sh_type == SHT_NOBITS
334 || (data2->d_buf != NULL || data1->d_size == 0));
335
336 if (data1->d_size != data2->d_size
337 || (shdr1->sh_type != SHT_NOBITS
338 && memcmp (data1->d_buf, data2->d_buf, data1->d_size) != 0))
339 {
340 if (! quiet)
341 error (0, 0, gettext ("%s %s differ: section content"),
342 fname1, fname2);
343 result = 1;
344 goto out;
345 }
346 break;
347 }
348 }
349
350 if (scn1 != scn2)
351 {
352 if (! quiet)
353 error (0, 0,
354 gettext ("%s %s differ: unequal amount of important sections"),
355 fname1, fname2);
356 result = 1;
357 goto out;
358 }
359
360 /* We we look at gaps, create artificial ones for the parts of the
361 program which we are not in sections. */
362 struct region ehdr_region;
363 struct region phdr_region;
364 if (gaps != gaps_ignore)
365 {
366 ehdr_region.from = 0;
367 ehdr_region.to = ehdr1->e_ehsize;
368 ehdr_region.next = &phdr_region;
369
370 phdr_region.from = ehdr1->e_phoff;
371 phdr_region.to = ehdr1->e_phoff + ehdr1->e_phnum * ehdr1->e_phentsize;
372 phdr_region.next = regions;
373
374 regions = &ehdr_region;
375 nregions += 2;
376 }
377
378 /* If we need to look at the gaps we need access to the file data. */
379 char *raw1 = NULL;
380 size_t size1 = 0;
381 char *raw2 = NULL;
382 size_t size2 = 0;
383 struct region *regionsarr = alloca (nregions * sizeof (struct region));
384 if (gaps != gaps_ignore)
385 {
386 raw1 = elf_rawfile (elf1, &size1);
387 if (raw1 == NULL )
388 error (EXIT_FAILURE, 0, gettext ("cannot load data of \"%s\": %s"),
389 fname1, elf_errmsg (-1));
390
391 raw2 = elf_rawfile (elf2, &size2);
392 if (raw2 == NULL )
393 error (EXIT_FAILURE, 0, gettext ("cannot load data of \"%s\": %s"),
394 fname2, elf_errmsg (-1));
395
396 for (size_t cnt = 0; cnt < nregions; ++cnt)
397 {
398 regionsarr[cnt] = *regions;
399 regions = regions->next;
400 }
401
402 qsort (regionsarr, nregions, sizeof (regionsarr[0]), regioncompare);
403 }
404
405 /* Compare the program header tables. */
406 for (int ndx = 0; ndx < ehdr1->e_phnum; ++ndx)
407 {
408 GElf_Phdr phdr1_mem;
409 GElf_Phdr *phdr1 = gelf_getphdr (elf1, ndx, &phdr1_mem);
410 if (ehdr1 == NULL)
411 error (EXIT_FAILURE, 0,
412 gettext ("cannot get program header entry %d of \"%s\": %s"),
413 ndx, fname1, elf_errmsg (-1));
414 GElf_Phdr phdr2_mem;
415 GElf_Phdr *phdr2 = gelf_getphdr (elf2, ndx, &phdr2_mem);
416 if (ehdr2 == NULL)
417 error (EXIT_FAILURE, 0,
418 gettext ("cannot get program header entry %d of \"%s\": %s"),
419 ndx, fname2, elf_errmsg (-1));
420
421 if (memcmp (phdr1, phdr2, sizeof (GElf_Phdr)) != 0)
422 {
423 if (! quiet)
424 error (0, 0, gettext ("%s %s differ: program header %d"),
425 fname1, fname2, ndx);
426 result = 1;
427 goto out;
428 }
429
430 if (gaps != gaps_ignore && phdr1->p_type == PT_LOAD)
431 {
432 size_t cnt = 0;
433 while (cnt < nregions && regionsarr[cnt].to < phdr1->p_offset)
434 ++cnt;
435
436 GElf_Off last = phdr1->p_offset;
437 GElf_Off end = phdr1->p_offset + phdr1->p_filesz;
438 while (cnt < nregions && regionsarr[cnt].from < end)
439 {
440 if (last < regionsarr[cnt].from)
441 {
442 /* Compare the [LAST,FROM) region. */
443 assert (gaps == gaps_match);
444 if (memcmp (raw1 + last, raw2 + last,
445 regionsarr[cnt].from - last) != 0)
446 {
447 gapmismatch:
448 if (!quiet)
449 error (0, 0, gettext ("%s %s differ: gap"),
450 fname1, fname2);
451 result = 1;
452 goto out;
453 }
454
455 }
456 last = regionsarr[cnt].to;
457 ++cnt;
458 }
459
460 if (cnt == nregions && last < end)
461 goto gapmismatch;
462 }
463 }
464
465 out:
466 elf_end (elf1);
467 elf_end (elf2);
468 close (fd1);
469 close (fd2);
470
471 return result;
472}
473
474
475/* Print the version information. */
476static void
477print_version (FILE *stream, struct argp_state *state __attribute__ ((unused)))
478{
479 fprintf (stream, "elfcmp (%s) %s\n", PACKAGE_NAME, VERSION);
480 fprintf (stream, gettext ("\
481Copyright (C) %s Red Hat, Inc.\n\
482This is free software; see the source for copying conditions. There is NO\n\
483warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
484"), "2005");
485 fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
486}
487
488
489/* Handle program arguments. */
490static error_t
491parse_opt (int key, char *arg,
492 struct argp_state *state __attribute__ ((unused)))
493{
494 switch (key)
495 {
496 case 'q':
497 quiet = true;
498 break;
499
500 case OPT_GAPS:
501 if (strcasecmp (arg, "ignore") == 0)
502 gaps = gaps_ignore;
503 else if (strcasecmp (arg, "match") == 0)
504 gaps = gaps_match;
505 else
506 {
507 fprintf (stderr,
508 gettext ("Invalid value \"%s\" for --gaps parameter."),
509 arg);
510 argp_help (&argp, stderr, ARGP_HELP_SEE,
511 program_invocation_short_name);
512 exit (1);
513 }
514 break;
515
516 default:
517 return ARGP_ERR_UNKNOWN;
518 }
519 return 0;
520}
521
522
523static Elf *
524open_file (const char *fname, int *fdp, Ebl **eblp)
525{
526 int fd = open (fname, O_RDONLY);
527 if (unlikely (fd == -1))
528 error (EXIT_FAILURE, errno, gettext ("cannot open \"%s\""), fname);
529 Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
530 if (elf == NULL)
531 error (EXIT_FAILURE, 0,
532 gettext ("cannot create ELF descriptor for \"%s\": %s"),
533 fname, elf_errmsg (-1));
534 Ebl *ebl = ebl_openbackend (elf);
535 if (ebl == NULL)
536 error (EXIT_FAILURE, 0,
537 gettext ("cannot create EBL descriptor for \"%s\""), fname);
538
539 *fdp = fd;
540 *eblp = ebl;
541 return elf;
542}
543
544
545static bool
546search_for_copy_reloc (Ebl *ebl, size_t scnndx, int symndx)
547{
548 Elf_Scn *scn = NULL;
549 while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
550 {
551 GElf_Shdr shdr_mem;
552 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
553 if (shdr == NULL)
554 error (EXIT_FAILURE, 0,
555 gettext ("cannot get section header of section %zu: %s"),
556 elf_ndxscn (scn), elf_errmsg (-1));
557
558 if ((shdr->sh_type != SHT_REL && shdr->sh_type != SHT_RELA)
559 || shdr->sh_link != scnndx)
560 continue;
561
562 Elf_Data *data = elf_getdata (scn, NULL);
563 if (data == NULL)
564 error (EXIT_FAILURE, 0,
565 gettext ("cannot get content of section %zu: %s"),
566 elf_ndxscn (scn), elf_errmsg (-1));
567
568 if (shdr->sh_type == SHT_REL)
569 for (int ndx = 0; ndx < (int) (shdr->sh_size / shdr->sh_entsize);
570 ++ndx)
571 {
572 GElf_Rel rel_mem;
573 GElf_Rel *rel = gelf_getrel (data, ndx, &rel_mem);
574 if (rel == NULL)
575 error (EXIT_FAILURE, 0, gettext ("cannot get relocation: %s"),
576 elf_errmsg (-1));
577
578 if ((int) GELF_R_SYM (rel->r_info) == symndx
579 && ebl_copy_reloc_p (ebl, GELF_R_TYPE (rel->r_info)))
580 return true;
581 }
582 else
583 for (int ndx = 0; ndx < (int) (shdr->sh_size / shdr->sh_entsize);
584 ++ndx)
585 {
586 GElf_Rela rela_mem;
587 GElf_Rela *rela = gelf_getrela (data, ndx, &rela_mem);
588 if (rela == NULL)
589 error (EXIT_FAILURE, 0, gettext ("cannot get relocation: %s"),
590 elf_errmsg (-1));
591
592 if ((int) GELF_R_SYM (rela->r_info) == symndx
593 && ebl_copy_reloc_p (ebl, GELF_R_TYPE (rela->r_info)))
594 return true;
595 }
596 }
597
598 return false;
599}
600
601
602static int
603regioncompare (const void *p1, const void *p2)
604{
605 const struct region *r1 = (const struct region *) p1;
606 const struct region *r2 = (const struct region *) p2;
607
608 if (r1->from < r2->from)
609 return -1;
610 return 1;
611}