| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <bfd.h> |
| |
| struct bfd_file { |
| bfd *bfd; |
| asymbol **symtab; |
| long symcount; |
| }; |
| |
| struct pe_relocs { |
| struct pe_relocs *next; |
| unsigned long start_rva; |
| unsigned int used_relocs; |
| unsigned int total_relocs; |
| uint16_t *relocs; |
| }; |
| |
| /** |
| * Allocate memory |
| * |
| * @v len Length of memory to allocate |
| * @ret ptr Pointer to allocated memory |
| */ |
| static void * xmalloc ( size_t len ) { |
| void *ptr; |
| |
| ptr = malloc ( len ); |
| if ( ! ptr ) { |
| fprintf ( stderr, "Could not allocate %zd bytes\n", len ); |
| exit ( 1 ); |
| } |
| |
| return ptr; |
| } |
| |
| /** |
| * Generate entry in PE relocation table |
| * |
| * @v pe_reltab PE relocation table |
| * @v rva RVA |
| * @v size Size of relocation entry |
| */ |
| static void generate_pe_reloc ( struct pe_relocs **pe_reltab, |
| unsigned long rva, size_t size ) { |
| unsigned long start_rva; |
| uint16_t reloc; |
| struct pe_relocs *pe_rel; |
| uint16_t *relocs; |
| |
| /* Construct */ |
| start_rva = ( rva & ~0xfff ); |
| reloc = ( rva & 0xfff ); |
| switch ( size ) { |
| case 4: |
| reloc |= 0x3000; |
| break; |
| case 2: |
| reloc |= 0x2000; |
| break; |
| default: |
| fprintf ( stderr, "Unsupported relocation size %zd\n", size ); |
| exit ( 1 ); |
| } |
| |
| /* Locate or create PE relocation table */ |
| for ( pe_rel = *pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) { |
| if ( pe_rel->start_rva == start_rva ) |
| break; |
| } |
| if ( ! pe_rel ) { |
| pe_rel = xmalloc ( sizeof ( *pe_rel ) ); |
| memset ( pe_rel, 0, sizeof ( *pe_rel ) ); |
| pe_rel->next = *pe_reltab; |
| *pe_reltab = pe_rel; |
| pe_rel->start_rva = start_rva; |
| } |
| |
| /* Expand relocation list if necessary */ |
| if ( pe_rel->used_relocs < pe_rel->total_relocs ) { |
| relocs = pe_rel->relocs; |
| } else { |
| pe_rel->total_relocs = ( pe_rel->total_relocs ? |
| ( pe_rel->total_relocs * 2 ) : 256 ); |
| relocs = xmalloc ( pe_rel->total_relocs * |
| sizeof ( pe_rel->relocs[0] ) ); |
| memset ( relocs, 0, |
| pe_rel->total_relocs * sizeof ( pe_rel->relocs[0] ) ); |
| memcpy ( relocs, pe_rel->relocs, |
| pe_rel->used_relocs * sizeof ( pe_rel->relocs[0] ) ); |
| free ( pe_rel->relocs ); |
| pe_rel->relocs = relocs; |
| } |
| |
| /* Store relocation */ |
| pe_rel->relocs[ pe_rel->used_relocs++ ] = reloc; |
| } |
| |
| /** |
| * Calculate size of binary PE relocation table |
| * |
| * @v pe_reltab PE relocation table |
| * @v buffer Buffer to contain binary table, or NULL |
| * @ret size Size of binary table |
| */ |
| static size_t output_pe_reltab ( struct pe_relocs *pe_reltab, |
| void *buffer ) { |
| struct pe_relocs *pe_rel; |
| unsigned int num_relocs; |
| size_t size; |
| size_t total_size = 0; |
| |
| for ( pe_rel = pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) { |
| num_relocs = ( ( pe_rel->used_relocs + 1 ) & ~1 ); |
| size = ( sizeof ( uint32_t ) /* VirtualAddress */ + |
| sizeof ( uint32_t ) /* SizeOfBlock */ + |
| ( num_relocs * sizeof ( uint16_t ) ) ); |
| if ( buffer ) { |
| *( (uint32_t *) ( buffer + total_size + 0 ) ) |
| = pe_rel->start_rva; |
| *( (uint32_t *) ( buffer + total_size + 4 ) ) = size; |
| memcpy ( ( buffer + total_size + 8 ), pe_rel->relocs, |
| ( num_relocs * sizeof ( uint16_t ) ) ); |
| } |
| total_size += size; |
| } |
| |
| return total_size; |
| } |
| |
| /** |
| * Read symbol table |
| * |
| * @v bfd BFD file |
| */ |
| static void read_symtab ( struct bfd_file *bfd ) { |
| long symtab_size; |
| |
| /* Get symbol table size */ |
| symtab_size = bfd_get_symtab_upper_bound ( bfd->bfd ); |
| if ( symtab_size < 0 ) { |
| bfd_perror ( "Could not get symbol table upper bound" ); |
| exit ( 1 ); |
| } |
| |
| /* Allocate and read symbol table */ |
| bfd->symtab = xmalloc ( symtab_size ); |
| bfd->symcount = bfd_canonicalize_symtab ( bfd->bfd, bfd->symtab ); |
| if ( bfd->symcount < 0 ) { |
| bfd_perror ( "Cannot read symbol table" ); |
| exit ( 1 ); |
| } |
| } |
| |
| /** |
| * Read relocation table |
| * |
| * @v bfd BFD file |
| * @v section Section |
| * @v symtab Symbol table |
| * @ret reltab Relocation table |
| */ |
| static arelent ** read_reltab ( struct bfd_file *bfd, asection *section ) { |
| long reltab_size; |
| arelent **reltab; |
| long numrels; |
| |
| /* Get relocation table size */ |
| reltab_size = bfd_get_reloc_upper_bound ( bfd->bfd, section ); |
| if ( reltab_size < 0 ) { |
| bfd_perror ( "Could not get relocation table upper bound" ); |
| exit ( 1 ); |
| } |
| |
| /* Allocate and read relocation table */ |
| reltab = xmalloc ( reltab_size ); |
| numrels = bfd_canonicalize_reloc ( bfd->bfd, section, reltab, |
| bfd->symtab ); |
| if ( numrels < 0 ) { |
| bfd_perror ( "Cannot read relocation table" ); |
| exit ( 1 ); |
| } |
| |
| return reltab; |
| } |
| |
| |
| /** |
| * Open input BFD file |
| * |
| * @v filename File name |
| * @ret ibfd BFD file |
| */ |
| static struct bfd_file * open_input_bfd ( const char *filename ) { |
| struct bfd_file *ibfd; |
| |
| /* Create BFD file */ |
| ibfd = xmalloc ( sizeof ( *ibfd ) ); |
| memset ( ibfd, 0, sizeof ( *ibfd ) ); |
| |
| /* Open the file */ |
| ibfd->bfd = bfd_openr ( filename, NULL ); |
| if ( ! ibfd->bfd ) { |
| fprintf ( stderr, "Cannot open %s: ", filename ); |
| bfd_perror ( NULL ); |
| exit ( 1 ); |
| } |
| |
| /* The call to bfd_check_format() must be present, otherwise |
| * we get a segfault from later BFD calls. |
| */ |
| if ( bfd_check_format ( ibfd->bfd, bfd_object ) < 0 ) { |
| fprintf ( stderr, "%s is not an object file\n", filename ); |
| exit ( 1 ); |
| } |
| |
| /* Read symbols and relocation entries */ |
| read_symtab ( ibfd ); |
| |
| return ibfd; |
| } |
| |
| /** |
| * Open output BFD file |
| * |
| * @v filename File name |
| * @v ibfd Input BFD file |
| * @ret obfd BFD file |
| */ |
| static struct bfd_file * open_output_bfd ( const char *filename, |
| struct bfd_file *ibfd ) { |
| struct bfd_file *obfd; |
| asection *isection; |
| asection *osection; |
| |
| /* |
| * Most of this code is based on what objcopy.c does. |
| * |
| */ |
| |
| /* Create BFD file */ |
| obfd = xmalloc ( sizeof ( *obfd ) ); |
| memset ( obfd, 0, sizeof ( *obfd ) ); |
| |
| /* Open the file */ |
| obfd->bfd = bfd_openw ( filename, ibfd->bfd->xvec->name ); |
| if ( ! obfd->bfd ) { |
| fprintf ( stderr, "Cannot open %s: ", filename ); |
| bfd_perror ( NULL ); |
| exit ( 1 ); |
| } |
| |
| /* Copy per-file data */ |
| if ( ! bfd_set_arch_mach ( obfd->bfd, bfd_get_arch ( ibfd->bfd ), |
| bfd_get_mach ( ibfd->bfd ) ) ) { |
| bfd_perror ( "Cannot copy architecture" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_set_format ( obfd->bfd, bfd_get_format ( ibfd->bfd ) ) ) { |
| bfd_perror ( "Cannot copy format" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_copy_private_header_data ( ibfd->bfd, obfd->bfd ) ) { |
| bfd_perror ( "Cannot copy private header data" ); |
| exit ( 1 ); |
| } |
| |
| /* Create sections */ |
| for ( isection = ibfd->bfd->sections ; isection ; |
| isection = isection->next ) { |
| osection = bfd_make_section_anyway ( obfd->bfd, |
| isection->name ); |
| if ( ! osection ) { |
| bfd_perror ( "Cannot create section" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_set_section_flags ( obfd->bfd, osection, |
| isection->flags ) ) { |
| bfd_perror ( "Cannot copy section flags" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_set_section_size ( obfd->bfd, osection, |
| bfd_section_size ( ibfd->bfd, isection ) ) ) { |
| bfd_perror ( "Cannot copy section size" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_set_section_vma ( obfd->bfd, osection, |
| bfd_section_vma ( ibfd->bfd, isection ) ) ) { |
| bfd_perror ( "Cannot copy section VMA" ); |
| exit ( 1 ); |
| } |
| osection->lma = bfd_section_lma ( ibfd->bfd, isection ); |
| if ( ! bfd_set_section_alignment ( obfd->bfd, osection, |
| bfd_section_alignment ( ibfd->bfd, isection ) ) ) { |
| bfd_perror ( "Cannot copy section alignment" ); |
| exit ( 1 ); |
| } |
| osection->entsize = isection->entsize; |
| isection->output_section = osection; |
| isection->output_offset = 0; |
| if ( ! bfd_copy_private_section_data ( ibfd->bfd, isection, |
| obfd->bfd, osection ) ){ |
| bfd_perror ( "Cannot copy section private data" ); |
| exit ( 1 ); |
| } |
| } |
| |
| /* Copy symbol table */ |
| bfd_set_symtab ( obfd->bfd, ibfd->symtab, ibfd->symcount ); |
| obfd->symtab = ibfd->symtab; |
| |
| return obfd; |
| } |
| |
| /** |
| * Copy section from input BFD file to output BFD file |
| * |
| * @v obfd Output BFD file |
| * @v ibfd Input BFD file |
| * @v section Section |
| */ |
| static void copy_bfd_section ( struct bfd_file *obfd, struct bfd_file *ibfd, |
| asection *isection ) { |
| size_t size; |
| void *buf; |
| arelent **reltab; |
| arelent **rel; |
| char *errmsg; |
| |
| /* Read in original section */ |
| size = bfd_section_size ( ibfd->bfd, isection ); |
| if ( ! size ) |
| return; |
| buf = xmalloc ( size ); |
| if ( ( ! bfd_get_section_contents ( ibfd->bfd, isection, |
| buf, 0, size ) ) ) { |
| fprintf ( stderr, "Cannot read section %s: ", isection->name ); |
| bfd_perror ( NULL ); |
| exit ( 1 ); |
| } |
| |
| /* Perform relocations. We do this here, rather than letting |
| * ld do it for us when creating the input ELF file, so that |
| * we can change symbol values as a result of having created |
| * the .reloc section. |
| */ |
| reltab = read_reltab ( ibfd, isection ); |
| for ( rel = reltab ; *rel ; rel++ ) { |
| bfd_perform_relocation ( ibfd->bfd, *rel, buf, isection, |
| NULL, &errmsg ); |
| } |
| free ( reltab ); |
| |
| /* Write out modified section */ |
| if ( ( ! bfd_set_section_contents ( obfd->bfd, |
| isection->output_section, |
| buf, 0, size ) ) ) { |
| fprintf ( stderr, "Cannot write section %s: ", |
| isection->output_section->name ); |
| bfd_perror ( NULL ); |
| exit ( 1 ); |
| } |
| |
| free ( buf ); |
| } |
| |
| /** |
| * Process relocation record |
| * |
| * @v section Section |
| * @v rel Relocation entry |
| * @v pe_reltab PE relocation table to fill in |
| */ |
| static void process_reloc ( asection *section, arelent *rel, |
| struct pe_relocs **pe_reltab ) { |
| reloc_howto_type *howto = rel->howto; |
| asymbol *sym = *(rel->sym_ptr_ptr); |
| unsigned long offset = ( section->lma + rel->address ); |
| |
| if ( bfd_is_abs_section ( sym->section ) ) { |
| /* Skip absolute symbols; the symbol value won't |
| * change when the object is loaded. |
| */ |
| } else if ( strcmp ( howto->name, "R_386_32" ) == 0 ) { |
| /* Generate a 4-byte PE relocation */ |
| generate_pe_reloc ( pe_reltab, offset, 4 ); |
| } else if ( strcmp ( howto->name, "R_386_16" ) == 0 ) { |
| /* Generate a 2-byte PE relocation */ |
| generate_pe_reloc ( pe_reltab, offset, 2 ); |
| } else if ( strcmp ( howto->name, "R_386_PC32" ) == 0 ) { |
| /* Skip PC-relative relocations; all relative offsets |
| * remain unaltered when the object is loaded. |
| */ |
| } else { |
| fprintf ( stderr, "Unrecognised relocation type %s\n", |
| howto->name ); |
| exit ( 1 ); |
| } |
| } |
| |
| /** |
| * Create .reloc section |
| * |
| * obfd Output BFD file |
| * section .reloc section in output file |
| * pe_reltab PE relocation table |
| */ |
| static void create_reloc_section ( struct bfd_file *obfd, asection *section, |
| struct pe_relocs *pe_reltab ) { |
| size_t raw_size; |
| size_t size; |
| size_t old_size; |
| void *buf; |
| asymbol **sym; |
| |
| /* Build binary PE relocation table */ |
| raw_size = output_pe_reltab ( pe_reltab, NULL ); |
| size = ( ( raw_size + 31 ) & ~31 ); |
| buf = xmalloc ( size ); |
| memset ( buf, 0, size ); |
| output_pe_reltab ( pe_reltab, buf ); |
| |
| /* Write .reloc section */ |
| old_size = bfd_section_size ( obfd->bfd, section ); |
| if ( ! bfd_set_section_size ( obfd->bfd, section, size ) ) { |
| bfd_perror ( "Cannot resize .reloc section" ); |
| exit ( 1 ); |
| } |
| if ( ! bfd_set_section_contents ( obfd->bfd, section, |
| buf, 0, size ) ) { |
| bfd_perror ( "Cannot set .reloc section contents" ); |
| exit ( 1 ); |
| } |
| |
| /* Update symbols pertaining to the relocation directory */ |
| for ( sym = obfd->symtab ; *sym ; sym++ ) { |
| if ( strcmp ( (*sym)->name, "_reloc_memsz" ) == 0 ) { |
| (*sym)->value = size; |
| } else if ( strcmp ( (*sym)->name, "_reloc_filesz" ) == 0 ){ |
| (*sym)->value = raw_size; |
| } else if ( strcmp ( (*sym)->name, "_filesz" ) == 0 ) { |
| (*sym)->value += ( size - old_size ); |
| } |
| } |
| } |
| |
| int main ( int argc, const char *argv[] ) { |
| const char *iname; |
| const char *oname; |
| struct bfd_file *ibfd; |
| struct bfd_file *obfd; |
| asection *section; |
| arelent **reltab; |
| arelent **rel; |
| struct pe_relocs *pe_reltab = NULL; |
| asection *reloc_section; |
| |
| /* Initialise libbfd */ |
| bfd_init(); |
| |
| /* Identify intput and output files */ |
| if ( argc != 3 ) { |
| fprintf ( stderr, "Syntax: %s infile outfile\n", argv[0] ); |
| exit ( 1 ); |
| } |
| iname = argv[1]; |
| oname = argv[2]; |
| |
| /* Open BFD files */ |
| ibfd = open_input_bfd ( iname ); |
| obfd = open_output_bfd ( oname, ibfd ); |
| |
| /* Process relocations in all sections */ |
| for ( section = ibfd->bfd->sections ; section ; |
| section = section->next ) { |
| reltab = read_reltab ( ibfd, section ); |
| for ( rel = reltab ; *rel ; rel++ ) { |
| process_reloc ( section, *rel, &pe_reltab ); |
| } |
| free ( reltab ); |
| } |
| |
| /* Create modified .reloc section */ |
| reloc_section = bfd_get_section_by_name ( obfd->bfd, ".reloc" ); |
| if ( ! reloc_section ) { |
| fprintf ( stderr, "Cannot find .reloc section\n" ); |
| exit ( 1 ); |
| } |
| create_reloc_section ( obfd, reloc_section, pe_reltab ); |
| |
| /* Copy other section contents */ |
| for ( section = ibfd->bfd->sections ; section ; |
| section = section->next ) { |
| if ( section->output_section != reloc_section ) |
| copy_bfd_section ( obfd, ibfd, section ); |
| } |
| |
| /* Write out files and clean up */ |
| bfd_close ( obfd->bfd ); |
| bfd_close ( ibfd->bfd ); |
| |
| return 0; |
| } |