#include #include #include #include #include #include #include #include #include #include struct mz_hdr { uint16_t signature; #define MZ_SIGNATURE 0x5a4d uint16_t last_page_size; uint16_t page_count; uint16_t relocation_count; uint16_t header_paras; uint16_t min_paras; uint16_t max_paras; uint16_t entry_ss; uint16_t entry_sp; uint16_t checksum; uint16_t entry_ip; uint16_t entry_cs; uint16_t relocations; uint16_t overlay; uint8_t reserved[32]; uint32_t extended_header_base; }; struct pe_hdr { uint32_t signature; #define PE_SIGNATURE 0x00004550 uint16_t cpu; uint16_t section_count; int32_t timestamp; uint32_t symbols_file_offset; uint32_t symbol_count; uint16_t opt_hdr_size; uint16_t flags; struct { uint16_t magic; #define PE_MAGIC_EXE32 0x010b #define PE_MAGIC_EXE32PLUS 0x020b uint8_t linker_major, linker_minor; uint32_t code_size, data_size, bss_size; uint32_t entry_rva, code_rva, data_rva; } opt_hdr; }; #define PE_PAGE_SIZE 0x1000 #define PE_BASE_RELOC_ABS 0 #define PE_BASE_RELOC_HIGHLOW 3 #define PE_BASE_RELOC_DIR64 10 struct coff_section { char name[8]; uint32_t size; uint32_t rva; uint32_t file_size; uint32_t file_offset; uint32_t relocation_file_offset; uint32_t line_number_file_offset; uint16_t relocation_count; uint16_t line_number_count; uint32_t flags; #define COFF_SECTION_BSS 0x00000080 #define COFF_SECTION_DISCARDABLE 0x02000000 }; static void usage(const char *cmd, int rc) { fprintf(rc ? stderr : stdout, "Usage: %s \n", cmd); exit(rc); } static unsigned int load(const char *name, int *handle, struct coff_section **sections, uint_fast64_t *image_base, uint32_t *image_size, unsigned int *width) { int in = open(name, O_RDONLY); struct mz_hdr mz_hdr; struct pe_hdr pe_hdr; uint32_t base; if ( in < 0 || read(in, &mz_hdr, sizeof(mz_hdr)) != sizeof(mz_hdr) ) { perror(name); exit(2); } if ( mz_hdr.signature != MZ_SIGNATURE || mz_hdr.relocations < sizeof(mz_hdr) || !mz_hdr.extended_header_base ) { fprintf(stderr, "%s: Wrong DOS file format\n", name); exit(2); } if ( lseek(in, mz_hdr.extended_header_base, SEEK_SET) < 0 || read(in, &pe_hdr, sizeof(pe_hdr)) != sizeof(pe_hdr) || read(in, &base, sizeof(base)) != sizeof(base) || /* * Luckily the image size field lives at the * same offset for both formats. */ lseek(in, 24, SEEK_CUR) < 0 || read(in, image_size, sizeof(*image_size)) != sizeof(*image_size) ) { perror(name); exit(3); } switch ( (pe_hdr.signature == PE_SIGNATURE && pe_hdr.opt_hdr_size > sizeof(pe_hdr.opt_hdr)) * pe_hdr.opt_hdr.magic ) { case PE_MAGIC_EXE32: *width = 32; *image_base = base; break; case PE_MAGIC_EXE32PLUS: *width = 64; *image_base = ((uint64_t)base << 32) | pe_hdr.opt_hdr.data_rva; break; default: fprintf(stderr, "%s: Wrong PE file format\n", name); exit(3); } *sections = malloc(pe_hdr.section_count * sizeof(**sections)); if ( !*sections ) { perror(NULL); exit(4); } if ( lseek(in, mz_hdr.extended_header_base + offsetof(struct pe_hdr, opt_hdr) + pe_hdr.opt_hdr_size, SEEK_SET) < 0 || read(in, *sections, pe_hdr.section_count * sizeof(**sections)) != pe_hdr.section_count * sizeof(**sections) ) { perror(name); exit(4); } *handle = in; return pe_hdr.section_count; } static long page_size; static const void *map_section(const struct coff_section *sec, int in, const char *name) { const char *ptr; unsigned long offs; if ( !page_size ) page_size = sysconf(_SC_PAGESIZE); offs = sec->file_offset & (page_size - 1); ptr = mmap(0, offs + sec->file_size, PROT_READ, MAP_PRIVATE, in, sec->file_offset - offs); if ( ptr == MAP_FAILED ) { perror(name); exit(6); } return ptr + offs; } static void unmap_section(const void *ptr, const struct coff_section *sec) { unsigned long offs = sec->file_offset & (page_size - 1); munmap((char *)ptr - offs, offs + sec->file_size); } static void diff_sections(const unsigned char *ptr1, const unsigned char *ptr2, const struct coff_section *sec, int_fast64_t diff, unsigned int width, uint_fast64_t base, uint_fast64_t end) { static uint_fast32_t cur_rva, reloc_size; unsigned int disp = 0; uint_fast32_t i; if ( !sec ) { reloc_size += reloc_size & 2; if ( reloc_size ) printf("\t.balign 4\n" "\t.equ rva_%08" PRIxFAST32 "_relocs, %#08" PRIxFAST32 "\n", cur_rva, reloc_size); return; } while ( !(diff & (((int_fast64_t)1 << ((disp + 1) * CHAR_BIT)) - 1)) ) ++disp; for ( i = 0; i < sec->file_size; ++i ) { uint_fast32_t rva; union { uint32_t u32; uint64_t u64; } val1, val2; int_fast64_t delta; unsigned int reloc = (width == 4 ? PE_BASE_RELOC_HIGHLOW : PE_BASE_RELOC_DIR64); if ( ptr1[i] == ptr2[i] ) continue; if ( i < disp || i + width - disp > sec->file_size ) { fprintf(stderr, "Bogus difference at %s:%08" PRIxFAST32 "\n", sec->name, i); exit(3); } memcpy(&val1, ptr1 + i - disp, width); memcpy(&val2, ptr2 + i - disp, width); delta = width == 4 ? val2.u32 - val1.u32 : val2.u64 - val1.u64; if ( delta != diff ) { fprintf(stderr, "Difference at %s:%08" PRIxFAST32 " is %#" PRIxFAST64 " (expected %#" PRIxFAST64 ")\n", sec->name, i, delta, diff); continue; } if ( width == 8 && (val1.u64 < base || val1.u64 > end) ) reloc = PE_BASE_RELOC_HIGHLOW; rva = (sec->rva + i - disp) & ~(PE_PAGE_SIZE - 1); if ( rva > cur_rva ) { reloc_size += reloc_size & 2; if ( reloc_size ) printf("\t.equ rva_%08" PRIxFAST32 "_relocs," " %#08" PRIxFAST32 "\n", cur_rva, reloc_size); printf("\t.balign 4\n" "\t.long %#08" PRIxFAST32 "," " rva_%08" PRIxFAST32 "_relocs\n", rva, rva); cur_rva = rva; reloc_size = 8; } else if ( rva != cur_rva ) { fprintf(stderr, "Cannot handle decreasing RVA (at %s:%08" PRIxFAST32 ")\n", sec->name, i); exit(3); } printf("\t.word (%u << 12) | 0x%03" PRIxFAST32 "\n", reloc, sec->rva + i - disp - rva); reloc_size += 2; i += width - disp - 1; } } int main(int argc, char *argv[]) { int in1, in2; unsigned int i, nsec, width1, width2; uint_fast64_t base1, base2; uint32_t size1, size2; struct coff_section *sec1, *sec2; if ( argc == 1 || !strcmp(argv[1], "-?") || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") ) usage(*argv, argc == 1); if ( argc != 3 ) usage(*argv, 1); nsec = load(argv[1], &in1, &sec1, &base1, &size1, &width1); if ( nsec != load(argv[2], &in2, &sec2, &base2, &size2, &width2) ) { fputs("Mismatched section counts\n", stderr); return 5; } if ( width1 != width2 ) { fputs("Mismatched image types\n", stderr); return 5; } width1 >>= 3; if ( base1 == base2 ) { fputs("Images must have different base addresses\n", stderr); return 5; } if ( size1 != size2 ) { fputs("Images must have identical sizes\n", stderr); return 5; } puts("\t.section .reloc, \"a\", @progbits\n" "\t.balign 4\n" "\t.globl __base_relocs_start, __base_relocs_end\n" "__base_relocs_start:"); for ( i = 0; i < nsec; ++i ) { const void *ptr1, *ptr2; if ( memcmp(sec1[i].name, sec2[i].name, sizeof(sec1[i].name)) || sec1[i].rva != sec2[i].rva || sec1[i].size != sec2[i].size || sec1[i].file_size != sec2[i].file_size || sec1[i].flags != sec2[i].flags ) { fprintf(stderr, "Mismatched section %u parameters\n", i); return 5; } if ( !sec1[i].size || (sec1[i].flags & (COFF_SECTION_DISCARDABLE|COFF_SECTION_BSS)) ) continue; /* * Don't generate relocations for sections that definitely * aren't used by the boot loader code. */ if ( memcmp(sec1[i].name, ".initcal", sizeof(sec1[i].name)) == 0 || memcmp(sec1[i].name, ".init.se", sizeof(sec1[i].name)) == 0 || memcmp(sec1[i].name, ".lockpro", sizeof(sec1[i].name)) == 0 ) continue; if ( !sec1[i].rva ) { fprintf(stderr, "Can't handle section %u with zero RVA\n", i); return 3; } if ( sec1[i].file_size > sec1[i].size ) { sec1[i].file_size = sec1[i].size; sec2[i].file_size = sec2[i].size; } ptr1 = map_section(sec1 + i, in1, argv[1]); ptr2 = map_section(sec2 + i, in2, argv[2]); diff_sections(ptr1, ptr2, sec1 + i, base2 - base1, width1, base1, base1 + size1); unmap_section(ptr1, sec1 + i); unmap_section(ptr2, sec2 + i); } diff_sections(NULL, NULL, NULL, 0, 0, 0, 0); puts("__base_relocs_end:"); close(in1); close(in2); return 0; }